import { IInspectable , InspectableType } from '@babylonjs/core/Misc/iInspectable.js' ; import { Node } from '@babylonjs/core/node.js' ; import { Scene } from '@babylonjs/core/scene.js' ; import { Nullable } from '@babylonjs/core/types.js' ; import { ValueAndUnit } from '@babylonjs/gui' ; import ReactReconciler , { Fiber , HostConfig } from 'react-reconciler' ; import { CreationType , GeneratedParameter } from './codeGenerationDescriptors' ; import { CreatedInstance , CreatedInstanceMetadata } from './CreatedInstance' ; import * as CUSTOM_HOSTS from './customHosts' ; import { ColumnDefinition } from './customHosts/grid/columnDefinition' ; import { RowDefinition } from './customHosts/grid/rowDefinition' ; import { AnyCustomProps , CustomProps } from './CustomProps' ; import * as BABYLONEXT from './extensions' ; import * as GENERATED from './generatedCode' ; import { DynamicHost , HostRegistrationStore } from './HostRegistrationStore' ; import { LifecycleListener } from './LifecycleListener' ; import { CustomPropsHandler , HasPropsHandlers , PropChangeType , PropertyUpdate , PropsHandler , UpdatePayload , } from './PropsHandler' ; import { applyInitialPropsToCreatedInstance , applyUpdateToInstance } from './UpdateInstance' ; type HostCreatedInstance < T > = CreatedInstance < T > | undefined ; type Props = { scene : Scene ; } & any ; export type Container = { scene : Nullable < Scene > ; rootInstance : CreatedInstance < Scene > ; } ; type HostContext = Container ; type TimeoutHandle = number | undefined ; type NoTimeout = number ; function createCreatedInstance < T , U extends HasPropsHandlers < any > > ( className , hostInstance , propsHandlers , metadata , customProps ? , lifecycleListener ? ) { if ( ( propsHandlers as any ) . isTargetable === true ) { metadata . isTargetable = true ; } return { children : [ ] , customProps , hostInstance , lifecycleListener , metadata , observers : { } , parent : null , propsHandlers , } as CreatedInstance < T > ; } param parentInstance param child function removeChild ( parentInstance , child ) { if ( child ) { const { hostInstance } = child ; if ( child . lifecycleListener ) { child . lifecycleListener . onUnmount ( ) ; } if ( child . metadata . isNode ) { ; ( hostInstance as Node ) . parent = null ; } else if ( child . metadata . isGUI3DControl === true ) { console . error ( '3D remove control not implemented.' ) ; } if ( parentInstance . metadata && parentInstance . metadata . isGUI2DControl === true && child . metadata . isGUI2DControl === true ) { parentInstance . hostInstance . removeControl ( child . hostInstance ) ; } if ( child . children ) { removeRecursive ( child . children , child ) ; } if ( child . hostInstance && child . customProps . disposeInstanceOnUnmount && typeof child . hostInstance . dispose === 'function' ) { hostInstance . dispose ( ) ; } parentInstance . children = parentInstance . children . filter ( ( ci ) => ci !== child ) ; child . parent = null ; } } /**
 * remove child recursive
 */ function removeRecursive ( array , parent , clone = false ) { if ( array ) { const target = clone ? [ ... array ] : array ; target . forEach ( ( child ) => removeChild ( parent , child ) ) ; } } function addChild ( parent , child , childIndex ? ) { if ( parent ) { if ( ! child ) { console . error ( 'undefined child' , parent ) ; } else { parent . children . push ( child ) ; child . parent = parent ; } } if ( child && child . lifecycleListener && child . lifecycleListener . onParented ) { child . lifecycleListener . onParented ( parent ! , child ) ; } if ( parent && parent . lifecycleListener && parent . lifecycleListener . onChildAdded ) { parent . lifecycleListener . onChildAdded ( child , parent ) ; } } param type param value const getConstructorValue = ( type , value ) : any => { switch ( type ) { case 'BabylonjsCoreVector3' : { const v3result = CustomPropsHandler . HandlePropsChange ( PropChangeType . Vector3 , undefined , value ) ; return v3result . processed ? v3result . value : value ; } case 'BabylonjsCoreColor3' : { const c3result = CustomPropsHandler . HandlePropsChange ( PropChangeType . Vector3 , undefined , value ) ; return c3result . processed ? c3result . value : value ; } case 'BabylonjsCoreColor4' : { const c4result = CustomPropsHandler . HandlePropsChange ( PropChangeType . Color4 , undefined , value ) ; return c4result . processed ? c4result . value : value ; } default : return value ; } } ; const ReactBabylonJSHostConfig = { get supportsMutation ( ) : boolean { return true ; } , now : ( ) => { return Date . now ( ) ; } , get isPrimaryRenderer ( ) : boolean { return false ; } , get supportsPersistence ( ) : boolean { return false ; } , get supportsHydration ( ) : boolean { return false ; } , getPublicInstance : ( instance ) => { return instance ?. hostInstance ; } , getRootHostContext : ( rootContainerInstance ) : HostContext => { return rootContainerInstance ; } , getChildHostContext : ( parentHostContext , type , rootContainerInstance ) : HostContext => { return parentHostContext ; } , prepareUpdate ( instance , type , oldProps , newProps , rootContainerInstance , hostContext ) { if ( ! instance || ( instance . metadata && instance . metadata . customType === true ) ) { return null ; } const updatePayload = [ ] ; instance . propsHandlers ! . getPropsHandlers ( ) . forEach ( ( propHandler ) => { const handlerUpdates = propHandler . getPropertyUpdates ( oldProps , newProps ) ; if ( handlerUpdates !== null ) { updatePayload . push ( ... handlerUpdates ) ; } } ) ; return updatePayload . length === 0 ? null : updatePayload ; } , clearContainer ( container ) { container . rootInstance . children . splice ( 0 ) ; } , insertBefore ( parentInstance , child , beforeChild ) { let index = undefined ; if ( parentInstance && beforeChild !== undefined ) { index = parentInstance . children . indexOf ( beforeChild as CreatedInstance < any > ) ; } addChild ( parentInstance , child , index ) ; } , /**
     * This is called when something is dynamically added to root (not on initial appendChildToContainer)
     */ insertInContainerBefore ( container , child , beforeChild ) { if ( child ) { const index = container . rootInstance . children . indexOf ( beforeChild ) ; if ( index >= 0 ) { container . rootInstance . children . splice ( index , 0 , child ) ; } else { container . rootInstance . children . push ( child ) ; } child . parent = container . rootInstance ; } else { console . error ( 'insertInContainerBefore. No child:' , child ) ; } } , createInstance : ( type , props , rootContainerInstance , hostContext , internalInstanceHandle ) : CreatedInstance < any > | undefined => { const customTypes = [ ] ; const { scene } = rootContainerInstance ; if ( customTypes . indexOf ( type ) !== - 1 ) { const metadata = { className : type , customType : true , ... props . metadata , } ; const createdInstance = { children : [ ] , customProps : { } , hostInstance : null , lifecycleListener : new ( CUSTOM_HOSTS as any ) [ type + 'Fiber' ] ( scene , scene ! . getEngine ( ) , props ) , metadata , observers : { } , parent : null , propsHandlers : undefined , } ; return createdInstance ; } const underlyingClassName = ( GENERATED . intrinsicClassMap as any ) [ type ] || type ; const classDefinition = ( GENERATED as any ) [ `Fiber${ underlyingClassName }` ] ; let dynamicRegisteredHost = undefined ; if ( classDefinition === undefined ) { const ownDynamicHosts = { rowDefinition : RowDefinition , columnDefinition : ColumnDefinition , } ; if ( underlyingClassName in ownDynamicHosts ) { dynamicRegisteredHost = ownDynamicHosts [ underlyingClassName ] ; } else { dynamicRegisteredHost = HostRegistrationStore . GetRegisteredHost ( type ) ; } } if ( classDefinition === undefined && dynamicRegisteredHost === undefined ) { throw new Error ( `Cannot generate type '${ type }/${ underlyingClassName }' inside 'react-babylonjs' (ie: no DOM rendering on HTMLCanvas)` ) ; } let metadata ; let babylonObject = undefined ; const customProps = { childrenAsContent : props . childrenAsContent === true , createForParentMesh : props . createForParentMesh === true , onControlAdded : typeof props . onControlAdded === 'function' ? props . onControlAdded : undefined , connectControlNames : props . connectControlNames , defaultKeyboard : props . defaultKeyboard === true , linkToTransformNodeByName : props . linkToTransformNodeByName , shadowCasters : props . shadowCasters , shadowCastersExcluding : props . shadowCastersExcluding , attachToMeshesByName : props . attachToMeshesByName , assignTo : props . assignTo , assignFrom : props . assignFrom , disposeInstanceOnUnmount : props . assignFrom === undefined , addIncludeOnlyChildren : props . addIncludeOnlyChildren === true , childMeshesNotTracked : props . childMeshesNotTracked === true , shadowCastChildren : props . shadowCastChildren , skipAutoAttach : props . skipAutoAttach , attachGizmoToMesh : props . attachGizmoToMesh , attachGizmoToNode : props . attachGizmoToNode , gridColumn : props . gridColumn , gridRow : props . gridRow , } ; if ( customProps . assignFrom !== undefined ) { metadata = dynamicRegisteredHost !== undefined ? dynamicRegisteredHost . metadata : classDefinition . Metadata ; } else if ( dynamicRegisteredHost !== undefined ) { metadata = dynamicRegisteredHost . metadata ; if ( metadata . delayCreation !== true ) { babylonObject = dynamicRegisteredHost . hostFactory ( scene ! ) ; } } else { const createInfoArgs = classDefinition . CreateInfo ; metadata = classDefinition . Metadata ; const generatedParameters = createInfoArgs . parameters ; if ( props . fromInstance !== undefined ) { if ( createInfoArgs . namespace . startsWith ( '@babylonjs/' ) ) { const clazz = GENERATED . babylonClassFactory ( type ) ; if ( props . fromInstance instanceof clazz ) { babylonObject = props . fromInstance ; customProps . disposeInstanceOnUnmount = props . disposeInstanceOnUnmount === true ; } else { console . error ( 'fromInstance wrong type.' , props . fromInstance , clazz ) ; } } else { console . error ( 'cannot generate non babylonjs using fromInstance' ) ; } } else { const args = generatedParameters . map ( ( generatedParameter ) => { if ( Array . isArray ( generatedParameter . type ) ) { const newParameter = { } as Record < string , any > ; generatedParameter . type . forEach ( ( subParameter ) => { const subPropValue = getConstructorValue ( subParameter . type as string , props [ subParameter . name ] ) ; if ( subPropValue === undefined && subParameter . optional === false && generatedParameter . optional === false ) { console . warn ( 'Missing a required secondary property:' , subParameter . name ) ; } else { newParameter [ subParameter . name ] = subPropValue ; } } ) ; return newParameter ; } else { let value = getConstructorValue ( generatedParameter . type , props [ generatedParameter . name ] ) ; if ( value === undefined ) { if ( generatedParameter . type . includes ( 'BabylonjsCoreScene' ) || ( generatedParameter . type === 'any' && generatedParameter . name === 'scene' ) ) { value = scene ; } } if ( value === undefined && generatedParameter . optional === false ) { console . warn ( `On ${ type } you are missing a non-optional parameter '${ generatedParameter . name }' of type '${ generatedParameter . type }'` ) ; } return value ; } } ) ; if ( createInfoArgs . creationType === CreationType . FactoryMethod ) { babylonObject = GENERATED . babylonClassFactory ( createInfoArgs . libraryLocation ) [ createInfoArgs . factoryMethod ! ] ( ... args ) ; } else { if ( metadata . delayCreation !== true ) { if ( createInfoArgs . namespace . startsWith ( '@babylonjs/' ) ) { const clazz = GENERATED . babylonClassFactory ( type ) ; if ( clazz === undefined ) { throw new Error ( `Cannot generate '${ type }' (react-babylonjs):` ) ; } babylonObject = new clazz ( ... args ) ; } else if ( createInfoArgs . namespace . startsWith ( './extensions/' ) ) { const extClassName = ( GENERATED . intrinsicClassMap as any ) [ type ] || type ; babylonObject = new ( BABYLONEXT as any ) [ extClassName ] ( ... args ) ; } else { console . error ( 'metadata defines (or does not) a namespace that is known' , createInfoArgs . namespace ) ; } } } } } if ( typeof props . onCreated === 'function' ) { props . onCreated ( babylonObject ) ; } const fiberObject = dynamicRegisteredHost !== undefined ? dynamicRegisteredHost . propHandlerInstance : new ( GENERATED as any ) [ `Fiber${ underlyingClassName }` ] ( ) ; let metadataLifecycleListenerName ; if ( metadata . isGizmo === true ) { metadataLifecycleListenerName = 'Gizmo' ; } else if ( metadata . isMaterial === true ) { metadataLifecycleListenerName = 'Materials' ; } else if ( metadata . isTexture === true ) { metadataLifecycleListenerName = 'Textures' ; } else if ( metadata . isGUI3DControl === true ) { metadataLifecycleListenerName = 'GUI3DControl' ; } else if ( metadata . isGUI2DControl === true ) { metadataLifecycleListenerName = 'GUI2DControl' ; } else if ( metadata . isCamera === true ) { metadataLifecycleListenerName = 'Camera' ; } else if ( metadata . isMesh === true ) { metadataLifecycleListenerName = 'AbstractMesh' ; } else if ( metadata . isNode === true ) { metadataLifecycleListenerName = 'Node' ; } else if ( metadata . isBehavior === true ) { metadataLifecycleListenerName = 'Behavior' ; } let lifecycleListener ; if ( ( CUSTOM_HOSTS as any ) [ underlyingClassName + 'LifecycleListener' ] !== undefined ) { lifecycleListener = new ( CUSTOM_HOSTS as any ) [ underlyingClassName + 'LifecycleListener' ] ( scene , props ) ; } else if ( metadataLifecycleListenerName !== undefined ) { lifecycleListener = new ( CUSTOM_HOSTS as any ) [ metadataLifecycleListenerName + 'LifecycleListener' ] ( scene , props ) ; } else if ( dynamicRegisteredHost ?. lifecycleListenerFactory ) { lifecycleListener = dynamicRegisteredHost . lifecycleListenerFactory ( scene ! , props ) ; } else { lifecycleListener = new CUSTOM_HOSTS . FallbackLifecycleListener ( scene ! , props ) ; } const createdReference = createCreatedInstance ( underlyingClassName , babylonObject , fiberObject , metadata , customProps , lifecycleListener ) ; if ( lifecycleListener . onCreated ) { lifecycleListener . onCreated ( createdReference , scene ! ) ; } if ( createdReference . metadata && createdReference . metadata . isTargetable === true ) { fiberObject . addPropsHandler ( new CUSTOM_HOSTS . TargetPropsHandler ( scene ! ) ) ; } if ( metadata . delayCreation !== true && customProps . assignFrom === undefined ) { applyInitialPropsToCreatedInstance ( createdReference , props ) ; } else { createdReference . deferredCreationProps = props ; } if ( createdReference . hostInstance && ! ( 'metadata-className' in createdReference . hostInstance ) ) { Object . defineProperty ( createdReference . hostInstance , 'metadata-className' , { get ( ) { return createdReference . metadata . className ; } , enumerable : true , } ) ; } if ( createdReference . hostInstance && ! ( '__rb_createdInstance' in createdReference . hostInstance ) ) { Object . defineProperty ( createdReference . hostInstance , '__rb_createdInstance' , { get ( ) { return createdReference ; } , enumerable : true , } ) ; } if ( babylonObject ) { babylonObject . inspectableCustomProperties = [ { label : 'React class' , propertyName : 'metadata-className' , type : InspectableType . String , } , ] as IInspectable [ ] ; } return createdReference ; } , hideInstance ( instance ) { } , unhideInstance ( instance , props ) { } , createTextInstance ( text ) { } , scheduleTimeout ( fn , delay ? ) { return window . setTimeout ( fn , delay ) ; } , cancelTimeout ( id ) { window . clearTimeout ( id ) ; } , noTimeout : - 1 , prepareForCommit : ( containerInfo ) => { return null ; } , preparePortalMount ( containerInfo ) { console . log ( 'prepare portal mount' , containerInfo ) ; } , resetAfterCommit : ( containerInfo ) : void => { } , appendInitialChild : ( parent , child ) => { addChild ( parent , child ) ; } , appendChild : ( parent , child ) : void => { addChild ( parent , child ) ; } , canHydrateInstance : ( instance , type , props ) : null | CreatedInstance < any > => { return null ; } , finalizeInitialChildren : ( ) : boolean => { return true ; } , commitMount : ( instance , type , newProps , internalInstanceHandle ) : void => { if ( instance && instance . lifecycleListener && instance . lifecycleListener . onMount ) { instance . lifecycleListener . onMount ( instance ) ; } } , appendChildToContainer : ( container , child ) : void => { if ( child ) { if ( container . rootInstance ) { container . rootInstance . children . push ( child ) ; child . parent = container . rootInstance ; if ( child . hostInstance === undefined && child . lifecycleListener ) { child . lifecycleListener ! . onParented ( container . rootInstance , child ) ; } } else { console . log ( 'addend child with no root (createPortal only?)' ) ; addChild ( container as unknown as CreatedInstance < any > , child ) ; } } } , commitUpdate ( instance , updatePayload , type ) { if ( updatePayload !== null ) { updatePayload . forEach ( ( update ) => { if ( instance ) { applyUpdateToInstance ( instance , update ) ; } else { } } ) ; } } , removeChildFromContainer : ( container , child ) => { /**
         * To fix two bugs when toggle meshes:
         * 1.  model's mesh can't be destroyed.
         * 2. `removeChildFromContainer()` only destroy babylon instance.
         * The model is rootAbstractMesh's and Parent's child.
         * `container.rootInstance.children` will be very large after few toggles.
         */ if ( child ) { removeChild ( container . rootInstance , child ) ; } } , removeChild , shouldSetTextContent : ( type , props ) => { return false ; } , } ; export default ReactBabylonJSHostConfig ;