From a8cd516b58a0ba6feeb5a483ee747083e9391ef8 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Fri, 2 Oct 2020 17:40:48 -0700 Subject: [PATCH 1/5] Workaround for promise continuations --- Modules/@babylonjs/react-native/EngineHook.ts | 8 -------- .../android/src/main/cpp/BabylonNativeInterop.cpp | 10 ++++++---- .../@babylonjs/react-native/ios/BabylonNative.cpp | 7 +++++-- .../@babylonjs/react-native/shared/SetTimeout.h | 15 +++++++++++++++ 4 files changed, 26 insertions(+), 14 deletions(-) create mode 100644 Modules/@babylonjs/react-native/shared/SetTimeout.h 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 3445b6241..1e1f49177 100644 --- a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp +++ b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp @@ -21,6 +21,8 @@ #include +#include "../../../../shared/SetTimeout.h" + namespace Babylon { namespace @@ -38,12 +40,14 @@ namespace Babylon Native(facebook::jsi::Runtime* jsiRuntime, ANativeWindow* windowPtr) : m_env{ Napi::Attach(*jsiRuntime) } { + using looper_scheduler_t = arcana::looper_scheduler) + sizeof(std::function) + sizeof(std::shared_ptr)>; auto looper_scheduler = std::make_shared(looper_scheduler_t::get_for_current_thread()); - JsRuntime::DispatchFunctionT dispatchFunction{[env = m_env, looper_scheduler = std::move(looper_scheduler)](std::function func) { - (*looper_scheduler)([env, func = std::move(func)]() + JsRuntime::DispatchFunctionT dispatchFunction{[env = m_env, looper_scheduler = std::move(looper_scheduler), setTimeout = GetSetTimeout(*jsiRuntime)](std::function func) { + (*looper_scheduler)([env, func = std::move(func), setTimeout]() { func(env); + setTimeout->call((static_cast(env))->rt, {}); }); }}; @@ -97,8 +101,6 @@ namespace Babylon } private: - using looper_scheduler_t = arcana::looper_scheduler) + sizeof(std::function)>; - Napi::Env m_env; JsRuntime* m_runtime; Plugins::NativeInput* m_nativeInput; diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index fcb4f8906..a3fe7b7a6 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -17,6 +17,8 @@ #include #include +#include "../shared/SetTimeout.h" + namespace Babylon { using namespace facebook; @@ -43,11 +45,12 @@ namespace Babylon 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) + JsRuntime::DispatchFunctionT dispatchFunction{[env = m_impl->env, run_loop_scheduler = std::move(run_loop_scheduler), setTimeout = GetSetTimeout(*jsiRuntime)](std::function func) { - (*run_loop_scheduler)([env, func = std::move(func)]() + (*run_loop_scheduler)([env, func = std::move(func), setTimeout]() { func(env); + setTimeout->call((static_cast(env))->rt, {}); }); }}; diff --git a/Modules/@babylonjs/react-native/shared/SetTimeout.h b/Modules/@babylonjs/react-native/shared/SetTimeout.h new file mode 100644 index 000000000..e10634cfc --- /dev/null +++ b/Modules/@babylonjs/react-native/shared/SetTimeout.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +// See https://github.com/BabylonJS/BabylonReactNative/issues/60 for original issue. +// This is a work around for what appears to be custom handling of promise continuations and +// asynchronous functions in React Native (e.g. setTimeout, requestAnimationFrame, etc.) +// Without a call to one of these asynchronous functions, the promise continuation is not +// triggered. This creates a no-op setTimeout function that is called in the Babylon Native +// JsRuntime dispatch function to poke the promise continuation execution. +inline std::shared_ptr GetSetTimeout(facebook::jsi::Runtime& rt) +{ + auto code{std::make_shared("() => setTimeout(() => {})")}; + return std::make_shared(rt.evaluateJavaScript(std::move(code), "").asObject(rt).asFunction(rt)); +} From 22147b2b2d40cc5fc6c3a57ace27bf0d50d63118 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 7 Oct 2020 12:33:20 -0700 Subject: [PATCH 2/5] Use flushedQueue instead of setTimeout --- .../src/main/cpp/BabylonNativeInterop.cpp | 31 +++++++++++++------ .../react-native/ios/BabylonNative.cpp | 29 +++++++++++------ .../react-native/shared/SetTimeout.h | 15 --------- .../@babylonjs/react-native/shared/Shared.h | 18 +++++++++++ 4 files changed, 60 insertions(+), 33 deletions(-) delete mode 100644 Modules/@babylonjs/react-native/shared/SetTimeout.h create mode 100644 Modules/@babylonjs/react-native/shared/Shared.h 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 1e1f49177..ccb2cb07b 100644 --- a/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp +++ b/Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp @@ -21,7 +21,7 @@ #include -#include "../../../../shared/SetTimeout.h" +#include "../../../../shared/Shared.h" namespace Babylon { @@ -40,16 +40,29 @@ namespace Babylon Native(facebook::jsi::Runtime* jsiRuntime, ANativeWindow* windowPtr) : m_env{ Napi::Attach(*jsiRuntime) } { - using looper_scheduler_t = arcana::looper_scheduler) + sizeof(std::function) + sizeof(std::shared_ptr)>; - 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; + + DispatchData(Napi::Env env) + : scheduler{ looper_scheduler_t::get_for_current_thread() } + , flushedQueue{ GetFlushedQueue(env) } + { + } + }; - JsRuntime::DispatchFunctionT dispatchFunction{[env = m_env, looper_scheduler = std::move(looper_scheduler), setTimeout = GetSetTimeout(*jsiRuntime)](std::function func) { - (*looper_scheduler)([env, func = std::move(func), setTimeout]() + JsRuntime::DispatchFunctionT dispatchFunction = + [env = m_env, data = std::make_shared(m_env)](std::function func) { - func(env); - setTimeout->call((static_cast(env))->rt, {}); - }); - }}; + (data->scheduler)([env, func = std::move(func), &data]() + { + func(env); + data->flushedQueue.Call({}); + }); + }; m_runtime = &JsRuntime::CreateForJavaScript(m_env, dispatchFunction); diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index a3fe7b7a6..98ad1c854 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -17,7 +17,7 @@ #include #include -#include "../shared/SetTimeout.h" +#include "../shared/Shared.h" namespace Babylon { @@ -43,16 +43,27 @@ namespace Babylon Plugins::NativeEngine::InitializeGraphics(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), setTimeout = GetSetTimeout(*jsiRuntime)](std::function func) + struct DispatchData { - (*run_loop_scheduler)([env, func = std::move(func), setTimeout]() + 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_env)](std::function func) { - func(env); - setTimeout->call((static_cast(env))->rt, {}); - }); - }}; + (*run_loop_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/SetTimeout.h b/Modules/@babylonjs/react-native/shared/SetTimeout.h deleted file mode 100644 index e10634cfc..000000000 --- a/Modules/@babylonjs/react-native/shared/SetTimeout.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include - -// See https://github.com/BabylonJS/BabylonReactNative/issues/60 for original issue. -// This is a work around for what appears to be custom handling of promise continuations and -// asynchronous functions in React Native (e.g. setTimeout, requestAnimationFrame, etc.) -// Without a call to one of these asynchronous functions, the promise continuation is not -// triggered. This creates a no-op setTimeout function that is called in the Babylon Native -// JsRuntime dispatch function to poke the promise continuation execution. -inline std::shared_ptr GetSetTimeout(facebook::jsi::Runtime& rt) -{ - auto code{std::make_shared("() => setTimeout(() => {})")}; - return std::make_shared(rt.evaluateJavaScript(std::move(code), "").asObject(rt).asFunction(rt)); -} 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); +} From 4da8f36ca3e4f7095eee503f40cbab3f1eaf4bab Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 7 Oct 2020 12:45:42 -0700 Subject: [PATCH 3/5] Fix typo --- Modules/@babylonjs/react-native/ios/BabylonNative.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index 450f92373..cffea8f58 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -60,7 +60,7 @@ namespace Babylon JsRuntime::DispatchFunctionT dispatchFunction = [env = m_impl->env, data = std::make_shared(m_env)](std::function func) { - (*run_loop_scheduler)([env, func = std::move(func), &data]() + (*data->scheduler)([env, func = std::move(func), &data]() { func(env); data->flushedQueue.Call({}); From 4f8837d62b5fdc7b92e72b4783b0ef40557d42dc Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 7 Oct 2020 13:06:24 -0700 Subject: [PATCH 4/5] Fix typo 2 --- Modules/@babylonjs/react-native/ios/BabylonNative.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index cffea8f58..6dfa51cc0 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -58,7 +58,7 @@ namespace Babylon }; JsRuntime::DispatchFunctionT dispatchFunction = - [env = m_impl->env, data = std::make_shared(m_env)](std::function func) + [env = m_impl->env, data = std::make_shared(m_impl->env)](std::function func) { (*data->scheduler)([env, func = std::move(func), &data]() { From b39798491047905c24787fc5b7df90ec5ac79b95 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 7 Oct 2020 13:20:37 -0700 Subject: [PATCH 5/5] Fix typo 3 --- Modules/@babylonjs/react-native/ios/BabylonNative.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp index 6dfa51cc0..b427c9cc7 100644 --- a/Modules/@babylonjs/react-native/ios/BabylonNative.cpp +++ b/Modules/@babylonjs/react-native/ios/BabylonNative.cpp @@ -60,7 +60,7 @@ namespace Babylon JsRuntime::DispatchFunctionT dispatchFunction = [env = m_impl->env, data = std::make_shared(m_impl->env)](std::function func) { - (*data->scheduler)([env, func = std::move(func), &data]() + (data->scheduler)([env, func = std::move(func), &data]() { func(env); data->flushedQueue.Call({});