Skip to content

Commit b1821a1

Browse files
authored
Add more Render hooks to allow direct usage of Engine without Camera (#346)
* Add Engine Render Hooks * More Hooks * Fix render loop hook
1 parent 2094d67 commit b1821a1

File tree

5 files changed

+90
-53
lines changed

5 files changed

+90
-53
lines changed

Modules/@babylonjs/react-native/EngineHook.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Platform } from 'react-native';
33
import { PERMISSIONS, check, request } from 'react-native-permissions';
44
import { Engine, WebXRSessionManager, WebXRExperienceHelper, Color4, Tools } from '@babylonjs/core';
55
import { ReactNativeEngine } from './ReactNativeEngine';
6-
import './VersionValidation';
6+
77
import * as base64 from 'base-64';
88

99
// These are errors that are normally thrown by WebXR's requestSession, so we should throw the same errors under similar circumstances so app code can be written the same for browser or native.

Modules/@babylonjs/react-native/EngineView.tsx

Lines changed: 13 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef } from 'react';
2-
import { requireNativeComponent, ViewProps, AppState, AppStateStatus, View, Text, findNodeHandle, UIManager } from 'react-native';
1+
import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef, useMemo } from 'react';
2+
import { ViewProps, View, Text, findNodeHandle, UIManager } from 'react-native';
33
import { Camera, SceneInstrumentation } from '@babylonjs/core';
4-
import { ensureInitialized } from './BabylonModule';
54
import { ReactNativeEngine } from './ReactNativeEngine';
6-
7-
declare const global: any;
8-
9-
interface NativeEngineViewProps extends ViewProps {
10-
isTransparent: boolean;
11-
onSnapshotDataReturned: (event: SyntheticEvent) => void;
12-
}
13-
14-
const NativeEngineView: {
15-
prototype: Component<NativeEngineViewProps>;
16-
new(props: Readonly<NativeEngineViewProps>): Component<NativeEngineViewProps>;
17-
} = global['EngineView'] || (global['EngineView'] = requireNativeComponent('EngineView'));
5+
import { useModuleInitializer, useRenderLoop } from './NativeEngineHook';
6+
import { NativeEngineViewProps, NativeEngineView } from './NativeEngineView';
187

198
export interface EngineViewProps extends ViewProps {
209
camera?: Camera;
@@ -33,53 +22,25 @@ interface SceneStats {
3322
}
3423

3524
export const EngineView: FunctionComponent<EngineViewProps> = (props: EngineViewProps) => {
36-
const [initialized, setInitialized] = useState<boolean>();
37-
const [appState, setAppState] = useState(AppState.currentState);
3825
//const [fps, setFps] = useState<number>();
3926
const [sceneStats, setSceneStats] = useState<SceneStats>();
4027
const engineViewRef = useRef<Component<NativeEngineViewProps>>(null);
4128
const snapshotPromise = useRef<{ promise: Promise<string>, resolve: (data: string) => void }>();
4229
const isTransparent = props.isTransparent || false
4330

44-
useEffect(() => {
45-
(async () => {
46-
setInitialized(await ensureInitialized());
47-
})();
48-
}, []);
31+
const initialized = useModuleInitializer();
4932

50-
useEffect(() => {
51-
const onAppStateChanged = (appState: AppStateStatus) => {
52-
setAppState(appState);
53-
};
54-
55-
AppState.addEventListener("change", onAppStateChanged);
33+
const engine = useMemo(() => {
34+
return props.camera?.getScene().getEngine() as ReactNativeEngine;
35+
}, [props.camera]);
5636

57-
return () => {
58-
AppState.removeEventListener("change", onAppStateChanged);
37+
const renderLoop = useCallback(() => {
38+
for (let scene of engine.scenes) {
39+
scene.render();
5940
}
60-
}, []);
61-
62-
useEffect(() => {
63-
if (props.camera && appState === "active") {
64-
const engine = props.camera.getScene().getEngine() as ReactNativeEngine;
41+
}, [engine]);
6542

66-
if (!engine.isDisposed) {
67-
engine.runRenderLoop(() => {
68-
for (let scene of engine.scenes) {
69-
scene.render();
70-
}
71-
});
72-
73-
return () => {
74-
if (!engine.isDisposed) {
75-
engine.stopRenderLoop();
76-
}
77-
};
78-
}
79-
}
80-
81-
return undefined;
82-
}, [props.camera, appState]);
43+
useRenderLoop(engine, renderLoop);
8344

8445
useEffect(() => {
8546
if (props.camera && (props.displayFrameRate ?? __DEV__)) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { useEffect, useState } from 'react';
2+
import { AppState, AppStateStatus } from 'react-native';
3+
import { ensureInitialized } from './BabylonModule';
4+
import { ReactNativeEngine } from './ReactNativeEngine';
5+
6+
import './VersionValidation';
7+
8+
export function useModuleInitializer(): boolean | undefined {
9+
const [initialized, setInitialized] = useState<boolean>();
10+
11+
useEffect(() => {
12+
(async () => {
13+
setInitialized(await ensureInitialized());
14+
})();
15+
}, []);
16+
17+
return initialized;
18+
}
19+
20+
function useAppState(): string {
21+
const [appState, setAppState] = useState(AppState.currentState);
22+
23+
useEffect(() => {
24+
const onAppStateChanged = (appState: AppStateStatus) => {
25+
setAppState(appState);
26+
};
27+
28+
AppState.addEventListener("change", onAppStateChanged);
29+
30+
return () => {
31+
AppState.removeEventListener("change", onAppStateChanged);
32+
}
33+
}, []);
34+
35+
return appState;
36+
}
37+
38+
export function useRenderLoop(engine: ReactNativeEngine | undefined, renderCallback: () => void): void {
39+
const appState = useAppState();
40+
41+
useEffect(() => {
42+
if (engine && appState === "active") {
43+
if (!engine.isDisposed) {
44+
engine.runRenderLoop(renderCallback);
45+
46+
return () => {
47+
if (!engine.isDisposed) {
48+
engine.stopRenderLoop();
49+
}
50+
};
51+
}
52+
}
53+
54+
return undefined;
55+
}, [appState, engine]);
56+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Component, SyntheticEvent } from 'react';
2+
import { requireNativeComponent, ViewProps } from 'react-native';
3+
4+
declare const global: any;
5+
6+
export interface NativeEngineViewProps extends ViewProps {
7+
isTransparent: boolean;
8+
onSnapshotDataReturned?: (event: SyntheticEvent) => void;
9+
}
10+
11+
export const NativeEngineView: {
12+
prototype: Component<NativeEngineViewProps>;
13+
new(props: Readonly<NativeEngineViewProps>): Component<NativeEngineViewProps>;
14+
} = global['EngineView'] || (global['EngineView'] = requireNativeComponent('EngineView'));

Package/gulpfile.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,12 @@ const validate = async () => {
478478
'Assembled/NativeCapture.d.ts',
479479
'Assembled/NativeCapture.js',
480480
'Assembled/NativeCapture.js.map',
481+
'Assembled/NativeEngineHook.d.ts',
482+
'Assembled/NativeEngineHook.js',
483+
'Assembled/NativeEngineHook.js.map',
484+
'Assembled/NativeEngineView.d.ts',
485+
'Assembled/NativeEngineView.js',
486+
'Assembled/NativeEngineView.js.map',
481487
'Assembled/FontFace.d.ts',
482488
'Assembled/FontFace.js',
483489
'Assembled/FontFace.js.map',

0 commit comments

Comments
 (0)