react-native-nitro-modules 0.1.6 → 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 +25 -8
- package/cpp/jsi/JSICache.hpp +31 -24
- package/cpp/jsi/JSIConverter+AnyMap.hpp +22 -0
- package/cpp/jsi/JSIConverter+ArrayBuffer.hpp +15 -3
- package/cpp/jsi/JSIConverter+Function.hpp +9 -1
- package/cpp/jsi/JSIConverter+HybridObject.hpp +31 -76
- package/cpp/jsi/JSIConverter+Optional.hpp +9 -0
- package/cpp/jsi/JSIConverter+Promise.hpp +4 -0
- package/cpp/jsi/JSIConverter+Tuple.hpp +33 -0
- package/cpp/jsi/JSIConverter+UnorderedMap.hpp +20 -0
- package/cpp/jsi/JSIConverter+Variant.hpp +25 -53
- package/cpp/jsi/JSIConverter+Vector.hpp +21 -0
- package/cpp/jsi/JSIConverter.hpp +55 -7
- package/cpp/jsi/JSIHelpers.hpp +52 -0
- 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/threading/Dispatcher.cpp +1 -1
- package/cpp/turbomodule/NativeNitroModules.cpp +3 -4
- package/cpp/turbomodule/NativeNitroModules.hpp +1 -1
- package/cpp/utils/BorrowingReference+Owning.hpp +1 -1
- package/cpp/utils/BorrowingReference.hpp +1 -1
- package/ios/turbomodule/NitroModuleOnLoad.mm +0 -5
- package/lib/HybridObject.d.ts +20 -1
- package/lib/commonjs/index.js +0 -11
- package/lib/commonjs/index.js.map +1 -1
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/HybridObject.d.ts +77 -0
- package/lib/typescript/HybridObject.d.ts.map +1 -0
- package/lib/typescript/NitroModules.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/HybridObject.ts +20 -1
- package/src/index.ts +0 -1
- package/cpp/core/PointerHolder.hpp +0 -94
- package/cpp/test-object/TestHybridObject.cpp +0 -37
- package/cpp/test-object/TestHybridObject.hpp +0 -87
- package/cpp/utils/GetRuntimeID.hpp +0 -28
- package/lib/commonjs/createTestObject.js +0 -15
- package/lib/commonjs/createTestObject.js.map +0 -1
- package/lib/module/createTestObject.js +0 -8
- package/lib/module/createTestObject.js.map +0 -1
- package/src/createTestObject.ts +0 -40
package/cpp/jsi/JSIConverter.hpp
CHANGED
|
@@ -25,18 +25,34 @@ using namespace facebook;
|
|
|
25
25
|
* Value types, custom types (HostObjects), and even functions with any number of arguments/types are supported.
|
|
26
26
|
* This type can be extended by just creating a new template for JSIConverter in a header.
|
|
27
27
|
*/
|
|
28
|
-
template <typename
|
|
28
|
+
template <typename T, typename Enable = void>
|
|
29
29
|
struct JSIConverter final {
|
|
30
30
|
JSIConverter() = delete;
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
static inline
|
|
37
|
-
static_assert(always_false<
|
|
32
|
+
/**
|
|
33
|
+
* Converts the given `jsi::Value` to type `T`.
|
|
34
|
+
* By default, this static-asserts.
|
|
35
|
+
*/
|
|
36
|
+
static inline T fromJSI(jsi::Runtime&, const jsi::Value&) {
|
|
37
|
+
static_assert(always_false<T>::value, "This type is not supported by the JSIConverter!");
|
|
38
|
+
return T();
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Converts `T` to a `jsi::Value`.
|
|
42
|
+
* By default, this static-asserts.
|
|
43
|
+
*/
|
|
44
|
+
static inline jsi::Value toJSI(jsi::Runtime&, T) {
|
|
45
|
+
static_assert(always_false<T>::value, "This type is not supported by the JSIConverter!");
|
|
38
46
|
return jsi::Value::undefined();
|
|
39
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Returns whether the given `jsi::Value` can be converted to `T`.
|
|
50
|
+
* This involves runtime type-checks.
|
|
51
|
+
* By default, this returns `false`.
|
|
52
|
+
*/
|
|
53
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value&) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
40
56
|
|
|
41
57
|
private:
|
|
42
58
|
template <typename>
|
|
@@ -52,6 +68,9 @@ struct JSIConverter<int> {
|
|
|
52
68
|
static inline jsi::Value toJSI(jsi::Runtime&, int arg) {
|
|
53
69
|
return jsi::Value(arg);
|
|
54
70
|
}
|
|
71
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
72
|
+
return value.isNumber();
|
|
73
|
+
}
|
|
55
74
|
};
|
|
56
75
|
|
|
57
76
|
// std::monostate <> null
|
|
@@ -63,6 +82,9 @@ struct JSIConverter<std::monostate> {
|
|
|
63
82
|
static inline jsi::Value toJSI(jsi::Runtime&, std::monostate arg) {
|
|
64
83
|
return jsi::Value::null();
|
|
65
84
|
}
|
|
85
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
86
|
+
return value.isNull() || value.isUndefined();
|
|
87
|
+
}
|
|
66
88
|
};
|
|
67
89
|
|
|
68
90
|
// double <> number
|
|
@@ -74,6 +96,9 @@ struct JSIConverter<double> {
|
|
|
74
96
|
static inline jsi::Value toJSI(jsi::Runtime&, double arg) {
|
|
75
97
|
return jsi::Value(arg);
|
|
76
98
|
}
|
|
99
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
100
|
+
return value.isNumber();
|
|
101
|
+
}
|
|
77
102
|
};
|
|
78
103
|
|
|
79
104
|
// float <> number
|
|
@@ -85,6 +110,9 @@ struct JSIConverter<float> {
|
|
|
85
110
|
static inline jsi::Value toJSI(jsi::Runtime&, float arg) {
|
|
86
111
|
return jsi::Value(static_cast<double>(arg));
|
|
87
112
|
}
|
|
113
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
114
|
+
return value.isNumber();
|
|
115
|
+
}
|
|
88
116
|
};
|
|
89
117
|
|
|
90
118
|
// int64_t <> BigInt
|
|
@@ -96,6 +124,13 @@ struct JSIConverter<int64_t> {
|
|
|
96
124
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, int64_t arg) {
|
|
97
125
|
return jsi::BigInt::fromInt64(runtime, arg);
|
|
98
126
|
}
|
|
127
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
128
|
+
if (value.isBigInt()) {
|
|
129
|
+
jsi::BigInt bigint = value.getBigInt(runtime);
|
|
130
|
+
return bigint.isInt64(runtime);
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
99
134
|
};
|
|
100
135
|
|
|
101
136
|
// uint64_t <> BigInt
|
|
@@ -107,6 +142,13 @@ struct JSIConverter<uint64_t> {
|
|
|
107
142
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, uint64_t arg) {
|
|
108
143
|
return jsi::BigInt::fromUint64(runtime, arg);
|
|
109
144
|
}
|
|
145
|
+
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
146
|
+
if (value.isBigInt()) {
|
|
147
|
+
jsi::BigInt bigint = value.getBigInt(runtime);
|
|
148
|
+
return bigint.isUint64(runtime);
|
|
149
|
+
}
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
110
152
|
};
|
|
111
153
|
|
|
112
154
|
// bool <> boolean
|
|
@@ -118,6 +160,9 @@ struct JSIConverter<bool> {
|
|
|
118
160
|
static inline jsi::Value toJSI(jsi::Runtime&, bool arg) {
|
|
119
161
|
return jsi::Value(arg);
|
|
120
162
|
}
|
|
163
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
164
|
+
return value.isBool();
|
|
165
|
+
}
|
|
121
166
|
};
|
|
122
167
|
|
|
123
168
|
// std::string <> string
|
|
@@ -129,6 +174,9 @@ struct JSIConverter<std::string> {
|
|
|
129
174
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const std::string& arg) {
|
|
130
175
|
return jsi::String::createFromUtf8(runtime, arg);
|
|
131
176
|
}
|
|
177
|
+
static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
|
|
178
|
+
return value.isString();
|
|
179
|
+
}
|
|
132
180
|
};
|
|
133
181
|
|
|
134
182
|
} // namespace margelo::nitro
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//
|
|
2
|
+
// JSIHelpers.hpp
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 07.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "ThreadUtils.hpp"
|
|
11
|
+
#include <jsi/jsi.h>
|
|
12
|
+
|
|
13
|
+
namespace margelo::nitro {
|
|
14
|
+
|
|
15
|
+
using namespace facebook;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Returns whether the given `jsi::Object` is a plain-JS object, or not.
|
|
19
|
+
* If it is not a plain-JS object, it could be an Array, ArrayBuffer, Function,
|
|
20
|
+
* HostObject or NativeState.
|
|
21
|
+
*/
|
|
22
|
+
static inline bool isPlainObject(jsi::Runtime& runtime, const jsi::Object& object) {
|
|
23
|
+
if (object.isArray(runtime)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (object.isArrayBuffer(runtime)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
if (object.isFunction(runtime)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (object.isHostObject(runtime)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (object.hasNativeState(runtime)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get an ID for the given Runtime.
|
|
43
|
+
*
|
|
44
|
+
* The ID usually consists of a Runtime description (e.g. "HermesRuntime"),
|
|
45
|
+
* and it's Thread (e.g. "com.facebook.react.runtime.JavaScript")
|
|
46
|
+
*/
|
|
47
|
+
static inline std::string getRuntimeId(jsi::Runtime& runtime) {
|
|
48
|
+
std::string threadName = ThreadUtils::getThreadName();
|
|
49
|
+
return runtime.description() + std::string(" (") + threadName + std::string(")");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
} // namespace margelo::nitro
|
package/cpp/jsi/Promise.cpp
CHANGED
|
@@ -9,8 +9,8 @@ using namespace facebook;
|
|
|
9
9
|
|
|
10
10
|
Promise::Promise(jsi::Runtime& runtime, jsi::Function&& resolver, jsi::Function&& rejecter) {
|
|
11
11
|
auto functionCache = JSICache::getOrCreateCache(runtime);
|
|
12
|
-
_resolver = functionCache.
|
|
13
|
-
_rejecter = functionCache.
|
|
12
|
+
_resolver = functionCache.makeShared(std::move(resolver));
|
|
13
|
+
_rejecter = functionCache.makeShared(std::move(rejecter));
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
jsi::Value Promise::createPromise(jsi::Runtime& runtime, RunPromise&& run) {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HybridObjectPrototype.cpp
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 07.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "HybridObjectPrototype.hpp"
|
|
9
|
+
#include "NitroLogger.hpp"
|
|
10
|
+
|
|
11
|
+
namespace margelo::nitro {
|
|
12
|
+
|
|
13
|
+
std::unordered_map<jsi::Runtime*, HybridObjectPrototype::PrototypeCache> HybridObjectPrototype::_prototypeCache;
|
|
14
|
+
|
|
15
|
+
jsi::Value HybridObjectPrototype::createPrototype(jsi::Runtime& runtime, const std::shared_ptr<Prototype>& prototype) {
|
|
16
|
+
// 0. Check if we're at the highest level of our prototype chain
|
|
17
|
+
if (prototype == nullptr) {
|
|
18
|
+
// There is no prototype - we just have an empty Object base - so `Object.create({})`
|
|
19
|
+
return jsi::Object(runtime);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 1. Try looking for the given prototype in cache.
|
|
23
|
+
// If we find it in cache, we can create instances faster and skip creating the prototype from scratch!
|
|
24
|
+
auto& prototypeCache = _prototypeCache[&runtime];
|
|
25
|
+
auto cachedPrototype = prototypeCache.find(prototype->getNativeInstanceId());
|
|
26
|
+
if (cachedPrototype != prototypeCache.end()) {
|
|
27
|
+
const OwningReference<jsi::Object>& cachedObject = cachedPrototype->second;
|
|
28
|
+
return jsi::Value(runtime, *cachedObject).getObject(runtime);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// 2. We didn't find the given prototype in cache (either it's a new prototype, or a new runtime),
|
|
32
|
+
// so we need to create it. First, we need some helper methods from JS
|
|
33
|
+
Logger::log(TAG, "Creating new JS prototype for C++ instance type \"%s\"...", prototype->getNativeInstanceId().name());
|
|
34
|
+
jsi::Object objectConstructor = runtime.global().getPropertyAsObject(runtime, "Object");
|
|
35
|
+
jsi::Function objectCreate = objectConstructor.getPropertyAsFunction(runtime, "create");
|
|
36
|
+
jsi::Function objectDefineProperty = objectConstructor.getPropertyAsFunction(runtime, "defineProperty");
|
|
37
|
+
|
|
38
|
+
// 3. Create an empty JS Object, inheriting from the base prototype (recursively!)
|
|
39
|
+
jsi::Object object = objectCreate.call(runtime, createPrototype(runtime, prototype->getBase())).getObject(runtime);
|
|
40
|
+
|
|
41
|
+
// 4. Add all Hybrid Methods to it
|
|
42
|
+
for (const auto& method : prototype->getMethods()) {
|
|
43
|
+
object.setProperty(runtime, method.first.c_str(), method.second.toJSFunction(runtime));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 5. Add all properties (getter + setter) to it using defineProperty
|
|
47
|
+
for (const auto& getter : prototype->getGetters()) {
|
|
48
|
+
jsi::Object property(runtime);
|
|
49
|
+
property.setProperty(runtime, "configurable", false);
|
|
50
|
+
property.setProperty(runtime, "enumerable", true);
|
|
51
|
+
property.setProperty(runtime, "get", getter.second.toJSFunction(runtime));
|
|
52
|
+
|
|
53
|
+
const auto& setter = prototype->getSetters().find(getter.first);
|
|
54
|
+
if (setter != prototype->getSetters().end()) {
|
|
55
|
+
// there also is a setter for this property!
|
|
56
|
+
property.setProperty(runtime, "set", setter->second.toJSFunction(runtime));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
property.setProperty(runtime, "name", jsi::String::createFromUtf8(runtime, getter.first.c_str()));
|
|
60
|
+
objectDefineProperty.call(runtime,
|
|
61
|
+
/* obj */ object,
|
|
62
|
+
/* propName */ jsi::String::createFromUtf8(runtime, getter.first.c_str()),
|
|
63
|
+
/* descriptorObj */ property);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 6. Throw it into our cache so the next lookup can be cached and therefore faster
|
|
67
|
+
JSICacheReference jsiCache = JSICache::getOrCreateCache(runtime);
|
|
68
|
+
OwningReference<jsi::Object> cachedObject = jsiCache.makeShared(std::move(object));
|
|
69
|
+
prototypeCache.emplace(prototype->getNativeInstanceId(), cachedObject);
|
|
70
|
+
|
|
71
|
+
// 7. In DEBUG, add a __type info to the prototype object.
|
|
72
|
+
#if DEBUG
|
|
73
|
+
auto typeName = "Prototype<" + std::string(prototype->getNativeInstanceId().name()) + ">";
|
|
74
|
+
cachedObject->setProperty(runtime, "__type", jsi::String::createFromUtf8(runtime, typeName));
|
|
75
|
+
#endif
|
|
76
|
+
|
|
77
|
+
// 8. Return it!
|
|
78
|
+
return jsi::Value(runtime, *cachedObject);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
jsi::Value HybridObjectPrototype::getPrototype(jsi::Runtime& runtime) {
|
|
82
|
+
ensureInitialized();
|
|
83
|
+
|
|
84
|
+
return createPrototype(runtime, _prototypeChain.getPrototype());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HybridObjectPrototype.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 "OwningReference.hpp"
|
|
12
|
+
#include "Prototype.hpp"
|
|
13
|
+
#include "PrototypeChain.hpp"
|
|
14
|
+
#include <functional>
|
|
15
|
+
#include <jsi/jsi.h>
|
|
16
|
+
#include <memory>
|
|
17
|
+
#include <mutex>
|
|
18
|
+
#include <string>
|
|
19
|
+
#include <type_traits>
|
|
20
|
+
|
|
21
|
+
namespace margelo::nitro {
|
|
22
|
+
|
|
23
|
+
using namespace facebook;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Represents a Hybrid Object's prototype.
|
|
27
|
+
* The prototype should be cached per Runtime, and can be assigned to multiple jsi::Objects.
|
|
28
|
+
* When assigned to a jsi::Object, all methods of this prototype can be called on that jsi::Object,
|
|
29
|
+
* as long as it has a valid NativeState (`this`).
|
|
30
|
+
*/
|
|
31
|
+
class HybridObjectPrototype {
|
|
32
|
+
private:
|
|
33
|
+
PrototypeChain _prototypeChain;
|
|
34
|
+
bool _didLoadMethods = false;
|
|
35
|
+
static constexpr auto TAG = "HybridObjectPrototype";
|
|
36
|
+
|
|
37
|
+
public:
|
|
38
|
+
HybridObjectPrototype() {}
|
|
39
|
+
|
|
40
|
+
public:
|
|
41
|
+
/**
|
|
42
|
+
* Get a fully initialized jsi::Object that represents this prototype to JS.
|
|
43
|
+
* The result of this value will be cached per Runtime, so it's safe to call this often.
|
|
44
|
+
*/
|
|
45
|
+
jsi::Value getPrototype(jsi::Runtime& runtime);
|
|
46
|
+
|
|
47
|
+
private:
|
|
48
|
+
static jsi::Value createPrototype(jsi::Runtime& runtime, const std::shared_ptr<Prototype>& prototype);
|
|
49
|
+
using PrototypeCache = std::unordered_map<NativeInstanceId, OwningReference<jsi::Object>>;
|
|
50
|
+
static std::unordered_map<jsi::Runtime*, PrototypeCache> _prototypeCache;
|
|
51
|
+
|
|
52
|
+
protected:
|
|
53
|
+
/**
|
|
54
|
+
* Loads all Hybrid Methods that will be initialized in this Prototype.
|
|
55
|
+
* This will only be called once for the first time the Prototype will be created,
|
|
56
|
+
* so don't conditionally register methods.
|
|
57
|
+
*/
|
|
58
|
+
virtual void loadHybridMethods() = 0;
|
|
59
|
+
|
|
60
|
+
private:
|
|
61
|
+
/**
|
|
62
|
+
* Ensures that all Hybrid Methods, Getters and Setters are initialized by calling loadHybridMethods().
|
|
63
|
+
*/
|
|
64
|
+
inline void ensureInitialized() {
|
|
65
|
+
if (!_didLoadMethods) [[unlikely]] {
|
|
66
|
+
// lazy-load all exposed methods
|
|
67
|
+
loadHybridMethods();
|
|
68
|
+
_didLoadMethods = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
protected:
|
|
73
|
+
using RegisterFn = void (*)(Prototype&);
|
|
74
|
+
/**
|
|
75
|
+
* Registers the given methods inside the Hybrid Object's prototype.
|
|
76
|
+
*
|
|
77
|
+
* For subsequent HybridObjects of the same type, `registerFunc` will not be called again, as the
|
|
78
|
+
* prototype will already be known and cached.
|
|
79
|
+
* **Do not conditionally register hybrid methods, getters or setter!**
|
|
80
|
+
*/
|
|
81
|
+
template <typename Derived>
|
|
82
|
+
inline void registerHybrids(Derived* thisInstance, RegisterFn registerFunc) {
|
|
83
|
+
const std::shared_ptr<Prototype>& prototype = _prototypeChain.extendPrototype<Derived>();
|
|
84
|
+
|
|
85
|
+
if (!prototype->hasHybrids()) {
|
|
86
|
+
// The `Prototype` does not have any methods or properties registered yet - so do it now
|
|
87
|
+
registerFunc(*prototype);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
} // namespace margelo::nitro
|
|
@@ -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
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
#include "CallInvokerDispatcher.hpp"
|
|
10
10
|
#include "Dispatcher.hpp"
|
|
11
11
|
#include "HybridObjectRegistry.hpp"
|
|
12
|
-
#include "TestHybridObject.hpp"
|
|
13
12
|
|
|
14
13
|
namespace facebook::react {
|
|
15
14
|
|
|
@@ -59,12 +58,12 @@ void NativeNitroModules::install(jsi::Runtime& runtime) {
|
|
|
59
58
|
Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
jsi::
|
|
63
|
-
|
|
61
|
+
jsi::Value NativeNitroModules::createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName,
|
|
62
|
+
const std::optional<jsi::Object>& args) {
|
|
64
63
|
auto name = hybridObjectName.utf8(runtime);
|
|
65
64
|
// TODO: Pass args? Do we need that?
|
|
66
65
|
auto hybridObject = HybridObjectRegistry::createHybridObject(name.c_str());
|
|
67
|
-
return
|
|
66
|
+
return hybridObject->toObject(runtime);
|
|
68
67
|
}
|
|
69
68
|
|
|
70
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) {
|