Skip to content

Commit 01355f6

Browse files
committed
feat(general,components): add zustand for store reactivity
1 parent fd6baba commit 01355f6

File tree

8 files changed

+174
-115
lines changed

8 files changed

+174
-115
lines changed

package-lock.json

Lines changed: 34 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/library/package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,18 @@
2929
"acorn": "^8.11.3",
3030
"its-fine": "^1.2.5",
3131
"lodash": "^4.17.21",
32-
"react-reconciler": "^0.29.2"
32+
"react-reconciler": "^0.29.2",
33+
"zustand": "^5.0.3"
3334
},
3435
"peerDependencies": {
3536
"@babylonjs/core": "^7.40.2 ",
3637
"@babylonjs/gui": "^7.40.2 ",
3738
"@babylonjs/havok": "^1.3.7",
3839
"@babylonjs/react-native": "^1.8.6",
39-
"react": "^18.2.0",
40-
"react-dom": "^18.3.1",
4140
"@types/react": "^18.2.0",
42-
"@types/react-dom": "^18.3.1"
41+
"@types/react-dom": "^18.3.1",
42+
"react": "^18.2.0",
43+
"react-dom": "^18.3.1"
4344
},
4445
"peerDependenciesMeta": {
4546
"react-dom": {

packages/library/src/core/Scene.tsx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import React, { useEffect, useRef } from 'react';
22
import { Scene as BabylonScene, SceneOptions, WebXRDefaultExperienceOptions, HavokPlugin, Vector3, Nullable, Camera } from '@babylonjs/core';
33
import { GUI3DManager } from '@babylonjs/gui';
44
import HavokPhysics from '@babylonjs/havok';
5-
import { SceneContext, EngineContextType } from './hooks';
5+
import { SceneContext, EngineStore, Store, createBabylonStore } from './store';
66
import { RootContainer } from '@types';
77
import Reactylon from '../reconciler';
88
import { type ContextBridge, useContextBridge } from 'its-fine';
99
import { type CameraProps } from '@props';
10+
import { type StoreApi } from 'zustand';
1011

1112
type SceneProps = React.PropsWithChildren<{
1213
/**
@@ -25,17 +26,19 @@ type SceneProps = React.PropsWithChildren<{
2526
* @internal
2627
* This prop is only for internal use and should not be passed to this component.
2728
*/
28-
_context?: EngineContextType;
29+
_context?: EngineStore;
2930
}>;
3031

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

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

40+
const store = useRef<StoreApi<Store>>();
41+
3942
// Returns a bridged context provider that forwards context
4043
const Bridge: ContextBridge = useContextBridge();
4144

@@ -125,13 +128,17 @@ export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneRea
125128
/* --------------------------------------------------------------------------------------- */
126129
/* RECONCILER
127130
------------------------------------------------------------------------------------------ */
128-
rootContainer.current = {
131+
store.current = createBabylonStore({
129132
engine,
130133
scene,
131134
canvas,
132135
isMultipleCanvas,
133136
isMultipleScene,
134137
xrExperience,
138+
});
139+
140+
rootContainer.current = {
141+
...store.current.getState(),
135142
metadata: {
136143
children: [],
137144
},
@@ -140,7 +147,7 @@ export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneRea
140147
// Renders children with bridged context into a secondary renderer
141148
Reactylon.render(
142149
<Bridge>
143-
<SceneContext.Provider value={{ engine, isMultipleCanvas, isMultipleScene, scene, xrExperience, canvas }}>{children}</SceneContext.Provider>
150+
<SceneContext.Provider value={store.current}>{children}</SceneContext.Provider>
144151
</Bridge>,
145152
rootContainer.current!,
146153
);
@@ -156,16 +163,17 @@ export const Scene: React.FC<SceneProps> = ({ children, sceneOptions, onSceneRea
156163

157164
useEffect(() => {
158165
if (!isFirstRender.current) {
159-
const { scene, xrExperience, canvas } = rootContainer.current!;
160-
// Renders children with bridged context into a secondary renderer
161-
Reactylon.render(
162-
<Bridge>
163-
<SceneContext.Provider value={{ engine, isMultipleCanvas, isMultipleScene, scene, xrExperience, canvas }}>{children}</SceneContext.Provider>
164-
</Bridge>,
165-
rootContainer.current!,
166-
);
167-
} else {
168-
isFirstRender.current = false;
166+
if (store.current) {
167+
// Renders children with bridged context into a secondary renderer
168+
Reactylon.render(
169+
<Bridge>
170+
<SceneContext.Provider value={store.current}>{children}</SceneContext.Provider>
171+
</Bridge>,
172+
rootContainer.current!,
173+
);
174+
} else {
175+
isFirstRender.current = false;
176+
}
169177
}
170178
});
171179

packages/library/src/core/hooks.tsx

Lines changed: 0 additions & 46 deletions
This file was deleted.

packages/library/src/core/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
export * from './hooks';
1+
export * from './store';
22
export * from './Scene';

packages/library/src/core/store.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createStore, StoreApi, useStore } from 'zustand';
2+
import { type Nullable, Engine, Scene, WebXRDefaultExperience } from '@babylonjs/core';
3+
import { createContext, useContext } from 'react';
4+
5+
export type EngineStore = {
6+
engine: Engine;
7+
isMultipleCanvas: boolean;
8+
isMultipleScene: boolean;
9+
};
10+
11+
export type Store = EngineStore & {
12+
scene: Scene;
13+
canvas: HTMLCanvasElement | WebGLRenderingContext;
14+
xrExperience: Nullable<WebXRDefaultExperience>;
15+
//sceneReady: boolean;
16+
};
17+
18+
export const SceneContext = createContext<StoreApi<Store> | null>(null);
19+
SceneContext.displayName = 'SceneContext';
20+
21+
export const createBabylonStore = (initialProps: Store) => {
22+
return createStore<Store>()(_set => initialProps);
23+
};
24+
25+
/**
26+
* Get the Babylon context.
27+
*/
28+
const useBabylonContext = <T>(selector: (state: Store) => T): T => {
29+
const store = useContext(SceneContext);
30+
if (!store) {
31+
throw new Error('Missing SceneContext.Provider in the tree');
32+
}
33+
return useStore(store, selector);
34+
};
35+
36+
/**
37+
* Get the engine from the context.
38+
*/
39+
export function useEngine(): Engine;
40+
export function useEngine<T>(selector: (engine: Engine) => T): T;
41+
42+
export function useEngine<T>(selector?: (engine: Engine) => T): T | Engine {
43+
return useBabylonContext(state => (selector ? selector(state.engine) : state.engine));
44+
}
45+
46+
/**
47+
* Get the scene from the context.
48+
*/
49+
export function useScene(): Scene;
50+
export function useScene<T>(selector: (scene: Scene) => T): T;
51+
52+
export function useScene<T>(selector?: (scene: Scene) => T): T | Scene {
53+
return useBabylonContext(state => (selector ? selector(state.scene) : state.scene));
54+
}
55+
56+
/**
57+
* Get the canvas DOM element from the context.
58+
*/
59+
export const useCanvas = () => useBabylonContext(state => state.canvas);
60+
61+
/**
62+
* Get the XR experience from the context.
63+
*/
64+
export function useXrExperience(): WebXRDefaultExperience;
65+
export function useXrExperience<T>(selector: (xrExperience: WebXRDefaultExperience) => T): T;
66+
67+
export function useXrExperience<T>(selector?: (xrExperience: WebXRDefaultExperience) => T): T | WebXRDefaultExperience {
68+
return useBabylonContext(state => (selector ? selector(state.xrExperience!) : state.xrExperience!));
69+
}

packages/library/src/types/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export type UpdatePayload = {
7676
export type RootContainer = {
7777
engine: Engine;
7878
scene: Scene;
79-
canvas: HTMLCanvasElement;
79+
canvas: HTMLCanvasElement | WebGLRenderingContext;
8080
isMultipleCanvas: boolean;
8181
isMultipleScene: boolean;
8282
xrExperience: Nullable<WebXRDefaultExperience>;

0 commit comments

Comments
 (0)