Skip to content

Commit

Permalink
fix: set Scene props on creation #77
Browse files Browse the repository at this point in the history
  • Loading branch information
brianzinn committed Jul 15, 2020
1 parent 416da78 commit 2e7b153
Showing 1 changed file with 136 additions and 151 deletions.
287 changes: 136 additions & 151 deletions src/Scene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
* LICENSE.txt file in the root directory of this source tree.
*/

import React, {createContext, useContext, useEffect, useRef, useState,} from 'react';
import ReactReconciler, {Reconciler} from "react-reconciler";
import React, { createContext, useContext, useEffect, useRef, useState, MutableRefObject } from 'react';
import ReactReconciler, { Reconciler } from "react-reconciler";

import {BabylonJSContext, withBabylonJS, WithBabylonJSContext} from './Engine';
import { BabylonJSContext, withBabylonJS, WithBabylonJSContext } from './Engine';
import {
AbstractMesh,
Engine as BabylonJSEngine,
Expand All @@ -20,11 +20,11 @@ import {
SceneOptions
} from '@babylonjs/core';

import {applyUpdateToInstance} from "./UpdateInstance";
import ReactBabylonJSHostConfig, {Container} from './ReactBabylonJSHostConfig';
import {FiberScenePropsHandler} from './generatedCode';
import {FiberSceneProps} from './generatedProps';
import {UpdatePayload} from './PropsHandler';
import { applyUpdateToInstance } from "./UpdateInstance";
import ReactBabylonJSHostConfig, { Container } from './ReactBabylonJSHostConfig';
import { FiberScenePropsHandler } from './generatedCode';
import { FiberSceneProps } from './generatedProps';
import { UpdatePayload } from './PropsHandler';

export interface WithSceneContext {
engine: Nullable<BabylonJSEngine>
Expand All @@ -46,21 +46,20 @@ export const SceneContext = createContext<WithSceneContext>({
sceneReady: false
})


export const useBabylonEngine = (): Nullable<BabylonJSEngine>=> useContext(SceneContext).engine
export const useBabylonEngine = (): Nullable<BabylonJSEngine> => useContext(SceneContext).engine
export const useBabylonScene = () => useContext(SceneContext).scene
export const useBabylonCanvas = (): Nullable<HTMLCanvasElement | WebGLRenderingContext> => useContext(SceneContext).canvas

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

export function withScene<P extends { sceneContext: WithSceneContext },
R = Omit<P, 'sceneContext'>>(
Component: React.ComponentClass<P> | React.FunctionComponent<P>
): React.FunctionComponent<R> {
Component: React.ComponentClass<P> | React.FunctionComponent<P>
): React.FunctionComponent<R> {
return function BoundComponent(props: R) {
return (
<SceneContext.Consumer>
{ctx => <Component {...props as any} sceneContext={ctx}/>}
{ctx => <Component {...props as any} sceneContext={ctx} />}
</SceneContext.Consumer>
);
};
Expand All @@ -77,18 +76,20 @@ type SceneProps = {
sceneOptions?: SceneOptions
} & FiberSceneProps

const usePrevious = <T extends any>(value: T) => {
const ref = useRef<T>();

useEffect(() => {
ref.current = value;
}, [value]);
const updateScene = (props: SceneProps, prevPropsRef: MutableRefObject<Partial<SceneProps>>, scene: BabylonJSScene, propsHandler: FiberScenePropsHandler) => {
const prevProps = prevPropsRef.current;
const updates: UpdatePayload = propsHandler.getPropertyUpdates(prevProps, props);

return ref.current;
};
if (updates !== null) {
updates.forEach(propertyUpdate => {
applyUpdateToInstance(scene, propertyUpdate, 'scene')
})
}
prevPropsRef.current = props;
}

const Scene: React.FC<SceneProps> = (props: SceneProps, context?: any) => {
const {engine} = useContext(BabylonJSContext)
const { engine } = useContext(BabylonJSContext)

const [propsHandler] = useState(new FiberScenePropsHandler());
const [sceneReady, setSceneReady] = useState(false);
Expand All @@ -98,157 +99,144 @@ const Scene: React.FC<SceneProps> = (props: SceneProps, context?: any) => {
// TODO: make this strongly typed
const [renderer, setRenderer] = useState<Nullable<Reconciler<any, any, any, any>>>(null);

const prevPropsRef = useRef({});
const prevPropsRef: MutableRefObject<Partial<SceneProps>> = useRef<Partial<SceneProps>>({});

// initialize babylon scene
useEffect(() => {
// const onSceneReady = (eventData: BabylonJSScene, eventState: EventState) => {
// setSceneReady(true);
// }

const scene = new BabylonJSScene(engine!, props.sceneOptions)
// const onReadyObservable: Nullable<Observer<BabylonJSScene>> = scene.onReadyObservable.add(onSceneReady);
if (scene.isReady()) {
// scene.onReadyObservable.remove(onReadyObservable);
setSceneReady(true)
} else {
console.error('Scene is not ready. Report issue in react-babylonjs repo')
}

setScene(scene);

const isAsync = false // Disables experimental async rendering
const scene = new BabylonJSScene(engine!, props.sceneOptions)
// const onReadyObservable: Nullable<Observer<BabylonJSScene>> = scene.onReadyObservable.add(onSceneReady);
if (scene.isReady()) {
// scene.onReadyObservable.remove(onReadyObservable);
setSceneReady(true)
} else {
console.error('Scene is not ready. Report issue in react-babylonjs repo')
}

const container: Container = {
engine: props.babylonJSContext.engine,
canvas: props.babylonJSContext.canvas,
scene: scene,
rootInstance: {
hostInstance: null,
children: [],
parent: null,
metadata: {
className: "root"
},
customProps: {}
}
setScene(scene);
updateScene(props, prevPropsRef, scene, propsHandler);

const isAsync = false // Disables experimental async rendering

const container: Container = {
engine: props.babylonJSContext.engine,
canvas: props.babylonJSContext.canvas,
scene: scene,
rootInstance: {
hostInstance: null,
children: [],
parent: null,
metadata: {
className: "root"
},
customProps: {}
}
}

const renderer = ReactReconciler(ReactBabylonJSHostConfig);
setRenderer(renderer)
const fiberRoot = renderer.createContainer(container, isAsync, false /* hydrate true == better HMR? */)
setFiberRoot(fiberRoot);
const renderer = ReactReconciler(ReactBabylonJSHostConfig);
setRenderer(renderer);
const fiberRoot = renderer.createContainer(container, isAsync, false /* hydrate true == better HMR? */);
setFiberRoot(fiberRoot);

renderer.injectIntoDevTools({
bundleType: process.env.NODE_ENV === 'production' ? 0 : 1,
version: '2.0.0',
rendererPackageName: 'react-babylonjs'
});

const pointerDownObservable: Nullable<Observer<PointerInfo>> = scene.onPointerObservable.add(
(evt: PointerInfo) => {
if (typeof props.onScenePointerDown === 'function') {
props.onScenePointerDown(evt, scene);
}

renderer.injectIntoDevTools({
bundleType: process.env.NODE_ENV === 'production' ? 0 : 1,
version: '2.0.0',
rendererPackageName: 'react-babylonjs'
})
if (evt && evt.pickInfo && evt.pickInfo.hit && evt.pickInfo.pickedMesh) {
let mesh = evt.pickInfo.pickedMesh;
if (typeof props.onMeshPicked === 'function') {
props.onMeshPicked(mesh, scene);
} else {
// console.log('onMeshPicked not being called')
}
}
},
PointerEventTypes.POINTERDOWN
);

const pointerDownObservable: Nullable<Observer<PointerInfo>> = scene.onPointerObservable.add(
// can only be assigned on init
let pointerUpObservable: Nullable<Observer<PointerInfo>> = null;
if (typeof props.onScenePointerUp === 'function') {
pointerUpObservable = scene.onPointerObservable.add(
(evt: PointerInfo) => {
if (typeof props.onScenePointerDown === 'function') {
props.onScenePointerDown(evt, scene)
}
props.onScenePointerUp!(evt, scene)
},
PointerEventTypes.POINTERUP
)
}

if (evt && evt.pickInfo && evt.pickInfo.hit && evt.pickInfo.pickedMesh) {
let mesh = evt.pickInfo.pickedMesh
if (typeof props.onMeshPicked === 'function') {
props.onMeshPicked(mesh, scene)
} else {
// console.log('onMeshPicked not being called')
}
}
// can only be assigned on init
let pointerMoveObservable: Nullable<Observer<PointerInfo>> = null;
if (typeof props.onScenePointerMove === 'function') {
pointerMoveObservable = scene.onPointerObservable.add(
(evt: PointerInfo) => {
props.onScenePointerMove!(evt, scene);
},
PointerEventTypes.POINTERDOWN
);

// can only be assigned on init
let pointerUpObservable: Nullable<Observer<PointerInfo>> = null;
if (typeof props.onScenePointerUp === 'function') {
pointerUpObservable = scene.onPointerObservable.add(
(evt: PointerInfo) => {
props.onScenePointerUp!(evt, scene)
},
PointerEventTypes.POINTERUP
)
}
;

// can only be assigned on init
let pointerMoveObservable: Nullable<Observer<PointerInfo>> = null;
if (typeof props.onScenePointerMove === 'function') {
pointerMoveObservable = scene.onPointerObservable.add(
(evt: PointerInfo) => {
props.onScenePointerMove!(evt, scene)
},
PointerEventTypes.POINTERMOVE)
}
;

if (typeof props.onSceneMount === 'function') {
props.onSceneMount({
scene: scene,
canvas: scene.getEngine().getRenderingCanvas()!
});
// TODO: console.error if canvas is not attached. runRenderLoop() is expected to be part of onSceneMount().
}
PointerEventTypes.POINTERMOVE)
}

// TODO: change enable physics to 'usePhysics' taking an object with a Vector3 and 'any'.
// NOTE: must be enabled for updating container (cannot add impostors w/o physics enabled)
if (Array.isArray(props.enablePhysics)) {
scene.enablePhysics(props.enablePhysics[0], props.enablePhysics[1]);
}
if (typeof props.onSceneMount === 'function') {
props.onSceneMount({
scene: scene,
canvas: scene.getEngine().getRenderingCanvas()!
});
// TODO: console.error if canvas is not attached. runRenderLoop() is expected to be part of onSceneMount().
}

// update the root Container
renderer.updateContainer(
<SceneContext.Provider value={{
engine: props.babylonJSContext.engine,
canvas: props.babylonJSContext.canvas,
scene,
sceneReady
}}>
{props.children}
</SceneContext.Provider>, fiberRoot, undefined, () => { /* empty */
}
)
// TODO: change enable physics to 'usePhysics' taking an object with a Vector3 and 'any'.
// NOTE: must be enabled for updating container (cannot add impostors w/o physics enabled)
if (Array.isArray(props.enablePhysics)) {
scene.enablePhysics(props.enablePhysics[0], props.enablePhysics[1]);
}

return () => {
if (pointerDownObservable) {
scene.onPointerObservable.remove(pointerDownObservable);
}
// update the root Container
renderer.updateContainer(
<SceneContext.Provider value={{
engine: props.babylonJSContext.engine,
canvas: props.babylonJSContext.canvas,
scene,
sceneReady
}}>
{props.children}
</SceneContext.Provider>, fiberRoot, undefined, () => { /* empty */
}
);

if (pointerUpObservable) {
scene.onPointerObservable.remove(pointerUpObservable);
}
return () => {
if (pointerDownObservable) {
scene.onPointerObservable.remove(pointerDownObservable);
}

if (pointerMoveObservable) {
scene.onPointerObservable.remove(pointerMoveObservable);
}
if (pointerUpObservable) {
scene.onPointerObservable.remove(pointerUpObservable);
}

if (scene.isDisposed === false) {
scene.dispose();
}
if (pointerMoveObservable) {
scene.onPointerObservable.remove(pointerMoveObservable);
}

if (scene.isDisposed === false) {
scene.dispose();
}
},
}
},
[/* no deps, so called only on un/mount */]
);


// update babylon scene
useEffect(() => {
if (engine === null || scene === null || renderer === null) {
return;
}

const prevProps = prevPropsRef.current;
const updates: UpdatePayload = propsHandler.getPropertyUpdates(prevProps, props);

if (updates !== null) {
updates.forEach(propertyUpdate => {
applyUpdateToInstance(scene, propertyUpdate, 'scene')
})
}
updateScene(props, prevPropsRef, scene, propsHandler);

renderer.updateContainer(
<SceneContext.Provider value={{
Expand All @@ -261,11 +249,8 @@ const Scene: React.FC<SceneProps> = (props: SceneProps, context?: any) => {
</SceneContext.Provider>,
fiberRoot,
undefined,
() => { /* called after container is updated. we may want an external observable here */
}
)

prevPropsRef.current = props;
() => { /* called after container is updated. we may want an external observable here */ }
);
});

return null;
Expand Down

0 comments on commit 2e7b153

Please sign in to comment.