From 3b28873948d337679467d5c48164db4ca4f7b3a9 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Tue, 15 Dec 2020 17:17:58 -0800 Subject: [PATCH 01/24] First pass, iOS only --- .../@babylonjs/react-native/BabylonModule.ts | 56 +++- Modules/@babylonjs/react-native/EngineHook.ts | 6 +- .../react-native/ios/BabylonModule.mm | 12 + .../react-native/ios/BabylonNative.cpp | 295 ++++++++++++++++++ .../react-native/ios/BabylonNative.h | 9 + .../react-native/ios/BabylonNativeInterop.h | 6 + .../react-native/ios/BabylonNativeInterop.mm | 75 +++++ .../react-native/ios/EngineViewManager.mm | 15 +- 8 files changed, 455 insertions(+), 19 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 9c03716d0..09a61228c 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -1,33 +1,65 @@ import { NativeModules } from 'react-native'; import { NativeEngine } from '@babylonjs/core'; +import { DisposeEngine } from './EngineHelpers'; // This global object is part of Babylon Native. declare const _native: { whenGraphicsReady: () => Promise; engineInstance: NativeEngine; -} +}; + +declare const BabylonNative: { + readonly initializationPromise: Promise; + //setEngineInstance: (engine: NativeEngine | null) => void; + reset: () => void; +}; const NativeBabylonModule: { - initialize(): Promise; - whenInitialized(): Promise; - reset(): Promise; + initialize2(): void; + // initialize(): Promise; + // whenInitialized(): Promise; + // reset(): Promise; } = NativeModules.BabylonModule; +let resolveInitializationPromise: (result: boolean) => void; +const initializationPromise = new Promise(resolve => resolveInitializationPromise = resolve); // TODO: Promise + export const BabylonModule = { initialize: async () => { - const initialized = await NativeBabylonModule.initialize(); - if (initialized) { - await _native.whenGraphicsReady(); - } - return initialized; + console.log("INITIALIZING"); + // const initialized = await NativeBabylonModule.initialize(); + // if (initialized) { + // await _native.whenGraphicsReady(); + // } + // return initialized; + NativeBabylonModule.initialize2(); + await BabylonNative.initializationPromise; + await _native.whenGraphicsReady(); + resolveInitializationPromise(true); + return true; // TODO: remove, and prevent most of this code from running if we are in remote debugging mode (see EngineView.tsx for example) }, - whenInitialized: NativeBabylonModule.whenInitialized, - reset: NativeBabylonModule.reset, + //whenInitialized: NativeBabylonModule.whenInitialized, + whenInitialized: () => { + console.log("WHEN INITIALIZED"); + //return BabylonNative.initializationPromise; + return initializationPromise; + }, + //reset: NativeBabylonModule.reset, + reset: () => { + console.log("RESET"); + BabylonNative.reset(); + }, createEngine: () => { const engine = new NativeEngine(); _native.engineInstance = engine; + //BabylonNative.setEngineInstance(engine); return engine; - } + }, + + disposeEngine: (engine: NativeEngine) => { + DisposeEngine(engine); + //BabylonNative.setEngineInstance(null); + }, }; \ No newline at end of file diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index 162be3c4b..a15944bb9 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -67,9 +67,10 @@ export function useEngine(): Engine | undefined { useEffect(() => { let disposed = false; - let engine: Engine | undefined = undefined; + let engine: NativeEngine | undefined = undefined; (async () => { + console.log("Calling BabylonModule.initialize"); if (await BabylonModule.initialize() && !disposed) { engine = BabylonModule.createEngine(); @@ -81,7 +82,8 @@ export function useEngine(): Engine | undefined { disposed = true; // NOTE: Do not use setEngine with a callback to dispose the engine instance as that callback does not get called during component unmount when compiled in release. if (engine) { - DisposeEngine(engine); + //DisposeEngine(engine); + BabylonModule.disposeEngine(engine); } // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). diff --git a/Modules/@babylonjs/react-native/ios/BabylonModule.mm b/Modules/@babylonjs/react-native/ios/BabylonModule.mm index 05af48afc..e9b51a26b 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonModule.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonModule.mm @@ -18,6 +18,18 @@ @implementation BabylonModule @synthesize bridge = _bridge; +//- (instancetype)init +//{ +// self = [super init]; +// return self; +//} + +// This would be called very early in EngineHook.js +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(initialize2) { + [BabylonNativeInterop2 initialize:self.bridge]; + return nil; +} + RCT_EXPORT_METHOD(initialize:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { [BabylonNativeInterop whenInitialized:self.bridge resolve:resolve]; } diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index 541c63d6c..e3c3a199f 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -29,6 +29,301 @@ namespace Babylon bool isShuttingDown{false}; } + class ReactNativeModule : public jsi::HostObject + { + public: +// static std::shared_ptr GetOrCreate(jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) +// { +// jsiRuntime.global().getProperty(jsiRuntime, JS_INSTANCE_NAME) +// auto nativeModule{std::make_shared(jsiRuntime, jsCallInvoker)}; +// //std::shared_ptr nativeModule{ new ReactNativeModule(jsiRuntime, jsCallInvoker) }; +// jsiRuntime.global().setProperty(jsiRuntime, JS_INSTANCE_NAME, jsi::Object::createFromHostObject(jsiRuntime, nativeModule)); +// return nativeModule; +// } + + ReactNativeModule(jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) + : m_env{ Napi::Attach(jsiRuntime) } + , m_jsCallInvoker{ jsCallInvoker } + , m_isRunning{ std::make_shared(true) } + { + isShuttingDown = false; + + // Initialize a JS promise that will be returned by whenInitialized, and completed when NativeEngine is initialized. + m_initPromise = jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "Promise").callAsConstructor + ( + jsiRuntime, + jsi::Function::createFromHostFunction(jsiRuntime, jsi::PropNameID::forAscii(jsiRuntime, "executor"), 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t) -> jsi::Value + { + m_resolveInitPromise = [&rt, resolve{ std::make_shared(rt, args[0]) }]() + { + resolve->asObject(rt).asFunction(rt).call(rt); + }; + return {}; + }) + ); + + // Initialize Babylon Native core components + JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, m_jsCallInvoker, isShuttingDown)); + + // Initialize Babylon Native plugins + Plugins::NativeXr::Initialize(m_env); + m_nativeInput = &Babylon::Plugins::NativeInput::CreateForJavaScript(m_env); + + // Initialize Babylon Native polyfills + Polyfills::Window::Initialize(m_env); + + // NOTE: React Native's XMLHttpRequest is slow and allocates a lot of memory. This does not override + // React Native's implementation, but rather adds a second one scoped to Babylon and used by WebRequest.ts. + Polyfills::XMLHttpRequest::Initialize(m_env); + } + + ~ReactNativeModule() + { + isShuttingDown = true; + *m_isRunning = false; + + // NOTE: This only happens when the JS engine is shutting down (other than when the app exits, this only + // happens during a dev mode reload). In this case, EngineHook.ts won't call NativeEngine.dispose, + // so we need to manually do it here to properly clean up these resources. +// auto native = JsRuntime::NativeObject::GetFromJavaScript(m_env); +// auto engine = native.Get("engineInstance").As(); +// auto dispose = engine.Get("dispose").As(); +// dispose.Call(engine, {}); + //m_disposeEngine(); + + Napi::Detach(m_env); + } + + // NOTE: This only happens when the JS engine is shutting down (other than when the app exits, this only + // happens during a dev mode reload). In this case, EngineHook.ts won't call NativeEngine.dispose, + // so we need to manually do it here to properly clean up these resources. + void Deinitialize() + { + auto native = JsRuntime::NativeObject::GetFromJavaScript(m_env); + auto engine = native.Get("engineInstance").As(); + auto dispose = engine.Get("dispose").As(); + dispose.Call(engine, {}); + } + + void UpdateView(void* windowPtr, size_t width, size_t height) + { + m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() { + if (!m_graphics) + { + m_graphics = Graphics::CreateGraphics(windowPtr, width, height); + m_graphics->AddToJavaScript(m_env); + Plugins::NativeEngine::Initialize(m_env, true); + m_resolveInitPromise(); + } + else + { + m_graphics->UpdateWindow(windowPtr); + m_graphics->UpdateSize(width, height); + m_graphics->EnableRendering(); + } + }); + } + +// void SetView(void* windowPtr, size_t width, size_t height) +// { +// if (m_graphics) +// { +// m_graphics->UpdateWindow(windowPtr); +// m_graphics->UpdateSize(width, height); +// m_graphics->EnableRendering(); +// } +// else +// { +// m_graphics = Graphics::CreateGraphics(windowPtr, width, height); +// m_graphics->AddToJavaScript(m_env); +// Plugins::NativeEngine::Initialize(m_env, true); +// m_resolveInitPromise(); +// } +// } +// +// void UpdateView(size_t width, size_t height) +// { +// if (m_graphics) +// { +// m_graphics->UpdateSize(width, height); +// } +// } + + void ResetView() + { + if (m_graphics) + { + m_graphics->DisableRendering(); + } + } + + void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) + { + if (isDown) + { + m_nativeInput->PointerDown(pointerId, buttonId, x, y); + } + else + { + m_nativeInput->PointerUp(pointerId, buttonId, x, y); + } + } + + void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y) + { + m_nativeInput->PointerMove(pointerId, x, y); + } + + jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& prop) override + { + if (prop.utf8(runtime) == "initializationPromise") + { + return { runtime, m_initPromise }; + } + else if (prop.utf8(runtime) == "reset") + { + return jsi::Function::createFromHostFunction(runtime, prop, 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t) -> jsi::Value + { + this->ResetView(); + return {}; + }); + } +// else if (prop.utf8(runtime) == "setEngineInstance") +// { +// return jsi::Function::createFromHostFunction(runtime, prop, 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value +// { +// if (count > 0 && args[0].isObject()) +// { +// +// } +// if (count == 0 || !args[0].isObject()) +// { +// m_disposeEngine = {}; +// } +// else +// { +// m_disposeEngine = [&rt, engineInstance{ std::make_shared(rt, args[0]) }]() +// { +// engineInstance->getObject(rt).getProperty(rt, "dispose").asObject(rt).getFunction(rt).call(rt); +// }; +// } +// return {}; +// }); +// } + + return jsi::Value::undefined(); + } + +// void set(jsi::Runtime& runtime, const jsi::PropNameID& prop, const jsi::Value& value) override +// { +// if (prop.utf8(runtime) == "engineInstance") +// { +// if (value.IsNull() || value.IsUndefined()) +// { +// m_disposeEngine = {}; +// } +// else if (value.IsObject()) +// { +// m_disposeEngine = [&runtime, engineInstance{ std::make_shared(runtime, value.asObject(runtime)) }]() +// { +// engineInstance->getProperty(runtime, "dispose").asObject(runtime).getFunction(runtime).call(runtime); +// }; +// } +// else +// { +// // TODO: throw? +// } +// } +// } + + private: + jsi::Value m_initPromise{}; + std::function m_resolveInitPromise{}; + + Napi::Env m_env; + std::shared_ptr m_jsCallInvoker{}; + + std::shared_ptr m_isRunning{}; + std::unique_ptr m_graphics{}; + Plugins::NativeInput* m_nativeInput{}; + +// std::function m_disposeEngine{}; + }; + + namespace + { + constexpr auto JS_INSTANCE_NAME{ "BabylonNative" }; + std::weak_ptr g_nativeModule{}; + } + + void Initialize(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) + { + if (jsiRuntime.global().getProperty(jsiRuntime, JS_INSTANCE_NAME).isUndefined()) + { + auto nativeModule{ std::make_shared(jsiRuntime, jsCallInvoker) }; + jsiRuntime.global().setProperty(jsiRuntime, JS_INSTANCE_NAME, jsi::Object::createFromHostObject(jsiRuntime, nativeModule)); + g_nativeModule = nativeModule; + } + } + + void Deinitialize() + { + if (auto nativeModule = g_nativeModule.lock()) + { + nativeModule->Deinitialize(); + } + } + + void UpdateView(void* windowPtr, size_t width, size_t height) + { + if (auto nativeModule = g_nativeModule.lock()) + { + nativeModule->UpdateView(windowPtr, width, height); + } + } + +// void SetView(void* windowPtr, size_t width, size_t height) +// { +// if (auto nativeModule = g_nativeModule.lock()) +// { +// nativeModule->SetView(windowPtr, width, height); +// } +// } +// +// void UpdateView(size_t width, size_t height) +// { +// if (auto nativeModule = g_nativeModule.lock()) +// { +// nativeModule->UpdateView(width, height); +// } +// } + +// void ResetView() +// { +// if (auto nativeModule = g_nativeModule.lock()) +// { +// nativeModule->ResetView(); +// } +// } + + void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) + { + if (auto nativeModule = g_nativeModule.lock()) + { + nativeModule->SetPointerButtonState(pointerId, buttonId, isDown, x, y); + } + } + + void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y) + { + if (auto nativeModule = g_nativeModule.lock()) + { + nativeModule->SetPointerPosition(pointerId, x, y); + } + } + + + class Native::Impl { public: diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.h b/Modules/@babylonjs/react-native/ios/BabylonNative.h index d7ce8fa97..e4eeae918 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.h +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.h @@ -5,6 +5,15 @@ namespace Babylon { + void Initialize(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker); + void Deinitialize(); + void UpdateView(void* windowPtr, size_t width, size_t height); + //void SetView(void* windowPtr, size_t width, size_t height); + //void UpdateView(size_t width, size_t height); + //void ResetView(); + void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y); + void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y); + class Native final { public: diff --git a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h index 2ecca3399..f3890fe2b 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h +++ b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h @@ -3,6 +3,12 @@ #import #import +@interface BabylonNativeInterop2 : NSObject ++ (void)initialize:(RCTBridge*)bridge; ++ (void)updateView:(MTKView*)mtkView; ++ (void)reportTouchEvent:(MTKView*)mtkView touches:(NSSet*)touches event:(UIEvent*)event; +@end + @interface BabylonNativeInterop : NSObject + (void)setView:(RCTBridge*)bridge jsRunLoop:(NSRunLoop*)jsRunLoop mktView:(MTKView*)mtkView; + (void)reportTouchEvent:(NSSet*)touches withEvent:(UIEvent*)event; diff --git a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm index 857b45ee1..8a44f8e6d 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm @@ -25,6 +25,81 @@ @interface RCTBridge (RCTTurboModule) } } +@implementation BabylonNativeInterop2 + +static NSMutableArray* activeTouches2; + ++ (void)initialize:(RCTBridge*)bridge { + Babylon::Initialize(*GetJSIRuntime(bridge), bridge.jsCallInvoker); + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:RCTBridgeWillInvalidateModulesNotification + object:bridge.parentBridge]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(onBridgeWillInvalidate:) + name:RCTBridgeWillInvalidateModulesNotification + object:bridge.parentBridge]; +} + +// NOTE: This happens during dev mode reload, when the JS engine is being shutdown and restarted. ++ (void)onBridgeWillInvalidate:(NSNotification*)notification +{ + Babylon::Deinitialize(); +} + ++ (void)updateView:(MTKView*)mtkView { + const int width = static_cast(mtkView.bounds.size.width * UIScreen.mainScreen.scale); + const int height = static_cast(mtkView.bounds.size.height * UIScreen.mainScreen.scale); + if (width != 0 && height != 0) { + Babylon::UpdateView((__bridge void*)mtkView, width, height); + } +} + ++ (void)reportTouchEvent:(MTKView*)mtkView touches:(NSSet*)touches event:(UIEvent*)event { + for (UITouch* touch in touches) { + if (touch.view == mtkView) { + const CGFloat scale = UIScreen.mainScreen.scale; + const CGPoint pointerPosition = [touch locationInView:mtkView]; + const uint32_t x = static_cast(pointerPosition.x * scale); + const uint32_t y = static_cast(pointerPosition.y * scale); + + switch (touch.phase) { + case UITouchPhaseBegan: { + NSUInteger pointerId = [activeTouches2 indexOfObject:[NSNull null]]; + if (pointerId == NSNotFound) { + pointerId = [activeTouches2 count]; + [activeTouches2 addObject:touch]; + } else { + [activeTouches2 replaceObjectAtIndex:pointerId withObject:touch]; + } + Babylon::SetPointerButtonState(static_cast(pointerId), 0, true, x, y); + break; + } + + case UITouchPhaseMoved: { + NSUInteger pointerId = [activeTouches2 indexOfObject:touch]; + Babylon::SetPointerPosition(static_cast(pointerId), x, y); + break; + } + + case UITouchPhaseEnded: + case UITouchPhaseCancelled: { + NSUInteger pointerId = [activeTouches2 indexOfObject:touch]; + [activeTouches2 replaceObjectAtIndex:pointerId withObject:[NSNull null]]; + Babylon::SetPointerButtonState(static_cast(pointerId), 0, false, x, y); + break; + } + + default: + break; + } + } + } +} + +@end + @implementation BabylonNativeInterop static RCTBridge* currentBridge; diff --git a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm index 00066406c..accdcb851 100644 --- a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm +++ b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm @@ -32,23 +32,28 @@ - (instancetype)init:(RCTBridge*)_bridge runLoop:(NSRunLoop*)_runLoop { - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; - [BabylonNativeInterop setView:bridge jsRunLoop:runLoop mktView:self]; + //[BabylonNativeInterop setView:bridge jsRunLoop:runLoop mktView:self]; + [BabylonNativeInterop2 updateView:self]; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - [BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - [BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - [BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - [BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; + [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; } - (void)takeSnapshot { From 0d33202c814d732ab1cfd8df97cbaef327c39ac3 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Tue, 15 Dec 2020 17:59:31 -0800 Subject: [PATCH 02/24] Don't even initialize Babylon Native if we have remote debugging enabled --- .../@babylonjs/react-native/BabylonModule.ts | 20 ++++++++++++++----- .../@babylonjs/react-native/EngineView.tsx | 10 ++-------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 09a61228c..6c22827b3 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -2,6 +2,11 @@ import { NativeModules } from 'react-native'; import { NativeEngine } from '@babylonjs/core'; import { DisposeEngine } from './EngineHelpers'; +declare const global: { + nativeCallSyncHook: any; +}; +const isRemoteDebuggingEnabled = !global.nativeCallSyncHook; + // This global object is part of Babylon Native. declare const _native: { whenGraphicsReady: () => Promise; @@ -32,11 +37,16 @@ export const BabylonModule = { // await _native.whenGraphicsReady(); // } // return initialized; - NativeBabylonModule.initialize2(); - await BabylonNative.initializationPromise; - await _native.whenGraphicsReady(); - resolveInitializationPromise(true); - return true; // TODO: remove, and prevent most of this code from running if we are in remote debugging mode (see EngineView.tsx for example) + if (isRemoteDebuggingEnabled) { + resolveInitializationPromise(false); + } else { + NativeBabylonModule.initialize2(); + await BabylonNative.initializationPromise; + await _native.whenGraphicsReady(); + resolveInitializationPromise(true); + } + //return true; // TODO: remove, and prevent most of this code from running if we are in remote debugging mode (see EngineView.tsx for example) + return await initializationPromise; }, //whenInitialized: NativeBabylonModule.whenInitialized, diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index 038bc572a..85051ebf7 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -5,17 +5,11 @@ import { IsEngineDisposed } from './EngineHelpers'; import { BabylonModule } from './BabylonModule'; declare const global: any; -const isRemoteDebuggingEnabled = !global['nativeCallSyncHook']; const EngineViewManager: { setJSThread(): void; } = NativeModules.EngineViewManager; -// Not all platforms need this, but for those that do, this is intended to be a synchronous call to boostrap the ability to run native code on the JavaScript thread. -if (EngineViewManager && EngineViewManager.setJSThread && !isRemoteDebuggingEnabled) { - EngineViewManager.setJSThread(); -} - interface NativeEngineViewProps extends ViewProps { onSnapshotDataReturned: (event: SyntheticEvent) => void; } @@ -23,7 +17,7 @@ interface NativeEngineViewProps extends ViewProps { const NativeEngineView: { prototype: Component; new(props: Readonly): Component; -} = requireNativeComponent('EngineView'); +} = global['EngineView'] || (global['EngineView'] = requireNativeComponent('EngineView')); export interface EngineViewProps extends ViewProps { camera?: Camera; @@ -161,7 +155,7 @@ export const EngineView: FunctionComponent = (props: EngineView return ( {message} - { isRemoteDebuggingEnabled && React Native remote debugging does not work with Babylon Native.} + React Native remote debugging does not work with Babylon Native. ); } From 722e90871029702dfaf9ea62fe87ba6344836d08 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Wed, 16 Dec 2020 12:56:25 -0800 Subject: [PATCH 03/24] First pass cleanup --- .../@babylonjs/react-native/BabylonModule.ts | 38 ++-- Modules/@babylonjs/react-native/EngineHook.ts | 2 +- .../@babylonjs/react-native/EngineView.tsx | 2 +- .../react-native/ios/BabylonModule.mm | 26 +-- .../react-native/ios/BabylonNative.cpp | 180 ------------------ .../react-native/ios/BabylonNative.h | 20 -- .../react-native/ios/BabylonNativeInterop.h | 9 +- .../react-native/ios/BabylonNativeInterop.mm | 166 +--------------- .../react-native/ios/EngineViewManager.mm | 17 +- 9 files changed, 32 insertions(+), 428 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 6c22827b3..dfbed04c7 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -7,54 +7,38 @@ declare const global: { }; const isRemoteDebuggingEnabled = !global.nativeCallSyncHook; -// This global object is part of Babylon Native. +// This global object is owned by Babylon Native. declare const _native: { whenGraphicsReady: () => Promise; engineInstance: NativeEngine; }; +// This JSI-based global object is owned by Babylon React Native. +// This will likely be converted to a TurboModule when they are fully supported. declare const BabylonNative: { readonly initializationPromise: Promise; - //setEngineInstance: (engine: NativeEngine | null) => void; reset: () => void; }; +// This legacy React Native module is created by Babylon React Native, and is only used to bootstrap the JSI object creation. +// This will likely be removed when the BabylonNative global object is eventually converted to a TurboModule. const NativeBabylonModule: { - initialize2(): void; - // initialize(): Promise; - // whenInitialized(): Promise; - // reset(): Promise; + initialize(): void; } = NativeModules.BabylonModule; -let resolveInitializationPromise: (result: boolean) => void; -const initializationPromise = new Promise(resolve => resolveInitializationPromise = resolve); // TODO: Promise - export const BabylonModule = { - initialize: async () => { + ensureInitialized: async () => { console.log("INITIALIZING"); - // const initialized = await NativeBabylonModule.initialize(); - // if (initialized) { - // await _native.whenGraphicsReady(); - // } - // return initialized; if (isRemoteDebuggingEnabled) { - resolveInitializationPromise(false); + return false; } else { - NativeBabylonModule.initialize2(); + NativeBabylonModule.initialize(); await BabylonNative.initializationPromise; await _native.whenGraphicsReady(); - resolveInitializationPromise(true); + return true; } - //return true; // TODO: remove, and prevent most of this code from running if we are in remote debugging mode (see EngineView.tsx for example) - return await initializationPromise; }, - //whenInitialized: NativeBabylonModule.whenInitialized, - whenInitialized: () => { - console.log("WHEN INITIALIZED"); - //return BabylonNative.initializationPromise; - return initializationPromise; - }, //reset: NativeBabylonModule.reset, reset: () => { console.log("RESET"); @@ -69,7 +53,9 @@ export const BabylonModule = { }, disposeEngine: (engine: NativeEngine) => { + console.log("Beginning dispose"); DisposeEngine(engine); + console.log("Finished dispose"); //BabylonNative.setEngineInstance(null); }, }; \ No newline at end of file diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index a15944bb9..c2b9a8b42 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -71,7 +71,7 @@ export function useEngine(): Engine | undefined { (async () => { console.log("Calling BabylonModule.initialize"); - if (await BabylonModule.initialize() && !disposed) + if (await BabylonModule.ensureInitialized() && !disposed) { engine = BabylonModule.createEngine(); setEngine(engine); diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index 85051ebf7..eba586eee 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -38,7 +38,7 @@ export const EngineView: FunctionComponent = (props: EngineView useEffect(() => { (async () => { - if (!await BabylonModule.whenInitialized()) { + if (!await BabylonModule.ensureInitialized()) { setFailedInitialization(true); } })(); diff --git a/Modules/@babylonjs/react-native/ios/BabylonModule.mm b/Modules/@babylonjs/react-native/ios/BabylonModule.mm index e9b51a26b..f4067c6d7 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonModule.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonModule.mm @@ -18,31 +18,9 @@ @implementation BabylonModule @synthesize bridge = _bridge; -//- (instancetype)init -//{ -// self = [super init]; -// return self; -//} - -// This would be called very early in EngineHook.js -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(initialize2) { - [BabylonNativeInterop2 initialize:self.bridge]; +RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(initialize) { + [BabylonNativeInterop initialize:self.bridge]; return nil; } -RCT_EXPORT_METHOD(initialize:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [BabylonNativeInterop whenInitialized:self.bridge resolve:resolve]; -} - -RCT_EXPORT_METHOD(whenInitialized:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - [BabylonNativeInterop whenInitialized:self.bridge resolve:resolve]; -} - -RCT_EXPORT_METHOD(reset:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { - self.bridge.jsCallInvoker->invokeAsync([resolve]() { - [BabylonNativeInterop reset]; - resolve([NSNull null]); - }); -} - @end diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index e3c3a199f..1cc482b3c 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -32,15 +32,6 @@ namespace Babylon class ReactNativeModule : public jsi::HostObject { public: -// static std::shared_ptr GetOrCreate(jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) -// { -// jsiRuntime.global().getProperty(jsiRuntime, JS_INSTANCE_NAME) -// auto nativeModule{std::make_shared(jsiRuntime, jsCallInvoker)}; -// //std::shared_ptr nativeModule{ new ReactNativeModule(jsiRuntime, jsCallInvoker) }; -// jsiRuntime.global().setProperty(jsiRuntime, JS_INSTANCE_NAME, jsi::Object::createFromHostObject(jsiRuntime, nativeModule)); -// return nativeModule; -// } - ReactNativeModule(jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) : m_env{ Napi::Attach(jsiRuntime) } , m_jsCallInvoker{ jsCallInvoker } @@ -81,16 +72,6 @@ namespace Babylon { isShuttingDown = true; *m_isRunning = false; - - // NOTE: This only happens when the JS engine is shutting down (other than when the app exits, this only - // happens during a dev mode reload). In this case, EngineHook.ts won't call NativeEngine.dispose, - // so we need to manually do it here to properly clean up these resources. -// auto native = JsRuntime::NativeObject::GetFromJavaScript(m_env); -// auto engine = native.Get("engineInstance").As(); -// auto dispose = engine.Get("dispose").As(); -// dispose.Call(engine, {}); - //m_disposeEngine(); - Napi::Detach(m_env); } @@ -123,31 +104,6 @@ namespace Babylon } }); } - -// void SetView(void* windowPtr, size_t width, size_t height) -// { -// if (m_graphics) -// { -// m_graphics->UpdateWindow(windowPtr); -// m_graphics->UpdateSize(width, height); -// m_graphics->EnableRendering(); -// } -// else -// { -// m_graphics = Graphics::CreateGraphics(windowPtr, width, height); -// m_graphics->AddToJavaScript(m_env); -// Plugins::NativeEngine::Initialize(m_env, true); -// m_resolveInitPromise(); -// } -// } -// -// void UpdateView(size_t width, size_t height) -// { -// if (m_graphics) -// { -// m_graphics->UpdateSize(width, height); -// } -// } void ResetView() { @@ -214,28 +170,6 @@ namespace Babylon return jsi::Value::undefined(); } -// void set(jsi::Runtime& runtime, const jsi::PropNameID& prop, const jsi::Value& value) override -// { -// if (prop.utf8(runtime) == "engineInstance") -// { -// if (value.IsNull() || value.IsUndefined()) -// { -// m_disposeEngine = {}; -// } -// else if (value.IsObject()) -// { -// m_disposeEngine = [&runtime, engineInstance{ std::make_shared(runtime, value.asObject(runtime)) }]() -// { -// engineInstance->getProperty(runtime, "dispose").asObject(runtime).getFunction(runtime).call(runtime); -// }; -// } -// else -// { -// // TODO: throw? -// } -// } -// } - private: jsi::Value m_initPromise{}; std::function m_resolveInitPromise{}; @@ -282,30 +216,6 @@ namespace Babylon } } -// void SetView(void* windowPtr, size_t width, size_t height) -// { -// if (auto nativeModule = g_nativeModule.lock()) -// { -// nativeModule->SetView(windowPtr, width, height); -// } -// } -// -// void UpdateView(size_t width, size_t height) -// { -// if (auto nativeModule = g_nativeModule.lock()) -// { -// nativeModule->UpdateView(width, height); -// } -// } - -// void ResetView() -// { -// if (auto nativeModule = g_nativeModule.lock()) -// { -// nativeModule->ResetView(); -// } -// } - void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) { if (auto nativeModule = g_nativeModule.lock()) @@ -321,94 +231,4 @@ namespace Babylon nativeModule->SetPointerPosition(pointerId, x, y); } } - - - - class Native::Impl - { - public: - Impl(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker) - : env{ Napi::Attach(jsiRuntime) } - , jsCallInvoker{ callInvoker } - { - } - - ~Impl() - { - Napi::Detach(env); - } - - Napi::Env env; - std::shared_ptr jsCallInvoker; - std::unique_ptr graphics{}; - JsRuntime* runtime{}; - Plugins::NativeInput* nativeInput{}; - }; - - Native::Native(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, void* windowPtr, size_t width, size_t height) - : m_impl{ std::make_unique(jsiRuntime, callInvoker) } - { - isShuttingDown = false; - m_impl->graphics = Graphics::CreateGraphics(reinterpret_cast(windowPtr), width, height); - - m_impl->runtime = &JsRuntime::CreateForJavaScript(m_impl->env, CreateJsRuntimeDispatcher(m_impl->env, jsiRuntime, std::move(callInvoker), isShuttingDown)); - - m_impl->graphics->AddToJavaScript(m_impl->env); - - Polyfills::Window::Initialize(m_impl->env); - // NOTE: React Native's XMLHttpRequest is slow and allocates a lot of memory. This does not override - // React Native's implementation, but rather adds a second one scoped to Babylon and used by WebRequest.ts. - Polyfills::XMLHttpRequest::Initialize(m_impl->env); - - Plugins::NativeEngine::Initialize(m_impl->env, true); - Plugins::NativeXr::Initialize(m_impl->env); - - m_impl->nativeInput = &Babylon::Plugins::NativeInput::CreateForJavaScript(m_impl->env); - } - - // NOTE: This only happens when the JS engine is shutting down (other than when the app exits, this only - // happens during a dev mode reload). In this case, EngineHook.ts won't call NativeEngine.dispose, - // so we need to manually do it here to properly clean up these resources. - Native::~Native() - { - auto native = JsRuntime::NativeObject::GetFromJavaScript(m_impl->env); - auto engine = native.Get("engineInstance").As(); - auto dispose = engine.Get("dispose").As(); - dispose.Call(engine, {}); - isShuttingDown = true; - } - - void Native::Refresh(void* windowPtr, size_t width, size_t height) - { - m_impl->graphics->UpdateWindow(windowPtr); - m_impl->graphics->UpdateSize(width, height); - m_impl->graphics->EnableRendering(); - } - - void Native::Resize(size_t width, size_t height) - { - m_impl->graphics->UpdateSize(width, height); - } - - void Native::Reset() - { - m_impl->graphics->DisableRendering(); - } - - void Native::SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) - { - if (isDown) - { - m_impl->nativeInput->PointerDown(pointerId, buttonId, x, y); - } - else - { - m_impl->nativeInput->PointerUp(pointerId, buttonId, x, y); - } - } - - void Native::SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y) - { - m_impl->nativeInput->PointerMove(pointerId, x, y); - } } diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.h b/Modules/@babylonjs/react-native/ios/BabylonNative.h index e4eeae918..ef701a817 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.h +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.h @@ -8,26 +8,6 @@ namespace Babylon void Initialize(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker); void Deinitialize(); void UpdateView(void* windowPtr, size_t width, size_t height); - //void SetView(void* windowPtr, size_t width, size_t height); - //void UpdateView(size_t width, size_t height); - //void ResetView(); void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y); void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y); - - class Native final - { - public: - // This class must be constructed from the JavaScript thread - Native(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, void* windowPtr, size_t width, size_t height); - ~Native(); - void Refresh(void* windowPtr, size_t width, size_t height); - void Resize(size_t width, size_t height); - void Reset(); - void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y); - void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y); - - private: - class Impl; - std::unique_ptr m_impl{}; - }; } diff --git a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h index f3890fe2b..c7417ba46 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h +++ b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.h @@ -3,15 +3,8 @@ #import #import -@interface BabylonNativeInterop2 : NSObject +@interface BabylonNativeInterop : NSObject + (void)initialize:(RCTBridge*)bridge; + (void)updateView:(MTKView*)mtkView; + (void)reportTouchEvent:(MTKView*)mtkView touches:(NSSet*)touches event:(UIEvent*)event; @end - -@interface BabylonNativeInterop : NSObject -+ (void)setView:(RCTBridge*)bridge jsRunLoop:(NSRunLoop*)jsRunLoop mktView:(MTKView*)mtkView; -+ (void)reportTouchEvent:(NSSet*)touches withEvent:(UIEvent*)event; -+ (void)whenInitialized:(RCTBridge*)bridge resolve:(RCTPromiseResolveBlock)resolve; -+ (void)reset; -@end diff --git a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm index 8a44f8e6d..a729d5e0c 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm @@ -6,11 +6,7 @@ #import -#import #import -#import -#import -#import using namespace facebook; @@ -25,9 +21,9 @@ @interface RCTBridge (RCTTurboModule) } } -@implementation BabylonNativeInterop2 +@implementation BabylonNativeInterop -static NSMutableArray* activeTouches2; +static NSMutableArray* activeTouches; + (void)initialize:(RCTBridge*)bridge { Babylon::Initialize(*GetJSIRuntime(bridge), bridge.jsCallInvoker); @@ -66,27 +62,27 @@ + (void)reportTouchEvent:(MTKView*)mtkView touches:(NSSet*)touches eve switch (touch.phase) { case UITouchPhaseBegan: { - NSUInteger pointerId = [activeTouches2 indexOfObject:[NSNull null]]; + NSUInteger pointerId = [activeTouches indexOfObject:[NSNull null]]; if (pointerId == NSNotFound) { - pointerId = [activeTouches2 count]; - [activeTouches2 addObject:touch]; + pointerId = [activeTouches count]; + [activeTouches addObject:touch]; } else { - [activeTouches2 replaceObjectAtIndex:pointerId withObject:touch]; + [activeTouches replaceObjectAtIndex:pointerId withObject:touch]; } Babylon::SetPointerButtonState(static_cast(pointerId), 0, true, x, y); break; } case UITouchPhaseMoved: { - NSUInteger pointerId = [activeTouches2 indexOfObject:touch]; + NSUInteger pointerId = [activeTouches indexOfObject:touch]; Babylon::SetPointerPosition(static_cast(pointerId), x, y); break; } case UITouchPhaseEnded: case UITouchPhaseCancelled: { - NSUInteger pointerId = [activeTouches2 indexOfObject:touch]; - [activeTouches2 replaceObjectAtIndex:pointerId withObject:[NSNull null]]; + NSUInteger pointerId = [activeTouches indexOfObject:touch]; + [activeTouches replaceObjectAtIndex:pointerId withObject:[NSNull null]]; Babylon::SetPointerButtonState(static_cast(pointerId), 0, false, x, y); break; } @@ -99,147 +95,3 @@ + (void)reportTouchEvent:(MTKView*)mtkView touches:(NSSet*)touches eve } @end - -@implementation BabylonNativeInterop - -static RCTBridge* currentBridge; -static MTKView* currentView; -static std::unique_ptr currentNativeInstance; -static std::unordered_map> initializationPromises; -static std::mutex mapMutex; -static NSMutableArray* activeTouches; - -+ (void)setView:(RCTBridge*)bridge jsRunLoop:(NSRunLoop*)jsRunLoop mktView:(MTKView*)mtkView { - const int width = static_cast(mtkView.bounds.size.width * UIScreen.mainScreen.scale); - const int height = static_cast(mtkView.bounds.size.height * UIScreen.mainScreen.scale); - if (width != 0 && height != 0) { - // NOTE: jsRunLoop should only be null when remote debugging is enabled. - // In this case, we can just use the main loop, because we are only - // going to set an error state (which can happen on any thread). - if (!jsRunLoop) { - jsRunLoop = NSRunLoop.mainRunLoop; - } - - [jsRunLoop performBlock:^{ - if (bridge != currentBridge) { - [BabylonNativeInterop setCurrentNativeInstance:bridge mtkView:mtkView width:width height:height]; - } else if (currentNativeInstance) { - if (mtkView != currentView) { - [BabylonNativeInterop setCurrentView:mtkView]; - currentNativeInstance->Refresh((__bridge void*)currentView, width, height); - } else { - // NOTE: This will cause Metal API Validation to fail if it is enabled when the debugger is attached, which stops app execution. - // For now, be sure to disable Metal API Validation under Product->Scheme->Edit Scheme. - currentNativeInstance->Resize(width, height); - } - } - }]; - } -} - -+ (void)reportTouchEvent:(NSSet*)touches withEvent:(UIEvent*)event { - if (currentNativeInstance) { - for (UITouch* touch in touches) { - if (touch.view == currentView) { - const CGFloat scale = UIScreen.mainScreen.scale; - const CGPoint pointerPosition = [touch locationInView:currentView]; - const uint32_t x = static_cast(pointerPosition.x * scale); - const uint32_t y = static_cast(pointerPosition.y * scale); - - switch (touch.phase) { - case UITouchPhaseBegan: { - NSUInteger pointerId = [activeTouches indexOfObject:[NSNull null]]; - if (pointerId == NSNotFound) { - pointerId = [activeTouches count]; - [activeTouches addObject:touch]; - } else { - [activeTouches replaceObjectAtIndex:pointerId withObject:touch]; - } - currentNativeInstance->SetPointerButtonState(static_cast(pointerId), 0, true, x, y); - break; - } - - case UITouchPhaseMoved: { - NSUInteger pointerId = [activeTouches indexOfObject:touch]; - currentNativeInstance->SetPointerPosition(static_cast(pointerId), x, y); - break; - } - - case UITouchPhaseEnded: - case UITouchPhaseCancelled: { - NSUInteger pointerId = [activeTouches indexOfObject:touch]; - [activeTouches replaceObjectAtIndex:pointerId withObject:[NSNull null]]; - currentNativeInstance->SetPointerButtonState(static_cast(pointerId), 0, false, x, y); - break; - } - - default: - break; - } - } - } - } -} - -+ (void)whenInitialized:(RCTBridge*)bridge resolve:(RCTPromiseResolveBlock)resolve { - const std::lock_guard lock(mapMutex); - if (bridge == currentBridge) { - resolve([NSNumber numberWithUnsignedLong:reinterpret_cast(currentNativeInstance.get())]); - } else { - initializationPromises[(__bridge void*)bridge].push_back(resolve); - } -} - -+ (void)reset { - if (currentNativeInstance) { - currentNativeInstance->Reset(); - } -} - -+ (void)setCurrentView:(MTKView*)mtkView { - currentView = mtkView; - activeTouches = [NSMutableArray new]; -} - -+ (void)setCurrentNativeInstance:(RCTBridge*)bridge mtkView:(MTKView*)mtkView width:(int)width height:(int)height { - [BabylonNativeInterop setCurrentView:mtkView]; - - { - const std::lock_guard lock(mapMutex); - - if (bridge != currentBridge) { - if (currentBridge == nil || currentBridge.parentBridge != bridge.parentBridge) { - [[NSNotificationCenter defaultCenter] addObserver:self - selector:@selector(onBridgeWillInvalidate:) - name:RCTBridgeWillInvalidateModulesNotification - object:bridge.parentBridge]; - } - - currentBridge = bridge; - } - - currentNativeInstance.reset(); - - jsi::Runtime* jsiRuntime = GetJSIRuntime(currentBridge); - if (jsiRuntime) { - currentNativeInstance = std::make_unique(*jsiRuntime, currentBridge.jsCallInvoker, (__bridge void*)mtkView, width, height); - } - } - - auto initializationPromisesIterator = initializationPromises.find((__bridge void*)currentBridge); - if (initializationPromisesIterator != initializationPromises.end()) { - for (RCTPromiseResolveBlock resolve : initializationPromisesIterator->second) { - resolve([NSNumber numberWithUnsignedLong:reinterpret_cast(currentNativeInstance.get())]); - } - - initializationPromises.erase(initializationPromisesIterator); - } -} - -// NOTE: This happens during dev mode reload, when the JS engine is being shutdown and restarted. -+ (void)onBridgeWillInvalidate:(NSNotification*)notification -{ - currentNativeInstance.reset(); -} - -@end diff --git a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm index accdcb851..c561e647e 100644 --- a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm +++ b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm @@ -15,7 +15,7 @@ @interface EngineView : MTKView @implementation EngineView { RCTBridge* bridge; - NSRunLoop* runLoop; + NSRunLoop* runLoop; // TODO: remove } - (instancetype)init:(RCTBridge*)_bridge runLoop:(NSRunLoop*)_runLoop { @@ -32,28 +32,23 @@ - (instancetype)init:(RCTBridge*)_bridge runLoop:(NSRunLoop*)_runLoop { - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; - //[BabylonNativeInterop setView:bridge jsRunLoop:runLoop mktView:self]; - [BabylonNativeInterop2 updateView:self]; + [BabylonNativeInterop updateView:self]; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { - //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; - [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; + [BabylonNativeInterop reportTouchEvent:self touches:touches event:event]; } - (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event { - //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; - [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; + [BabylonNativeInterop reportTouchEvent:self touches:touches event:event]; } - (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event { - //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; - [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; + [BabylonNativeInterop reportTouchEvent:self touches:touches event:event]; } - (void)touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event { - //[BabylonNativeInterop reportTouchEvent:touches withEvent:event]; - [BabylonNativeInterop2 reportTouchEvent:self touches:touches event:event]; + [BabylonNativeInterop reportTouchEvent:self touches:touches event:event]; } - (void)takeSnapshot { From 06f26e8ba511457580eb2616c7df544a0ab74754 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Wed, 16 Dec 2020 17:02:45 -0800 Subject: [PATCH 04/24] JS cleanup --- .../@babylonjs/react-native/BabylonModule.ts | 83 ++++++++++--------- .../@babylonjs/react-native/EngineHelpers.ts | 14 ---- Modules/@babylonjs/react-native/EngineHook.ts | 11 ++- .../@babylonjs/react-native/EngineView.tsx | 11 ++- .../react-native/ios/BabylonNative.cpp | 62 +++++++------- Package/gulpfile.js | 1 - 6 files changed, 86 insertions(+), 96 deletions(-) delete mode 100644 Modules/@babylonjs/react-native/EngineHelpers.ts diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index dfbed04c7..962be95ff 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -1,6 +1,7 @@ import { NativeModules } from 'react-native'; -import { NativeEngine } from '@babylonjs/core'; -import { DisposeEngine } from './EngineHelpers'; +import { Engine, NativeEngine } from '@babylonjs/core'; + +const disposedPropertyName = "BabylonReactNative_IsDisposed"; declare const global: { nativeCallSyncHook: any; @@ -10,52 +11,60 @@ const isRemoteDebuggingEnabled = !global.nativeCallSyncHook; // This global object is owned by Babylon Native. declare const _native: { whenGraphicsReady: () => Promise; - engineInstance: NativeEngine; }; // This JSI-based global object is owned by Babylon React Native. // This will likely be converted to a TurboModule when they are fully supported. declare const BabylonNative: { readonly initializationPromise: Promise; + setEngineInstance: (engine: NativeEngine | null) => void; reset: () => void; }; // This legacy React Native module is created by Babylon React Native, and is only used to bootstrap the JSI object creation. // This will likely be removed when the BabylonNative global object is eventually converted to a TurboModule. -const NativeBabylonModule: { +const BabylonModule: { initialize(): void; } = NativeModules.BabylonModule; -export const BabylonModule = { - ensureInitialized: async () => { - console.log("INITIALIZING"); - if (isRemoteDebuggingEnabled) { - return false; - } else { - NativeBabylonModule.initialize(); - await BabylonNative.initializationPromise; - await _native.whenGraphicsReady(); - return true; - } - }, - - //reset: NativeBabylonModule.reset, - reset: () => { - console.log("RESET"); - BabylonNative.reset(); - }, - - createEngine: () => { - const engine = new NativeEngine(); - _native.engineInstance = engine; - //BabylonNative.setEngineInstance(engine); - return engine; - }, - - disposeEngine: (engine: NativeEngine) => { - console.log("Beginning dispose"); - DisposeEngine(engine); - console.log("Finished dispose"); - //BabylonNative.setEngineInstance(null); - }, -}; \ No newline at end of file +export async function ensureInitialized(): Promise { + if (isRemoteDebuggingEnabled) { + // When remote debugging is enabled, JavaScript runs on the debugging host machine, not on the device where the app is running. + // JSI (which Babylon Native uses heavily) can not work in this mode. In the future, this debugging mode will be phased out as it is incompatible with TurboModules for the same reason. + return false; + } else { + // This does the first stage of Babylon Native initialization synchronously, including creating the BabylonNative JSI object. + BabylonModule.initialize(); + + // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). + await BabylonNative.initializationPromise; + + // This waits for the Graphics system to be up and running. + await _native.whenGraphicsReady(); + + return true; + } +} + +export function reset(): void { + BabylonNative.reset(); +} + +export function createEngine(): NativeEngine { + const engine = new NativeEngine(); + BabylonNative.setEngineInstance(engine); + return engine; +} + +export function isEngineDisposed(engine: Engine): boolean { + return (engine as any)[disposedPropertyName]; +} + +export function disposeEngine(engine: NativeEngine): void { + if (engine && !isEngineDisposed(engine)) { + engine.dispose(); + (engine as any)[disposedPropertyName] = true; + } + + BabylonNative.setEngineInstance(null); +} \ No newline at end of file diff --git a/Modules/@babylonjs/react-native/EngineHelpers.ts b/Modules/@babylonjs/react-native/EngineHelpers.ts deleted file mode 100644 index cb3051adc..000000000 --- a/Modules/@babylonjs/react-native/EngineHelpers.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Engine } from '@babylonjs/core'; - -const disposedPropertyName = "EngineHelper_IsDisposed"; - -export function IsEngineDisposed(engine: Engine): boolean { - return (engine as any)[disposedPropertyName]; -} - -export function DisposeEngine(engine: Engine) { - if (engine && !IsEngineDisposed(engine)) { - engine.dispose(); - (engine as any)[disposedPropertyName] = true; - } -} \ No newline at end of file diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index c2b9a8b42..4caae4527 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -2,8 +2,7 @@ import { useEffect, useState } from 'react'; import { Platform } from 'react-native'; import { PERMISSIONS, check, request } from 'react-native-permissions'; import { Engine, NativeEngine, WebXRSessionManager } from '@babylonjs/core'; -import { BabylonModule } from './BabylonModule'; -import { DisposeEngine } from './EngineHelpers'; +import { ensureInitialized, createEngine, disposeEngine, reset } from './BabylonModule'; 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. @@ -71,9 +70,9 @@ export function useEngine(): Engine | undefined { (async () => { console.log("Calling BabylonModule.initialize"); - if (await BabylonModule.ensureInitialized() && !disposed) + if (await ensureInitialized() && !disposed) { - engine = BabylonModule.createEngine(); + engine = createEngine(); setEngine(engine); } })(); @@ -83,13 +82,13 @@ export function useEngine(): Engine | undefined { // NOTE: Do not use setEngine with a callback to dispose the engine instance as that callback does not get called during component unmount when compiled in release. if (engine) { //DisposeEngine(engine); - BabylonModule.disposeEngine(engine); + disposeEngine(engine); } // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 if (!__DEV__) { - BabylonModule.reset(); + reset(); } setEngine(undefined); }; diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index eba586eee..38190c008 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -1,8 +1,7 @@ import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef } from 'react'; import { requireNativeComponent, NativeModules, ViewProps, AppState, AppStateStatus, View, Text, findNodeHandle, UIManager } from 'react-native'; import { Camera } from '@babylonjs/core'; -import { IsEngineDisposed } from './EngineHelpers'; -import { BabylonModule } from './BabylonModule'; +import { ensureInitialized, isEngineDisposed } from './BabylonModule'; declare const global: any; @@ -38,7 +37,7 @@ export const EngineView: FunctionComponent = (props: EngineView useEffect(() => { (async () => { - if (!await BabylonModule.ensureInitialized()) { + if (!await ensureInitialized()) { setFailedInitialization(true); } })(); @@ -60,7 +59,7 @@ export const EngineView: FunctionComponent = (props: EngineView if (props.camera && appState === "active") { const engine = props.camera.getScene().getEngine(); - if (!IsEngineDisposed(engine)) { + if (!isEngineDisposed(engine)) { engine.runRenderLoop(() => { for (let scene of engine.scenes) { scene.render(); @@ -68,7 +67,7 @@ export const EngineView: FunctionComponent = (props: EngineView }); return () => { - if (!IsEngineDisposed(engine)) { + if (!isEngineDisposed(engine)) { engine.stopRenderLoop(); } }; @@ -82,7 +81,7 @@ export const EngineView: FunctionComponent = (props: EngineView if (props.camera && (props.displayFrameRate ?? __DEV__)) { const engine = props.camera.getScene().getEngine(); - if (!IsEngineDisposed(engine)) { + if (!isEngineDisposed(engine)) { setFps(engine.getFps()); const timerHandle = setInterval(() => { setFps(engine.getFps()); diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index 1cc482b3c..16478468d 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -52,10 +52,10 @@ namespace Babylon return {}; }) ); - + // Initialize Babylon Native core components JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, m_jsCallInvoker, isShuttingDown)); - + // Initialize Babylon Native plugins Plugins::NativeXr::Initialize(m_env); m_nativeInput = &Babylon::Plugins::NativeInput::CreateForJavaScript(m_env); @@ -67,23 +67,24 @@ namespace Babylon // React Native's implementation, but rather adds a second one scoped to Babylon and used by WebRequest.ts. Polyfills::XMLHttpRequest::Initialize(m_env); } - + ~ReactNativeModule() { isShuttingDown = true; *m_isRunning = false; Napi::Detach(m_env); } - + // NOTE: This only happens when the JS engine is shutting down (other than when the app exits, this only // happens during a dev mode reload). In this case, EngineHook.ts won't call NativeEngine.dispose, // so we need to manually do it here to properly clean up these resources. void Deinitialize() { - auto native = JsRuntime::NativeObject::GetFromJavaScript(m_env); - auto engine = native.Get("engineInstance").As(); - auto dispose = engine.Get("dispose").As(); - dispose.Call(engine, {}); + if (m_disposeEngine) + { + m_disposeEngine(); + m_disposeEngine = {}; + } } void UpdateView(void* windowPtr, size_t width, size_t height) @@ -144,28 +145,25 @@ namespace Babylon return {}; }); } -// else if (prop.utf8(runtime) == "setEngineInstance") -// { -// return jsi::Function::createFromHostFunction(runtime, prop, 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value -// { -// if (count > 0 && args[0].isObject()) -// { -// -// } -// if (count == 0 || !args[0].isObject()) -// { -// m_disposeEngine = {}; -// } -// else -// { -// m_disposeEngine = [&rt, engineInstance{ std::make_shared(rt, args[0]) }]() -// { -// engineInstance->getObject(rt).getProperty(rt, "dispose").asObject(rt).getFunction(rt).call(rt); -// }; -// } -// return {}; -// }); -// } + else if (prop.utf8(runtime) == "setEngineInstance") + { + return jsi::Function::createFromHostFunction(runtime, prop, 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value + { + if (count == 0 || !args[0].isObject()) + { + m_disposeEngine = {}; + } + else + { + m_disposeEngine = [&rt, engineInstanceValue{ std::make_shared(rt, args[0]) }]() + { + auto engineInstance = engineInstanceValue->getObject(rt); + engineInstance.getPropertyAsFunction(rt, "dispose").callWithThis(rt, engineInstance); + }; + } + return {}; + }); + } return jsi::Value::undefined(); } @@ -180,8 +178,8 @@ namespace Babylon std::shared_ptr m_isRunning{}; std::unique_ptr m_graphics{}; Plugins::NativeInput* m_nativeInput{}; - -// std::function m_disposeEngine{}; + + std::function m_disposeEngine{}; }; namespace diff --git a/Package/gulpfile.js b/Package/gulpfile.js index 4cd308add..b2b493acb 100644 --- a/Package/gulpfile.js +++ b/Package/gulpfile.js @@ -124,7 +124,6 @@ Assembled/ios/ReactNativeBabylon.xcodeproj/project.xcworkspace/xcshareddata/Work Assembled/ios/BabylonModule.mm Assembled/ios/BabylonNative.h Assembled/README.md -Assembled/EngineHelpers.ts Assembled/package.json Assembled/android Assembled/android/build.gradle From 8c9f977e3f9d98c82853e96cb1574348072fc866 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Wed, 16 Dec 2020 17:04:45 -0800 Subject: [PATCH 05/24] Remove comments and logs --- Modules/@babylonjs/react-native/EngineHook.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index 4caae4527..d5494f0bd 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -69,7 +69,6 @@ export function useEngine(): Engine | undefined { let engine: NativeEngine | undefined = undefined; (async () => { - console.log("Calling BabylonModule.initialize"); if (await ensureInitialized() && !disposed) { engine = createEngine(); @@ -81,7 +80,6 @@ export function useEngine(): Engine | undefined { disposed = true; // NOTE: Do not use setEngine with a callback to dispose the engine instance as that callback does not get called during component unmount when compiled in release. if (engine) { - //DisposeEngine(engine); disposeEngine(engine); } // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that From fd28325a893bc681c6cdd27d3a217fb91d1ef3c9 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 17 Dec 2020 11:46:57 -0800 Subject: [PATCH 06/24] Remove dead code --- .../@babylonjs/react-native/ios/BabylonNative.cpp | 10 +--------- .../react-native/ios/EngineViewManager.mm | 15 +++------------ .../react-native/shared/DispatchFunction.h | 8 ++++---- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index 16478468d..cc2d73c8a 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -24,11 +24,6 @@ namespace Babylon { using namespace facebook; - namespace - { - bool isShuttingDown{false}; - } - class ReactNativeModule : public jsi::HostObject { public: @@ -37,8 +32,6 @@ namespace Babylon , m_jsCallInvoker{ jsCallInvoker } , m_isRunning{ std::make_shared(true) } { - isShuttingDown = false; - // Initialize a JS promise that will be returned by whenInitialized, and completed when NativeEngine is initialized. m_initPromise = jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "Promise").callAsConstructor ( @@ -54,7 +47,7 @@ namespace Babylon ); // Initialize Babylon Native core components - JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, m_jsCallInvoker, isShuttingDown)); + JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, m_jsCallInvoker, m_isRunning)); // Initialize Babylon Native plugins Plugins::NativeXr::Initialize(m_env); @@ -70,7 +63,6 @@ namespace Babylon ~ReactNativeModule() { - isShuttingDown = true; *m_isRunning = false; Napi::Detach(m_env); } diff --git a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm index c561e647e..177982006 100644 --- a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm +++ b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm @@ -15,13 +15,11 @@ @interface EngineView : MTKView @implementation EngineView { RCTBridge* bridge; - NSRunLoop* runLoop; // TODO: remove } -- (instancetype)init:(RCTBridge*)_bridge runLoop:(NSRunLoop*)_runLoop { +- (instancetype)init:(RCTBridge*)_bridge { if (self = [super initWithFrame:CGRectZero device:MTLCreateSystemDefaultDevice()]) { bridge = _bridge; - runLoop = _runLoop; super.translatesAutoresizingMaskIntoConstraints = false; super.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB; @@ -79,9 +77,7 @@ - (void)takeSnapshot { @interface EngineViewManager : RCTViewManager @end -@implementation EngineViewManager { - NSRunLoop* runLoop; -} +@implementation EngineViewManager RCT_EXPORT_MODULE(EngineViewManager) @@ -98,13 +94,8 @@ @implementation EngineViewManager { }]; } -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(setJSThread) { - runLoop = [NSRunLoop currentRunLoop]; - return nil; -} - - (UIView*)view { - return [[EngineView alloc] init:self.bridge runLoop:runLoop]; + return [[EngineView alloc] init:self.bridge]; } @end diff --git a/Modules/@babylonjs/react-native/shared/DispatchFunction.h b/Modules/@babylonjs/react-native/shared/DispatchFunction.h index ae05a5706..306e93a70 100644 --- a/Modules/@babylonjs/react-native/shared/DispatchFunction.h +++ b/Modules/@babylonjs/react-native/shared/DispatchFunction.h @@ -10,9 +10,9 @@ namespace Babylon using namespace facebook; // Creates a JsRuntime::DispatchFunctionT that integrates with the React Native execution environment. - inline JsRuntime::DispatchFunctionT CreateJsRuntimeDispatcher(Napi::Env env, jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, const bool& isShuttingDown) + inline JsRuntime::DispatchFunctionT CreateJsRuntimeDispatcher(Napi::Env env, jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, const std::shared_ptr isRunning) { - return [env, &jsiRuntime, callInvoker, &isShuttingDown](std::function func) + return [env, &jsiRuntime, callInvoker, isRunning{ std::move(isRunning) }](std::function func) { // Ideally we would just use CallInvoker::invokeAsync directly, but currently it does not seem to integrate well with the React Native logbox. // To work around this, we wrap all functions in a try/catch, and when there is an exception, we do the following: @@ -23,12 +23,12 @@ namespace Babylon // 1. setImmediate queues the callback, and that queue is drained immediately following the invocation of the function passed to CallInvoker::invokeAsync. // 2. The immediates queue is drained as part of the class bridge, which knows how to display the logbox for unhandled exceptions. // In the future, CallInvoker::invokeAsync likely will properly integrate with logbox, at which point we can remove the try/catch and just call func directly. - callInvoker->invokeAsync([env, &jsiRuntime, func{std::move(func)}, &isShuttingDown] + callInvoker->invokeAsync([env, &jsiRuntime, isRunning{ std::move(isRunning) }, func{ std::move(func) }] { try { // If JS engine shutdown is in progress, don't dispatch any new work. - if (!isShuttingDown) + if (*isRunning) { func(env); } From f65336c4331a0a5f83a30c12290661ed5f7810a6 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 17 Dec 2020 12:14:55 -0800 Subject: [PATCH 07/24] Convert BabylonReactNative shared lib to just a shared CMakeLists.txt include (since it will contain source) --- Modules/@babylonjs/react-native/ios/CMakeLists.txt | 6 ++++-- Modules/@babylonjs/react-native/shared/CMakeLists.txt | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Modules/@babylonjs/react-native/ios/CMakeLists.txt b/Modules/@babylonjs/react-native/ios/CMakeLists.txt index 4dbf11777..887ac6c15 100644 --- a/Modules/@babylonjs/react-native/ios/CMakeLists.txt +++ b/Modules/@babylonjs/react-native/ios/CMakeLists.txt @@ -1,6 +1,7 @@ cmake_minimum_required(VERSION 3.13.2) - project(ReactNativeBabylon) +include(${CMAKE_CURRENT_LIST_DIR}/../shared/CMakeLists.txt) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") @@ -27,12 +28,14 @@ set(BABYLON_REACT_NATIVE_SHARED_DIR "${CMAKE_CURRENT_LIST_DIR}/../shared") add_subdirectory(${BABYLON_REACT_NATIVE_SHARED_DIR} ${CMAKE_CURRENT_BINARY_DIR}/shared) add_library(BabylonNative + ${SHARED_SOURCES} ${CMAKE_CURRENT_LIST_DIR}/BabylonNative.h ${CMAKE_CURRENT_LIST_DIR}/BabylonNative.cpp) target_compile_definitions(BabylonNative PRIVATE UNICODE) target_compile_definitions(BabylonNative PRIVATE _UNICODE) +target_include_directories(BabylonNative PRIVATE ${SHARED_INCLUDES}) target_include_directories(BabylonNative PUBLIC ${CMAKE_CURRENT_LIST_DIR}) target_link_libraries(BabylonNative @@ -41,7 +44,6 @@ target_link_libraries(BabylonNative Graphics jsi reactnative - BabylonReactNativeShared JsRuntime NativeEngine NativeInput diff --git a/Modules/@babylonjs/react-native/shared/CMakeLists.txt b/Modules/@babylonjs/react-native/shared/CMakeLists.txt index ea2151292..fecc97af7 100644 --- a/Modules/@babylonjs/react-native/shared/CMakeLists.txt +++ b/Modules/@babylonjs/react-native/shared/CMakeLists.txt @@ -1,2 +1,5 @@ -add_library(BabylonReactNativeShared INTERFACE) -target_include_directories(BabylonReactNativeShared INTERFACE ".") \ No newline at end of file +set(SHARED_INCLUDES + "${CMAKE_CURRENT_LIST_DIR}") + +set(SHARED_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/DispatchFunction.h") \ No newline at end of file From 209f2d3b9b336aeabaee5c200a03435270818227 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 17 Dec 2020 12:44:26 -0800 Subject: [PATCH 08/24] Move shared code --- .../@babylonjs/react-native/ios/BabylonNativeInterop.mm | 2 +- Modules/@babylonjs/react-native/ios/CMakeLists.txt | 4 +--- .../@babylonjs/react-native/react-native-babylon.podspec | 1 - .../react-native/{ios => shared}/BabylonNative.cpp | 0 .../react-native/{ios => shared}/BabylonNative.h | 0 Modules/@babylonjs/react-native/shared/CMakeLists.txt | 4 +++- Package/gulpfile.js | 7 ++++++- 7 files changed, 11 insertions(+), 7 deletions(-) rename Modules/@babylonjs/react-native/{ios => shared}/BabylonNative.cpp (100%) rename Modules/@babylonjs/react-native/{ios => shared}/BabylonNative.h (100%) diff --git a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm index a729d5e0c..a3bd8148f 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm @@ -1,5 +1,5 @@ #import "BabylonNativeInterop.h" -#import "BabylonNative.h" +#import "../shared/BabylonNative.h" #import #import diff --git a/Modules/@babylonjs/react-native/ios/CMakeLists.txt b/Modules/@babylonjs/react-native/ios/CMakeLists.txt index 887ac6c15..5b32f7ae4 100644 --- a/Modules/@babylonjs/react-native/ios/CMakeLists.txt +++ b/Modules/@babylonjs/react-native/ios/CMakeLists.txt @@ -28,9 +28,7 @@ set(BABYLON_REACT_NATIVE_SHARED_DIR "${CMAKE_CURRENT_LIST_DIR}/../shared") add_subdirectory(${BABYLON_REACT_NATIVE_SHARED_DIR} ${CMAKE_CURRENT_BINARY_DIR}/shared) add_library(BabylonNative - ${SHARED_SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/BabylonNative.h - ${CMAKE_CURRENT_LIST_DIR}/BabylonNative.cpp) + ${SHARED_SOURCES}) target_compile_definitions(BabylonNative PRIVATE UNICODE) target_compile_definitions(BabylonNative PRIVATE _UNICODE) diff --git a/Modules/@babylonjs/react-native/react-native-babylon.podspec b/Modules/@babylonjs/react-native/react-native-babylon.podspec index 8638e7abb..2e9be0360 100644 --- a/Modules/@babylonjs/react-native/react-native-babylon.podspec +++ b/Modules/@babylonjs/react-native/react-native-babylon.podspec @@ -14,7 +14,6 @@ Pod::Spec.new do |s| s.source = { :git => package["repository"]["url"], :tag => s.version } s.source_files = "ios/**/*.{h,m,mm}" - s.exclude_files = "ios/BabylonNative.h" s.requires_arc = true s.libraries = 'astc-codec', diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp similarity index 100% rename from Modules/@babylonjs/react-native/ios/BabylonNative.cpp rename to Modules/@babylonjs/react-native/shared/BabylonNative.cpp diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.h b/Modules/@babylonjs/react-native/shared/BabylonNative.h similarity index 100% rename from Modules/@babylonjs/react-native/ios/BabylonNative.h rename to Modules/@babylonjs/react-native/shared/BabylonNative.h diff --git a/Modules/@babylonjs/react-native/shared/CMakeLists.txt b/Modules/@babylonjs/react-native/shared/CMakeLists.txt index fecc97af7..2744bc19c 100644 --- a/Modules/@babylonjs/react-native/shared/CMakeLists.txt +++ b/Modules/@babylonjs/react-native/shared/CMakeLists.txt @@ -2,4 +2,6 @@ set(SHARED_INCLUDES "${CMAKE_CURRENT_LIST_DIR}") set(SHARED_SOURCES - "${CMAKE_CURRENT_LIST_DIR}/DispatchFunction.h") \ No newline at end of file + "${CMAKE_CURRENT_LIST_DIR}/DispatchFunction.h" + "${CMAKE_CURRENT_LIST_DIR}/BabylonNative.h" + "${CMAKE_CURRENT_LIST_DIR}/BabylonNative.cpp") \ No newline at end of file diff --git a/Package/gulpfile.js b/Package/gulpfile.js index b2b493acb..40ce1dfc5 100644 --- a/Package/gulpfile.js +++ b/Package/gulpfile.js @@ -48,6 +48,11 @@ const copyCommonFiles = () => { .pipe(gulp.dest('Assembled')); }; +const copySharedFiles = () => { + return gulp.src('../Apps/Playground/node_modules/@babylonjs/react-native/shared/BabylonNative.h') + .pipe(gulp.dest('Assembled/shared')); +}; + const copyIOSFiles = () => { return gulp.src('../Apps/Playground/node_modules/@babylonjs/react-native/ios/*.h') .pipe(gulp.src('../Apps/Playground/node_modules/@babylonjs/react-native/ios/*.mm')) @@ -164,7 +169,7 @@ const createPackage = async () => { exec('npm pack', 'Assembled'); }; -const copyFiles = gulp.parallel(copyCommonFiles, copyIOSFiles, copyAndroidFiles); +const copyFiles = gulp.parallel(copyCommonFiles, copySharedFiles, copyIOSFiles, copyAndroidFiles); const build = gulp.series(buildIOS, buildAndroid, createIOSUniversalLibs, copyFiles, validate); const rebuild = gulp.series(clean, build); From 2d54fdc63aef16deacca3523a36b652a187fa750 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 17 Dec 2020 16:43:18 -0800 Subject: [PATCH 09/24] Update Android to use shared code --- .../@babylonjs/react-native/BabylonModule.ts | 6 +- .../react-native/android/CMakeLists.txt | 8 +- .../src/main/cpp/BabylonNativeInterop.cpp | 156 +++---------- .../com/babylonreactnative/BabylonModule.java | 29 +-- .../BabylonNativeInterop.java | 216 ++++++------------ .../com/babylonreactnative/EngineView.java | 9 +- .../react-native/ios/BabylonModule.mm | 8 +- .../react-native/shared/BabylonNative.cpp | 16 +- 8 files changed, 125 insertions(+), 323 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 962be95ff..4c55d1109 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -24,7 +24,7 @@ declare const BabylonNative: { // This legacy React Native module is created by Babylon React Native, and is only used to bootstrap the JSI object creation. // This will likely be removed when the BabylonNative global object is eventually converted to a TurboModule. const BabylonModule: { - initialize(): void; + initialize(): Promise; } = NativeModules.BabylonModule; export async function ensureInitialized(): Promise { @@ -33,8 +33,8 @@ export async function ensureInitialized(): Promise { // JSI (which Babylon Native uses heavily) can not work in this mode. In the future, this debugging mode will be phased out as it is incompatible with TurboModules for the same reason. return false; } else { - // This does the first stage of Babylon Native initialization synchronously, including creating the BabylonNative JSI object. - BabylonModule.initialize(); + // This does the first stage of Babylon Native initialization, including creating the BabylonNative JSI object. + await BabylonModule.initialize(); // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). await BabylonNative.initializationPromise; diff --git a/Modules/@babylonjs/react-native/android/CMakeLists.txt b/Modules/@babylonjs/react-native/android/CMakeLists.txt index dd11974d1..c9d01b8e8 100644 --- a/Modules/@babylonjs/react-native/android/CMakeLists.txt +++ b/Modules/@babylonjs/react-native/android/CMakeLists.txt @@ -19,6 +19,8 @@ cmake_minimum_required(VERSION 3.13.2) # detection. # [24, infinite) ES2 & ES3 & Vulkan project(ReactNativeBabylon) +include(${CMAKE_CURRENT_LIST_DIR}/../shared/CMakeLists.txt) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall") set(BABYLON_NATIVE_PLATFORM "Android") set(CMAKE_CXX_EXTENSIONS OFF) @@ -55,7 +57,10 @@ target_link_libraries(turbomodulejsijni fbjni) add_library(BabylonNative SHARED - src/main/cpp/BabylonNativeInterop.cpp) + src/main/cpp/BabylonNativeInterop.cpp + ${SHARED_SOURCES}) + +target_include_directories(BabylonNative PRIVATE ${SHARED_INCLUDES}) target_link_libraries(BabylonNative GLESv3 @@ -67,7 +72,6 @@ target_link_libraries(BabylonNative fbjni jsi turbomodulejsijni - BabylonReactNativeShared AndroidExtensions Graphics JsRuntime diff --git a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp index a69bd1b82..8cea764ab 100644 --- a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp +++ b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp @@ -10,176 +10,72 @@ #include -#include #include #include -#include -#include -#include - #include #include -#include +#include using namespace facebook; -namespace Babylon +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_initialize(JNIEnv* env, jclass obj, jobject context, jlong jsiRuntimeRef, jobject jsCallInvokerHolder) { - namespace + static bool initializedJVM = false; + if (!initializedJVM) { - void log(const char *str) + JavaVM *javaVM{}; + if (env->GetJavaVM(&javaVM) != JNI_OK) { - __android_log_print(ANDROID_LOG_VERBOSE, "BabylonNative", "%s", str); + throw std::runtime_error("Failed to get Java VM"); } - bool isShuttingDown{false}; - } - - class Native final - { - public: - // This class must be constructed from the JavaScript thread - Native(jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, ANativeWindow* windowPtr) - : m_env{ Napi::Attach(jsiRuntime) } - { - isShuttingDown = false; - - m_runtime = &JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, std::move(callInvoker), isShuttingDown)); - - auto width = static_cast(ANativeWindow_getWidth(windowPtr)); - auto height = static_cast(ANativeWindow_getHeight(windowPtr)); - - m_graphics = Graphics::CreateGraphics(reinterpret_cast(windowPtr), width, height); - m_graphics->AddToJavaScript(m_env); - - Plugins::NativeEngine::Initialize(m_env, true); - Plugins::NativeXr::Initialize(m_env); - - Polyfills::Window::Initialize(m_env); - // NOTE: React Native's XMLHttpRequest is slow and allocates a lot of memory. This does not override - // React Native's implementation, but rather adds a second one scoped to Babylon and used by WebRequest.ts. - Polyfills::XMLHttpRequest::Initialize(m_env); - - m_nativeInput = &Babylon::Plugins::NativeInput::CreateForJavaScript(m_env); - } - - // NOTE: This only happens when the JS engine is shutting down (other than when the app exits, this only - // happens during a dev mode reload). In this case, EngineHook.ts won't call NativeEngine.dispose, - // so we need to manually do it here to properly clean up these resources. - ~Native() - { - auto native = JsRuntime::NativeObject::GetFromJavaScript(m_env); - auto engine = native.Get("engineInstance").As(); - auto dispose = engine.Get("dispose").As(); - dispose.Call(engine, {}); - isShuttingDown = true; - - Napi::Detach(m_env); - } - - void Refresh(ANativeWindow* windowPtr) - { - auto width = static_cast(ANativeWindow_getWidth(windowPtr)); - auto height = static_cast(ANativeWindow_getHeight(windowPtr)); - m_graphics->UpdateWindow(windowPtr); - m_graphics->UpdateSize(width, height); - m_graphics->EnableRendering(); - } - - void Reset() - { - m_graphics->DisableRendering(); - } + android::global::Initialize(javaVM, context); - void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) - { - if (isDown) - { - m_nativeInput->PointerDown(pointerId, buttonId, x, y); - } - else - { - m_nativeInput->PointerUp(pointerId, buttonId, x, y); - } - } + initializedJVM = true; + } - void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y) - { - m_nativeInput->PointerMove(pointerId, x, y); - } + auto jsiRuntime = reinterpret_cast(jsiRuntimeRef); + auto jsCallInvoker = jni::alias_ref {reinterpret_cast(jsCallInvokerHolder)}->cthis()->getCallInvoker(); - private: - std::unique_ptr m_graphics{}; - Napi::Env m_env; - JsRuntime* m_runtime; - Plugins::NativeInput* m_nativeInput; - }; + Babylon::Initialize(*jsiRuntime, jsCallInvoker); } -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_initialize(JNIEnv* env, jclass obj, jobject context) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_deinitialize(JNIEnv* env, jclass obj) { - JavaVM* javaVM{}; - if (env->GetJavaVM(&javaVM) != JNI_OK) - { - throw std::runtime_error("Failed to get Java VM"); - } - - android::global::Initialize(javaVM, context); + Babylon::Deinitialize(); } -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_setCurrentActivity(JNIEnv* env, jclass obj, jobject activity) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_setCurrentActivity(JNIEnv* env, jclass obj, jobject activity) { android::global::SetCurrentActivity(activity); } -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_pause(JNIEnv* env, jclass obj) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_pause(JNIEnv* env, jclass obj) { android::global::Pause(); } -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_resume(JNIEnv* env, jclass obj) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_resume(JNIEnv* env, jclass obj) { android::global::Resume(); } -extern "C" JNIEXPORT jlong JNICALL Java_com_babylonreactnative_BabylonNativeInterop_create(JNIEnv* env, jclass obj, jlong jsiRuntimeRef, jobject jsCallInvokerHolder, jobject surface) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_updateView(JNIEnv* env, jclass obj, jobject surface) { - auto jsiRuntime = reinterpret_cast(jsiRuntimeRef); - auto callInvoker = jni::alias_ref {reinterpret_cast(jsCallInvokerHolder)}->cthis()->getCallInvoker(); ANativeWindow* windowPtr = ANativeWindow_fromSurface(env, surface); - auto native = new Babylon::Native(*jsiRuntime, callInvoker, windowPtr); - return reinterpret_cast(native); -} - -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_refresh(JNIEnv* env, jclass obj, jlong instanceRef, jobject surface) -{ - auto native = reinterpret_cast(instanceRef); - ANativeWindow* windowPtr = ANativeWindow_fromSurface(env, surface); - native->Refresh(windowPtr); -} - -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_setPointerButtonState(JNIEnv* env, jclass obj, jlong instanceRef, jint pointerId, jint buttonId, jboolean isDown, jint x, jint y) -{ - auto native = reinterpret_cast(instanceRef); - native->SetPointerButtonState(static_cast(pointerId), static_cast(buttonId), isDown, static_cast(x), static_cast(y)); -} - -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_setPointerPosition(JNIEnv* env, jclass obj, jlong instanceRef, jint pointerId, jint x, jint y) -{ - auto native = reinterpret_cast(instanceRef); - native->SetPointerPosition(static_cast(pointerId), static_cast(x), static_cast(y)); + auto width = static_cast(ANativeWindow_getWidth(windowPtr)); + auto height = static_cast(ANativeWindow_getHeight(windowPtr)); + Babylon::UpdateView(windowPtr, width, height); } -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_reset(JNIEnv* env, jclass obj, jlong instanceRef) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_setPointerButtonState(JNIEnv* env, jclass obj, jint pointerId, jint buttonId, jboolean isDown, jint x, jint y) { - auto native = reinterpret_cast(instanceRef); - native->Reset(); + Babylon::SetPointerButtonState(static_cast(pointerId), static_cast(buttonId), isDown, static_cast(x), static_cast(y)); } -extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_destroy(JNIEnv* env, jclass obj, jlong instanceRef) +extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_setPointerPosition(JNIEnv* env, jclass obj, jint pointerId, jint x, jint y) { - auto native = reinterpret_cast(instanceRef); - delete native; + Babylon::SetPointerPosition(static_cast(pointerId), static_cast(x), static_cast(y)); } diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonModule.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonModule.java index 573bc0e76..94ae5fb77 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonModule.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonModule.java @@ -1,8 +1,5 @@ package com.babylonreactnative; -import android.os.Handler; -import android.os.Looper; - import androidx.annotation.NonNull; import com.facebook.react.bridge.Promise; @@ -22,29 +19,17 @@ public String getName() { return "BabylonModule"; } - // NOTE: This happens during dev mode reload, when the JS engine is being shutdown and restarted. - @Override - public void onCatalystInstanceDestroy() { - this.getReactApplicationContext().runOnJSQueueThread(BabylonNativeInterop::deinitialize); - } - @ReactMethod public void initialize(Promise promise) { - // Ideally we'd do all the initialization here that is scoped to a javascript engine instance, but this is tied up in the view initialization in Babylon Native currently. - // For now, just await initialization by the first EngineView that is created. - BabylonNativeInterop.whenInitialized(this.getReactApplicationContext()).thenAccept(instanceRef -> promise.resolve(instanceRef != 0)); - } - - @ReactMethod - public void whenInitialized(Promise promise) { - BabylonNativeInterop.whenInitialized(this.getReactApplicationContext()).thenAccept(instanceRef -> promise.resolve(instanceRef != 0)); - } - - @ReactMethod - public void reset(Promise promise) { this.getReactApplicationContext().runOnJSQueueThread(() -> { - BabylonNativeInterop.reset(this.getReactApplicationContext()); + BabylonNativeInterop.initialize(this.getReactApplicationContext()); promise.resolve(null); }); } + + // NOTE: This happens during dev mode reload, when the JS engine is being shutdown and restarted. + @Override + public void onCatalystInstanceDestroy() { + this.getReactApplicationContext().runOnJSQueueThread(BabylonNativeInterop::deinitialize); + } } diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java index fa4d8231f..ce87e8696 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java @@ -7,169 +7,97 @@ import android.view.Surface; import com.facebook.react.bridge.ActivityEventListener; -import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactContext; import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; +public final class BabylonNativeInterop { -final class BabylonNativeInterop { - // JNI interface - static { - System.loadLibrary("BabylonNative"); + private static class BabylonNative { + static { + System.loadLibrary("BabylonNative"); + } + + public static native void initialize(Context context, long jsiRuntimeRef, CallInvokerHolder jsCallInvokerHolder); + public static native void deinitialize(); + public static native void setCurrentActivity(Activity activity); + public static native void pause(); + public static native void resume(); + public static native void updateView(Surface surface); + public static native void setPointerButtonState(int pointerId, int buttonId, boolean isDown, int x, int y); + public static native void setPointerPosition(int pointerId, int x, int y); } - private static boolean isInitialized; - private static final Hashtable> nativeInstances = new Hashtable<>(); - - private static native void initialize(Context context); - private static native void setCurrentActivity(Activity activity); - private static native void pause(); - private static native void resume(); - private static native long create(long jsiRuntimeRef, CallInvokerHolder callInvokerHolder, Surface surface); - private static native void refresh(long instanceRef, Surface surface); - private static native void setPointerButtonState(long instanceRef, int pointerId, int buttonId, boolean isDown, int x, int y); - private static native void setPointerPosition(long instanceRef, int pointerId, int x, int y); - private static native void reset(long instanceRef); - private static native void destroy(long instanceRef); - - // Must be called from the Android UI thread - static void setView(ReactContext reactContext, Surface surface) { - // This is global initialization that only needs to happen once - if (!BabylonNativeInterop.isInitialized) { - BabylonNativeInterop.initialize(reactContext); - BabylonNativeInterop.isInitialized = true; + private static ReactContext currentContext; + + private final static LifecycleEventListener lifeCycleEventListener = new LifecycleEventListener() { + @Override + public void onHostResume() { + BabylonNative.setCurrentActivity(BabylonNativeInterop.currentContext.getCurrentActivity()); + BabylonNative.resume(); } - BabylonNativeInterop.destroyOldNativeInstances(reactContext); - - CompletableFuture instanceRefFuture = BabylonNativeInterop.getOrCreateFuture(reactContext); - - reactContext.runOnJSQueueThread(() -> { - Long instanceRef = instanceRefFuture.getNow(null); - if (instanceRef == null) - { - long jsiRuntimeRef = reactContext.getJavaScriptContextHolder().get(); - if (jsiRuntimeRef == 0) { - instanceRefFuture.complete(0L); - } else { - instanceRef = BabylonNativeInterop.create(jsiRuntimeRef, reactContext.getCatalystInstance().getJSCallInvokerHolder(), surface); - final long finalInstanceRef = instanceRef; - - reactContext.addLifecycleEventListener(new LifecycleEventListener() { - @Override - public void onHostResume() { - BabylonNativeInterop.setCurrentActivity(reactContext.getCurrentActivity()); - BabylonNativeInterop.resume(); - } - - @Override - public void onHostPause() { - BabylonNativeInterop.pause(); - } - - @Override - public void onHostDestroy() { - BabylonNativeInterop.deinitialize(); - } - }); - - reactContext.addActivityEventListener(new ActivityEventListener() { - @Override - public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { - // Nothing to do here - } - - @Override - public void onNewIntent(Intent intent) { - BabylonNativeInterop.setCurrentActivity(reactContext.getCurrentActivity()); - } - }); - - instanceRefFuture.complete(finalInstanceRef); - } - } else if (instanceRef != 0) { - BabylonNativeInterop.refresh(instanceRef, surface); - } - }); - } + @Override + public void onHostPause() { + BabylonNative.pause(); + } - static void reportMotionEvent(ReactContext reactContext, MotionEvent motionEvent) { - CompletableFuture instanceRefFuture = BabylonNativeInterop.nativeInstances.get(reactContext.getJavaScriptContextHolder()); - if (instanceRefFuture != null) { - Long instanceRef = instanceRefFuture.getNow(null); - if (instanceRef != null) { - int maskedAction = motionEvent.getActionMasked(); - boolean isPointerDown = maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN; - boolean isPointerUp = maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP; - boolean isPointerMove = maskedAction == MotionEvent.ACTION_MOVE; - - if (isPointerDown || isPointerUp) { - int pointerIndex = motionEvent.getActionIndex(); - int pointerId = motionEvent.getPointerId(pointerIndex); - int buttonId = motionEvent.getActionButton(); - int x = (int)motionEvent.getX(pointerIndex); - int y = (int)motionEvent.getY(pointerIndex); - BabylonNativeInterop.setPointerButtonState(instanceRef, pointerId, buttonId, isPointerDown, x, y); - } else if (isPointerMove) { - for (int pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); pointerIndex++) { - int pointerId = motionEvent.getPointerId(pointerIndex); - int x = (int)motionEvent.getX(pointerIndex); - int y = (int)motionEvent.getY(pointerIndex); - BabylonNativeInterop.setPointerPosition(instanceRef, pointerId, x, y); - } - } - } + @Override + public void onHostDestroy() { + BabylonNative.deinitialize(); } - } + }; - // Must be called from the Android UI thread - static CompletionStage whenInitialized(ReactContext reactContext) { - return BabylonNativeInterop.getOrCreateFuture(reactContext); - } + private final static ActivityEventListener activityEventListener = new ActivityEventListener() { + @Override + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + // Nothing to do here + } + + @Override + public void onNewIntent(Intent intent) { + BabylonNative.setCurrentActivity(BabylonNativeInterop.currentContext.getCurrentActivity()); + } + }; + + public static void initialize(ReactContext reactContext) { + BabylonNativeInterop.currentContext = reactContext; - // Must be called from the JavaScript thread - static void deinitialize() { - BabylonNativeInterop.destroyOldNativeInstances(null); + long jsiRuntimeRef = BabylonNativeInterop.currentContext.getJavaScriptContextHolder().get(); + CallInvokerHolder jsCallInvokerHolder = BabylonNativeInterop.currentContext.getCatalystInstance().getJSCallInvokerHolder(); + BabylonNative.initialize(BabylonNativeInterop.currentContext, jsiRuntimeRef, jsCallInvokerHolder); + + BabylonNativeInterop.currentContext.removeLifecycleEventListener(lifeCycleEventListener); + BabylonNativeInterop.currentContext.addLifecycleEventListener(lifeCycleEventListener); } - static void reset(ReactContext reactContext) { - JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder(); - CompletableFuture instanceRefFuture = BabylonNativeInterop.nativeInstances.get(jsContext); - if (instanceRefFuture != null) { - Long instanceRef = instanceRefFuture.getNow(null); - if (instanceRef != null) { - BabylonNativeInterop.reset(instanceRef); - } - } + public static void deinitialize() { + BabylonNative.deinitialize(); } - private static CompletableFuture getOrCreateFuture(ReactContext reactContext) { - JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder(); - CompletableFuture instanceRefFuture = BabylonNativeInterop.nativeInstances.get(jsContext); - if (instanceRefFuture == null) - { - instanceRefFuture = new CompletableFuture<>(); - BabylonNativeInterop.nativeInstances.put(jsContext, instanceRefFuture); - } - return instanceRefFuture; + public static void updateView(Surface surface) { + BabylonNative.updateView(surface); } - private static void destroyOldNativeInstances(ReactContext currentReactContext) { - Iterator>> nativeInstanceIterator = BabylonNativeInterop.nativeInstances.entrySet().iterator(); - while (nativeInstanceIterator.hasNext()) { - Map.Entry> nativeInstanceInfo = nativeInstanceIterator.next(); - if (currentReactContext == null || nativeInstanceInfo.getKey() != currentReactContext.getJavaScriptContextHolder()) { - Long oldInstanceRef = nativeInstanceInfo.getValue().getNow(null); - if (oldInstanceRef != null && oldInstanceRef != 0) { - BabylonNativeInterop.destroy(oldInstanceRef); - } - nativeInstanceIterator.remove(); + public static void reportMotionEvent(MotionEvent motionEvent) { + int maskedAction = motionEvent.getActionMasked(); + boolean isPointerDown = maskedAction == MotionEvent.ACTION_DOWN || maskedAction == MotionEvent.ACTION_POINTER_DOWN; + boolean isPointerUp = maskedAction == MotionEvent.ACTION_UP || maskedAction == MotionEvent.ACTION_POINTER_UP; + boolean isPointerMove = maskedAction == MotionEvent.ACTION_MOVE; + + if (isPointerDown || isPointerUp) { + int pointerIndex = motionEvent.getActionIndex(); + int pointerId = motionEvent.getPointerId(pointerIndex); + int buttonId = motionEvent.getActionButton(); + int x = (int)motionEvent.getX(pointerIndex); + int y = (int)motionEvent.getY(pointerIndex); + BabylonNative.setPointerButtonState(pointerId, buttonId, isPointerDown, x, y); + } else if (isPointerMove) { + for (int pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); pointerIndex++) { + int pointerId = motionEvent.getPointerId(pointerIndex); + int x = (int)motionEvent.getX(pointerIndex); + int y = (int)motionEvent.getY(pointerIndex); + BabylonNative.setPointerPosition(pointerId, x, y); } } } diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java index d3bccab57..ed36fb336 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java @@ -19,15 +19,13 @@ import java.io.ByteArrayOutputStream; public final class EngineView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener { - private final ReactContext reactContext; private final EventDispatcher reactEventDispatcher; public EngineView(ReactContext reactContext) { super(reactContext); - this.reactContext = reactContext; this.getHolder().addCallback(this); this.setOnTouchListener(this); - this.reactEventDispatcher = this.reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); + this.reactEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); } @Override @@ -37,17 +35,16 @@ public void surfaceCreated(SurfaceHolder surfaceHolder) { @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int width, int height) { - BabylonNativeInterop.setView(this.reactContext, surfaceHolder.getSurface()); + BabylonNativeInterop.updateView(surfaceHolder.getSurface()); } @Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) { - } @Override public boolean onTouch(View view, MotionEvent motionEvent) { - BabylonNativeInterop.reportMotionEvent(this.reactContext, motionEvent); + BabylonNativeInterop.reportMotionEvent(motionEvent); return true; } diff --git a/Modules/@babylonjs/react-native/ios/BabylonModule.mm b/Modules/@babylonjs/react-native/ios/BabylonModule.mm index f4067c6d7..3769a55d1 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonModule.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonModule.mm @@ -18,9 +18,11 @@ @implementation BabylonModule @synthesize bridge = _bridge; -RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(initialize) { - [BabylonNativeInterop initialize:self.bridge]; - return nil; +RCT_EXPORT_METHOD(initialize:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject) { + self.bridge.jsCallInvoker->invokeAsync([bridge{ self.bridge }, resolve]() { + [BabylonNativeInterop initialize:bridge]; + resolve([NSNull null]); + }); } @end diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index cc2d73c8a..eac2b6a96 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -8,16 +8,6 @@ #include #include -#include - -#include - -#include - -#include -#include -#include - #include namespace Babylon @@ -29,7 +19,7 @@ namespace Babylon public: ReactNativeModule(jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) : m_env{ Napi::Attach(jsiRuntime) } - , m_jsCallInvoker{ jsCallInvoker } + , m_jsCallInvoker{ std::move(jsCallInvoker) } , m_isRunning{ std::make_shared(true) } { // Initialize a JS promise that will be returned by whenInitialized, and completed when NativeEngine is initialized. @@ -61,7 +51,7 @@ namespace Babylon Polyfills::XMLHttpRequest::Initialize(m_env); } - ~ReactNativeModule() + ~ReactNativeModule() override { *m_isRunning = false; Napi::Detach(m_env); @@ -78,7 +68,7 @@ namespace Babylon m_disposeEngine = {}; } } - + void UpdateView(void* windowPtr, size_t width, size_t height) { m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() { From 66907291b830dd7b18a52ce798695aba8e4dc3b1 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Thu, 17 Dec 2020 17:14:21 -0800 Subject: [PATCH 10/24] Cleanup and queue updateView on JS thread --- .../BabylonNativeInterop.java | 70 +++++++++++-------- .../com/babylonreactnative/EngineView.java | 6 +- .../react-native/ios/EngineViewManager.mm | 9 ++- .../react-native/shared/BabylonNative.cpp | 23 +++--- 4 files changed, 63 insertions(+), 45 deletions(-) diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java index ce87e8696..167960294 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java @@ -28,47 +28,55 @@ private static class BabylonNative { public static native void setPointerPosition(int pointerId, int x, int y); } - private static ReactContext currentContext; + private static LifecycleEventListener lifeCycleEventListener; + private static ActivityEventListener activityEventListener; - private final static LifecycleEventListener lifeCycleEventListener = new LifecycleEventListener() { - @Override - public void onHostResume() { - BabylonNative.setCurrentActivity(BabylonNativeInterop.currentContext.getCurrentActivity()); - BabylonNative.resume(); - } + public static void initialize(ReactContext reactContext) { + long jsiRuntimeRef = reactContext.getJavaScriptContextHolder().get(); + CallInvokerHolder jsCallInvokerHolder = reactContext.getCatalystInstance().getJSCallInvokerHolder(); + BabylonNative.initialize(reactContext, jsiRuntimeRef, jsCallInvokerHolder); - @Override - public void onHostPause() { - BabylonNative.pause(); + if (BabylonNativeInterop.lifeCycleEventListener != null) { + reactContext.removeLifecycleEventListener(lifeCycleEventListener); } - @Override - public void onHostDestroy() { - BabylonNative.deinitialize(); - } - }; + BabylonNativeInterop.lifeCycleEventListener = new LifecycleEventListener() { + @Override + public void onHostResume() { + BabylonNative.setCurrentActivity(reactContext.getCurrentActivity()); + BabylonNative.resume(); + } - private final static ActivityEventListener activityEventListener = new ActivityEventListener() { - @Override - public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { - // Nothing to do here - } + @Override + public void onHostPause() { + BabylonNative.pause(); + } - @Override - public void onNewIntent(Intent intent) { - BabylonNative.setCurrentActivity(BabylonNativeInterop.currentContext.getCurrentActivity()); + @Override + public void onHostDestroy() { + BabylonNative.deinitialize(); + } + }; + + reactContext.addLifecycleEventListener(lifeCycleEventListener); + + if (BabylonNativeInterop.activityEventListener != null) { + reactContext.removeActivityEventListener(BabylonNativeInterop.activityEventListener); } - }; - public static void initialize(ReactContext reactContext) { - BabylonNativeInterop.currentContext = reactContext; + BabylonNativeInterop.activityEventListener = new ActivityEventListener() { + @Override + public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) { + // Nothing to do here + } - long jsiRuntimeRef = BabylonNativeInterop.currentContext.getJavaScriptContextHolder().get(); - CallInvokerHolder jsCallInvokerHolder = BabylonNativeInterop.currentContext.getCatalystInstance().getJSCallInvokerHolder(); - BabylonNative.initialize(BabylonNativeInterop.currentContext, jsiRuntimeRef, jsCallInvokerHolder); + @Override + public void onNewIntent(Intent intent) { + BabylonNative.setCurrentActivity(reactContext.getCurrentActivity()); + } + }; - BabylonNativeInterop.currentContext.removeLifecycleEventListener(lifeCycleEventListener); - BabylonNativeInterop.currentContext.addLifecycleEventListener(lifeCycleEventListener); + reactContext.addActivityEventListener(BabylonNativeInterop.activityEventListener); } public static void deinitialize() { diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java index ed36fb336..d4d7de5fd 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java @@ -19,13 +19,15 @@ import java.io.ByteArrayOutputStream; public final class EngineView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener { + private final ReactContext reactContext; private final EventDispatcher reactEventDispatcher; public EngineView(ReactContext reactContext) { super(reactContext); + this.reactContext = reactContext; this.getHolder().addCallback(this); this.setOnTouchListener(this); - this.reactEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); + this.reactEventDispatcher = this.reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); } @Override @@ -35,7 +37,7 @@ public void surfaceCreated(SurfaceHolder surfaceHolder) { @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int width, int height) { - BabylonNativeInterop.updateView(surfaceHolder.getSurface()); + this.reactContext.runOnJSQueueThread(() -> BabylonNativeInterop.updateView(surfaceHolder.getSurface())); } @Override diff --git a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm index 177982006..96e7537ae 100644 --- a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm +++ b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm @@ -2,11 +2,16 @@ #import #import +#import #import #import #import +@interface RCTBridge (RCTTurboModule) +- (std::shared_ptr)jsCallInvoker; +@end + @interface EngineView : MTKView @property (nonatomic, copy) RCTDirectEventBlock onSnapshotDataReturned; @@ -30,7 +35,9 @@ - (instancetype)init:(RCTBridge*)_bridge { - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; - [BabylonNativeInterop updateView:self]; + bridge.jsCallInvoker->invokeAsync([self]() { + [BabylonNativeInterop updateView:self]; + }); } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index eac2b6a96..27395b6c1 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -71,21 +71,22 @@ namespace Babylon void UpdateView(void* windowPtr, size_t width, size_t height) { - m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() { - if (!m_graphics) + if (!m_graphics) + { + m_graphics = Graphics::CreateGraphics(windowPtr, width, height); + m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() { - m_graphics = Graphics::CreateGraphics(windowPtr, width, height); m_graphics->AddToJavaScript(m_env); Plugins::NativeEngine::Initialize(m_env, true); m_resolveInitPromise(); - } - else - { - m_graphics->UpdateWindow(windowPtr); - m_graphics->UpdateSize(width, height); - m_graphics->EnableRendering(); - } - }); + }); + } + else + { + m_graphics->UpdateWindow(windowPtr); + m_graphics->UpdateSize(width, height); + m_graphics->EnableRendering(); + } } void ResetView() From f56d5e9f804a804535c4d1de1076840b6c0ad6fc Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 10:24:57 -0800 Subject: [PATCH 11/24] Fix crash when toggling engine screen off from XR and then toggling engine view back on and re-entering XR --- .../@babylonjs/react-native/shared/BabylonNative.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index 27395b6c1..e77ef0c57 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -91,10 +91,15 @@ namespace Babylon void ResetView() { - if (m_graphics) + // TODO: There is a race condition between EngineView.tsx stopping the render loop and EngineHook.ts resetting. + // If we DisableRendering synchronously, one more frame will still be rendered which calls EnableRendering. + m_jsCallInvoker->invokeAsync([this]() { - m_graphics->DisableRendering(); - } + if (m_graphics) + { + m_graphics->DisableRendering(); + } + }); } void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) From 1701e2d446a2022f1fc2c02d24591c2e182836ec Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 13:28:04 -0800 Subject: [PATCH 12/24] Ensure UpdateView is called after Initialize (from the JS side) --- .../@babylonjs/react-native/BabylonModule.ts | 15 ++++---- Modules/@babylonjs/react-native/EngineHook.ts | 2 +- .../@babylonjs/react-native/EngineView.tsx | 12 +++---- .../com/babylonreactnative/EngineView.java | 6 ++-- .../react-native/ios/EngineViewManager.mm | 4 +-- .../react-native/shared/BabylonNative.cpp | 34 +++++++++++-------- 6 files changed, 36 insertions(+), 37 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 4c55d1109..3b7c6d656 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -35,13 +35,6 @@ export async function ensureInitialized(): Promise { } else { // This does the first stage of Babylon Native initialization, including creating the BabylonNative JSI object. await BabylonModule.initialize(); - - // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). - await BabylonNative.initializationPromise; - - // This waits for the Graphics system to be up and running. - await _native.whenGraphicsReady(); - return true; } } @@ -50,7 +43,13 @@ export function reset(): void { BabylonNative.reset(); } -export function createEngine(): NativeEngine { +export async function createEngine(): Promise { + // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). + await BabylonNative.initializationPromise; + + // This waits for the Graphics system to be up and running. + await _native.whenGraphicsReady(); + const engine = new NativeEngine(); BabylonNative.setEngineInstance(engine); return engine; diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index d5494f0bd..dd3723d6f 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -71,7 +71,7 @@ export function useEngine(): Engine | undefined { (async () => { if (await ensureInitialized() && !disposed) { - engine = createEngine(); + engine = await createEngine(); setEngine(engine); } })(); diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index 38190c008..1e6453031 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -29,7 +29,7 @@ export interface EngineViewCallbacks { } export const EngineView: FunctionComponent = (props: EngineViewProps) => { - const [failedInitialization, setFailedInitialization] = useState(false); + const [initialized, setInitialized] = useState(); const [appState, setAppState] = useState(AppState.currentState); const [fps, setFps] = useState(); const engineViewRef = useRef>(null); @@ -37,9 +37,7 @@ export const EngineView: FunctionComponent = (props: EngineView useEffect(() => { (async () => { - if (!await ensureInitialized()) { - setFailedInitialization(true); - } + setInitialized(await ensureInitialized()); })(); }, []); @@ -138,11 +136,11 @@ export const EngineView: FunctionComponent = (props: EngineView } }, []); - if (!failedInitialization) { + if (initialized !== false) { return ( - - { fps && FPS: {Math.round(fps)}} + { initialized && } + { fps && FPS: {Math.round(fps)} } ); } else { diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java index d4d7de5fd..ed36fb336 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/EngineView.java @@ -19,15 +19,13 @@ import java.io.ByteArrayOutputStream; public final class EngineView extends SurfaceView implements SurfaceHolder.Callback, View.OnTouchListener { - private final ReactContext reactContext; private final EventDispatcher reactEventDispatcher; public EngineView(ReactContext reactContext) { super(reactContext); - this.reactContext = reactContext; this.getHolder().addCallback(this); this.setOnTouchListener(this); - this.reactEventDispatcher = this.reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); + this.reactEventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher(); } @Override @@ -37,7 +35,7 @@ public void surfaceCreated(SurfaceHolder surfaceHolder) { @Override public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int width, int height) { - this.reactContext.runOnJSQueueThread(() -> BabylonNativeInterop.updateView(surfaceHolder.getSurface())); + BabylonNativeInterop.updateView(surfaceHolder.getSurface()); } @Override diff --git a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm index 96e7537ae..1cc14d5d1 100644 --- a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm +++ b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm @@ -35,9 +35,7 @@ - (instancetype)init:(RCTBridge*)_bridge { - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; - bridge.jsCallInvoker->invokeAsync([self]() { - [BabylonNativeInterop updateView:self]; - }); + [BabylonNativeInterop updateView:self]; } - (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event { diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index e77ef0c57..5c6c7fe5f 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -71,28 +71,34 @@ namespace Babylon void UpdateView(void* windowPtr, size_t width, size_t height) { - if (!m_graphics) + // TODO: We shouldn't have to dispatch to the JS thread most of this, but not doing so results in a crash. + // I don't understand the issue yet, but for now just retain the pre-refactor logic. We'll need to + // resolve this to enable manual non-JS thread rendering. Note this only repros in release builds + // where we actually call ResetView. + m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() { - m_graphics = Graphics::CreateGraphics(windowPtr, width, height); - m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() + if (!m_graphics) { + m_graphics = Graphics::CreateGraphics(windowPtr, width, height); m_graphics->AddToJavaScript(m_env); Plugins::NativeEngine::Initialize(m_env, true); m_resolveInitPromise(); - }); - } - else - { - m_graphics->UpdateWindow(windowPtr); - m_graphics->UpdateSize(width, height); - m_graphics->EnableRendering(); - } + } + else + { + m_graphics->UpdateWindow(windowPtr); + m_graphics->UpdateSize(width, height); + m_graphics->EnableRendering(); + } + }); } void ResetView() { - // TODO: There is a race condition between EngineView.tsx stopping the render loop and EngineHook.ts resetting. - // If we DisableRendering synchronously, one more frame will still be rendered which calls EnableRendering. + // TODO: We shouldn't have to dispatch to the JS thread for this since we are already on the JS thread, + // but there is an issue in NativeEngine where it will Dispatch a call to RenderCurrentFrame, then + // get disposed, then try to actually render the frame. This results in immediately re-enabling + // graphics after disabling it here. For now, retain the pre-refactor logic (queueing on the JS thread). m_jsCallInvoker->invokeAsync([this]() { if (m_graphics) @@ -101,7 +107,7 @@ namespace Babylon } }); } - + void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) { if (isDown) From 2aeb5d2095251daefb2b129c7ed1f26c766b739b Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 13:42:08 -0800 Subject: [PATCH 13/24] Minor cleanup --- Modules/@babylonjs/react-native/BabylonModule.ts | 11 +++++++---- Modules/@babylonjs/react-native/EngineHook.ts | 8 +------- .../@babylonjs/react-native/shared/BabylonNative.cpp | 4 ++++ 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 3b7c6d656..7e289cf0b 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -39,10 +39,6 @@ export async function ensureInitialized(): Promise { } } -export function reset(): void { - BabylonNative.reset(); -} - export async function createEngine(): Promise { // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). await BabylonNative.initializationPromise; @@ -63,6 +59,13 @@ export function disposeEngine(engine: NativeEngine): void { if (engine && !isEngineDisposed(engine)) { engine.dispose(); (engine as any)[disposedPropertyName] = true; + + // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that + // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). + // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 + if (!__DEV__) { + BabylonNative.reset(); + } } BabylonNative.setEngineInstance(null); diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index dd3723d6f..abc29621a 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { Platform } from 'react-native'; import { PERMISSIONS, check, request } from 'react-native-permissions'; import { Engine, NativeEngine, WebXRSessionManager } from '@babylonjs/core'; -import { ensureInitialized, createEngine, disposeEngine, reset } from './BabylonModule'; +import { ensureInitialized, createEngine, disposeEngine } from './BabylonModule'; 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. @@ -82,12 +82,6 @@ export function useEngine(): Engine | undefined { if (engine) { disposeEngine(engine); } - // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that - // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). - // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 - if (!__DEV__) { - reset(); - } setEngine(undefined); }; }, []); diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index 5c6c7fe5f..f9ec7515d 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -206,6 +206,10 @@ namespace Babylon { nativeModule->UpdateView(windowPtr, width, height); } + else + { + throw std::runtime_error { "UpdateView must not be called before Initialize." }; + } } void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) From 17111f25ac9e1d413e18e3dda5da4f959298d9a4 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 15:13:47 -0800 Subject: [PATCH 14/24] Fix package build Fix an issue with xr.mm --- Modules/@babylonjs/react-native/submodules/BabylonNative | 2 +- Package/gulpfile.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Modules/@babylonjs/react-native/submodules/BabylonNative b/Modules/@babylonjs/react-native/submodules/BabylonNative index 31968d582..ef6fa1e12 160000 --- a/Modules/@babylonjs/react-native/submodules/BabylonNative +++ b/Modules/@babylonjs/react-native/submodules/BabylonNative @@ -1 +1 @@ -Subproject commit 31968d582cbb4efd834100ba990d07601fa0d10c +Subproject commit ef6fa1e12c0ea138f94d524b0b700f91806de98e diff --git a/Package/gulpfile.js b/Package/gulpfile.js index 40ce1dfc5..10755e92f 100644 --- a/Package/gulpfile.js +++ b/Package/gulpfile.js @@ -89,6 +89,8 @@ const validate = async () => { const expected = `Assembled Assembled/EngineHook.ts +Assembled/shared +Assembled/shared/BabylonNative.h Assembled/EngineView.tsx Assembled/ios Assembled/ios/BabylonNativeInterop.mm @@ -127,7 +129,6 @@ Assembled/ios/ReactNativeBabylon.xcodeproj/project.xcworkspace Assembled/ios/ReactNativeBabylon.xcodeproj/project.xcworkspace/xcshareddata Assembled/ios/ReactNativeBabylon.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings Assembled/ios/BabylonModule.mm -Assembled/ios/BabylonNative.h Assembled/README.md Assembled/package.json Assembled/android From 9f344965ba61ae7e6c5d5074747b177b4991a6f9 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 15:31:29 -0800 Subject: [PATCH 15/24] Remove unused decl of jsCallInvoker --- Modules/@babylonjs/react-native/ios/EngineViewManager.mm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm index 1cc14d5d1..4291514c6 100644 --- a/Modules/@babylonjs/react-native/ios/EngineViewManager.mm +++ b/Modules/@babylonjs/react-native/ios/EngineViewManager.mm @@ -8,10 +8,6 @@ #import #import -@interface RCTBridge (RCTTurboModule) -- (std::shared_ptr)jsCallInvoker; -@end - @interface EngineView : MTKView @property (nonatomic, copy) RCTDirectEventBlock onSnapshotDataReturned; From e13f54c2fcc390c928463b9b5384d9ff5d98c426 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 16:12:42 -0800 Subject: [PATCH 16/24] Remove old setJSThread stuff from EngineView.tsx --- Modules/@babylonjs/react-native/EngineView.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index 1e6453031..454db8888 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -5,10 +5,6 @@ import { ensureInitialized, isEngineDisposed } from './BabylonModule'; declare const global: any; -const EngineViewManager: { - setJSThread(): void; -} = NativeModules.EngineViewManager; - interface NativeEngineViewProps extends ViewProps { onSnapshotDataReturned: (event: SyntheticEvent) => void; } From 895e0c976a7f256f8f702596e436fa00dcc11b3b Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 16:27:30 -0800 Subject: [PATCH 17/24] std::move jsCallInvoker in dispatcher --- Modules/@babylonjs/react-native/shared/DispatchFunction.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/@babylonjs/react-native/shared/DispatchFunction.h b/Modules/@babylonjs/react-native/shared/DispatchFunction.h index 306e93a70..be7af5122 100644 --- a/Modules/@babylonjs/react-native/shared/DispatchFunction.h +++ b/Modules/@babylonjs/react-native/shared/DispatchFunction.h @@ -12,7 +12,7 @@ namespace Babylon // Creates a JsRuntime::DispatchFunctionT that integrates with the React Native execution environment. inline JsRuntime::DispatchFunctionT CreateJsRuntimeDispatcher(Napi::Env env, jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, const std::shared_ptr isRunning) { - return [env, &jsiRuntime, callInvoker, isRunning{ std::move(isRunning) }](std::function func) + return [env, &jsiRuntime, callInvoker{ std::move(callInvoker) }, isRunning{ std::move(isRunning) }](std::function func) { // Ideally we would just use CallInvoker::invokeAsync directly, but currently it does not seem to integrate well with the React Native logbox. // To work around this, we wrap all functions in a try/catch, and when there is an exception, we do the following: From 6031d6934bcab551f5d971257e386f8995849dba Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Fri, 18 Dec 2020 17:01:52 -0800 Subject: [PATCH 18/24] Remove use of getActionButton, which is only available in API levle 23+, but is not needed for touch (button id is always 0 for touch inputs) --- .../main/java/com/babylonreactnative/BabylonNativeInterop.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java index 167960294..319bc9fb1 100644 --- a/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java +++ b/Modules/@babylonjs/react-native/android/src/main/java/com/babylonreactnative/BabylonNativeInterop.java @@ -96,10 +96,9 @@ public static void reportMotionEvent(MotionEvent motionEvent) { if (isPointerDown || isPointerUp) { int pointerIndex = motionEvent.getActionIndex(); int pointerId = motionEvent.getPointerId(pointerIndex); - int buttonId = motionEvent.getActionButton(); int x = (int)motionEvent.getX(pointerIndex); int y = (int)motionEvent.getY(pointerIndex); - BabylonNative.setPointerButtonState(pointerId, buttonId, isPointerDown, x, y); + BabylonNative.setPointerButtonState(pointerId, 0, isPointerDown, x, y); } else if (isPointerMove) { for (int pointerIndex = 0; pointerIndex < motionEvent.getPointerCount(); pointerIndex++) { int pointerId = motionEvent.getPointerId(pointerIndex); From d34d0db123c3ad6735a34625da6170e27dbaa5cd Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Mon, 21 Dec 2020 15:44:35 -0800 Subject: [PATCH 19/24] Add ReactNativeEngine --- .../@babylonjs/react-native/BabylonModule.ts | 76 ++++++++++--------- Modules/@babylonjs/react-native/EngineHook.ts | 13 ++-- .../@babylonjs/react-native/EngineView.tsx | 14 ++-- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index 7e289cf0b..ca471c668 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -1,7 +1,5 @@ import { NativeModules } from 'react-native'; -import { Engine, NativeEngine } from '@babylonjs/core'; - -const disposedPropertyName = "BabylonReactNative_IsDisposed"; +import { NativeEngine } from '@babylonjs/core'; declare const global: { nativeCallSyncHook: any; @@ -27,46 +25,54 @@ const BabylonModule: { initialize(): Promise; } = NativeModules.BabylonModule; -export async function ensureInitialized(): Promise { - if (isRemoteDebuggingEnabled) { - // When remote debugging is enabled, JavaScript runs on the debugging host machine, not on the device where the app is running. - // JSI (which Babylon Native uses heavily) can not work in this mode. In the future, this debugging mode will be phased out as it is incompatible with TurboModules for the same reason. - return false; - } else { - // This does the first stage of Babylon Native initialization, including creating the BabylonNative JSI object. - await BabylonModule.initialize(); - return true; +export class ReactNativeEngine extends NativeEngine { + private _isDisposed = false; + + private constructor() { + super(); + BabylonNative.setEngineInstance(this); } -} -export async function createEngine(): Promise { - // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). - await BabylonNative.initializationPromise; + public static async createAsync(): Promise { + // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). + await BabylonNative.initializationPromise; - // This waits for the Graphics system to be up and running. - await _native.whenGraphicsReady(); + // This waits for the Graphics system to be up and running. + await _native.whenGraphicsReady(); - const engine = new NativeEngine(); - BabylonNative.setEngineInstance(engine); - return engine; -} + return new ReactNativeEngine(); + } -export function isEngineDisposed(engine: Engine): boolean { - return (engine as any)[disposedPropertyName]; -} + public get isDisposed() { + return this._isDisposed; + } + + public dispose(): void { + if (!this.isDisposed) { + super.dispose(); -export function disposeEngine(engine: NativeEngine): void { - if (engine && !isEngineDisposed(engine)) { - engine.dispose(); - (engine as any)[disposedPropertyName] = true; + // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that + // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). + // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 + if (!__DEV__) { + BabylonNative.reset(); + } - // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that - // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). - // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 - if (!__DEV__) { - BabylonNative.reset(); + this._isDisposed = true; } + + BabylonNative.setEngineInstance(null); } +} - BabylonNative.setEngineInstance(null); +export async function ensureInitialized(): Promise { + if (isRemoteDebuggingEnabled) { + // When remote debugging is enabled, JavaScript runs on the debugging host machine, not on the device where the app is running. + // JSI (which Babylon Native uses heavily) can not work in this mode. In the future, this debugging mode will be phased out as it is incompatible with TurboModules for the same reason. + return false; + } else { + // This does the first stage of Babylon Native initialization, including creating the BabylonNative JSI object. + await BabylonModule.initialize(); + return true; + } } \ No newline at end of file diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index abc29621a..73b61cfd3 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -1,8 +1,8 @@ import { useEffect, useState } from 'react'; import { Platform } from 'react-native'; import { PERMISSIONS, check, request } from 'react-native-permissions'; -import { Engine, NativeEngine, WebXRSessionManager } from '@babylonjs/core'; -import { ensureInitialized, createEngine, disposeEngine } from './BabylonModule'; +import { Engine, WebXRSessionManager } from '@babylonjs/core'; +import { ensureInitialized, ReactNativeEngine } from './BabylonModule'; 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. @@ -66,22 +66,19 @@ export function useEngine(): Engine | undefined { useEffect(() => { let disposed = false; - let engine: NativeEngine | undefined = undefined; + let engine: ReactNativeEngine | undefined = undefined; (async () => { if (await ensureInitialized() && !disposed) { - engine = await createEngine(); - setEngine(engine); + setEngine(await ReactNativeEngine.createAsync()); } })(); return () => { disposed = true; // NOTE: Do not use setEngine with a callback to dispose the engine instance as that callback does not get called during component unmount when compiled in release. - if (engine) { - disposeEngine(engine); - } + engine?.dispose(); setEngine(undefined); }; }, []); diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index 454db8888..cbc364630 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -1,7 +1,7 @@ import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef } from 'react'; -import { requireNativeComponent, NativeModules, ViewProps, AppState, AppStateStatus, View, Text, findNodeHandle, UIManager } from 'react-native'; +import { requireNativeComponent, ViewProps, AppState, AppStateStatus, View, Text, findNodeHandle, UIManager } from 'react-native'; import { Camera } from '@babylonjs/core'; -import { ensureInitialized, isEngineDisposed } from './BabylonModule'; +import { ensureInitialized, ReactNativeEngine } from './BabylonModule'; declare const global: any; @@ -51,9 +51,9 @@ export const EngineView: FunctionComponent = (props: EngineView useEffect(() => { if (props.camera && appState === "active") { - const engine = props.camera.getScene().getEngine(); + const engine = props.camera.getScene().getEngine() as ReactNativeEngine; - if (!isEngineDisposed(engine)) { + if (!engine.isDisposed) { engine.runRenderLoop(() => { for (let scene of engine.scenes) { scene.render(); @@ -61,7 +61,7 @@ export const EngineView: FunctionComponent = (props: EngineView }); return () => { - if (!isEngineDisposed(engine)) { + if (!engine.isDisposed) { engine.stopRenderLoop(); } }; @@ -73,9 +73,9 @@ export const EngineView: FunctionComponent = (props: EngineView useEffect(() => { if (props.camera && (props.displayFrameRate ?? __DEV__)) { - const engine = props.camera.getScene().getEngine(); + const engine = props.camera.getScene().getEngine() as ReactNativeEngine; - if (!isEngineDisposed(engine)) { + if (!engine.isDisposed) { setFps(engine.getFps()); const timerHandle = setInterval(() => { setFps(engine.getFps()); From bbe9e6069e831aeb3d99067ee0f58691d04b1413 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Mon, 21 Dec 2020 16:17:32 -0800 Subject: [PATCH 20/24] Address feedback on C++ side --- .../src/main/cpp/BabylonNativeInterop.cpp | 4 ++-- .../react-native/shared/BabylonNative.cpp | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp index 8cea764ab..36c523520 100644 --- a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp +++ b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp @@ -25,7 +25,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInter static bool initializedJVM = false; if (!initializedJVM) { - JavaVM *javaVM{}; + JavaVM* javaVM{}; if (env->GetJavaVM(&javaVM) != JNI_OK) { throw std::runtime_error("Failed to get Java VM"); @@ -37,7 +37,7 @@ extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInter } auto jsiRuntime = reinterpret_cast(jsiRuntimeRef); - auto jsCallInvoker = jni::alias_ref {reinterpret_cast(jsCallInvokerHolder)}->cthis()->getCallInvoker(); + auto jsCallInvoker = jni::alias_ref{ reinterpret_cast(jsCallInvokerHolder) }->cthis()->getCallInvoker(); Babylon::Initialize(*jsiRuntime, jsCallInvoker); } diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index f9ec7515d..63c054288 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -158,10 +158,10 @@ namespace Babylon return {}; }); } - - return jsi::Value::undefined(); + + return {}; } - + private: jsi::Value m_initPromise{}; std::function m_resolveInitPromise{}; @@ -184,7 +184,7 @@ namespace Babylon void Initialize(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) { - if (jsiRuntime.global().getProperty(jsiRuntime, JS_INSTANCE_NAME).isUndefined()) + if (!jsiRuntime.global().hasProperty(jsiRuntime, JS_INSTANCE_NAME)) { auto nativeModule{ std::make_shared(jsiRuntime, jsCallInvoker) }; jsiRuntime.global().setProperty(jsiRuntime, JS_INSTANCE_NAME, jsi::Object::createFromHostObject(jsiRuntime, nativeModule)); @@ -194,7 +194,7 @@ namespace Babylon void Deinitialize() { - if (auto nativeModule = g_nativeModule.lock()) + if (auto nativeModule{ g_nativeModule.lock() }) { nativeModule->Deinitialize(); } @@ -202,19 +202,19 @@ namespace Babylon void UpdateView(void* windowPtr, size_t width, size_t height) { - if (auto nativeModule = g_nativeModule.lock()) + if (auto nativeModule{ g_nativeModule.lock() }) { nativeModule->UpdateView(windowPtr, width, height); } else { - throw std::runtime_error { "UpdateView must not be called before Initialize." }; + throw std::runtime_error{ "UpdateView must not be called before Initialize." }; } } void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y) { - if (auto nativeModule = g_nativeModule.lock()) + if (auto nativeModule{ g_nativeModule.lock() }) { nativeModule->SetPointerButtonState(pointerId, buttonId, isDown, x, y); } @@ -222,7 +222,7 @@ namespace Babylon void SetPointerPosition(uint32_t pointerId, uint32_t x, uint32_t y) { - if (auto nativeModule = g_nativeModule.lock()) + if (auto nativeModule{ g_nativeModule.lock() }) { nativeModule->SetPointerPosition(pointerId, x, y); } From 9a8e53b6452e60a89069f2b7a6521b851f7c5846 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Mon, 21 Dec 2020 16:37:10 -0800 Subject: [PATCH 21/24] Move ReactNativeEngine to its own file and clean it up more --- .../@babylonjs/react-native/BabylonModule.ts | 54 --------------- Modules/@babylonjs/react-native/EngineHook.ts | 11 ++- .../react-native/ReactNativeEngine.ts | 69 +++++++++++++++++++ 3 files changed, 73 insertions(+), 61 deletions(-) create mode 100644 Modules/@babylonjs/react-native/ReactNativeEngine.ts diff --git a/Modules/@babylonjs/react-native/BabylonModule.ts b/Modules/@babylonjs/react-native/BabylonModule.ts index ca471c668..479887089 100644 --- a/Modules/@babylonjs/react-native/BabylonModule.ts +++ b/Modules/@babylonjs/react-native/BabylonModule.ts @@ -1,70 +1,16 @@ import { NativeModules } from 'react-native'; -import { NativeEngine } from '@babylonjs/core'; declare const global: { nativeCallSyncHook: any; }; const isRemoteDebuggingEnabled = !global.nativeCallSyncHook; -// This global object is owned by Babylon Native. -declare const _native: { - whenGraphicsReady: () => Promise; -}; - -// This JSI-based global object is owned by Babylon React Native. -// This will likely be converted to a TurboModule when they are fully supported. -declare const BabylonNative: { - readonly initializationPromise: Promise; - setEngineInstance: (engine: NativeEngine | null) => void; - reset: () => void; -}; - // This legacy React Native module is created by Babylon React Native, and is only used to bootstrap the JSI object creation. // This will likely be removed when the BabylonNative global object is eventually converted to a TurboModule. const BabylonModule: { initialize(): Promise; } = NativeModules.BabylonModule; -export class ReactNativeEngine extends NativeEngine { - private _isDisposed = false; - - private constructor() { - super(); - BabylonNative.setEngineInstance(this); - } - - public static async createAsync(): Promise { - // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). - await BabylonNative.initializationPromise; - - // This waits for the Graphics system to be up and running. - await _native.whenGraphicsReady(); - - return new ReactNativeEngine(); - } - - public get isDisposed() { - return this._isDisposed; - } - - public dispose(): void { - if (!this.isDisposed) { - super.dispose(); - - // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that - // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). - // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 - if (!__DEV__) { - BabylonNative.reset(); - } - - this._isDisposed = true; - } - - BabylonNative.setEngineInstance(null); - } -} - export async function ensureInitialized(): Promise { if (isRemoteDebuggingEnabled) { // When remote debugging is enabled, JavaScript runs on the debugging host machine, not on the device where the app is running. diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index 73b61cfd3..566ca2cd2 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'; import { Platform } from 'react-native'; import { PERMISSIONS, check, request } from 'react-native-permissions'; import { Engine, WebXRSessionManager } from '@babylonjs/core'; -import { ensureInitialized, ReactNativeEngine } from './BabylonModule'; +import { ReactNativeEngine } from './ReactNativeEngine'; 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. @@ -65,18 +65,15 @@ export function useEngine(): Engine | undefined { const [engine, setEngine] = useState(); useEffect(() => { - let disposed = false; + const abortController = new AbortController(); let engine: ReactNativeEngine | undefined = undefined; (async () => { - if (await ensureInitialized() && !disposed) - { - setEngine(await ReactNativeEngine.createAsync()); - } + setEngine(engine = await ReactNativeEngine.tryCreateAsync(abortController.signal) ?? undefined); })(); return () => { - disposed = true; + abortController.abort(); // NOTE: Do not use setEngine with a callback to dispose the engine instance as that callback does not get called during component unmount when compiled in release. engine?.dispose(); setEngine(undefined); diff --git a/Modules/@babylonjs/react-native/ReactNativeEngine.ts b/Modules/@babylonjs/react-native/ReactNativeEngine.ts new file mode 100644 index 000000000..9287f2a75 --- /dev/null +++ b/Modules/@babylonjs/react-native/ReactNativeEngine.ts @@ -0,0 +1,69 @@ +import { ensureInitialized } from './BabylonModule'; +import { NativeEngine } from '@babylonjs/core'; + +// This global object is owned by Babylon Native. +declare const _native: { + whenGraphicsReady: () => Promise; +}; + +// This JSI-based global object is owned by Babylon React Native. +// This will likely be converted to a TurboModule when they are fully supported. +declare const BabylonNative: { + readonly initializationPromise: Promise; + setEngineInstance: (engine: NativeEngine | null) => void; + reset: () => void; +}; + +export class ReactNativeEngine extends NativeEngine { + private _isDisposed = false; + + private constructor() { + super(); + BabylonNative.setEngineInstance(this); + } + + public static async tryCreateAsync(abortSignal: AbortSignal): Promise { + if (!await ensureInitialized() || abortSignal.aborted) { + return null; + } + + // This waits Graphics/NativeEngine to be created (which in turn makes the whenGraphicsReady available). + await BabylonNative.initializationPromise; + + // Check for cancellation. + if (abortSignal.aborted) { + return null; + } + + // This waits for the Graphics system to be up and running. + await _native.whenGraphicsReady(); + + // Check for cancellation. + if (abortSignal.aborted) { + return null; + } + + return new ReactNativeEngine(); + } + + public get isDisposed() { + return this._isDisposed; + } + + public dispose(): void { + if (!this.isDisposed) { + super.dispose(); + + // Ideally we would always do a reset here as we don't want different behavior between debug and release. Unfortunately, fast refresh has some strange behavior that + // makes it quite difficult to get this to work correctly (e.g. it re-runs previous useEffect instances, which means it can try to use Babylon Native in a de-initialized state). + // TODO: https://github.com/BabylonJS/BabylonReactNative/issues/125 + if (!__DEV__) { + BabylonNative.reset(); + } + + this._isDisposed = true; + } + + BabylonNative.setEngineInstance(null); + } +} \ No newline at end of file From cc32e2dabbaa46fe47986831d3e84b019d664855 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Mon, 21 Dec 2020 16:48:10 -0800 Subject: [PATCH 22/24] Add ReactNativeEngine to package Small fix for CR feedback on caching prop name as std::string --- Modules/@babylonjs/react-native/shared/BabylonNative.cpp | 8 +++++--- Package/gulpfile.js | 1 + 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index 63c054288..43bf06bab 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -127,11 +127,13 @@ namespace Babylon jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& prop) override { - if (prop.utf8(runtime) == "initializationPromise") + const auto propName{ prop.utf8(runtime) }; + + if (propName == "initializationPromise") { return { runtime, m_initPromise }; } - else if (prop.utf8(runtime) == "reset") + else if (propName == "reset") { return jsi::Function::createFromHostFunction(runtime, prop, 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value*, size_t) -> jsi::Value { @@ -139,7 +141,7 @@ namespace Babylon return {}; }); } - else if (prop.utf8(runtime) == "setEngineInstance") + else if (propName == "setEngineInstance") { return jsi::Function::createFromHostFunction(runtime, prop, 0, [this](jsi::Runtime& rt, const jsi::Value&, const jsi::Value* args, size_t count) -> jsi::Value { diff --git a/Package/gulpfile.js b/Package/gulpfile.js index 10755e92f..3eb917a27 100644 --- a/Package/gulpfile.js +++ b/Package/gulpfile.js @@ -158,6 +158,7 @@ Assembled/android/src/main/jniLibs/arm64-v8a/libBabylonNative.so Assembled/react-native-babylon.podspec Assembled/index.ts Assembled/BabylonModule.ts +Assembled/ReactNativeEngine.ts `; const result = shelljs.exec('find Assembled', {silent: true}); From 8c9848c8c13afa02dcf5d9d5ef455ff76b58b2a4 Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Tue, 22 Dec 2020 14:51:19 -0800 Subject: [PATCH 23/24] Switch from CallInvoker to a Dispatcher function since on UWP CallInvoker is wrapped and not directly exposed --- .../src/main/cpp/BabylonNativeInterop.cpp | 21 ++++++++++++------- .../react-native/ios/BabylonNativeInterop.mm | 12 ++++++++++- .../react-native/shared/BabylonNative.cpp | 20 +++++++++--------- .../react-native/shared/BabylonNative.h | 5 +++-- .../react-native/shared/DispatchFunction.h | 7 +++---- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp index 36c523520..182f6f004 100644 --- a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp +++ b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp @@ -22,7 +22,7 @@ using namespace facebook; extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_initialize(JNIEnv* env, jclass obj, jobject context, jlong jsiRuntimeRef, jobject jsCallInvokerHolder) { - static bool initializedJVM = false; + static bool initializedJVM{ false }; if (!initializedJVM) { JavaVM* javaVM{}; @@ -36,10 +36,17 @@ extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInter initializedJVM = true; } - auto jsiRuntime = reinterpret_cast(jsiRuntimeRef); - auto jsCallInvoker = jni::alias_ref{ reinterpret_cast(jsCallInvokerHolder) }->cthis()->getCallInvoker(); + auto jsiRuntime{ reinterpret_cast(jsiRuntimeRef) }; + auto jsCallInvoker{ jni::alias_ref{ reinterpret_cast(jsCallInvokerHolder) }->cthis()->getCallInvoker() }; + auto jsDispatcher{ [jsCallInvoker{ std::move(jsCallInvoker) }](std::function func) + { + jsCallInvoker->invokeAsync([func{ std::move(func) }] + { + func(); + }); + } }; - Babylon::Initialize(*jsiRuntime, jsCallInvoker); + Babylon::Initialize(*jsiRuntime, std::move(jsDispatcher)); } extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_deinitialize(JNIEnv* env, jclass obj) @@ -64,9 +71,9 @@ extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInter extern "C" JNIEXPORT void JNICALL Java_com_babylonreactnative_BabylonNativeInterop_00024BabylonNative_updateView(JNIEnv* env, jclass obj, jobject surface) { - ANativeWindow* windowPtr = ANativeWindow_fromSurface(env, surface); - auto width = static_cast(ANativeWindow_getWidth(windowPtr)); - auto height = static_cast(ANativeWindow_getHeight(windowPtr)); + ANativeWindow* windowPtr{ ANativeWindow_fromSurface(env, surface) }; + auto width{ static_cast(ANativeWindow_getWidth(windowPtr)) }; + auto height{ static_cast(ANativeWindow_getHeight(windowPtr)) }; Babylon::UpdateView(windowPtr, width, height); } diff --git a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm index a3bd8148f..9b0afe1c0 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm +++ b/Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm @@ -3,6 +3,7 @@ #import #import +#include #import @@ -26,7 +27,16 @@ @implementation BabylonNativeInterop static NSMutableArray* activeTouches; + (void)initialize:(RCTBridge*)bridge { - Babylon::Initialize(*GetJSIRuntime(bridge), bridge.jsCallInvoker); + auto jsCallInvoker{ bridge.jsCallInvoker }; + auto jsDispatcher{ [jsCallInvoker{ std::move(jsCallInvoker) }](std::function func) + { + jsCallInvoker->invokeAsync([func{ std::move(func) }] + { + func(); + }); + } }; + + Babylon::Initialize(*GetJSIRuntime(bridge), std::move(jsDispatcher)); [[NSNotificationCenter defaultCenter] removeObserver:self name:RCTBridgeWillInvalidateModulesNotification diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp index 43bf06bab..3ef50c425 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.cpp @@ -17,9 +17,9 @@ namespace Babylon class ReactNativeModule : public jsi::HostObject { public: - ReactNativeModule(jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) + ReactNativeModule(jsi::Runtime& jsiRuntime, Dispatcher jsDispatcher) : m_env{ Napi::Attach(jsiRuntime) } - , m_jsCallInvoker{ std::move(jsCallInvoker) } + , m_jsDispatcher{ std::move(jsDispatcher) } , m_isRunning{ std::make_shared(true) } { // Initialize a JS promise that will be returned by whenInitialized, and completed when NativeEngine is initialized. @@ -37,12 +37,12 @@ namespace Babylon ); // Initialize Babylon Native core components - JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, m_jsCallInvoker, m_isRunning)); + JsRuntime::CreateForJavaScript(m_env, CreateJsRuntimeDispatcher(m_env, jsiRuntime, m_jsDispatcher, m_isRunning)); // Initialize Babylon Native plugins Plugins::NativeXr::Initialize(m_env); m_nativeInput = &Babylon::Plugins::NativeInput::CreateForJavaScript(m_env); - + // Initialize Babylon Native polyfills Polyfills::Window::Initialize(m_env); @@ -75,7 +75,7 @@ namespace Babylon // I don't understand the issue yet, but for now just retain the pre-refactor logic. We'll need to // resolve this to enable manual non-JS thread rendering. Note this only repros in release builds // where we actually call ResetView. - m_jsCallInvoker->invokeAsync([this, windowPtr, width, height]() + m_jsDispatcher([this, windowPtr, width, height]() { if (!m_graphics) { @@ -99,7 +99,7 @@ namespace Babylon // but there is an issue in NativeEngine where it will Dispatch a call to RenderCurrentFrame, then // get disposed, then try to actually render the frame. This results in immediately re-enabling // graphics after disabling it here. For now, retain the pre-refactor logic (queueing on the JS thread). - m_jsCallInvoker->invokeAsync([this]() + m_jsDispatcher([this]() { if (m_graphics) { @@ -153,7 +153,7 @@ namespace Babylon { m_disposeEngine = [&rt, engineInstanceValue{ std::make_shared(rt, args[0]) }]() { - auto engineInstance = engineInstanceValue->getObject(rt); + auto engineInstance{ engineInstanceValue->getObject(rt) }; engineInstance.getPropertyAsFunction(rt, "dispose").callWithThis(rt, engineInstance); }; } @@ -169,7 +169,7 @@ namespace Babylon std::function m_resolveInitPromise{}; Napi::Env m_env; - std::shared_ptr m_jsCallInvoker{}; + Dispatcher m_jsDispatcher{}; std::shared_ptr m_isRunning{}; std::unique_ptr m_graphics{}; @@ -184,11 +184,11 @@ namespace Babylon std::weak_ptr g_nativeModule{}; } - void Initialize(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker) + void Initialize(facebook::jsi::Runtime& jsiRuntime, Dispatcher jsDispatcher) { if (!jsiRuntime.global().hasProperty(jsiRuntime, JS_INSTANCE_NAME)) { - auto nativeModule{ std::make_shared(jsiRuntime, jsCallInvoker) }; + auto nativeModule{ std::make_shared(jsiRuntime, jsDispatcher) }; jsiRuntime.global().setProperty(jsiRuntime, JS_INSTANCE_NAME, jsi::Object::createFromHostObject(jsiRuntime, nativeModule)); g_nativeModule = nativeModule; } diff --git a/Modules/@babylonjs/react-native/shared/BabylonNative.h b/Modules/@babylonjs/react-native/shared/BabylonNative.h index ef701a817..4dcadfd6e 100644 --- a/Modules/@babylonjs/react-native/shared/BabylonNative.h +++ b/Modules/@babylonjs/react-native/shared/BabylonNative.h @@ -1,11 +1,12 @@ #pragma once #include -#include namespace Babylon { - void Initialize(facebook::jsi::Runtime& jsiRuntime, std::shared_ptr jsCallInvoker); + using Dispatcher = std::function)>; + + void Initialize(facebook::jsi::Runtime& jsiRuntime, Dispatcher jsDispatcher); void Deinitialize(); void UpdateView(void* windowPtr, size_t width, size_t height); void SetPointerButtonState(uint32_t pointerId, uint32_t buttonId, bool isDown, uint32_t x, uint32_t y); diff --git a/Modules/@babylonjs/react-native/shared/DispatchFunction.h b/Modules/@babylonjs/react-native/shared/DispatchFunction.h index be7af5122..c3fb19a26 100644 --- a/Modules/@babylonjs/react-native/shared/DispatchFunction.h +++ b/Modules/@babylonjs/react-native/shared/DispatchFunction.h @@ -3,16 +3,15 @@ #include #include -#include namespace Babylon { using namespace facebook; // Creates a JsRuntime::DispatchFunctionT that integrates with the React Native execution environment. - inline JsRuntime::DispatchFunctionT CreateJsRuntimeDispatcher(Napi::Env env, jsi::Runtime& jsiRuntime, std::shared_ptr callInvoker, const std::shared_ptr isRunning) + inline JsRuntime::DispatchFunctionT CreateJsRuntimeDispatcher(Napi::Env env, jsi::Runtime& jsiRuntime, Dispatcher dispatcher, const std::shared_ptr isRunning) { - return [env, &jsiRuntime, callInvoker{ std::move(callInvoker) }, isRunning{ std::move(isRunning) }](std::function func) + return [env, &jsiRuntime, dispatcher{ std::move(dispatcher) }, isRunning{ std::move(isRunning) }](std::function func) { // Ideally we would just use CallInvoker::invokeAsync directly, but currently it does not seem to integrate well with the React Native logbox. // To work around this, we wrap all functions in a try/catch, and when there is an exception, we do the following: @@ -23,7 +22,7 @@ namespace Babylon // 1. setImmediate queues the callback, and that queue is drained immediately following the invocation of the function passed to CallInvoker::invokeAsync. // 2. The immediates queue is drained as part of the class bridge, which knows how to display the logbox for unhandled exceptions. // In the future, CallInvoker::invokeAsync likely will properly integrate with logbox, at which point we can remove the try/catch and just call func directly. - callInvoker->invokeAsync([env, &jsiRuntime, isRunning{ std::move(isRunning) }, func{ std::move(func) }] + dispatcher([env, &jsiRuntime, isRunning{ std::move(isRunning) }, func{ std::move(func) }] { try { From 9d351b7f444828d4813c6571be2b8f6d93d1caee Mon Sep 17 00:00:00 2001 From: Ryan Tremblay Date: Tue, 22 Dec 2020 15:52:59 -0800 Subject: [PATCH 24/24] Fix bad import --- Modules/@babylonjs/react-native/EngineView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/@babylonjs/react-native/EngineView.tsx b/Modules/@babylonjs/react-native/EngineView.tsx index cbc364630..30b87207a 100644 --- a/Modules/@babylonjs/react-native/EngineView.tsx +++ b/Modules/@babylonjs/react-native/EngineView.tsx @@ -1,7 +1,8 @@ import React, { Component, FunctionComponent, SyntheticEvent, useCallback, useEffect, useState, useRef } from 'react'; import { requireNativeComponent, ViewProps, AppState, AppStateStatus, View, Text, findNodeHandle, UIManager } from 'react-native'; import { Camera } from '@babylonjs/core'; -import { ensureInitialized, ReactNativeEngine } from './BabylonModule'; +import { ensureInitialized } from './BabylonModule'; +import { ReactNativeEngine } from './ReactNativeEngine'; declare const global: any;