Skip to content

Commit

Permalink
Rollout TurboModuleBinding::Prototype (#38220)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
javache authored and facebook-github-bot committed Jul 10, 2023
1 parent c82f2e9 commit 20dba39
Show file tree
Hide file tree
Showing 6 changed files with 21 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
* <ul>
* <li>0 = as a HostObject
* <li>1 = as a plain object, backed with a HostObject as prototype
* <li>2 = as a plain object, with all methods eagerly configured
* </ul>
*/
public static int turboModuleBindingMode = 0;

/**
* Feature Flag to enable View Recycling. When enabled, individual ViewManagers must still opt-in.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,6 @@ class JMethodDescriptor : public jni::JavaClass<JMethodDescriptor> {
};
} // 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<jint>(name);
return reactFeatureFlagsJavaDescriptor->getStaticFieldValue(field);
}

TurboModuleManager::TurboModuleManager(
jni::alias_ref<TurboModuleManager::javaobject> jThis,
RuntimeExecutor runtimeExecutor,
Expand Down Expand Up @@ -319,20 +308,13 @@ void TurboModuleManager::installJSIBindings(bool shouldCreateLegacyModules) {
bool isInteropLayerDisabled = !shouldCreateLegacyModules;

runtimeExecutor_([this, isInteropLayerDisabled](jsi::Runtime &runtime) {
TurboModuleBindingMode bindingMode = static_cast<TurboModuleBindingMode>(
getFeatureFlagValue("turboModuleBindingMode"));

if (isInteropLayerDisabled) {
TurboModuleBinding::install(
runtime, bindingMode, createTurboModuleProvider());
TurboModuleBinding::install(runtime, createTurboModuleProvider());
return;
}

TurboModuleBinding::install(
runtime,
bindingMode,
createTurboModuleProvider(),
createLegacyModuleProvider());
runtime, createTurboModuleProvider(), createLegacyModuleProvider());
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
Expand All @@ -135,7 +132,7 @@ void TurboModuleBinding::install(
runtime,
std::make_shared<BridgelessNativeModuleProxy>(
std::make_unique<TurboModuleBinding>(
bindingMode, std::move(legacyModuleProvider)))));
std::move(legacyModuleProvider)))));
} else {
defineReadOnlyGlobal(
runtime,
Expand All @@ -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
//
Expand All @@ -186,26 +178,19 @@ jsi::Value TurboModuleBinding::getModule(
weakJsRepresentation =
std::make_unique<jsi::WeakObject>(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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@

namespace facebook::react {

enum class TurboModuleBindingMode : uint8_t {
HostObject = 0,
Prototype = 1,
Eager = 2,
};

class BridgelessNativeModuleProxy;

/**
Expand All @@ -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:
Expand All @@ -52,7 +43,6 @@ class TurboModuleBinding {
jsi::Value getModule(jsi::Runtime &runtime, const std::string &moduleName)
const;

TurboModuleBindingMode bindingMode_;
TurboModuleProviderFunctionType moduleProvider_;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
#import <ReactCommon/TurboModuleBinding.h>
#import "RCTTurboModule.h"

RCT_EXTERN void RCTTurboModuleSetBindingMode(facebook::react::TurboModuleBindingMode bindingMode);

@protocol RCTTurboModuleManagerDelegate <NSObject>

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<RCTBridgeModule> objects.
*/
Expand Down Expand Up @@ -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));
}
}

Expand Down

0 comments on commit 20dba39

Please sign in to comment.