Skip to content

Commit

Permalink
allow props of type Observable<T> to be updated #139
Browse files Browse the repository at this point in the history
  • Loading branch information
brianzinn committed Jun 4, 2021
1 parent 441dcd0 commit 21a3e67
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 80 deletions.
17 changes: 10 additions & 7 deletions src/CreatedInstance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Mesh } from "@babylonjs/core"
import { Observer } from "@babylonjs/core/Misc/observable"
import { Nullable } from "@babylonjs/core/types";
import { LifecycleListener } from "./LifecycleListener"
import { HasPropsHandlers } from "./PropsHandler"

Expand Down Expand Up @@ -111,15 +112,17 @@ export interface CreatedInstance<T> {
propsHandlers?: HasPropsHandlers<T> // These are mostly generated
lifecycleListener?: LifecycleListener<T> // Only custom types currently support LifecycleListeners (ie: AttachesToParent)
deferredCreationProps?: any // deferred props for instance with delayed creation (ie: ShadowGenerator that needs Light).
observers: Record<string, Nullable<Observer<any>>> // tracks observer for deregistering when the handler changes (ie: pointer events for GUI)
}

export class CreatedInstanceImpl<T> implements CreatedInstance<T> {
public readonly hostInstance: T
public readonly metadata: CreatedInstanceMetadata
public parent: CreatedInstance<any> | null = null // Not the same as parent in BabylonJS, this is for internal reconciler structure. ie: graph walking
public children: CreatedInstance<any>[] = []
public propsHandlers: HasPropsHandlers<T>
public customProps: CustomProps
public readonly hostInstance: T;
public readonly metadata: CreatedInstanceMetadata;
public parent: CreatedInstance<any> | null = null; // Not the same as parent in BabylonJS, this is for internal reconciler structure. ie: graph walking
public children: CreatedInstance<any>[] = [];
public propsHandlers: HasPropsHandlers<T>;
public customProps: CustomProps;
public observers: Record<string, Nullable<Observer<any>>> = {};

constructor(hostInstance: T, metadata: CreatedInstanceMetadata, fiberObject: HasPropsHandlers<T>, customProps: CustomProps) {
this.hostInstance = hostInstance
Expand Down
3 changes: 1 addition & 2 deletions src/PropsHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,7 @@ export const checkNumericArrayDiff = (oldProp: number[] | undefined, newProp: nu

export const checkObservableDiff = (oldProp: Observable<any> | undefined, newProp: Observable<any> | undefined, propertyName: string, changedProps: PropertyUpdate[]): void => {
propertyCheck<Observable<any>>(oldProp, newProp, propertyName, PropChangeType.Observable, changedProps, (oldProp, newProp, changedProps) => {
// if it starts with 'on' then we have different handling.
if (oldProp === undefined && oldProp !== newProp) {
if (oldProp !== newProp) {
changedProps.push({
propertyName,
changeType: PropChangeType.Observable,
Expand Down
27 changes: 10 additions & 17 deletions src/ReactBabylonJSHostConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type Props = {

export type Container = {
scene: Nullable<Scene>
rootInstance: CreatedInstance<any>
rootInstance: CreatedInstance<Scene>
}

type HostContext = {} & Container
Expand All @@ -60,13 +60,14 @@ function createCreatedInstance<T, U extends HasPropsHandlers<any>>(
}

return {
children: [], // set later in lifecycle
customProps,
hostInstance,
lifecycleListener,
metadata: createdMetadata,
observers: {},
parent: null, // set later in lifecycle
children: [], // set later in lifecycle
propsHandlers,
lifecycleListener,
customProps
} as CreatedInstance<T>
}

Expand Down Expand Up @@ -300,13 +301,14 @@ const ReactBabylonJSHostConfig: HostConfig<
}

let createdInstance: CreatedInstance<null> = {
children: [],
customProps: {},
hostInstance: null,
lifecycleListener: new (CUSTOM_HOSTS as any)[type + "Fiber"](scene, scene!.getEngine(), props),
metadata,
observers: {},
parent: null,
children: [],
propsHandlers: undefined,
customProps: {},
lifecycleListener: new (CUSTOM_HOSTS as any)[type + "Fiber"](scene, scene!.getEngine(), props)
}

// onCreated and other lifecycle hooks are not called for built-in host
Expand Down Expand Up @@ -482,15 +484,6 @@ const ReactBabylonJSHostConfig: HostConfig<

if (metadata.delayCreation !== true && customProps.assignFrom === undefined) {
applyInitialPropsToCreatedInstance(createdReference, props);

// fromInstance can cause issues when used multiple times - this could point incorrectly
if (!('__propsHandlers' in createdReference.hostInstance)) {
// This property is only needed by `applyPropsToRef`, so if the propsHandlers can be made available there in another way then we don't need this property.
Object.defineProperty(createdReference.hostInstance, '__propsHandlers', {
get() { return createdReference.propsHandlers; },
enumerable: true,
});
}
} else {
createdReference.deferredCreationProps = props;
}
Expand Down Expand Up @@ -602,7 +595,7 @@ const ReactBabylonJSHostConfig: HostConfig<
if (updatePayload !== null) {
updatePayload.forEach((update: PropertyUpdate) => {
if (instance) {
applyUpdateToInstance(instance!.hostInstance, update)
applyUpdateToInstance(instance, update)
} else {
// console.warn("skipped applying update to missing instance...", update, type);
}
Expand Down
14 changes: 8 additions & 6 deletions src/Scene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { FiberScenePropsHandler } from './generatedCode';
import { FiberSceneProps } from './generatedProps';
import { UpdatePayload } from './PropsHandler';
import { Container } from './ReactBabylonJSHostConfig';
import { CreatedInstance } from './CreatedInstance';

export declare type SceneEventArgs = {
scene: BabylonJSScene;
Expand All @@ -34,7 +35,7 @@ type SceneProps = {
sceneOptions?: SceneOptions
} & FiberSceneProps

const updateScene = (props: SceneProps, prevPropsRef: MutableRefObject<Partial<SceneProps>>, scene: BabylonJSScene, propsHandler: FiberScenePropsHandler) => {
const updateScene = (props: SceneProps, prevPropsRef: MutableRefObject<Partial<SceneProps>>, scene: CreatedInstance<BabylonJSScene>, propsHandler: FiberScenePropsHandler) => {
const prevProps = prevPropsRef.current;
const updates: UpdatePayload = propsHandler.getPropertyUpdates(prevProps, props);

Expand Down Expand Up @@ -72,21 +73,22 @@ const Scene: React.FC<SceneProps> = (props: SceneProps, context?: any) => {
}

setScene(scene);
updateScene(props, prevPropsRef, scene, propsHandler);

// TODO: try to move the scene to parentComponent in updateContainer
const container: Container = {
scene: scene,
rootInstance: {
hostInstance: null,
children: [],
parent: null,
customProps: {},
hostInstance: scene,
metadata: {
className: "root"
},
customProps: {}
observers: {},
parent: null,
}
};
updateScene(props, prevPropsRef, container.rootInstance, propsHandler);

containerRef.current = container;

Expand Down Expand Up @@ -189,7 +191,7 @@ const Scene: React.FC<SceneProps> = (props: SceneProps, context?: any) => {
return;
}

updateScene(props, prevPropsRef, scene, propsHandler);
updateScene(props, prevPropsRef, containerRef.current!.rootInstance, propsHandler);

const sceneGraph = (
<SceneContext.Provider value={{
Expand Down
37 changes: 20 additions & 17 deletions src/UpdateInstance.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Vector3, Color3, Color4, Quaternion } from '@babylonjs/core';
import { PropertyUpdate, PropsHandler, PropChangeType, HasPropsHandlers } from './PropsHandler';
import { PropertyUpdate, PropsHandler, PropChangeType } from './PropsHandler';
import { CreatedInstance } from './CreatedInstance';
import { Observable, Observer } from '@babylonjs/core/Misc/observable';
import { Nullable } from '@babylonjs/core/types';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';

export const applyUpdateToInstance = (hostInstance: any, update: PropertyUpdate): void => {
let target = update.target !== undefined ? hostInstance[update.target] : hostInstance;
export const applyUpdateToInstance = (createdInstance: CreatedInstance<any>, update: PropertyUpdate): void => {
let target = update.target !== undefined ? createdInstance.hostInstance[update.target] : createdInstance.hostInstance;

switch (update.changeType) {
case PropChangeType.Primitive:
Expand Down Expand Up @@ -44,7 +46,11 @@ export const applyUpdateToInstance = (hostInstance: any, update: PropertyUpdate)
target[update.propertyName] = update.value;
break;
case PropChangeType.Observable:
target[update.propertyName].add(update.value);
const observer: Nullable<Observer<any>> = (target[update.propertyName] as Observable<any>).add(update.value);
if(update.propertyName in createdInstance.observers) {
(target[update.propertyName] as Observable<any>).remove(createdInstance.observers[update.propertyName]);
}
createdInstance.observers[update.propertyName] = observer;
break;
case PropChangeType.Method:
if (typeof target[update.propertyName] === "function") {
Expand Down Expand Up @@ -105,18 +111,20 @@ export const applyInitialPropsToCreatedInstance = (createdInstance: CreatedInsta

if (initPayload.length > 0) {
initPayload.forEach(update => {
applyUpdateToInstance(createdInstance.hostInstance, update);
applyUpdateToInstance(createdInstance, update);
})
}
}

/**
* @deprecated Please use @see applyPropsToRef instead (same functionality better name)
* @deprecated Please use @see applyPropsToRef instead
* (same functionality different parameters better name, but doesn't work with "public" ref provided by reconciler)
* @param hostInstance a babylonjs hosted instance (available with useRef)
* @param props
*/
export const applyInitialPropsToInstance = (hostInstance: any, props: any): void => {
applyPropsToRef(hostInstance, props);
// this is a bad cast. it is here for backwards compatibility with a react-spring dependency that only uses vector/color prop changes.
applyPropsToRef({hostInstance} as CreatedInstance<any>, props);
}

/**
Expand All @@ -126,15 +134,9 @@ export const applyInitialPropsToInstance = (hostInstance: any, props: any): void
* @param hostInstance babylonjs hosted instance (available with useRef)
* @param props props to apply
*/
export const applyPropsToRef = (hostInstance: any, props: Record<string, any>): void => {
// deferred creation will not have this set (ie: ShadowGenerator & PhysicsImposter). Could be added...
const propsHandlers: HasPropsHandlers<any> | undefined = hostInstance.__propsHandlers;
if (propsHandlers === undefined) {
return;
}

export const applyPropsToRef = (createdInstance: CreatedInstance<any>, props: Record<string, any>): void => {
let initPayload: PropertyUpdate[] = []
propsHandlers.getPropsHandlers().forEach((propHandler: PropsHandler<any>) => {
createdInstance.propsHandlers?.getPropsHandlers().forEach((propHandler: PropsHandler<any>) => {
let handlerUpdates: PropertyUpdate[] | null = propHandler.getPropertyUpdates(
{}, // We will reapply any props passed in (will not "clear" props, if we pass in an undefined prop)
props
Expand All @@ -146,7 +148,8 @@ export const applyPropsToRef = (hostInstance: any, props: Record<string, any>):

if (initPayload.length > 0) {
initPayload.forEach(update => {
applyUpdateToInstance(hostInstance, update);
// this is not entirely true.
applyUpdateToInstance(createdInstance, update);
})
}
}
36 changes: 18 additions & 18 deletions src/generatedCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10185,7 +10185,7 @@ export class FiberTexture implements HasPropsHandlers<FiberThinTextureProps> {
},
{
"name": "buffer",
"type": "string | HTMLImageElement | ArrayBufferView | ArrayBuffer | Blob | ImageBitmap",
"type": "string | ArrayBufferView | ArrayBuffer | HTMLImageElement | Blob | ImageBitmap",
"optional": true
},
{
Expand Down Expand Up @@ -12425,7 +12425,7 @@ export class FiberStandardRenderingPipelinePropsHandler implements PropsHandler<
checkPrimitiveDiff(oldProps.samples, newProps.samples, 'samples', changedProps)
// type: 'BabylonjsCoreScreenSpaceReflectionPostProcess' property (not coded) BabylonjsCoreStandardRenderingPipeline.screenSpaceReflectionPostProcess.
checkPrimitiveDiff(oldProps.screenSpaceReflectionsEnabled, newProps.screenSpaceReflectionsEnabled, 'screenSpaceReflectionsEnabled', changedProps)
// type: 'BabylonjsCoreDirectionalLight | BabylonjsCoreSpotLight' property (not coded) BabylonjsCoreStandardRenderingPipeline.sourceLight.
// type: 'BabylonjsCoreSpotLight | BabylonjsCoreDirectionalLight' property (not coded) BabylonjsCoreStandardRenderingPipeline.sourceLight.
// type: 'BabylonjsCorePostProcess' property (not coded) BabylonjsCoreStandardRenderingPipeline.textureAdderFinalPostProcess.
// type: 'BabylonjsCorePostProcess' property (not coded) BabylonjsCoreStandardRenderingPipeline.textureAdderPostProcess.
checkPrimitiveDiff(oldProps.VLSEnabled, newProps.VLSEnabled, 'VLSEnabled', changedProps)
Expand Down Expand Up @@ -14143,11 +14143,11 @@ export class FiberMotionBlurPostProcessPropsHandler implements PropsHandler<Fibe
* The Motion Blur Post Process which blurs an image based on the objects velocity in scene.
* Velocity can be affected by each object's rotation, position and scale depending on the transformation speed.
* As an example, all you have to do is to create the post-process:
* var mb = new BABYLON.MotionBlurPostProcess(
* 'mb', // The name of the effect.
* scene, // The scene containing the objects to blur according to their velocity.
* 1.0, // The required width/height ratio to downsize to before computing the render pass.
* camera // The camera to apply the render pass to.
* var mb = new BABYLON.MotionBlurPostProcess(
* 'mb', // The name of the effect.
* scene, // The scene containing the objects to blur according to their velocity.
* 1.0, // The required width/height ratio to downsize to before computing the render pass.
* camera // The camera to apply the render pass to.
* );
* Then, all objects moving, rotating and/or scaling will be blurred depending on the transformation speed.
*
Expand Down Expand Up @@ -15762,12 +15762,12 @@ export class FiberDynamicTerrainPropsHandler implements PropsHandler<FiberDynami
checkPrimitiveDiff(oldProps.LODNegativeZ, newProps.LODNegativeZ, 'LODNegativeZ', changedProps)
checkPrimitiveDiff(oldProps.LODPositiveX, newProps.LODPositiveX, 'LODPositiveX', changedProps)
checkPrimitiveDiff(oldProps.LODPositiveZ, newProps.LODPositiveZ, 'LODPositiveZ', changedProps)
// type: 'BabylonjsCoreFloatArray' property (not coded) ExtensionsDynamicTerrain.mapColors.
// type: 'BabylonjsCoreFloatArray' property (not coded) ExtensionsDynamicTerrain.mapData.
// type: 'BabylonjsCoreFloatArray' property (not coded) ExtensionsDynamicTerrain.mapNormals.
// type: 'number[] | Float32Array' property (not coded) ExtensionsDynamicTerrain.mapColors.
// type: 'number[] | Float32Array' property (not coded) ExtensionsDynamicTerrain.mapData.
// type: 'number[] | Float32Array' property (not coded) ExtensionsDynamicTerrain.mapNormals.
checkPrimitiveDiff(oldProps.mapSubX, newProps.mapSubX, 'mapSubX', changedProps)
checkPrimitiveDiff(oldProps.mapSubZ, newProps.mapSubZ, 'mapSubZ', changedProps)
// type: 'BabylonjsCoreFloatArray' property (not coded) ExtensionsDynamicTerrain.mapUVs.
// type: 'number[] | Float32Array' property (not coded) ExtensionsDynamicTerrain.mapUVs.
checkPrimitiveDiff(oldProps.name, newProps.name, 'name', changedProps)
checkPrimitiveDiff(oldProps.precomputeNormalsFromMap, newProps.precomputeNormalsFromMap, 'precomputeNormalsFromMap', changedProps)
checkPrimitiveDiff(oldProps.refreshEveryFrame, newProps.refreshEveryFrame, 'refreshEveryFrame', changedProps)
Expand Down Expand Up @@ -15822,7 +15822,7 @@ export class FiberDynamicTerrain implements HasPropsHandlers<FiberDynamicTerrain
},
{
"name": "mapData",
"type": "BabylonjsCoreFloatArray",
"type": "number[] | Float32Array",
"optional": true
},
{
Expand All @@ -15837,17 +15837,17 @@ export class FiberDynamicTerrain implements HasPropsHandlers<FiberDynamicTerrain
},
{
"name": "mapUVs",
"type": "BabylonjsCoreFloatArray",
"type": "number[] | Float32Array",
"optional": true
},
{
"name": "mapColors",
"type": "BabylonjsCoreFloatArray",
"type": "number[] | Float32Array",
"optional": true
},
{
"name": "mapNormals",
"type": "BabylonjsCoreFloatArray",
"type": "number[] | Float32Array",
"optional": true
},
{
Expand Down Expand Up @@ -16109,9 +16109,9 @@ export class FiberDetailMapConfigurationPropsHandler implements PropsHandler<Fib
* Define the code related to the detail map parameters of a material
*
* Inspired from:
* Unity: https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@9.0/manual/Mask-Map-and-Detail-Map.html and https://docs.unity3d.com/Manual/StandardShaderMaterialParameterDetail.html
* Unreal: https://docs.unrealengine.com/en-US/Engine/Rendering/Materials/HowTo/DetailTexturing/index.html
* Cryengine: https://docs.cryengine.com/display/SDKDOC2/Detail+Maps
* Unity: https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@9.0/manual/Mask-Map-and-Detail-Map.html and https://docs.unity3d.com/Manual/StandardShaderMaterialParameterDetail.html
* Unreal: https://docs.unrealengine.com/en-US/Engine/Rendering/Materials/HowTo/DetailTexturing/index.html
* Cryengine: https://docs.cryengine.com/display/SDKDOC2/Detail+Maps
*
* This code has been generated
*/
Expand Down
20 changes: 10 additions & 10 deletions src/generatedProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2524,7 +2524,7 @@ export type FiberTexturePropsCtor = {
samplingMode?: number;
onLoad?: () => void;
onError?: (message?: string, exception?: any) => void;
buffer?: string | HTMLImageElement | ArrayBufferView | ArrayBuffer | Blob | ImageBitmap;
buffer?: string | ArrayBufferView | ArrayBuffer | HTMLImageElement | Blob | ImageBitmap;
deleteBuffer?: boolean;
format?: number;
mimeType?: string;
Expand Down Expand Up @@ -3001,7 +3001,7 @@ export type FiberStandardRenderingPipelineProps = {
samples?: number;
screenSpaceReflectionPostProcess?: BabylonjsCoreScreenSpaceReflectionPostProcess;
screenSpaceReflectionsEnabled?: boolean;
sourceLight?: BabylonjsCoreDirectionalLight | BabylonjsCoreSpotLight;
sourceLight?: BabylonjsCoreSpotLight | BabylonjsCoreDirectionalLight;
textureAdderFinalPostProcess?: BabylonjsCorePostProcess;
textureAdderPostProcess?: BabylonjsCorePostProcess;
VLSEnabled?: boolean;
Expand Down Expand Up @@ -3673,12 +3673,12 @@ export type FiberDynamicTerrainProps = {
LODNegativeZ?: boolean;
LODPositiveX?: boolean;
LODPositiveZ?: boolean;
mapColors?: BabylonjsCoreFloatArray;
mapData?: BabylonjsCoreFloatArray;
mapNormals?: BabylonjsCoreFloatArray;
mapColors?: number[] | Float32Array;
mapData?: number[] | Float32Array;
mapNormals?: number[] | Float32Array;
mapSubX?: number;
mapSubZ?: number;
mapUVs?: BabylonjsCoreFloatArray;
mapUVs?: number[] | Float32Array;
name?: string;
precomputeNormalsFromMap?: boolean;
refreshEveryFrame?: boolean;
Expand All @@ -3690,12 +3690,12 @@ export type FiberDynamicTerrainProps = {
export type FiberDynamicTerrainPropsCtor = {
name: string;
terrainSub?: number;
mapData?: BabylonjsCoreFloatArray;
mapData?: number[] | Float32Array;
mapSubX?: number;
mapSubZ?: number;
mapUVs?: BabylonjsCoreFloatArray;
mapColors?: BabylonjsCoreFloatArray;
mapNormals?: BabylonjsCoreFloatArray;
mapUVs?: number[] | Float32Array;
mapColors?: number[] | Float32Array;
mapNormals?: number[] | Float32Array;
invertSide?: boolean;
camera?: BabylonjsCoreCamera;
SPmapData?: number[][] | Float32Array[];
Expand Down
Loading

0 comments on commit 21a3e67

Please sign in to comment.