react-native-nitro-modules 0.1.7 → 0.3.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 +15 -5
- package/android/CMakeLists.txt +1 -0
- package/cpp/core/HybridFunction.hpp +167 -0
- package/cpp/core/HybridObject.cpp +37 -129
- 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/registry/HybridObjectRegistry.cpp +9 -2
- package/cpp/registry/HybridObjectRegistry.hpp +2 -2
- 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 +31 -6
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/HybridObject.d.ts +31 -6
- package/lib/typescript/HybridObject.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/HybridObject.ts +31 -6
- package/cpp/core/PointerHolder.hpp +0 -94
- package/lib/NativeNitro.d.ts +0 -8
- package/lib/NativeNitro.js +0 -3
- package/lib/createTestObject.d.ts +0 -22
- package/lib/createTestObject.js +0 -7
package/cpp/jsi/JSICache.cpp
CHANGED
|
@@ -8,16 +8,16 @@
|
|
|
8
8
|
#include "JSICache.hpp"
|
|
9
9
|
#include "JSIHelpers.hpp"
|
|
10
10
|
|
|
11
|
+
#define DOUBLE_CHECK_GLOBAL_CACHE 1
|
|
12
|
+
|
|
11
13
|
namespace margelo::nitro {
|
|
12
14
|
|
|
13
15
|
static constexpr auto CACHE_PROP_NAME = "__nitroModulesJSICache";
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
for (auto& func : _cache) {
|
|
20
|
-
OwningReference<jsi::Object> owning = func.lock();
|
|
17
|
+
template <typename T>
|
|
18
|
+
inline void destroyReferences(const std::vector<BorrowingReference<T>>& references) {
|
|
19
|
+
for (auto& func : references) {
|
|
20
|
+
OwningReference<T> owning = func.lock();
|
|
21
21
|
if (owning) {
|
|
22
22
|
// Destroy all functions that we might still have in cache, some callbacks and Promises may now become invalid.
|
|
23
23
|
owning.destroy();
|
|
@@ -25,9 +25,19 @@ JSICache::~JSICache() {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
JSICache::~JSICache() {
|
|
29
|
+
Logger::log(TAG, "Destroying JSICache...");
|
|
30
|
+
std::unique_lock lock(_mutex);
|
|
31
|
+
|
|
32
|
+
destroyReferences(_objectCache);
|
|
33
|
+
destroyReferences(_functionCache);
|
|
34
|
+
destroyReferences(_weakObjectCache);
|
|
35
|
+
destroyReferences(_arrayBufferCache);
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
JSICacheReference JSICache::getOrCreateCache(jsi::Runtime& runtime) {
|
|
29
39
|
auto found = _globalCache.find(&runtime);
|
|
30
|
-
if (found != _globalCache.end()) {
|
|
40
|
+
if (found != _globalCache.end()) [[likely]] {
|
|
31
41
|
// Fast path: get weak_ptr to JSICache from our global list.
|
|
32
42
|
std::weak_ptr<JSICache> weak = found->second;
|
|
33
43
|
std::shared_ptr<JSICache> strong = weak.lock();
|
|
@@ -38,10 +48,16 @@ JSICacheReference JSICache::getOrCreateCache(jsi::Runtime& runtime) {
|
|
|
38
48
|
Logger::log(TAG, "JSICache was created, but it is no longer strong!");
|
|
39
49
|
}
|
|
40
50
|
|
|
51
|
+
#if DOUBLE_CHECK_GLOBAL_CACHE
|
|
52
|
+
if (runtime.global().hasProperty(runtime, CACHE_PROP_NAME)) [[unlikely]] {
|
|
53
|
+
throw std::runtime_error("The Runtime \"" + getRuntimeId(runtime) + "\" already has a global cache! (\"" + CACHE_PROP_NAME + "\")");
|
|
54
|
+
}
|
|
55
|
+
#endif
|
|
56
|
+
|
|
41
57
|
// Cache doesn't exist yet.
|
|
42
58
|
Logger::log(TAG, "Creating new JSICache<T> for runtime %s..", getRuntimeId(runtime));
|
|
43
59
|
// Create new cache
|
|
44
|
-
auto nativeState = std::
|
|
60
|
+
auto nativeState = std::shared_ptr<JSICache>(new JSICache(&runtime));
|
|
45
61
|
// Wrap it in a jsi::Value using NativeState
|
|
46
62
|
jsi::Object cache(runtime);
|
|
47
63
|
cache.setNativeState(runtime, nativeState);
|
package/cpp/jsi/JSICache.hpp
CHANGED
|
@@ -23,19 +23,18 @@ using namespace facebook;
|
|
|
23
23
|
class JSICacheReference;
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* A `JSICache` can safely store `jsi::
|
|
26
|
+
* A `JSICache` can safely store `jsi::Value` instances (e.g. `jsi::Object` or
|
|
27
27
|
* `jsi::Function`) inside `OwningReference<T>`.
|
|
28
28
|
*
|
|
29
|
-
* `jsi::
|
|
30
|
-
* is deleted - even if there are still strong references to the `jsi::
|
|
29
|
+
* `jsi::Value`s are managed by a `jsi::Runtime`, and will be deleted if the `jsi::Runtime`
|
|
30
|
+
* is deleted - even if there are still strong references to the `jsi::Value`.
|
|
31
31
|
*
|
|
32
|
-
* To access a `OwningReference<jsi::
|
|
33
|
-
* This will allow you to access the `jsi::
|
|
32
|
+
* To access a `OwningReference<jsi::Value>` safely, use `lock()` to get an `OwningLock<jsi::Value>`.
|
|
33
|
+
* This will allow you to access the `jsi::Value` as long as the `OwningLock` is alive,
|
|
34
34
|
* and `JSICache` will hold any garbage collection calls until the `OwningLock` is destroyed.
|
|
35
35
|
*/
|
|
36
36
|
class JSICache final : public jsi::NativeState {
|
|
37
37
|
public:
|
|
38
|
-
explicit JSICache(jsi::Runtime* runtime) : _runtime(runtime) {}
|
|
39
38
|
~JSICache();
|
|
40
39
|
|
|
41
40
|
public:
|
|
@@ -43,6 +42,9 @@ public:
|
|
|
43
42
|
JSICache(const JSICache&) = delete;
|
|
44
43
|
JSICache(JSICache&&) = delete;
|
|
45
44
|
|
|
45
|
+
private:
|
|
46
|
+
explicit JSICache(jsi::Runtime* runtime) : _runtime(runtime) {}
|
|
47
|
+
|
|
46
48
|
public:
|
|
47
49
|
/**
|
|
48
50
|
Gets or creates a `JSICache` for the given `jsi::Runtime`.
|
|
@@ -59,7 +61,10 @@ private:
|
|
|
59
61
|
private:
|
|
60
62
|
jsi::Runtime* _runtime;
|
|
61
63
|
std::mutex _mutex;
|
|
62
|
-
std::vector<BorrowingReference<jsi::Object>>
|
|
64
|
+
std::vector<BorrowingReference<jsi::Object>> _objectCache;
|
|
65
|
+
std::vector<BorrowingReference<jsi::Function>> _functionCache;
|
|
66
|
+
std::vector<BorrowingReference<jsi::WeakObject>> _weakObjectCache;
|
|
67
|
+
std::vector<BorrowingReference<jsi::ArrayBuffer>> _arrayBufferCache;
|
|
63
68
|
|
|
64
69
|
private:
|
|
65
70
|
static inline std::unordered_map<jsi::Runtime*, std::weak_ptr<JSICache>> _globalCache;
|
|
@@ -79,22 +84,25 @@ public:
|
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
public:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
OwningReference<jsi::Object> makeShared(jsi::Object&& value) {
|
|
88
|
+
OwningReference<jsi::Object> owning(new jsi::Object(std::move(value)));
|
|
89
|
+
_strongCache->_objectCache.push_back(owning.weak());
|
|
90
|
+
return owning;
|
|
91
|
+
}
|
|
92
|
+
OwningReference<jsi::Function> makeShared(jsi::Function&& value) {
|
|
93
|
+
OwningReference<jsi::Function> owning(new jsi::Function(std::move(value)));
|
|
94
|
+
_strongCache->_functionCache.push_back(owning.weak());
|
|
95
|
+
return owning;
|
|
96
|
+
}
|
|
97
|
+
OwningReference<jsi::WeakObject> makeShared(jsi::WeakObject&& value) {
|
|
98
|
+
OwningReference<jsi::WeakObject> owning(new jsi::WeakObject(std::move(value)));
|
|
99
|
+
_strongCache->_weakObjectCache.push_back(owning.weak());
|
|
100
|
+
return owning;
|
|
101
|
+
}
|
|
102
|
+
OwningReference<jsi::ArrayBuffer> makeShared(jsi::ArrayBuffer&& value) {
|
|
103
|
+
OwningReference<jsi::ArrayBuffer> owning(new jsi::ArrayBuffer(std::move(value)));
|
|
104
|
+
_strongCache->_arrayBufferCache.push_back(owning.weak());
|
|
105
|
+
return owning;
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
private:
|
|
@@ -31,15 +31,19 @@ template <typename T>
|
|
|
31
31
|
struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::MutableBuffer>>> {
|
|
32
32
|
static inline std::shared_ptr<ArrayBuffer> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
33
33
|
jsi::Object object = arg.asObject(runtime);
|
|
34
|
+
|
|
35
|
+
#if DEBUG
|
|
34
36
|
if (!object.isArrayBuffer(runtime)) [[unlikely]] {
|
|
35
|
-
throw std::runtime_error("Object \"" + arg.toString(runtime).utf8(runtime) +
|
|
37
|
+
throw std::runtime_error("Object \"" + arg.toString(runtime).utf8(runtime) +
|
|
38
|
+
"\" is not an ArrayBuffer! "
|
|
36
39
|
"Are you maybe passing a TypedArray (e.g. Uint8Array)? Try to pass it's `.buffer` value.");
|
|
37
40
|
}
|
|
41
|
+
#endif
|
|
38
42
|
|
|
39
43
|
JSICacheReference cache = JSICache::getOrCreateCache(runtime);
|
|
40
|
-
auto
|
|
44
|
+
auto borrowingArrayBuffer = cache.makeShared(object.getArrayBuffer(runtime));
|
|
41
45
|
|
|
42
|
-
return std::make_shared<JSArrayBuffer>(&runtime,
|
|
46
|
+
return std::make_shared<JSArrayBuffer>(&runtime, borrowingArrayBuffer);
|
|
43
47
|
}
|
|
44
48
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const std::shared_ptr<jsi::MutableBuffer>& buffer) {
|
|
45
49
|
return jsi::ArrayBuffer(runtime, buffer);
|
|
@@ -35,7 +35,7 @@ struct JSIConverter<std::function<ReturnType(Args...)>> {
|
|
|
35
35
|
// Make function global - it'll be managed by the Runtime's memory, and we only have a weak_ref to it.
|
|
36
36
|
auto cache = JSICache::getOrCreateCache(runtime);
|
|
37
37
|
jsi::Function function = arg.asObject(runtime).asFunction(runtime);
|
|
38
|
-
OwningReference<jsi::Function> sharedFunction = cache.
|
|
38
|
+
OwningReference<jsi::Function> sharedFunction = cache.makeShared(std::move(function));
|
|
39
39
|
|
|
40
40
|
std::shared_ptr<Dispatcher> strongDispatcher = Dispatcher::getRuntimeGlobalDispatcher(runtime);
|
|
41
41
|
std::weak_ptr<Dispatcher> weakDispatcher = strongDispatcher;
|
|
@@ -7,125 +7,64 @@
|
|
|
7
7
|
// Forward declare a few of the common types that might have cyclic includes.
|
|
8
8
|
namespace margelo::nitro {
|
|
9
9
|
class HybridObject;
|
|
10
|
-
|
|
11
|
-
template <typename T, typename Enable>
|
|
12
|
-
struct JSIConverter;
|
|
13
10
|
} // namespace margelo::nitro
|
|
14
11
|
|
|
15
|
-
#include "JSIConverter.hpp"
|
|
16
|
-
|
|
17
|
-
#include "HybridObject.hpp"
|
|
18
12
|
#include "IsSharedPtrTo.hpp"
|
|
19
13
|
#include "TypeInfo.hpp"
|
|
20
14
|
#include <jsi/jsi.h>
|
|
21
|
-
#include <memory>
|
|
22
15
|
#include <type_traits>
|
|
23
16
|
|
|
24
|
-
#define DO_NULL_CHECKS true
|
|
25
|
-
|
|
26
17
|
namespace margelo::nitro {
|
|
27
18
|
|
|
28
19
|
using namespace facebook;
|
|
29
20
|
|
|
30
|
-
// HybridObject <> {}
|
|
21
|
+
// HybridObject(NativeState) <> {}
|
|
31
22
|
template <typename T>
|
|
32
|
-
struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::
|
|
23
|
+
struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::NativeState>>> {
|
|
33
24
|
using TPointee = typename T::element_type;
|
|
34
25
|
|
|
35
26
|
static inline T fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
36
|
-
#if
|
|
37
|
-
if (arg.isUndefined()) [[unlikely]] {
|
|
38
|
-
throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
|
|
39
|
-
}
|
|
27
|
+
#if DEBUG
|
|
40
28
|
if (!arg.isObject()) [[unlikely]] {
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
if (arg.isUndefined()) [[unlikely]] {
|
|
30
|
+
throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
|
|
31
|
+
} else {
|
|
32
|
+
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
|
|
33
|
+
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not an object!"));
|
|
34
|
+
}
|
|
43
35
|
}
|
|
44
36
|
#endif
|
|
45
37
|
jsi::Object object = arg.asObject(runtime);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
38
|
+
|
|
39
|
+
#if DEBUG
|
|
40
|
+
if (!object.hasNativeState<TPointee>(runtime)) [[unlikely]] {
|
|
41
|
+
if (!object.hasNativeState(runtime)) [[unlikely]] {
|
|
42
|
+
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
|
|
43
|
+
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It does not have a NativeState!"));
|
|
44
|
+
} else {
|
|
45
|
+
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
|
|
46
|
+
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It has a different NativeState<T>!"));
|
|
47
|
+
}
|
|
54
48
|
}
|
|
55
49
|
#endif
|
|
56
|
-
return object.
|
|
50
|
+
return object.getNativeState<TPointee>(runtime);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
53
|
static inline jsi::Value toJSI(jsi::Runtime& runtime, const T& arg) {
|
|
60
|
-
#if DO_NULL_CHECKS
|
|
61
54
|
if (arg == nullptr) [[unlikely]] {
|
|
62
55
|
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
|
|
63
|
-
throw jsi::JSError(runtime, "Cannot convert nullptr to
|
|
56
|
+
throw jsi::JSError(runtime, "Cannot convert nullptr to NativeState<" + typeName + ">!");
|
|
64
57
|
}
|
|
65
|
-
|
|
58
|
+
|
|
66
59
|
if constexpr (std::is_base_of_v<HybridObject, TPointee>) {
|
|
67
60
|
// It's a HybridObject - use it's internal constructor which caches jsi::Objects for proper memory management!
|
|
68
61
|
return arg->toObject(runtime);
|
|
69
62
|
} else {
|
|
70
|
-
// It's any other kind of jsi::HostObject - just create it as normal.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
|
76
|
-
if (value.isObject()) {
|
|
77
|
-
jsi::Object object = value.getObject(runtime);
|
|
78
|
-
return object.isHostObject<TPointee>(runtime);
|
|
79
|
-
}
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
private:
|
|
84
|
-
static inline std::string invalidTypeErrorMessage(const std::string& typeDescription, const std::string& reason) {
|
|
85
|
-
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
|
|
86
|
-
return "Cannot convert \"" + typeDescription + "\" to HostObject<" + typeName + ">! " + reason;
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
// NativeState <> {}
|
|
91
|
-
template <typename T>
|
|
92
|
-
struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::NativeState>>> {
|
|
93
|
-
using TPointee = typename T::element_type;
|
|
94
|
-
|
|
95
|
-
static inline T fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
96
|
-
#if DO_NULL_CHECKS
|
|
97
|
-
if (arg.isUndefined()) [[unlikely]] {
|
|
98
|
-
throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
|
|
99
|
-
}
|
|
100
|
-
if (!arg.isObject()) [[unlikely]] {
|
|
101
|
-
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
|
|
102
|
-
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not an object!"));
|
|
103
|
-
}
|
|
104
|
-
#endif
|
|
105
|
-
jsi::Object object = arg.asObject(runtime);
|
|
106
|
-
#if DO_NULL_CHECKS
|
|
107
|
-
if (!object.hasNativeState(runtime)) [[unlikely]] {
|
|
108
|
-
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
|
|
109
|
-
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not a NativeState!"));
|
|
63
|
+
// It's any other kind of jsi::HostObject - just create it as normal. This will not have a prototype then!
|
|
64
|
+
jsi::Object object(runtime);
|
|
65
|
+
object.setNativeState(runtime, arg);
|
|
66
|
+
return object;
|
|
110
67
|
}
|
|
111
|
-
if (!object.hasNativeState<TPointee>(runtime)) [[unlikely]] {
|
|
112
|
-
std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
|
|
113
|
-
throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is a different NativeState<T>!"));
|
|
114
|
-
}
|
|
115
|
-
#endif
|
|
116
|
-
return object.getNativeState<TPointee>(runtime);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
static inline jsi::Value toJSI(jsi::Runtime& runtime, const T& arg) {
|
|
120
|
-
#if DO_NULL_CHECKS
|
|
121
|
-
if (arg == nullptr) [[unlikely]] {
|
|
122
|
-
std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
|
|
123
|
-
throw jsi::JSError(runtime, "Cannot convert nullptr to NativeState<" + typeName + ">!");
|
|
124
|
-
}
|
|
125
|
-
#endif
|
|
126
|
-
jsi::Object object(runtime);
|
|
127
|
-
object.setNativeState(runtime, arg);
|
|
128
|
-
return object;
|
|
129
68
|
}
|
|
130
69
|
|
|
131
70
|
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
|
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
|