diff --git a/Modules/@babylonjs/react-native/EngineHook.ts b/Modules/@babylonjs/react-native/EngineHook.ts index 833229cd8..5ff3df80e 100644 --- a/Modules/@babylonjs/react-native/EngineHook.ts +++ b/Modules/@babylonjs/react-native/EngineHook.ts @@ -75,14 +75,6 @@ export function useEngine(): Engine | undefined { } })(); - // NOTE: This is a workaround for https://github.com/BabylonJS/BabylonReactNative/issues/60 - function heartbeat() { - if (!disposed) { - setTimeout(heartbeat, 10); - } - } - heartbeat(); - return () => { disposed = true; setEngine(engine => { 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 123c36b6a..0e87a009e 100644 --- a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp +++ b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp @@ -22,6 +22,8 @@ #include +#include "../../../../shared/Shared.h" + namespace Babylon { namespace @@ -39,14 +41,29 @@ namespace Babylon Native(facebook::jsi::Runtime* jsiRuntime, ANativeWindow* windowPtr) : m_env{ Napi::Attach(*jsiRuntime) } { - auto looper_scheduler = std::make_shared(looper_scheduler_t::get_for_current_thread()); + struct DispatchData + { + using looper_scheduler_t = arcana::looper_scheduler<128>; + + looper_scheduler_t scheduler; + Napi::FunctionReference flushedQueue; - JsRuntime::DispatchFunctionT dispatchFunction{[env = m_env, looper_scheduler = std::move(looper_scheduler)](std::function func) { - (*looper_scheduler)([env, func = std::move(func)]() + DispatchData(Napi::Env env) + : scheduler{ looper_scheduler_t::get_for_current_thread() } + , flushedQueue{ GetFlushedQueue(env) } { - func(env); - }); - }}; + } + }; + + JsRuntime::DispatchFunctionT dispatchFunction = + [env = m_env, data = std::make_shared(m_env)](std::function func) + { + (data->scheduler)([env, func = std::move(func), &data]() + { + func(env); + data->flushedQueue.Call({}); + }); + }; m_runtime = &JsRuntime::CreateForJavaScript(m_env, dispatchFunction); @@ -100,8 +117,6 @@ namespace Babylon } private: - using looper_scheduler_t = arcana::looper_scheduler) + sizeof(std::function)>; - std::unique_ptr m_graphics{}; Napi::Env m_env; JsRuntime* m_runtime; diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index bd37ea26b..b427c9cc7 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -18,6 +18,8 @@ #include #include +#include "../shared/Shared.h" + namespace Babylon { using namespace facebook; @@ -43,15 +45,27 @@ namespace Babylon m_impl->m_graphics = Graphics::InitializeFromWindow(windowPtr, width, height); }); - auto run_loop_scheduler = std::make_shared(arcana::run_loop_scheduler::get_for_current_thread()); - - JsRuntime::DispatchFunctionT dispatchFunction{[env = m_impl->env, run_loop_scheduler = std::move(run_loop_scheduler)](std::function func) + struct DispatchData { - (*run_loop_scheduler)([env, func = std::move(func)]() + arcana::run_loop_scheduler scheduler; + Napi::FunctionReference flushedQueue; + + DispatchData(Napi::Env env) + : scheduler{ arcana::run_loop_scheduler::get_for_current_thread() } + , flushedQueue{ GetFlushedQueue(env) } + { + } + }; + + JsRuntime::DispatchFunctionT dispatchFunction = + [env = m_impl->env, data = std::make_shared(m_impl->env)](std::function func) { - func(env); - }); - }}; + (data->scheduler)([env, func = std::move(func), &data]() + { + func(env); + data->flushedQueue.Call({}); + }); + }; m_impl->runtime = &JsRuntime::CreateForJavaScript(m_impl->env, std::move(dispatchFunction)); diff --git a/Modules/@babylonjs/react-native/shared/Shared.h b/Modules/@babylonjs/react-native/shared/Shared.h new file mode 100644 index 000000000..dcaef312b --- /dev/null +++ b/Modules/@babylonjs/react-native/shared/Shared.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +// See https://github.com/BabylonJS/BabylonReactNative/issues/60 for original issue. +// This is a work around to poke the React Native message queue to run setImmediate callbacks. +// React Native uses a custom promise implementation based on setImmediate. The promise +// continuations will only continue once these setImmediate callbacks are triggered by the +// flushedQueue call. This is explicitly called at the end of JsRuntime's dispatch function +// to flush the queue. +inline Napi::FunctionReference GetFlushedQueue(Napi::Env env) +{ + // HACK: The __fbBatchedBridge global is an internal implementation of React Native. + // This hack will break if React Native internals changes, but it should blow up right here. + auto batchedBridge{ env.Global().Get("__fbBatchedBridge").As() }; + auto flushedQueue{ batchedBridge.Get("flushedQueue").As() }; + return Napi::Persistent(flushedQueue); +}