Worklets/Threading
Nitro itself is fully runtime-agnostic, which means every Hybrid Object can be used from any JS Runtime or Worklet Context.
This allows the caller to call into native Nitro Modules from libraries like react-native-worklets-core, or react-native-reanimated. You can use a Nitro Hybrid Object on the default React JS context, on the UI context, or on any other background worklet context.
- react-native-worklets (Reanimated)
- react-native-worklets-core (Margelo)
const math = NitroModules.createHybridObject<Math>('Math')
runOnUI(() => {
'worklet'
const result = math.add(5, 3)
console.log(result) // --> 8
})()
const math = NitroModules.createHybridObject<Math>('Math')
const boxed = NitroModules.box(math)
const context = Worklets.createContext('DummyContext')
context.runAsync(() => {
'worklet'
const unboxed = boxed.unbox()
const result = unboxed.add(5, 3)
console.log(result) // --> 8
})
Dispatcher
All synchronous APIs of Nitro work ✨ automagically ✨ on any runtime, but asynchronous APIs (Promises and callbacks) require a Dispatcher.
If you call an asynchronous API on a runtime that Nitro doesn't know, it likely doesn't have a Dispatcher, so it doesn't know how to call back to the JS Thread after the asynchronous operation has finished (Promise resolve or callback call).
If you created that jsi::Runtime, you need to create a Dispatcher for it and implement runSync and runAsync:
#include <NitroModules/Dispatcher.hpp>
using namespace margelo::nitro;
class MyRuntimeDispatcher: public Dispatcher {
public:
void runSync(std::function<void()>&& function) override;
void runAsync(std::function<void()>&& function) override;
};
Then, simply install this Dispatcher into your runtime so Nitro can use it:
auto myDispatcher = std::make_shared<MyRuntimeDispatcher>();
Dispatcher::installRuntimeGlobalDispatcher(myRuntime, myDispatcher);
This needs to be done once, ideally immediately after creating the jsi::Runtime.
Your runSync and runAsync implementations must run the given function on the same Thread that the jsi::Runtime was created on - see CallInvokerDispatcher.hpp for an example.
Boxing
A Hybrid Object is a JS object with jsi::NativeState and a prototype chain.
If you need to interop with legacy APIs or APIs that can't deal with jsi::NativeState yet, you can box the Hybrid Object into a jsi::HostObject:
const math = NitroModules.createHybridObject<Math>('Math')
const boxed = NitroModules.box(math) // <-- jsi::HostObject
The boxed object is a simple jsi::HostObject (see BoxedHybridObject.hpp), which can later be unboxed again:
const unboxed = boxed.unbox() // <-- Math
const result = unboxed.add(5, 3) // <-- 8
This is how Hybrid Objects are captured inside Worklet Contexts under the hood as well!