react-native-nitro-modules 0.1.7 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/android/CMakeLists.txt +1 -0
- package/cpp/core/HybridFunction.hpp +167 -0
- package/cpp/core/HybridObject.cpp +37 -128
- package/cpp/core/HybridObject.hpp +13 -149
- package/cpp/jsi/JSICache.cpp +24 -8
- package/cpp/jsi/JSICache.hpp +31 -23
- package/cpp/jsi/JSIConverter+ArrayBuffer.hpp +7 -3
- package/cpp/jsi/JSIConverter+Function.hpp +1 -1
- package/cpp/jsi/JSIConverter+HybridObject.hpp +26 -87
- package/cpp/jsi/Promise.cpp +2 -2
- package/cpp/prototype/HybridObjectPrototype.cpp +87 -0
- package/cpp/prototype/HybridObjectPrototype.hpp +92 -0
- package/cpp/prototype/Prototype.hpp +163 -0
- package/cpp/prototype/PrototypeChain.hpp +78 -0
- package/cpp/turbomodule/NativeNitroModules.cpp +3 -3
- package/cpp/turbomodule/NativeNitroModules.hpp +1 -1
- package/cpp/utils/BorrowingReference+Owning.hpp +1 -1
- package/cpp/utils/BorrowingReference.hpp +1 -1
- package/lib/HybridObject.d.ts +20 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/HybridObject.d.ts +20 -1
- package/lib/typescript/HybridObject.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/HybridObject.ts +20 -1
- package/cpp/core/PointerHolder.hpp +0 -94
- package/lib/typescript/AnyMap.d.ts +0 -17
- package/lib/typescript/AnyMap.d.ts.map +0 -1
- package/lib/typescript/ModuleNotFoundError.d.ts +0 -7
- package/lib/typescript/ModuleNotFoundError.d.ts.map +0 -1
- package/lib/typescript/NativeNitroModules.d.ts +0 -13
- package/lib/typescript/NativeNitroModules.d.ts.map +0 -1
- package/lib/typescript/NativeNitroModules.web.d.ts +0 -5
- package/lib/typescript/NativeNitroModules.web.d.ts.map +0 -1
- package/lib/typescript/NitroModules.d.ts +0 -18
- package/lib/typescript/__tests__/index.test.d.ts +0 -1
- package/lib/typescript/__tests__/index.test.d.ts.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
//
|
|
2
|
+
// PrototypeChain.hpp
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 07.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "HybridFunction.hpp"
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <typeindex>
|
|
14
|
+
#include <unordered_map>
|
|
15
|
+
|
|
16
|
+
namespace margelo::nitro {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Represents a Prototype's native C++ type ID.
|
|
20
|
+
* This can be used to identify a prototype against a C++ instance,
|
|
21
|
+
* or used as a cache-key.
|
|
22
|
+
*/
|
|
23
|
+
using NativeInstanceId = std::type_index;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Represents a JS `Prototype`'s structure.
|
|
27
|
+
* Every prototype has a related C++ type ID (`instanceTypeId`).
|
|
28
|
+
* Prototypes can be sub-classes, in which case they have a `base` prototype.
|
|
29
|
+
* Each prototype has a list of methods, and properties (getters + setters).
|
|
30
|
+
*
|
|
31
|
+
* By using this `Prototype` structure, we can create JS objects that act
|
|
32
|
+
* as prototypes for `HybridObject`s.
|
|
33
|
+
*
|
|
34
|
+
* While a `Prototype` actually holds all the methods, a `HybridObject` only
|
|
35
|
+
* contains state and memory.
|
|
36
|
+
* This way the JS VM doesn't need to re-create methods for each `HybridObject`,
|
|
37
|
+
* they are only initialized once on the shared `Prototype`.
|
|
38
|
+
*/
|
|
39
|
+
struct Prototype final {
|
|
40
|
+
private:
|
|
41
|
+
std::shared_ptr<Prototype> _base = nullptr;
|
|
42
|
+
NativeInstanceId _instanceTypeId;
|
|
43
|
+
std::unordered_map<std::string, HybridFunction> _methods;
|
|
44
|
+
std::unordered_map<std::string, HybridFunction> _getters;
|
|
45
|
+
std::unordered_map<std::string, HybridFunction> _setters;
|
|
46
|
+
|
|
47
|
+
private:
|
|
48
|
+
Prototype(const NativeInstanceId& typeId, const std::shared_ptr<Prototype>& base) : _instanceTypeId(typeId), _base(base) {}
|
|
49
|
+
|
|
50
|
+
public:
|
|
51
|
+
/**
|
|
52
|
+
* Gets a `Prototype` specification/node for the given native C++ type ID.
|
|
53
|
+
*
|
|
54
|
+
* If the given C++ type ID is unknown, a new `Prototype` node is created,
|
|
55
|
+
* which has to be initialized with methods, getters and setters first.
|
|
56
|
+
*
|
|
57
|
+
* If the given C++ type ID is already known in the static `Prototype` tree,
|
|
58
|
+
* a shared reference to it is returned.
|
|
59
|
+
*/
|
|
60
|
+
static std::shared_ptr<Prototype> get(const NativeInstanceId& typeId, const std::shared_ptr<Prototype>& base = nullptr) {
|
|
61
|
+
static std::unordered_map<NativeInstanceId, std::shared_ptr<Prototype>> _prototypesCache;
|
|
62
|
+
|
|
63
|
+
const auto& found = _prototypesCache.find(typeId);
|
|
64
|
+
if (found != _prototypesCache.end()) {
|
|
65
|
+
// We know this C++ type ID / Prototype - return it!
|
|
66
|
+
return found->second;
|
|
67
|
+
} else {
|
|
68
|
+
// This is the first time we see this C++ type ID - create a new base Prototype for this.
|
|
69
|
+
auto prototype = std::shared_ptr<Prototype>(new Prototype(typeId, base));
|
|
70
|
+
_prototypesCache.emplace(typeId, prototype);
|
|
71
|
+
return prototype;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public:
|
|
76
|
+
template <typename T>
|
|
77
|
+
inline bool isNativeInstance() const noexcept {
|
|
78
|
+
return _instanceTypeId == std::type_index(typeid(T));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
inline bool hasHybrids() const {
|
|
82
|
+
return !_methods.empty() || !_getters.empty() || !_setters.empty();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
inline bool hasBase() const noexcept {
|
|
86
|
+
return _base != nullptr;
|
|
87
|
+
}
|
|
88
|
+
inline const std::shared_ptr<Prototype>& getBase() const noexcept {
|
|
89
|
+
return _base;
|
|
90
|
+
}
|
|
91
|
+
inline const NativeInstanceId& getNativeInstanceId() const noexcept {
|
|
92
|
+
return _instanceTypeId;
|
|
93
|
+
}
|
|
94
|
+
inline const std::unordered_map<std::string, HybridFunction>& getMethods() const noexcept {
|
|
95
|
+
return _methods;
|
|
96
|
+
}
|
|
97
|
+
inline const std::unordered_map<std::string, HybridFunction>& getGetters() const noexcept {
|
|
98
|
+
return _getters;
|
|
99
|
+
}
|
|
100
|
+
inline const std::unordered_map<std::string, HybridFunction>& getSetters() const noexcept {
|
|
101
|
+
return _setters;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
public:
|
|
105
|
+
/**
|
|
106
|
+
* Registers the given C++ method as a Hybrid Method that can be called from JS, through the object's Prototype.
|
|
107
|
+
* Example:
|
|
108
|
+
* ```cpp
|
|
109
|
+
* registerHybridMethod("sayHello", &MyObject::sayHello);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
template <typename Derived, typename ReturnType, typename... Args>
|
|
113
|
+
inline void registerHybridMethod(std::string name, ReturnType (Derived::*method)(Args...)) {
|
|
114
|
+
if (_getters.contains(name) || _setters.contains(name)) [[unlikely]] {
|
|
115
|
+
throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a property with that name already exists!");
|
|
116
|
+
}
|
|
117
|
+
if (_methods.contains(name)) [[unlikely]] {
|
|
118
|
+
throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a method with that name already exists!");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_methods.emplace(name, HybridFunction::createHybridFunction(name, method, FunctionType::METHOD));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Registers the given C++ method as a property getter that can be called from JS, through the object's Prototype.
|
|
126
|
+
* Example:
|
|
127
|
+
* ```cpp
|
|
128
|
+
* registerHybridGetter("foo", &MyObject::getFoo);
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
template <typename Derived, typename ReturnType>
|
|
132
|
+
inline void registerHybridGetter(std::string name, ReturnType (Derived::*method)()) {
|
|
133
|
+
if (_getters.contains(name)) [[unlikely]] {
|
|
134
|
+
throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a getter with that name already exists!");
|
|
135
|
+
}
|
|
136
|
+
if (_methods.contains(name)) [[unlikely]] {
|
|
137
|
+
throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a method with that name already exists!");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
_getters.emplace(name, HybridFunction::createHybridFunction(name, method, FunctionType::GETTER));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Registers the given C++ method as a property setter that can be called from JS, through the object's Prototype.
|
|
145
|
+
* Example:
|
|
146
|
+
* ```cpp
|
|
147
|
+
* registerHybridSetter("foo", &MyObject::setFoo);
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
template <typename Derived, typename ValueType>
|
|
151
|
+
inline void registerHybridSetter(std::string name, void (Derived::*method)(ValueType)) {
|
|
152
|
+
if (_setters.contains(name)) [[unlikely]] {
|
|
153
|
+
throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a setter with that name already exists!");
|
|
154
|
+
}
|
|
155
|
+
if (_methods.contains(name)) [[unlikely]] {
|
|
156
|
+
throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a method with that name already exists!");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
_setters.emplace(name, HybridFunction::createHybridFunction(name, method, FunctionType::SETTER));
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// PrototypeChain.hpp
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 07.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "Prototype.hpp"
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro {
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Represents a mutable chain of prototypes.
|
|
16
|
+
* Callers can use this class to incrementally add sub-classes to prototypes and build
|
|
17
|
+
* prototype chains/trees using C++ type information.
|
|
18
|
+
*
|
|
19
|
+
* The template methods can be used to find a specific C++ instance in the prototype tree,
|
|
20
|
+
* or create a new sub-class if it cannot be found.
|
|
21
|
+
*/
|
|
22
|
+
class PrototypeChain final {
|
|
23
|
+
private:
|
|
24
|
+
std::shared_ptr<Prototype> _prototype;
|
|
25
|
+
|
|
26
|
+
public:
|
|
27
|
+
PrototypeChain() {}
|
|
28
|
+
|
|
29
|
+
public:
|
|
30
|
+
/**
|
|
31
|
+
* Gets the current `Prototype` as a whole.
|
|
32
|
+
* This does not do any modifications to the Prototype tree.
|
|
33
|
+
*/
|
|
34
|
+
inline const std::shared_ptr<Prototype>& getPrototype() const {
|
|
35
|
+
return _prototype;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public:
|
|
39
|
+
/**
|
|
40
|
+
* Extends the Prototype with the given type `Derived`.
|
|
41
|
+
* If the Prototype already extended `Derived`, this returns the current state.
|
|
42
|
+
*/
|
|
43
|
+
template <typename Derived>
|
|
44
|
+
inline const std::shared_ptr<Prototype>& extendPrototype() {
|
|
45
|
+
if (_prototype == nullptr) {
|
|
46
|
+
_prototype = Prototype::get(typeid(Derived));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return getOrExtendPrototype<Derived>(_prototype);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private:
|
|
53
|
+
/**
|
|
54
|
+
* Perform a bottom-down search of the given `Derived` C++ type info.
|
|
55
|
+
* If the current prototype tree does not have a Prototype that represents the
|
|
56
|
+
* C++ type `Derived`, it will extend it at the bottom and shift the `Prototype` tree
|
|
57
|
+
* up by one.
|
|
58
|
+
*/
|
|
59
|
+
template <typename Derived>
|
|
60
|
+
inline const std::shared_ptr<Prototype>& getOrExtendPrototype(const std::shared_ptr<Prototype>& node) {
|
|
61
|
+
if (node->isNativeInstance<Derived>()) {
|
|
62
|
+
// If the Prototype represents the caller type (`Derived`), we work with this Prototype.
|
|
63
|
+
return node;
|
|
64
|
+
} else {
|
|
65
|
+
if (node->hasBase()) {
|
|
66
|
+
// We didn't find a match in this prototype, let's recursively try it's parent!
|
|
67
|
+
return getOrExtendPrototype<Derived>(node->getBase());
|
|
68
|
+
} else {
|
|
69
|
+
// We didn't find `Derived` and we don't have a base- add a child and shift the tree by one.
|
|
70
|
+
auto newBase = _prototype;
|
|
71
|
+
_prototype = Prototype::get(typeid(Derived), newBase);
|
|
72
|
+
return _prototype;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
} // namespace margelo::nitro
|
|
@@ -58,12 +58,12 @@ void NativeNitroModules::install(jsi::Runtime& runtime) {
|
|
|
58
58
|
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
jsi::
|
|
62
|
-
|
|
61
|
+
jsi::Value NativeNitroModules::createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName,
|
|
62
|
+
const std::optional<jsi::Object>& args) {
|
|
63
63
|
auto name = hybridObjectName.utf8(runtime);
|
|
64
64
|
// TODO: Pass args? Do we need that?
|
|
65
65
|
auto hybridObject = HybridObjectRegistry::createHybridObject(name.c_str());
|
|
66
|
-
return
|
|
66
|
+
return hybridObject->toObject(runtime);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
} // namespace facebook::react
|
|
@@ -23,7 +23,7 @@ public:
|
|
|
23
23
|
jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) override;
|
|
24
24
|
|
|
25
25
|
void install(jsi::Runtime& runtime);
|
|
26
|
-
jsi::
|
|
26
|
+
jsi::Value createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName, const std::optional<jsi::Object>& args);
|
|
27
27
|
|
|
28
28
|
public:
|
|
29
29
|
constexpr static auto kModuleName = "NitroModulesCxx";
|
|
@@ -22,7 +22,7 @@ BorrowingReference<T>::BorrowingReference(const OwningReference<T>& ref) {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
template <typename T>
|
|
25
|
-
OwningReference<T> BorrowingReference<T>::lock() {
|
|
25
|
+
OwningReference<T> BorrowingReference<T>::lock() const {
|
|
26
26
|
std::unique_lock lock(*_mutex);
|
|
27
27
|
|
|
28
28
|
if (*_isDeleted) {
|
package/lib/HybridObject.d.ts
CHANGED
|
@@ -9,7 +9,10 @@ export interface PlatformSpec {
|
|
|
9
9
|
* Represents a Nitro `HybridObject` which is implemented natively in either C++,
|
|
10
10
|
* or Swift/Kotlin.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
12
|
+
* `HybridObject`s use the Nitro Tunnel for efficient, low-overhead JS <-> Native communication.
|
|
13
|
+
*
|
|
14
|
+
* All `HybridObject`s are implemented using `NativeState`, and inherit their properties
|
|
15
|
+
* and methods from their prototype, so the actual JS object is empty.
|
|
13
16
|
*
|
|
14
17
|
* @example
|
|
15
18
|
* ```ts
|
|
@@ -22,6 +25,22 @@ export interface PlatformSpec {
|
|
|
22
25
|
* ```
|
|
23
26
|
*/
|
|
24
27
|
export interface HybridObject<Spec extends PlatformSpec> {
|
|
28
|
+
/**
|
|
29
|
+
* Holds a type-name describing the native `HybridObject` instance.
|
|
30
|
+
*
|
|
31
|
+
* This is the only property actually present on the actual JavaScript object,
|
|
32
|
+
* because all other properties and methods are inherited from a shared Prototype.
|
|
33
|
+
*
|
|
34
|
+
* Nitro prototypes also have a `__type`.
|
|
35
|
+
*
|
|
36
|
+
* For actual HybridObject instances, this is `NativeState<...>`, for
|
|
37
|
+
* prototypes this is `Prototype<...>`.
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
* @private
|
|
41
|
+
* @note This value is available in debug only.
|
|
42
|
+
*/
|
|
43
|
+
readonly __type?: string;
|
|
25
44
|
/**
|
|
26
45
|
* The `HybridObject`'s name.
|
|
27
46
|
*/
|