Skip to content

Commit eca1d7c

Browse files
authored
Use React Native's Callinvoker to schedule work on the JS thread (#97)
1 parent 6eac1e0 commit eca1d7c

File tree

11 files changed

+88
-93
lines changed

11 files changed

+88
-93
lines changed

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,6 @@ export function useEngine(): Engine | undefined {
7575
}
7676
})();
7777

78-
// NOTE: This is a workaround for https://github.com/BabylonJS/BabylonReactNative/issues/60
79-
function heartbeat() {
80-
if (!disposed) {
81-
setTimeout(heartbeat, 10);
82-
}
83-
}
84-
heartbeat();
85-
8678
return () => {
8779
disposed = true;
8880
setEngine(engine => {

Modules/@babylonjs/react-native/android/CMakeLists.txt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ set(BABYLON_NATIVE_PLATFORM "Android")
2424
set(CMAKE_CXX_EXTENSIONS OFF)
2525
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17")
2626

27-
# configure Babylon Native to use JSI
27+
# Configure Babylon Native to use JSI
2828
set(NAPI_JAVASCRIPT_ENGINE "JSI" CACHE STRING "The JavaScript engine to power N-API")
2929
add_subdirectory(${REACTNATIVE_DIR}/ReactCommon/jsi/jsi ${CMAKE_CURRENT_BINARY_DIR}/jsi)
3030
target_include_directories(jsi INTERFACE ${REACTNATIVE_DIR}/ReactCommon/jsi)
@@ -34,6 +34,22 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/src/")
3434
set(BABYLON_NATIVE_DIR "${CMAKE_CURRENT_LIST_DIR}/../submodules/BabylonNative")
3535
add_subdirectory(${BABYLON_NATIVE_DIR} ${BABYLON_NATIVE_DIR}/build/Android_${CMAKE_ANDROID_ARCH_ABI}/)
3636

37+
add_library(fbjni SHARED IMPORTED)
38+
set_target_properties(fbjni PROPERTIES
39+
IMPORTED_LOCATION ${FBJNI_LIBPATH}/${ANDROID_ABI}/libfbjni.so
40+
INTERFACE_INCLUDE_DIRECTORIES ${FBJNI_INCPATH})
41+
42+
# Define a minimal version of libturbomodulejsijni.so that includes CallInvokerHolder.cpp.
43+
# This is the only part of the TurboModule system we need for now. Eventually when TurboModule
44+
# support ships with React Native, we'll need to strip this back out.
45+
list(APPEND TURBOMODULE_INC_DIRS "${REACTNATIVE_DIR}/ReactCommon/callinvoker")
46+
list(APPEND TURBOMODULE_INC_DIRS "${REACTNATIVE_DIR}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni")
47+
add_library(turbomodulejsijni SHARED
48+
${REACTNATIVE_DIR}/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp)
49+
target_include_directories(turbomodulejsijni PUBLIC "${TURBOMODULE_INC_DIRS}")
50+
target_link_libraries(turbomodulejsijni
51+
fbjni)
52+
3753
add_library(BabylonNative SHARED
3854
src/main/cpp/BabylonNativeInterop.cpp)
3955

@@ -44,7 +60,9 @@ target_link_libraries(BabylonNative
4460
log
4561
-lz
4662
arcana
63+
fbjni
4764
jsi
65+
turbomodulejsijni
4866
AndroidExtensions
4967
Graphics
5068
JsRuntime

Modules/@babylonjs/react-native/android/build.gradle

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ buildscript {
4444
apply plugin: 'com.android.library'
4545
apply plugin: 'maven'
4646

47-
configurations { natives }
47+
configurations {
48+
natives
49+
fbjniHeaders
50+
fbjniLibs
51+
}
4852

4953
android {
5054
compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
@@ -62,6 +66,8 @@ android {
6266
'-DENABLE_GLSLANG_BINARIES=OFF',
6367
'-DSPIRV_CROSS_CLI=OFF',
6468
"-DARCORE_LIBPATH=${extractedLibDir}/jni",
69+
"-DFBJNI_INCPATH=${extractedLibDir}/fbjni/",
70+
"-DFBJNI_LIBPATH=${extractedLibDir}/jni",
6571
"-DREACTNATIVE_DIR=${rootDir}/../node_modules/react-native/"
6672
}
6773
}
@@ -105,7 +111,11 @@ dependencies {
105111
implementation fileTree(dir: "libs", include: ["*.jar"])
106112
implementation 'com.google.ar:core:1.14.0'
107113

114+
api("com.facebook.fbjni:fbjni-java-only:0.0.3")
115+
108116
natives 'com.google.ar:core:1.14.0'
117+
fbjniHeaders 'com.facebook.fbjni:fbjni:0.0.2:headers'
118+
fbjniLibs 'com.facebook.fbjni:fbjni:0.0.2'
109119
}
110120

111121
def configureReactNativePom(def pom) {
@@ -197,9 +207,35 @@ task extractNativeLibraries() {
197207
}
198208
}
199209

210+
task extractFBJNIHeaders {
211+
doLast {
212+
configurations.fbjniHeaders.files.each { f ->
213+
copy {
214+
from zipTree(f)
215+
into "$extractedLibDir/fbjni"
216+
include "**/*.h"
217+
}
218+
}
219+
}
220+
}
221+
222+
task extractFBJNILibs {
223+
doLast {
224+
configurations.fbjniLibs.files.each { f ->
225+
copy {
226+
from zipTree(f)
227+
into extractedLibDir
228+
include "jni/**/*"
229+
}
230+
}
231+
}
232+
}
233+
200234
tasks.whenTaskAdded { task ->
201235
if (task.name.contains("external") && !task.name.contains("Clean")) {
202236
task.dependsOn(extractNativeLibraries)
237+
task.dependsOn(extractFBJNIHeaders)
238+
task.dependsOn(extractFBJNILibs)
203239
}
204240
}
205241

Modules/@babylonjs/react-native/android/src/main/cpp/BabylonNativeInterop.cpp

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
#include <AndroidExtensions/Globals.h>
1313

14-
#include <arcana/threading/task_schedulers.h>
15-
1614
#include <android/log.h>
1715
#include <android/native_window.h>
1816
#include <android/native_window_jni.h>
@@ -22,8 +20,7 @@
2220
#include <unistd.h>
2321

2422
#include <jsi/jsi.h>
25-
26-
#include "../../../../shared/Shared.h"
23+
#include <ReactCommon/CallInvokerHolder.h>
2724

2825
namespace Babylon
2926
{
@@ -39,32 +36,15 @@ namespace Babylon
3936
{
4037
public:
4138
// This class must be constructed from the JavaScript thread
42-
Native(facebook::jsi::Runtime* jsiRuntime, ANativeWindow* windowPtr)
39+
Native(facebook::jsi::Runtime* jsiRuntime, std::shared_ptr<facebook::react::CallInvoker> callInvoker, ANativeWindow* windowPtr)
4340
: m_env{ Napi::Attach<facebook::jsi::Runtime&>(*jsiRuntime) }
4441
{
45-
struct DispatchData
46-
{
47-
using looper_scheduler_t = arcana::looper_scheduler<128>;
48-
49-
looper_scheduler_t scheduler;
50-
Napi::FunctionReference flushedQueue;
51-
52-
DispatchData(Napi::Env env)
53-
: scheduler{ looper_scheduler_t::get_for_current_thread() }
54-
, flushedQueue{ GetFlushedQueue(env) }
55-
{
56-
}
57-
};
58-
5942
JsRuntime::DispatchFunctionT dispatchFunction =
60-
[env = m_env, data = std::make_shared<DispatchData>(m_env)](std::function<void(Napi::Env)> func)
43+
[env = m_env, callInvoker](std::function<void(Napi::Env)> func)
6144
{
62-
(data->scheduler)([env, func = std::move(func), &data]()
45+
callInvoker->invokeAsync([env, func = std::move(func)]
6346
{
6447
func(env);
65-
// NOTE: This doesn't work quite right on iOS, so we'll use a different work around until
66-
// we have a better solution (see Shared.h and EngineHook.ts for more details).
67-
//data->flushedQueue.Call({});
6848
});
6949
};
7050

@@ -153,11 +133,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_reactlibrary_BabylonNativeInterop_res
153133
android::global::Resume();
154134
}
155135

156-
extern "C" JNIEXPORT jlong JNICALL Java_com_reactlibrary_BabylonNativeInterop_create(JNIEnv* env, jclass obj, jlong jsiRuntimeRef, jobject surface)
136+
extern "C" JNIEXPORT jlong JNICALL Java_com_reactlibrary_BabylonNativeInterop_create(JNIEnv* env, jclass obj, jlong jsiRuntimeRef, jobject jsCallInvokerHolder, jobject surface)
157137
{
158138
auto jsiRuntime = reinterpret_cast<facebook::jsi::Runtime*>(jsiRuntimeRef);
139+
auto callInvoker = facebook::jni::alias_ref<facebook::react::CallInvokerHolder::javaobject> {reinterpret_cast<facebook::react::CallInvokerHolder::javaobject>(jsCallInvokerHolder)}->cthis()->getCallInvoker();
159140
ANativeWindow* windowPtr = ANativeWindow_fromSurface(env, surface);
160-
auto native = new Babylon::Native(jsiRuntime, windowPtr);
141+
auto native = new Babylon::Native(jsiRuntime, callInvoker, windowPtr);
161142
return reinterpret_cast<intptr_t>(native);
162143
}
163144

Modules/@babylonjs/react-native/android/src/main/java/com/reactlibrary/BabylonNativeInterop.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.facebook.react.bridge.JavaScriptContextHolder;
1111
import com.facebook.react.bridge.LifecycleEventListener;
1212
import com.facebook.react.bridge.ReactContext;
13+
import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder;
1314

1415
import java.util.Hashtable;
1516
import java.util.Iterator;
@@ -30,7 +31,7 @@ final class BabylonNativeInterop {
3031
private static native void setCurrentActivity(Activity activity);
3132
private static native void pause();
3233
private static native void resume();
33-
private static native long create(long jsiRuntimeRef, Surface surface);
34+
private static native long create(long jsiRuntimeRef, CallInvokerHolder callInvokerHolder, Surface surface);
3435
private static native void refresh(long instanceRef, Surface surface);
3536
private static native void setPointerButtonState(long instanceRef, int pointerId, int buttonId, boolean isDown, int x, int y);
3637
private static native void setPointerPosition(long instanceRef, int pointerId, int x, int y);
@@ -56,7 +57,7 @@ static void setView(ReactContext reactContext, Surface surface) {
5657
if (jsiRuntimeRef == 0) {
5758
instanceRefFuture.complete(0L);
5859
} else {
59-
instanceRef = BabylonNativeInterop.create(jsiRuntimeRef, surface);
60+
instanceRef = BabylonNativeInterop.create(jsiRuntimeRef, reactContext.getCatalystInstance().getJSCallInvokerHolder(), surface);
6061
final long finalInstanceRef = instanceRef;
6162

6263
reactContext.addLifecycleEventListener(new LifecycleEventListener() {

Modules/@babylonjs/react-native/ios/BabylonNative.cpp

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,54 +19,39 @@
1919
#include <sstream>
2020
#include <unistd.h>
2121

22-
#include "../shared/Shared.h"
23-
2422
namespace Babylon
2523
{
2624
using namespace facebook;
2725

2826
class Native::Impl
2927
{
3028
public:
31-
Impl(facebook::jsi::Runtime* jsiRuntime)
29+
Impl(facebook::jsi::Runtime* jsiRuntime, std::shared_ptr<facebook::react::CallInvoker> callInvoker)
3230
: env{ Napi::Attach<facebook::jsi::Runtime&>(*jsiRuntime) }
31+
, jsCallInvoker{ callInvoker }
3332
{
3433
}
3534

36-
std::unique_ptr<Graphics> m_graphics{};
3735
Napi::Env env;
36+
std::shared_ptr<facebook::react::CallInvoker> jsCallInvoker;
37+
std::unique_ptr<Graphics> m_graphics{};
3838
JsRuntime* runtime{};
3939
Plugins::NativeInput* nativeInput{};
4040
};
4141

42-
Native::Native(facebook::jsi::Runtime* jsiRuntime, void* windowPtr, size_t width, size_t height)
43-
: m_impl{ std::make_unique<Native::Impl>(jsiRuntime) }
42+
Native::Native(facebook::jsi::Runtime* jsiRuntime, std::shared_ptr<facebook::react::CallInvoker> callInvoker, void* windowPtr, size_t width, size_t height)
43+
: m_impl{ std::make_unique<Native::Impl>(jsiRuntime, callInvoker) }
4444
{
4545
dispatch_sync(dispatch_get_main_queue(), ^{
4646
m_impl->m_graphics = Graphics::InitializeFromWindow<void*>(windowPtr, width, height);
4747
});
4848

49-
struct DispatchData
50-
{
51-
arcana::run_loop_scheduler scheduler;
52-
std::shared_ptr<facebook::jsi::Function> setTimeout;
53-
54-
DispatchData(facebook::jsi::Runtime& rt)
55-
: scheduler{ arcana::run_loop_scheduler::get_for_current_thread() }
56-
, setTimeout{ GetSetTimeout(rt) }
57-
{
58-
}
59-
};
60-
6149
JsRuntime::DispatchFunctionT dispatchFunction =
62-
[env = m_impl->env, data = std::make_shared<DispatchData>(*jsiRuntime)](std::function<void(Napi::Env)> func)
50+
[env = m_impl->env, callInvoker = m_impl->jsCallInvoker](std::function<void(Napi::Env)> func)
6351
{
64-
(data->scheduler)([env, func = std::move(func), &data]()
52+
callInvoker->invokeAsync([env, func = std::move(func)]
6553
{
6654
func(env);
67-
// NOTE: This doesn't work quite right on iOS, so we'll use a different work around until
68-
// we have a better solution (see Shared.h and EngineHook.ts for more details).
69-
//data->setTimeout->call((static_cast<napi_env>(env))->rt, {});
7055
});
7156
};
7257

Modules/@babylonjs/react-native/ios/BabylonNative.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
#pragma once
22

33
#include <jsi/jsi.h>
4+
#include <ReactCommon/CallInvoker.h>
45

56
namespace Babylon
67
{
78
class Native final
89
{
910
public:
1011
// This class must be constructed from the JavaScript thread
11-
Native(facebook::jsi::Runtime* jsiRuntime, void* windowPtr, size_t width, size_t height);
12+
Native(facebook::jsi::Runtime* jsiRuntime, std::shared_ptr<facebook::react::CallInvoker> callInvoker, void* windowPtr, size_t width, size_t height);
1213
~Native();
1314
void Refresh(void* windowPtr, size_t width, size_t height);
1415
void Resize(size_t width, size_t height);

Modules/@babylonjs/react-native/ios/BabylonNativeInterop.mm

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
using namespace facebook;
1616

17+
@interface RCTBridge (RCTTurboModule)
18+
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
19+
@end
20+
1721
namespace {
1822
jsi::Runtime* GetJSIRuntime(RCTBridge* bridge) {
1923
RCTCxxBridge* cxxBridge = reinterpret_cast<RCTCxxBridge*>(bridge);
@@ -128,7 +132,7 @@ + (void)setCurrentNativeInstance:(RCTBridge*)bridge mtkView:(MTKView*)mtkView wi
128132

129133
jsi::Runtime* jsiRuntime = GetJSIRuntime(currentBridge);
130134
if (jsiRuntime) {
131-
currentNativeInstance = std::make_unique<Babylon::Native>(GetJSIRuntime(currentBridge), (__bridge void*)mtkView, width, height);
135+
currentNativeInstance = std::make_unique<Babylon::Native>(GetJSIRuntime(currentBridge), currentBridge.jsCallInvoker, (__bridge void*)mtkView, width, height);
132136
}
133137
}
134138

Modules/@babylonjs/react-native/ios/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ file(COPY ${REACTNATIVE_DIR}/ReactCommon/jsi/jsi DESTINATION ${CMAKE_CURRENT_BIN
1515
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/jsi/jsi ${CMAKE_CURRENT_BINARY_DIR}/jsi)
1616
target_include_directories(jsi INTERFACE ${REACTNATIVE_DIR}/ReactCommon/jsi)
1717

18+
add_library(reactnative INTERFACE)
19+
target_include_directories(reactnative INTERFACE ${REACTNATIVE_DIR}/ReactCommon/callinvoker)
20+
1821
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/")
1922

2023
set(BABYLON_NATIVE_DIR "${CMAKE_CURRENT_LIST_DIR}/../submodules/BabylonNative")
@@ -34,6 +37,7 @@ target_link_libraries(BabylonNative
3437
arcana
3538
Graphics
3639
jsi
40+
reactnative
3741
JsRuntime
3842
NativeWindow
3943
NativeEngine

Modules/@babylonjs/react-native/shared/Shared.h

Lines changed: 0 additions & 30 deletions
This file was deleted.

0 commit comments

Comments
 (0)