-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add
NitroModules.box(...)
to support using Nitro Modules from…
… any Runtime/Worklets context (#138) * feat: Add `NitroModules.box(...)` to support using Nitro Modules from any Runtime/Worklets context * feat: Add `getPropertyNames` for `BoxedHybridObject` * docs: Create Worklets/Threading docs
- Loading branch information
Showing
8 changed files
with
183 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
--- | ||
--- | ||
|
||
import Tabs from '@theme/Tabs'; | ||
import TabItem from '@theme/TabItem'; | ||
|
||
# Worklets/Threading | ||
|
||
Nitro itself is fully runtime-agnostic, which means every [Hybrid Object](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](https://github.com/margelo/react-native-worklets-core), or [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated). | ||
You can use a Nitro [Hybrid Object](hybrid-object) on the default React JS context, on the UI context, or on any other background worklet context. | ||
|
||
<Tabs groupId="worklet-library"> | ||
<TabItem value="rnwc" label="Worklets Core" default> | ||
```ts | ||
const math = NitroModules.createHybridObject<Math>('Math') | ||
const boxed = NitroModules.box(math) | ||
|
||
const context = Worklets.createContext('DummyContext') | ||
context.runAsync(() => { | ||
'worklet' | ||
const unboxed = boxed.unbox() | ||
console.log(unboxed.add(5, 3)) // --> 8 | ||
}) | ||
``` | ||
</TabItem> | ||
<TabItem value="rea" label="Reanimated"> | ||
```ts | ||
const math = NitroModules.createHybridObject<Math>('Math') | ||
const boxed = NitroModules.box(math) | ||
|
||
runOnUI(() => { | ||
'worklet' | ||
const unboxed = boxed.unbox() | ||
console.log(unboxed.add(5, 3)) // --> 8 | ||
})() | ||
``` | ||
</TabItem> | ||
</Tabs> | ||
|
||
## Boxing | ||
|
||
Since Nitro uses newer JSI APIs like `jsi::NativeState` - which current worklet libraries (like [react-native-worklets-core](https://github.com/margelo/react-native-worklets-core) or [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated)) do not yet fully support - Hybrid Objects cannot yet be _directly_ used in worklet contexts - they have to be _boxed_. | ||
|
||
A _boxed_ Hybrid Object is a native `jsi::HostObject`, which is supported by worklet libraries. The process is as following: | ||
|
||
1. In the runtime your `HybridObject` was created in (probably the default runtime), call `NitroModules.box(...)` to box it. | ||
2. The boxed result can be shared in any (worklet-)runtime if needed. | ||
3. To use the original `HybridObject`, simply call `.unbox()` on it in the desired (worklet-)runtime. | ||
4. The result of `.unbox()` is the original `HybridObject` - you can now call any methods on it as usual. | ||
|
||
In future versions of [react-native-worklets-core](https://github.com/margelo/react-native-worklets-core) or [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated) we expect fullly automatic `jsi::NativeState` support, which will make boxing obsolete. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
packages/react-native-nitro-modules/cpp/core/BoxedHybridObject.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// | ||
// BoxedHybridObject.cpp | ||
// NitroModules | ||
// | ||
// Created by Marc Rousavy on 17.09.24. | ||
// | ||
|
||
#include "BoxedHybridObject.hpp" | ||
|
||
namespace margelo::nitro { | ||
|
||
std::vector<jsi::PropNameID> BoxedHybridObject::getPropertyNames(facebook::jsi::Runtime& runtime) { | ||
return jsi::PropNameID::names(runtime, "unbox"); | ||
} | ||
|
||
jsi::Value BoxedHybridObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) { | ||
std::string name = propName.utf8(runtime); | ||
|
||
if (name == "unbox") { | ||
return jsi::Function::createFromHostFunction( | ||
runtime, jsi::PropNameID::forUtf8(runtime, "unbox"), 0, | ||
[hybridObject = _hybridObject](jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, | ||
size_t count) -> jsi::Value { return hybridObject->toObject(runtime); }); | ||
} | ||
|
||
return jsi::Value::undefined(); | ||
} | ||
|
||
} // namespace margelo::nitro |
36 changes: 36 additions & 0 deletions
36
packages/react-native-nitro-modules/cpp/core/BoxedHybridObject.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// | ||
// Created by Marc Rousavy on 21.02.24. | ||
// | ||
|
||
#pragma once | ||
|
||
#include "HybridObject.hpp" | ||
#include <jsi/jsi.h> | ||
#include <memory> | ||
|
||
namespace margelo::nitro { | ||
|
||
using namespace facebook; | ||
|
||
/** | ||
* Represents a `HybridObject` that has been boxed into a `jsi::HostObject`. | ||
* | ||
* While `HybridObject`s are runtime agnostic, some threading/worklet libraries do not support copying over objects | ||
* with `jsi::NativeState` and a prototype chain (which is what a `HybridObject` is), so Nitro offers support for | ||
* boxing those `HybridObject`s into a type that those libraries support - which is a `jsi::HostObject`. | ||
* | ||
* Simply call `unbox()` on this `jsi::HostObject` from the new Runtime/context to get the `HybridObject` again. | ||
*/ | ||
class BoxedHybridObject : public jsi::HostObject { | ||
public: | ||
explicit BoxedHybridObject(const std::shared_ptr<HybridObject>& hybridObject) : _hybridObject(hybridObject) {} | ||
|
||
public: | ||
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) override; | ||
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& runtime) override; | ||
|
||
private: | ||
std::shared_ptr<HybridObject> _hybridObject; | ||
}; | ||
|
||
} // namespace margelo::nitro |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters