Skip to content

Commit

Permalink
chore(general,components): add zustand for store reactivity
Browse files Browse the repository at this point in the history
  • Loading branch information
simonedevit committed Jan 24, 2025
1 parent fd6baba commit 8c20e08
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 115 deletions.
38 changes: 34 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions packages/library/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@
"acorn": "^8.11.3",
"its-fine": "^1.2.5",
"lodash": "^4.17.21",
"react-reconciler": "^0.29.2"
"react-reconciler": "^0.29.2",
"zustand": "^5.0.3"
},
"peerDependencies": {
"@babylonjs/core": "^7.40.2 ",
"@babylonjs/gui": "^7.40.2 ",
"@babylonjs/havok": "^1.3.7",
"@babylonjs/react-native": "^1.8.6",
"react": "^18.2.0",
"react-dom": "^18.3.1",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.3.1"
"@types/react-dom": "^18.3.1",
"react": "^18.2.0",
"react-dom": "^18.3.1"
},
"peerDependenciesMeta": {
"react-dom": {
Expand Down
38 changes: 23 additions & 15 deletions packages/library/src/core/Scene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import React, { useEffect, useRef } from 'react';
import { Scene as BabylonScene, SceneOptions, WebXRDefaultExperienceOptions, HavokPlugin, Vector3, Nullable, Camera } from '@babylonjs/core';
import { GUI3DManager } from '@babylonjs/gui';
import HavokPhysics from '@babylonjs/havok';
import { SceneContext, EngineContextType } from './hooks';
import { SceneContext, EngineStore, Store, createBabylonStore } from './store';
import { RootContainer } from '@types';
import Reactylon from '../reconciler';
import { type ContextBridge, useContextBridge } from 'its-fine';
import { type CameraProps } from '@props';
import { type StoreApi } from 'zustand';

type SceneProps = React.PropsWithChildren<{
/**
Expand All @@ -25,17 +26,19 @@ type SceneProps = React.PropsWithChildren<{
* @internal
* This prop is only for internal use and should not be passed to this component.
*/
_context?: EngineContextType;
_context?: EngineStore;
}>;

//FIXME: replace global var with a singleton Manager
export let activeScene: BabylonScene | null = null;

export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneReady, isGui3DManager, xrDefaultExperienceOptions, physicsOptions, _context, ...rest }) => {
const { engine, isMultipleCanvas, isMultipleScene } = _context as EngineContextType;
const { engine, isMultipleCanvas, isMultipleScene } = _context as EngineStore;
const rootContainer = useRef<Nullable<RootContainer>>(null);
const isFirstRender = useRef(false);

const store = useRef<StoreApi<Store>>();

// Returns a bridged context provider that forwards context
const Bridge: ContextBridge = useContextBridge();

Expand Down Expand Up @@ -125,13 +128,17 @@ export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneRea
/* --------------------------------------------------------------------------------------- */
/* RECONCILER
------------------------------------------------------------------------------------------ */
rootContainer.current = {
store.current = createBabylonStore({
engine,
scene,
canvas,
isMultipleCanvas,
isMultipleScene,
xrExperience,
});

rootContainer.current = {
...store.current.getState(),
metadata: {
children: [],
},
Expand All @@ -140,7 +147,7 @@ export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneRea
// Renders children with bridged context into a secondary renderer
Reactylon.render(
<Bridge>
<SceneContext.Provider value={{ engine, isMultipleCanvas, isMultipleScene, scene, xrExperience, canvas }}>{children}</SceneContext.Provider>
<SceneContext.Provider value={store.current}>{children}</SceneContext.Provider>
</Bridge>,
rootContainer.current!,
);
Expand All @@ -156,16 +163,17 @@ export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneRea

useEffect(() => {
if (!isFirstRender.current) {
const { scene, xrExperience, canvas } = rootContainer.current!;
// Renders children with bridged context into a secondary renderer
Reactylon.render(
<Bridge>
<SceneContext.Provider value={{ engine, isMultipleCanvas, isMultipleScene, scene, xrExperience, canvas }}>{children}</SceneContext.Provider>
</Bridge>,
rootContainer.current!,
);
} else {
isFirstRender.current = false;
if (store.current) {
// Renders children with bridged context into a secondary renderer
Reactylon.render(
<Bridge>
<SceneContext.Provider value={store.current}>{children}</SceneContext.Provider>
</Bridge>,
rootContainer.current!,
);
} else {
isFirstRender.current = false;
}
}
});

Expand Down
46 changes: 0 additions & 46 deletions packages/library/src/core/hooks.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion packages/library/src/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './hooks';
export * from './store';
export * from './Scene';
69 changes: 69 additions & 0 deletions packages/library/src/core/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { createStore, StoreApi, useStore } from 'zustand';
import { type Nullable, Engine, Scene, WebXRDefaultExperience } from '@babylonjs/core';
import { createContext, useContext } from 'react';

export type EngineStore = {
engine: Engine;
isMultipleCanvas: boolean;
isMultipleScene: boolean;
};

export type Store = EngineStore & {
scene: Scene;
canvas: HTMLCanvasElement | WebGLRenderingContext;
xrExperience: Nullable<WebXRDefaultExperience>;
//sceneReady: boolean;
};

export const SceneContext = createContext<StoreApi<Store> | null>(null);
SceneContext.displayName = 'SceneContext';

export const createBabylonStore = (initialProps: Store) => {
return createStore<Store>()(_set => initialProps);
};

/**
* Get the Babylon context.
*/
const useBabylonContext = <T>(selector: (state: Store) => T): T => {
const store = useContext(SceneContext);
if (!store) {
throw new Error('Missing SceneContext.Provider in the tree');
}
return useStore(store, selector);
};

/**
* Get the engine from the context.
*/
export function useEngine(): Engine;
export function useEngine<T>(selector: (engine: Engine) => T): T;

export function useEngine<T>(selector?: (engine: Engine) => T): T | Engine {
return useBabylonContext(state => (selector ? selector(state.engine) : state.engine));
}

/**
* Get the scene from the context.
*/
export function useScene(): Scene;
export function useScene<T>(selector: (scene: Scene) => T): T;

export function useScene<T>(selector?: (scene: Scene) => T): T | Scene {
return useBabylonContext(state => (selector ? selector(state.scene) : state.scene));
}

/**
* Get the canvas DOM element from the context.
*/
export const useCanvas = () => useBabylonContext(state => state.canvas);

/**
* Get the XR experience from the context.
*/
export function useXrExperience(): WebXRDefaultExperience;
export function useXrExperience<T>(selector: (xrExperience: WebXRDefaultExperience) => T): T;

export function useXrExperience<T>(selector?: (xrExperience: WebXRDefaultExperience) => T): T | WebXRDefaultExperience {
return useBabylonContext(state => (selector ? selector(state.xrExperience!) : state.xrExperience!));
}
2 changes: 1 addition & 1 deletion packages/library/src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export type UpdatePayload = {
export type RootContainer = {
engine: Engine;
scene: Scene;
canvas: HTMLCanvasElement;
canvas: HTMLCanvasElement | WebGLRenderingContext;
isMultipleCanvas: boolean;
isMultipleScene: boolean;
xrExperience: Nullable<WebXRDefaultExperience>;
Expand Down
Loading

0 comments on commit 8c20e08

Please sign in to comment.