Skip to content

Commit

Permalink
Implement ability to emit device events from C++ TurboModules
Browse files Browse the repository at this point in the history
Summary:
[Changelog][Internal]

This adds a method, `emitDeviceEvent` to the C++ API of TurboModules, which allows to make calls to JS's `RCTDeviceEventEmitter.emit` from a C++ TurboModules.

This is a very common pattern, specifically for the VR apps, but not only for them - e.g. Desktop fork also has a [custom implementation for this](https://www.internalfb.com/code/fbsource/third-party/microsoft-fork-of-react-native/react-native-utils/RCTEventEmitter.cpp).

Note that my original intent was to actually backport the latter, however there are some complications with wiring things in a robust way, without exposing too much stuff and relying on singletons or folly::dynamic.

So I ended up adding it to the TurboModule API itself and use the scheduler/JSI facilities instead.

This approach is arguably well self-contained, uses high level APIs, and shouldn't be abusable much.

Since I was trying to avoid usage of folly::dynamic in this case, I used a kind of "value factory" pattern instead in order to send the arguments to the JS thread in a thread safe way (see [the discussion here](https://fb.workplace.com/groups/rn.fabric/permalink/1398711453593610/)).

Reviewed By: christophpurrer

Differential Revision: D43466326

fbshipit-source-id: a3cb8359d08a46421559edd0f854772863cb5c39
  • Loading branch information
rshest authored and facebook-github-bot committed Feb 21, 2023
1 parent 817948e commit 407fb5c
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 1 deletion.
10 changes: 9 additions & 1 deletion Libraries/EventEmitter/RCTDeviceEventEmitter.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@ type RCTDeviceEventDefinitions = $FlowFixMe;
*
* NativeModules that emit events should instead subclass `NativeEventEmitter`.
*/
export default (new EventEmitter(): IEventEmitter<RCTDeviceEventDefinitions>);
const RCTDeviceEventEmitter: IEventEmitter<RCTDeviceEventDefinitions> =
new EventEmitter();

Object.defineProperty(global, '__rctDeviceEventEmitter', {
configurable: true,
value: RCTDeviceEventEmitter,
});

export default (RCTDeviceEventEmitter: IEventEmitter<RCTDeviceEventDefinitions>);
24 changes: 24 additions & 0 deletions ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,29 @@ jsi::Value TurboModule::get(
return result;
}

void TurboModule::emitDeviceEvent(
jsi::Runtime &runtime,
const std::string &eventName,
ArgFactory argFactory) {
jsInvoker_->invokeAsync([&runtime, eventName, argFactory]() {
jsi::Value emitter =
runtime.global().getProperty(runtime, "__rctDeviceEventEmitter");
if (!emitter.isUndefined()) {
jsi::Object emitterObject = emitter.asObject(runtime);
// TODO: consider caching these
jsi::Function emitFunction =
emitterObject.getPropertyAsFunction(runtime, "emit");
std::vector<jsi::Value> args;
args.emplace_back(
jsi::String::createFromAscii(runtime, eventName.c_str()));
if (argFactory) {
argFactory(runtime, args);
}
emitFunction.callWithThis(
runtime, emitterObject, (const jsi::Value *)args.data(), args.size());
}
});
}

} // namespace react
} // namespace facebook
20 changes: 20 additions & 0 deletions ReactCommon/react/nativemodule/core/ReactCommon/TurboModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,26 @@ class JSI_EXPORT TurboModule : public facebook::jsi::HostObject {
};
std::unordered_map<std::string, MethodMetadata> methodMap_;

using ArgFactory =
std::function<void(jsi::Runtime &runtime, std::vector<jsi::Value> &args)>;

/**
* Calls RCTDeviceEventEmitter.emit to JavaScript, with given event name and
* an optional list of arguments.
* If present, argFactory is a callback used to construct extra arguments,
* e.g.
*
* emitDeviceEvent(rt, "myCustomEvent",
* [](jsi::Runtime& rt, std::vector<jsi::Value>& args) {
* args.emplace_back(jsi::Value(true));
* args.emplace_back(jsi::Value(42));
* });
*/
void emitDeviceEvent(
jsi::Runtime &runtime,
const std::string &eventName,
ArgFactory argFactory = nullptr);

private:
friend class TurboCxxModule;
friend class TurboModuleBinding;
Expand Down

0 comments on commit 407fb5c

Please sign in to comment.