diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index 46fcc66aa..f99a9cf94 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -3,7 +3,7 @@ import { Platform } from 'react-native'; import { PERMISSIONS, check, request } from 'react-native-permissions'; import { Engine, WebXRSessionManager, WebXRExperienceHelper, Color4, Tools } from '@babylonjs/core'; import { ReactNativeEngine } from './ReactNativeEngine'; -import './VersionValidation'; + import * as base64 from 'base-64'; // 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. diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index 5f43ce04c..ddcfdea8c 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -1,20 +1,9 @@ -import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef } from 'react'; -import { requireNativeComponent, ViewProps, AppState, AppStateStatus, View, Text, findNodeHandle, UIManager } from 'react-native'; +import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef, useMemo } from 'react'; +import { ViewProps, View, Text, findNodeHandle, UIManager } from 'react-native'; import { Camera, SceneInstrumentation } from '@babylonjs/core'; -import { ensureInitialized } from './BabylonModule'; import { ReactNativeEngine } from './ReactNativeEngine'; - -declare const global: any; - -interface NativeEngineViewProps extends ViewProps { - isTransparent: boolean; - onSnapshotDataReturned: (event: SyntheticEvent) => void; -} - -const NativeEngineView: { - prototype: Component; - new(props: Readonly): Component; -} = global['EngineView'] || (global['EngineView'] = requireNativeComponent('EngineView')); +import { useModuleInitializer, useRenderLoop } from './NativeEngineHook'; +import { NativeEngineViewProps, NativeEngineView } from './NativeEngineView'; export interface EngineViewProps extends ViewProps { camera?: Camera; @@ -33,53 +22,25 @@ interface SceneStats { } export const EngineView: FunctionComponent = (props: EngineViewProps) => { - const [initialized, setInitialized] = useState(); - const [appState, setAppState] = useState(AppState.currentState); //const [fps, setFps] = useState(); const [sceneStats, setSceneStats] = useState(); const engineViewRef = useRef>(null); const snapshotPromise = useRef<{ promise: Promise, resolve: (data: string) => void }>(); const isTransparent = props.isTransparent || false - useEffect(() => { - (async () => { - setInitialized(await ensureInitialized()); - })(); - }, []); + const initialized = useModuleInitializer(); - useEffect(() => { - const onAppStateChanged = (appState: AppStateStatus) => { - setAppState(appState); - }; - - AppState.addEventListener("change", onAppStateChanged); + const engine = useMemo(() => { + return props.camera?.getScene().getEngine() as ReactNativeEngine; + }, [props.camera]); - return () => { - AppState.removeEventListener("change", onAppStateChanged); + const renderLoop = useCallback(() => { + for (let scene of engine.scenes) { + scene.render(); } - }, []); - - useEffect(() => { - if (props.camera && appState === "active") { - const engine = props.camera.getScene().getEngine() as ReactNativeEngine; + }, [engine]); - if (!engine.isDisposed) { - engine.runRenderLoop(() => { - for (let scene of engine.scenes) { - scene.render(); - } - }); - - return () => { - if (!engine.isDisposed) { - engine.stopRenderLoop(); - } - }; - } - } - - return undefined; - }, [props.camera, appState]); + useRenderLoop(engine, renderLoop); useEffect(() => { if (props.camera && (props.displayFrameRate ?? __DEV__)) { diff --git a/Modules/@babylonjs/react-native/NativeEngineHook.ts b/Modules/@babylonjs/react-native/NativeEngineHook.ts new file mode 100644 index 000000000..4a9b61519 --- /dev/null +++ b/Modules/@babylonjs/react-native/NativeEngineHook.ts @@ -0,0 +1,56 @@ +import { useEffect, useState } from 'react'; +import { AppState, AppStateStatus } from 'react-native'; +import { ensureInitialized } from './BabylonModule'; +import { ReactNativeEngine } from './ReactNativeEngine'; + +import './VersionValidation'; + +export function useModuleInitializer(): boolean | undefined { + const [initialized, setInitialized] = useState(); + + useEffect(() => { + (async () => { + setInitialized(await ensureInitialized()); + })(); + }, []); + + return initialized; +} + +function useAppState(): string { + const [appState, setAppState] = useState(AppState.currentState); + + useEffect(() => { + const onAppStateChanged = (appState: AppStateStatus) => { + setAppState(appState); + }; + + AppState.addEventListener("change", onAppStateChanged); + + return () => { + AppState.removeEventListener("change", onAppStateChanged); + } + }, []); + + return appState; +} + +export function useRenderLoop(engine: ReactNativeEngine | undefined, renderCallback: () => void): void { + const appState = useAppState(); + + useEffect(() => { + if (engine && appState === "active") { + if (!engine.isDisposed) { + engine.runRenderLoop(renderCallback); + + return () => { + if (!engine.isDisposed) { + engine.stopRenderLoop(); + } + }; + } + } + + return undefined; + }, [appState, engine]); +} diff --git a/Modules/@babylonjs/react-native/NativeEngineView.tsx b/Modules/@babylonjs/react-native/NativeEngineView.tsx new file mode 100644 index 000000000..edf7c8028 --- /dev/null +++ b/Modules/@babylonjs/react-native/NativeEngineView.tsx @@ -0,0 +1,14 @@ +import { Component, SyntheticEvent } from 'react'; +import { requireNativeComponent, ViewProps } from 'react-native'; + +declare const global: any; + +export interface NativeEngineViewProps extends ViewProps { + isTransparent: boolean; + onSnapshotDataReturned?: (event: SyntheticEvent) => void; +} + +export const NativeEngineView: { + prototype: Component; + new(props: Readonly): Component; +} = global['EngineView'] || (global['EngineView'] = requireNativeComponent('EngineView')); diff --git a/Package/gulpfile.js b/Package/gulpfile.js index 7809bf423..060d94940 100644 --- a/Package/gulpfile.js +++ b/Package/gulpfile.js @@ -478,6 +478,12 @@ const validate = async () => { 'Assembled/NativeCapture.d.ts', 'Assembled/NativeCapture.js', 'Assembled/NativeCapture.js.map', + 'Assembled/NativeEngineHook.d.ts', + 'Assembled/NativeEngineHook.js', + 'Assembled/NativeEngineHook.js.map', + 'Assembled/NativeEngineView.d.ts', + 'Assembled/NativeEngineView.js', + 'Assembled/NativeEngineView.js.map', 'Assembled/FontFace.d.ts', 'Assembled/FontFace.js', 'Assembled/FontFace.js.map',