From 20dba39dab4ef85eb28659a89b19750cec3193a4 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Mon, 10 Jul 2023 04:44:05 -0700 Subject: [PATCH] Rollout TurboModuleBinding::Prototype (#38220) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38220 We ran an experiment to test different implementations of TurboModules HostObjects, as the current one has various inefficiencies, such as re-creating HostFunctions on every property access. The strategy we found to be most efficient and flexible longer-term is to represent the TurboModule with a plain JavaScript object and use a HostObject as its prototype. Whenever a property is accessed through the prototype, we cache the property value on the plain object, so it can be efficiently resolved by the VM for future accesses. Changelog: [General] TurboModules are now exposed to JS as the prototype of a plain JS object, so methods can be cached Reviewed By: sammy-SC Differential Revision: D47258286 fbshipit-source-id: 4562ac5316164daaf673e713b35cb31315ff9652 --- .../react/config/ReactFeatureFlags.java | 11 ----- .../ReactCommon/TurboModuleManager.cpp | 22 +-------- .../core/ReactCommon/TurboModuleBinding.cpp | 47 +++++++------------ .../core/ReactCommon/TurboModuleBinding.h | 12 +---- .../ios/ReactCommon/RCTTurboModuleManager.h | 2 - .../ios/ReactCommon/RCTTurboModuleManager.mm | 11 +---- 6 files changed, 21 insertions(+), 84 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index fce0e58f9c69a5..528d102b4cafb1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -104,17 +104,6 @@ public class ReactFeatureFlags { /** Feature Flag to enable the pending event queue in fabric before mounting views */ public static boolean enableFabricPendingEventQueue = false; - /** - * Feature flag that controls how turbo modules are exposed to JS - * - * - */ - public static int turboModuleBindingMode = 0; - /** * Feature Flag to enable View Recycling. When enabled, individual ViewManagers must still opt-in. */ diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp index fdcee5f672d1ab..baa3e2eeaf066d 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/turbomodule/ReactCommon/TurboModuleManager.cpp @@ -91,17 +91,6 @@ class JMethodDescriptor : public jni::JavaClass { }; } // namespace -constexpr static auto ReactFeatureFlagsJavaDescriptor = - "com/facebook/react/config/ReactFeatureFlags"; - -static int getFeatureFlagValue(const char *name) { - static const auto reactFeatureFlagsJavaDescriptor = - jni::findClassStatic(ReactFeatureFlagsJavaDescriptor); - const auto field = - reactFeatureFlagsJavaDescriptor->getStaticField(name); - return reactFeatureFlagsJavaDescriptor->getStaticFieldValue(field); -} - TurboModuleManager::TurboModuleManager( jni::alias_ref jThis, RuntimeExecutor runtimeExecutor, @@ -319,20 +308,13 @@ void TurboModuleManager::installJSIBindings(bool shouldCreateLegacyModules) { bool isInteropLayerDisabled = !shouldCreateLegacyModules; runtimeExecutor_([this, isInteropLayerDisabled](jsi::Runtime &runtime) { - TurboModuleBindingMode bindingMode = static_cast( - getFeatureFlagValue("turboModuleBindingMode")); - if (isInteropLayerDisabled) { - TurboModuleBinding::install( - runtime, bindingMode, createTurboModuleProvider()); + TurboModuleBinding::install(runtime, createTurboModuleProvider()); return; } TurboModuleBinding::install( - runtime, - bindingMode, - createTurboModuleProvider(), - createLegacyModuleProvider()); + runtime, createTurboModuleProvider(), createLegacyModuleProvider()); }); } diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp index e7039b5850934b..eb84bf6c6be560 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp @@ -95,13 +95,11 @@ static void defineReadOnlyGlobal( */ TurboModuleBinding::TurboModuleBinding( - TurboModuleBindingMode bindingMode, TurboModuleProviderFunctionType &&moduleProvider) - : bindingMode_(bindingMode), moduleProvider_(std::move(moduleProvider)) {} + : moduleProvider_(std::move(moduleProvider)) {} void TurboModuleBinding::install( jsi::Runtime &runtime, - TurboModuleBindingMode bindingMode, TurboModuleProviderFunctionType &&moduleProvider, TurboModuleProviderFunctionType &&legacyModuleProvider) { runtime.global().setProperty( @@ -111,8 +109,7 @@ void TurboModuleBinding::install( runtime, jsi::PropNameID::forAscii(runtime, "__turboModuleProxy"), 1, - [binding = - TurboModuleBinding(bindingMode, std::move(moduleProvider))]( + [binding = TurboModuleBinding(std::move(moduleProvider))]( jsi::Runtime &rt, const jsi::Value &thisVal, const jsi::Value *args, @@ -135,7 +132,7 @@ void TurboModuleBinding::install( runtime, std::make_shared( std::make_unique( - bindingMode, std::move(legacyModuleProvider))))); + std::move(legacyModuleProvider))))); } else { defineReadOnlyGlobal( runtime, @@ -160,11 +157,6 @@ jsi::Value TurboModuleBinding::getModule( module = moduleProvider_(moduleName); } if (module) { - // Default behaviour - if (bindingMode_ == TurboModuleBindingMode::HostObject) { - return jsi::Object::createFromHostObject(runtime, std::move(module)); - } - // What is jsRepresentation? A cache for the TurboModule's properties // Henceforth, always return the cache (i.e: jsRepresentation) to JavaScript // @@ -186,26 +178,19 @@ jsi::Value TurboModuleBinding::getModule( weakJsRepresentation = std::make_unique(runtime, jsRepresentation); - if (bindingMode_ == TurboModuleBindingMode::Prototype) { - // Option 1: Lazily populate the jsRepresentation, on property access. - // - // How does this work? - // 1. Initially jsRepresentation is empty: {} - // 2. If property lookup on jsRepresentation fails, the JS runtime will - // search jsRepresentation's prototype: jsi::Object(TurboModule). - // 3. TurboModule::get(runtime, propKey) executes. This creates the - // property, caches it on jsRepresentation, then returns it to - // JavaScript. - auto hostObject = - jsi::Object::createFromHostObject(runtime, std::move(module)); - jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject)); - } else { - // Option 2: Eagerly populate the jsRepresentation, on create. - // Object.assign(jsRepresentation, jsi::Object(TurboModule)) - for (auto &propName : module->getPropertyNames(runtime)) { - module->get(runtime, propName); - } - } + // Lazily populate the jsRepresentation, on property access. + // + // How does this work? + // 1. Initially jsRepresentation is empty: {} + // 2. If property lookup on jsRepresentation fails, the JS runtime will + // search jsRepresentation's prototype: jsi::Object(TurboModule). + // 3. TurboModule::get(runtime, propKey) executes. This creates the + // property, caches it on jsRepresentation, then returns it to + // JavaScript. + auto hostObject = + jsi::Object::createFromHostObject(runtime, std::move(module)); + jsRepresentation.setProperty(runtime, "__proto__", std::move(hostObject)); + return jsRepresentation; } else { return jsi::Value::null(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h index 1aef473c63d0ee..2fc6e17cf4ac86 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.h @@ -14,12 +14,6 @@ namespace facebook::react { -enum class TurboModuleBindingMode : uint8_t { - HostObject = 0, - Prototype = 1, - Eager = 2, -}; - class BridgelessNativeModuleProxy; /** @@ -33,13 +27,10 @@ class TurboModuleBinding { */ static void install( jsi::Runtime &runtime, - TurboModuleBindingMode bindingMode, TurboModuleProviderFunctionType &&moduleProvider, TurboModuleProviderFunctionType &&legacyModuleProvider = nullptr); - TurboModuleBinding( - TurboModuleBindingMode bindingMode, - TurboModuleProviderFunctionType &&moduleProvider); + TurboModuleBinding(TurboModuleProviderFunctionType &&moduleProvider); virtual ~TurboModuleBinding(); private: @@ -52,7 +43,6 @@ class TurboModuleBinding { jsi::Value getModule(jsi::Runtime &runtime, const std::string &moduleName) const; - TurboModuleBindingMode bindingMode_; TurboModuleProviderFunctionType moduleProvider_; }; diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h index 417da9d857e5dd..2130bb136ac953 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.h @@ -17,8 +17,6 @@ #import #import "RCTTurboModule.h" -RCT_EXTERN void RCTTurboModuleSetBindingMode(facebook::react::TurboModuleBindingMode bindingMode); - @protocol RCTTurboModuleManagerDelegate /** diff --git a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm index 1f8846763bf557..053a31781c740e 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm +++ b/packages/react-native/ReactCommon/react/nativemodule/core/platform/ios/ReactCommon/RCTTurboModuleManager.mm @@ -33,12 +33,6 @@ using namespace facebook; using namespace facebook::react; -static TurboModuleBindingMode sTurboModuleBindingMode = TurboModuleBindingMode::HostObject; -void RCTTurboModuleSetBindingMode(TurboModuleBindingMode bindingMode) -{ - sTurboModuleBindingMode = bindingMode; -} - /** * A global variable whose address we use to associate method queues to id objects. */ @@ -962,10 +956,9 @@ - (void)installJSBindings:(facebook::jsi::Runtime &)runtime return turboModule; }; - TurboModuleBinding::install( - runtime, sTurboModuleBindingMode, std::move(turboModuleProvider), std::move(legacyModuleProvider)); + TurboModuleBinding::install(runtime, std::move(turboModuleProvider), std::move(legacyModuleProvider)); } else { - TurboModuleBinding::install(runtime, sTurboModuleBindingMode, std::move(turboModuleProvider)); + TurboModuleBinding::install(runtime, std::move(turboModuleProvider)); } }