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/README.md
CHANGED
|
@@ -72,10 +72,14 @@ public:
|
|
|
72
72
|
|
|
73
73
|
public:
|
|
74
74
|
void loadHybridMethods() override {
|
|
75
|
+
// Call base method to make sure we properly inherit `toString()` and `equals()`
|
|
76
|
+
HybridObject::loadHybridMethods();
|
|
75
77
|
// Register all methods that need to be exposed to JS
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
registerHybrids(this, [](Prototype& prototype) {
|
|
79
|
+
prototype.registerHybridGetter("number", &MyHybridObject::getNumber);
|
|
80
|
+
prototype.registerHybridSetter("number", &MyHybridObject::setNumber);
|
|
81
|
+
prototype.registerHybridMethod("add", &MyHybridObject::add);
|
|
82
|
+
});
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
private:
|
package/android/CMakeLists.txt
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//
|
|
2
|
+
// HybridFunction.hpp
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 07.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro {
|
|
11
|
+
template <typename T, typename Enable>
|
|
12
|
+
struct JSIConverter;
|
|
13
|
+
} // namespace margelo::nitro
|
|
14
|
+
|
|
15
|
+
#include "CountTrailingOptionals.hpp"
|
|
16
|
+
#include "JSIConverter.hpp"
|
|
17
|
+
#include "TypeInfo.hpp"
|
|
18
|
+
#include <functional>
|
|
19
|
+
#include <jsi/jsi.h>
|
|
20
|
+
#include <memory>
|
|
21
|
+
#include <string>
|
|
22
|
+
#include <type_traits>
|
|
23
|
+
|
|
24
|
+
namespace margelo::nitro {
|
|
25
|
+
|
|
26
|
+
using namespace facebook;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents the type of a function - it can be either a normal function ("METHOD"),
|
|
30
|
+
* or a property ("GETTER" + "SETTER")
|
|
31
|
+
*/
|
|
32
|
+
enum class FunctionType { METHOD, GETTER, SETTER };
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Represents a Hybrid Function.
|
|
36
|
+
*/
|
|
37
|
+
class HybridFunction final {
|
|
38
|
+
private:
|
|
39
|
+
std::string _name;
|
|
40
|
+
size_t _paramCount;
|
|
41
|
+
jsi::HostFunctionType _function;
|
|
42
|
+
|
|
43
|
+
public:
|
|
44
|
+
// getters
|
|
45
|
+
inline const std::string& getName() const {
|
|
46
|
+
return _name;
|
|
47
|
+
}
|
|
48
|
+
inline size_t getParamCount() const {
|
|
49
|
+
return _paramCount;
|
|
50
|
+
}
|
|
51
|
+
inline const jsi::HostFunctionType& getHostFunction() const {
|
|
52
|
+
return _function;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public:
|
|
56
|
+
// functions
|
|
57
|
+
inline jsi::Function toJSFunction(jsi::Runtime& runtime) const {
|
|
58
|
+
return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, _name), _paramCount, _function);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private:
|
|
62
|
+
HybridFunction(jsi::HostFunctionType&& function, size_t paramCount, const std::string& name)
|
|
63
|
+
: _function(std::move(function)), _paramCount(paramCount), _name(name) {}
|
|
64
|
+
|
|
65
|
+
public:
|
|
66
|
+
/**
|
|
67
|
+
* Create a new `HybridFunction` that can be called from JS.
|
|
68
|
+
* This performs proper JSI -> C++ conversion using `JSIConverter<T>`,
|
|
69
|
+
* and assumes that the object this is called on has a proper `this` configured.
|
|
70
|
+
* The object's `this` needs to be a `NativeState`.
|
|
71
|
+
*/
|
|
72
|
+
template <typename Derived, typename ReturnType, typename... Args>
|
|
73
|
+
static inline HybridFunction createHybridFunction(const std::string& name, ReturnType (Derived::*method)(Args...), FunctionType type) {
|
|
74
|
+
jsi::HostFunctionType hostFunction = [name, method, type](/* JS Runtime */ jsi::Runtime& runtime,
|
|
75
|
+
/* HybridObject */ const jsi::Value& thisValue,
|
|
76
|
+
/* JS arguments */ const jsi::Value* args,
|
|
77
|
+
/* argument size */ size_t count) -> jsi::Value {
|
|
78
|
+
// 1. Get actual `HybridObject` instance from `thisValue` (it's stored as `NativeState`)
|
|
79
|
+
#if DEBUG
|
|
80
|
+
if (!thisValue.isObject()) [[unlikely]] {
|
|
81
|
+
throw jsi::JSError(runtime, "Cannot call hybrid function " + name + "(...) - `this` is not bound!");
|
|
82
|
+
}
|
|
83
|
+
#endif
|
|
84
|
+
jsi::Object thisObject = thisValue.getObject(runtime);
|
|
85
|
+
|
|
86
|
+
#if DEBUG
|
|
87
|
+
if (!thisObject.hasNativeState<Derived>(runtime)) [[unlikely]] {
|
|
88
|
+
if (thisObject.hasNativeState(runtime)) {
|
|
89
|
+
throw jsi::JSError(runtime, "Cannot call hybrid function " + name + "(...) - `this` has a NativeState, but it's the wrong type!");
|
|
90
|
+
} else {
|
|
91
|
+
throw jsi::JSError(runtime, "Cannot call hybrid function " + name + "(...) - `this` does not have a NativeState!");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
#endif
|
|
95
|
+
std::shared_ptr<Derived> hybridInstance = thisObject.getNativeState<Derived>(runtime);
|
|
96
|
+
|
|
97
|
+
// 2. Make sure the given arguments match, either with a static size, or with potentially optional arguments size.
|
|
98
|
+
constexpr size_t optionalArgsCount = trailing_optionals_count_v<Args...>;
|
|
99
|
+
constexpr size_t maxArgsCount = sizeof...(Args);
|
|
100
|
+
constexpr size_t minArgsCount = maxArgsCount - optionalArgsCount;
|
|
101
|
+
bool isWithinArgsRange = (count >= minArgsCount && count <= maxArgsCount);
|
|
102
|
+
if (!isWithinArgsRange) [[unlikely]] {
|
|
103
|
+
// invalid amount of arguments passed!
|
|
104
|
+
std::string hybridObjectName = hybridInstance->getName();
|
|
105
|
+
if constexpr (minArgsCount == maxArgsCount) {
|
|
106
|
+
// min and max args length is the same, so we don't have any optional parameters. fixed count
|
|
107
|
+
throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected " + std::to_string(maxArgsCount) +
|
|
108
|
+
" arguments, but received " + std::to_string(count) + "!");
|
|
109
|
+
} else {
|
|
110
|
+
// min and max args length are different, so we have optional parameters - variable length arguments.
|
|
111
|
+
throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected between " + std::to_string(minArgsCount) + " and " +
|
|
112
|
+
std::to_string(maxArgsCount) + " arguments, but received " + std::to_string(count) + "!");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 3. Actually call method - either raw JSI method, or by going through `JSIConverter<T>` first.
|
|
117
|
+
try {
|
|
118
|
+
if constexpr (std::is_same_v<ReturnType, jsi::Value>) {
|
|
119
|
+
// If the return type is a jsi::Value, we assume the user wants full JSI code control.
|
|
120
|
+
// The signature must be identical to jsi::HostFunction (jsi::Runtime&, jsi::Value& this, ...)
|
|
121
|
+
return (hybridInstance->*method)(runtime, thisValue, args, count);
|
|
122
|
+
} else {
|
|
123
|
+
// Call the actual method with JSI values as arguments and return a JSI value again.
|
|
124
|
+
// Internally, this method converts the JSI values to C++ values.
|
|
125
|
+
return callMethod(hybridInstance.get(), method, runtime, args, count, std::index_sequence_for<Args...>{});
|
|
126
|
+
}
|
|
127
|
+
} catch (const std::exception& exception) {
|
|
128
|
+
// Some exception was thrown - add method name information and re-throw as `JSError`.
|
|
129
|
+
std::string hybridObjectName = hybridInstance->getName();
|
|
130
|
+
std::string message = exception.what();
|
|
131
|
+
std::string suffix = type == FunctionType::METHOD ? "(...)" : "";
|
|
132
|
+
throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + ": " + message);
|
|
133
|
+
} catch (...) {
|
|
134
|
+
// Some unknown exception was thrown - add method name information and re-throw as `JSError`.
|
|
135
|
+
std::string hybridObjectName = hybridInstance->getName();
|
|
136
|
+
std::string errorName = TypeInfo::getCurrentExceptionName();
|
|
137
|
+
std::string suffix = type == FunctionType::METHOD ? "(...)" : "";
|
|
138
|
+
throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + " threw an unknown " + errorName + " error.");
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
return HybridFunction(std::move(hostFunction), sizeof...(Args), name);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private:
|
|
146
|
+
/**
|
|
147
|
+
* Calls the given method on the given instance with the given `jsi::Value` arguments by converting them to the desired target types.
|
|
148
|
+
* The given method's return value will be converted to a `jsi::Value` again.
|
|
149
|
+
*/
|
|
150
|
+
template <typename Derived, typename ReturnType, typename... Args, size_t... Is>
|
|
151
|
+
static inline jsi::Value callMethod(Derived* obj, ReturnType (Derived::*method)(Args...), jsi::Runtime& runtime, const jsi::Value* args,
|
|
152
|
+
size_t argsSize, std::index_sequence<Is...>) {
|
|
153
|
+
jsi::Value defaultValue;
|
|
154
|
+
|
|
155
|
+
if constexpr (std::is_void_v<ReturnType>) {
|
|
156
|
+
// It's a void method.
|
|
157
|
+
(obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, Is < argsSize ? args[Is] : defaultValue)...);
|
|
158
|
+
return jsi::Value::undefined();
|
|
159
|
+
} else {
|
|
160
|
+
// It's returning some C++ type, we need to convert that to a JSI value now.
|
|
161
|
+
ReturnType result = (obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, Is < argsSize ? args[Is] : defaultValue)...);
|
|
162
|
+
return JSIConverter<std::decay_t<ReturnType>>::toJSI(runtime, std::move(result));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
} // namespace margelo::nitro
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
#include "JSIConverter.hpp"
|
|
8
8
|
#include "NitroLogger.hpp"
|
|
9
9
|
|
|
10
|
-
#define LOG_MEMORY_ALLOCATIONS
|
|
10
|
+
#define LOG_MEMORY_ALLOCATIONS false
|
|
11
11
|
|
|
12
12
|
namespace margelo::nitro {
|
|
13
13
|
|
|
@@ -45,7 +45,7 @@ static int getId(const char* name) {
|
|
|
45
45
|
}
|
|
46
46
|
#endif
|
|
47
47
|
|
|
48
|
-
HybridObject::HybridObject(const char* name) :
|
|
48
|
+
HybridObject::HybridObject(const char* name) : HybridObjectPrototype(), _name(name) {
|
|
49
49
|
#if LOG_MEMORY_ALLOCATIONS
|
|
50
50
|
_instanceId = getId(name);
|
|
51
51
|
uint32_t alive = incrementAliveInstancesAndGet(_name);
|
|
@@ -62,55 +62,6 @@ HybridObject::~HybridObject() {
|
|
|
62
62
|
Logger::log(TAG, "(MEMORY) ❌ Deleting %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i) ", _name, _instanceId, _name, alive,
|
|
63
63
|
totalObjects);
|
|
64
64
|
#endif
|
|
65
|
-
_functionCache.clear();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
size_t HybridObject::getTotalExternalMemorySize() noexcept {
|
|
69
|
-
static constexpr int STRING_KEY_AVERAGE_SIZE = 32; // good average for string keys
|
|
70
|
-
static constexpr int CACHED_SIZE = STRING_KEY_AVERAGE_SIZE + sizeof(jsi::Function);
|
|
71
|
-
static constexpr int METHOD_SIZE = STRING_KEY_AVERAGE_SIZE + sizeof(HybridFunction);
|
|
72
|
-
static constexpr int GETTER_SIZE = STRING_KEY_AVERAGE_SIZE + sizeof(jsi::HostFunctionType);
|
|
73
|
-
static constexpr int SETTER_SIZE = STRING_KEY_AVERAGE_SIZE + sizeof(jsi::HostFunctionType);
|
|
74
|
-
|
|
75
|
-
size_t cachedFunctions = 0;
|
|
76
|
-
for (const auto& cache : _functionCache) {
|
|
77
|
-
cachedFunctions += cache.second.size();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
size_t externalSize = getExternalMemorySize();
|
|
81
|
-
return (_getters.size() * GETTER_SIZE) + (_setters.size() * SETTER_SIZE) + (_methods.size() * METHOD_SIZE) +
|
|
82
|
-
(cachedFunctions * CACHED_SIZE) + sizeof(std::mutex) + externalSize;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
jsi::Value HybridObject::toObject(jsi::Runtime& runtime) {
|
|
86
|
-
// 1. Try to find existing jsi::Object from cache
|
|
87
|
-
auto found = _jsObjects.find(&runtime);
|
|
88
|
-
if (found != _jsObjects.end()) {
|
|
89
|
-
// 1.1 We found an object in cache, let's see if it's still alive..
|
|
90
|
-
BorrowingReference<jsi::Object> weak = found->second;
|
|
91
|
-
OwningReference<jsi::Object> strong = weak.lock();
|
|
92
|
-
if (strong) {
|
|
93
|
-
// 1.2. It is alive (strong) - update memory size and return to JS!
|
|
94
|
-
size_t memorySize = getTotalExternalMemorySize();
|
|
95
|
-
strong->setExternalMemoryPressure(runtime, memorySize);
|
|
96
|
-
// 1.3. It is alive (strong) - copy the JSI Pointer into a new jsi::Value, and return to JS
|
|
97
|
-
return jsi::Value(runtime, *strong);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// 2. There is either no value in cache, or it's already dead. Create a new one
|
|
102
|
-
auto cache = JSICache::getOrCreateCache(runtime);
|
|
103
|
-
// 3. Create a new jsi::Object from this HybridObject/jsi::HostObject
|
|
104
|
-
std::shared_ptr<HybridObject> shared = shared_from_this();
|
|
105
|
-
jsi::Object object = jsi::Object::createFromHostObject(runtime, shared);
|
|
106
|
-
// 4. HybridObjects expose their external memory size, so inform JS GC about it!
|
|
107
|
-
size_t memorySize = getTotalExternalMemorySize();
|
|
108
|
-
object.setExternalMemoryPressure(runtime, memorySize);
|
|
109
|
-
// 5. Make it a global (weak) reference
|
|
110
|
-
auto global = cache.makeGlobal<jsi::Object>(std::move(object));
|
|
111
|
-
_jsObjects[&runtime] = global.weak();
|
|
112
|
-
// 6. Return a jsi::Value copy again to the caller.
|
|
113
|
-
return jsi::Value(runtime, *global);
|
|
114
65
|
}
|
|
115
66
|
|
|
116
67
|
std::string HybridObject::toString() {
|
|
@@ -126,95 +77,53 @@ bool HybridObject::equals(std::shared_ptr<HybridObject> other) {
|
|
|
126
77
|
}
|
|
127
78
|
|
|
128
79
|
void HybridObject::loadHybridMethods() {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
80
|
+
registerHybrids(this, [](Prototype& prototype) {
|
|
81
|
+
prototype.registerHybridGetter("name", &HybridObject::getName);
|
|
82
|
+
prototype.registerHybridMethod("toString", &HybridObject::toString);
|
|
83
|
+
prototype.registerHybridMethod("equals", &HybridObject::equals);
|
|
84
|
+
});
|
|
132
85
|
}
|
|
133
86
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
for (const auto& item : _getters) {
|
|
146
|
-
result.push_back(jsi::PropNameID::forUtf8(runtime, item.first));
|
|
147
|
-
}
|
|
148
|
-
for (const auto& item : _setters) {
|
|
149
|
-
result.push_back(jsi::PropNameID::forUtf8(runtime, item.first));
|
|
150
|
-
}
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
jsi::Value HybridObject::get(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName) {
|
|
155
|
-
std::unique_lock lock(*_mutex);
|
|
156
|
-
ensureInitialized(runtime);
|
|
157
|
-
|
|
158
|
-
std::string name = propName.utf8(runtime);
|
|
159
|
-
|
|
160
|
-
auto& functionCache = _functionCache[&runtime];
|
|
161
|
-
|
|
162
|
-
if (functionCache.contains(name)) [[likely]] {
|
|
163
|
-
// cache hit - let's see if the function is still alive..
|
|
164
|
-
OwningReference<jsi::Function> function = functionCache[name];
|
|
165
|
-
if (function) [[likely]] {
|
|
166
|
-
// function is still alive, we can use it.
|
|
167
|
-
return jsi::Value(runtime, *function);
|
|
87
|
+
jsi::Value HybridObject::toObject(jsi::Runtime& runtime) {
|
|
88
|
+
// 1. Check if we have a jsi::WeakObject in cache that we can use to avoid re-creating the object each time
|
|
89
|
+
auto cachedObject = _objectCache.find(&runtime);
|
|
90
|
+
if (cachedObject != _objectCache.end()) {
|
|
91
|
+
// 1.1. We have a WeakObject, try to see if it is still alive
|
|
92
|
+
OwningLock<jsi::WeakObject> lock = cachedObject->second.lock();
|
|
93
|
+
jsi::Value object = cachedObject->second->lock(runtime);
|
|
94
|
+
if (!object.isUndefined()) {
|
|
95
|
+
// 1.2. It is still alive - we can use it instead of creating a new one!
|
|
96
|
+
return object;
|
|
168
97
|
}
|
|
169
98
|
}
|
|
170
99
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return _getters[name](runtime, jsi::Value::undefined(), nullptr, 0);
|
|
174
|
-
}
|
|
100
|
+
// 2. Get the object's base prototype (global & shared)
|
|
101
|
+
jsi::Value prototype = getPrototype(runtime);
|
|
175
102
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// get (or create) a runtime-specific function cache
|
|
180
|
-
auto runtimeCache = JSICache::getOrCreateCache(runtime);
|
|
181
|
-
// create the jsi::Function
|
|
182
|
-
jsi::Function function = jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, name),
|
|
183
|
-
hybridFunction.parameterCount, hybridFunction.function);
|
|
184
|
-
// throw it into the cache for next time
|
|
185
|
-
OwningReference<jsi::Function> globalFunction = runtimeCache.makeGlobal<jsi::Function>(std::move(function));
|
|
186
|
-
functionCache[name] = globalFunction;
|
|
187
|
-
// copy the reference & return it to JS
|
|
188
|
-
return jsi::Value(runtime, *globalFunction);
|
|
189
|
-
}
|
|
103
|
+
// 3. Get the global JS Object.create(...) constructor so we can create an object from the given prototype
|
|
104
|
+
jsi::Object objectConstructor = runtime.global().getPropertyAsObject(runtime, "Object");
|
|
105
|
+
jsi::Function create = objectConstructor.getPropertyAsFunction(runtime, "create");
|
|
190
106
|
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
}
|
|
107
|
+
// 4. Create the object using Object.create(...)
|
|
108
|
+
jsi::Object object = create.call(runtime, prototype).asObject(runtime);
|
|
194
109
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
ensureInitialized(runtime);
|
|
110
|
+
// 5. Assign NativeState to the object so the prototype can resolve the native methods
|
|
111
|
+
object.setNativeState(runtime, shared_from_this());
|
|
198
112
|
|
|
199
|
-
|
|
113
|
+
// 6. Set memory size so Hermes GC knows about actual memory
|
|
114
|
+
object.setExternalMemoryPressure(runtime, getExternalMemorySize());
|
|
200
115
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
116
|
+
#if DEBUG
|
|
117
|
+
// 7. Assign a private __type property for debugging - this will be used so users know it's not just an empty object.
|
|
118
|
+
object.setProperty(runtime, "__type", jsi::String::createFromUtf8(runtime, "NativeState<" + std::string(_name) + ">"));
|
|
119
|
+
#endif
|
|
206
120
|
|
|
207
|
-
//
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
}
|
|
121
|
+
// 8. Throw a jsi::WeakObject pointing to our object into cache so subsequent calls can use it from cache
|
|
122
|
+
JSICacheReference cache = JSICache::getOrCreateCache(runtime);
|
|
123
|
+
_objectCache.emplace(&runtime, cache.makeShared(jsi::WeakObject(runtime, object)));
|
|
211
124
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
// lazy-load all exposed methods
|
|
215
|
-
loadHybridMethods();
|
|
216
|
-
_didLoadMethods = true;
|
|
217
|
-
}
|
|
125
|
+
// 9. Return it!
|
|
126
|
+
return object;
|
|
218
127
|
}
|
|
219
128
|
|
|
220
129
|
} // namespace margelo::nitro
|
|
@@ -4,21 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
#pragma once
|
|
6
6
|
|
|
7
|
-
#include "
|
|
8
|
-
|
|
9
|
-
#include "OwningReference.hpp"
|
|
10
|
-
#include "TypeInfo.hpp"
|
|
11
|
-
#include <functional>
|
|
7
|
+
#include "HybridObjectPrototype.hpp"
|
|
8
|
+
|
|
12
9
|
#include <jsi/jsi.h>
|
|
13
10
|
#include <memory>
|
|
14
|
-
#include <mutex>
|
|
15
11
|
#include <type_traits>
|
|
16
|
-
#include <unordered_map>
|
|
17
12
|
|
|
18
13
|
namespace margelo::nitro {
|
|
19
14
|
|
|
20
|
-
enum class MethodType { METHOD, GETTER, SETTER };
|
|
21
|
-
|
|
22
15
|
using namespace facebook;
|
|
23
16
|
|
|
24
17
|
/**
|
|
@@ -30,13 +23,7 @@ using namespace facebook;
|
|
|
30
23
|
*
|
|
31
24
|
* The new class can then be passed to JS using the `JSIConverter<HybridObject>`.
|
|
32
25
|
*/
|
|
33
|
-
class HybridObject : public jsi::
|
|
34
|
-
public:
|
|
35
|
-
struct HybridFunction {
|
|
36
|
-
jsi::HostFunctionType function;
|
|
37
|
-
size_t parameterCount;
|
|
38
|
-
};
|
|
39
|
-
|
|
26
|
+
class HybridObject : public jsi::NativeState, public HybridObjectPrototype, public std::enable_shared_from_this<HybridObject> {
|
|
40
27
|
public:
|
|
41
28
|
/**
|
|
42
29
|
* Create a new instance of a `HybridObject`.
|
|
@@ -58,9 +45,13 @@ public:
|
|
|
58
45
|
HybridObject(HybridObject&& move) = delete;
|
|
59
46
|
|
|
60
47
|
public:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Return the `jsi::Object` that holds this `HybridObject`. (boxed in a `jsi::Value`)
|
|
50
|
+
* This properly assigns (or creates) the base prototype for this type,
|
|
51
|
+
* and assigns it's NativeState.
|
|
52
|
+
* Additionally, this sets the external memory pressure for proper GC memory management.
|
|
53
|
+
*/
|
|
54
|
+
jsi::Value toObject(jsi::Runtime& runtime);
|
|
64
55
|
|
|
65
56
|
public:
|
|
66
57
|
/**
|
|
@@ -72,22 +63,6 @@ public:
|
|
|
72
63
|
return std::static_pointer_cast<Derived>(shared_from_this());
|
|
73
64
|
}
|
|
74
65
|
|
|
75
|
-
public:
|
|
76
|
-
/**
|
|
77
|
-
* Get the total size of any external (heap) allocations this `HybridObject` (or
|
|
78
|
-
* a subclass of it) has made.
|
|
79
|
-
* This includes any base allocations (such as function cache), as well as
|
|
80
|
-
* overridden extra memory size.
|
|
81
|
-
*/
|
|
82
|
-
size_t getTotalExternalMemorySize() noexcept;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Return the `jsi::Object` that holds this `HybridObject` (boxed in a `jsi::Value`)
|
|
86
|
-
* Compared to other `jsi::HostObject`s, the `HybridObject` actually
|
|
87
|
-
* caches the created `jsi::Object` instances for safer memory management.
|
|
88
|
-
*/
|
|
89
|
-
jsi::Value toObject(jsi::Runtime& runtime);
|
|
90
|
-
|
|
91
66
|
public:
|
|
92
67
|
/**
|
|
93
68
|
* Get the HybridObject's name
|
|
@@ -128,128 +103,17 @@ protected:
|
|
|
128
103
|
*
|
|
129
104
|
* void User::loadHybridMethods() {
|
|
130
105
|
* HybridObject::loadHybridMethods();
|
|
131
|
-
* registerHybridMethod("getAge", &User::getAge
|
|
106
|
+
* registerHybridMethod("getAge", &User::getAge);
|
|
132
107
|
* }
|
|
133
108
|
* ```
|
|
134
109
|
*/
|
|
135
|
-
virtual void loadHybridMethods();
|
|
110
|
+
virtual void loadHybridMethods() override;
|
|
136
111
|
|
|
137
112
|
private:
|
|
138
113
|
static constexpr auto TAG = "HybridObject";
|
|
139
114
|
const char* _name = TAG;
|
|
140
115
|
int _instanceId = 1;
|
|
141
|
-
|
|
142
|
-
std::unique_ptr<std::mutex> _mutex;
|
|
143
|
-
std::unordered_map<std::string, HybridFunction> _methods;
|
|
144
|
-
std::unordered_map<std::string, jsi::HostFunctionType> _getters;
|
|
145
|
-
std::unordered_map<std::string, jsi::HostFunctionType> _setters;
|
|
146
|
-
std::unordered_map<jsi::Runtime*, std::unordered_map<std::string, OwningReference<jsi::Function>>> _functionCache;
|
|
147
|
-
std::unordered_map<jsi::Runtime*, BorrowingReference<jsi::Object>> _jsObjects;
|
|
148
|
-
|
|
149
|
-
private:
|
|
150
|
-
inline void ensureInitialized(jsi::Runtime& runtime);
|
|
151
|
-
|
|
152
|
-
private:
|
|
153
|
-
template <typename Derived, typename ReturnType, typename... Args, size_t... Is>
|
|
154
|
-
static inline jsi::Value callMethod(Derived* obj, ReturnType (Derived::*method)(Args...), jsi::Runtime& runtime, const jsi::Value* args,
|
|
155
|
-
size_t argsSize, std::index_sequence<Is...>) {
|
|
156
|
-
jsi::Value defaultValue;
|
|
157
|
-
|
|
158
|
-
if constexpr (std::is_void_v<ReturnType>) {
|
|
159
|
-
// It's a void method.
|
|
160
|
-
(obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, Is < argsSize ? args[Is] : defaultValue)...);
|
|
161
|
-
return jsi::Value::undefined();
|
|
162
|
-
} else {
|
|
163
|
-
// It's returning some C++ type, we need to convert that to a JSI value now.
|
|
164
|
-
ReturnType result = (obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, Is < argsSize ? args[Is] : defaultValue)...);
|
|
165
|
-
return JSIConverter<std::decay_t<ReturnType>>::toJSI(runtime, std::move(result));
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
template <typename Derived, typename ReturnType, typename... Args>
|
|
170
|
-
static inline jsi::HostFunctionType createHybridMethod(std::string name, ReturnType (Derived::*method)(Args...), Derived* derivedInstance,
|
|
171
|
-
MethodType type) {
|
|
172
|
-
return [name, derivedInstance, method, type](jsi::Runtime& runtime, const jsi::Value& thisVal, const jsi::Value* args,
|
|
173
|
-
size_t count) -> jsi::Value {
|
|
174
|
-
constexpr size_t optionalArgsCount = trailing_optionals_count_v<Args...>;
|
|
175
|
-
constexpr size_t maxArgsCount = sizeof...(Args);
|
|
176
|
-
constexpr size_t minArgsCount = maxArgsCount - optionalArgsCount;
|
|
177
|
-
bool isWithinArgsRange = (count >= minArgsCount && count <= maxArgsCount);
|
|
178
|
-
if (!isWithinArgsRange) [[unlikely]] {
|
|
179
|
-
// invalid amount of arguments passed!
|
|
180
|
-
std::string hybridObjectName = derivedInstance->_name;
|
|
181
|
-
if constexpr (minArgsCount == maxArgsCount) {
|
|
182
|
-
// min and max args length is the same, so we don't have any optional parameters. fixed count
|
|
183
|
-
throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected " + std::to_string(maxArgsCount) +
|
|
184
|
-
" arguments, but received " + std::to_string(count) + "!");
|
|
185
|
-
} else {
|
|
186
|
-
// min and max args length are different, so we have optional parameters - variable length arguments.
|
|
187
|
-
throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected between " + std::to_string(minArgsCount) + " and " +
|
|
188
|
-
std::to_string(maxArgsCount) + " arguments, but received " + std::to_string(count) + "!");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
if constexpr (std::is_same_v<ReturnType, jsi::Value>) {
|
|
194
|
-
// If the return type is a jsi::Value, we assume the user wants full JSI code control.
|
|
195
|
-
// The signature must be identical to jsi::HostFunction (jsi::Runtime&, jsi::Value& this, ...)
|
|
196
|
-
return (derivedInstance->*method)(runtime, thisVal, args, count);
|
|
197
|
-
} else {
|
|
198
|
-
// Call the actual method with JSI values as arguments and return a JSI value again.
|
|
199
|
-
// Internally, this method converts the JSI values to C++ values.
|
|
200
|
-
return callMethod(derivedInstance, method, runtime, args, count, std::index_sequence_for<Args...>{});
|
|
201
|
-
}
|
|
202
|
-
} catch (const std::exception& exception) {
|
|
203
|
-
std::string hybridObjectName = derivedInstance->_name;
|
|
204
|
-
std::string message = exception.what();
|
|
205
|
-
std::string suffix = type == MethodType::METHOD ? "(...)" : "";
|
|
206
|
-
throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + ": " + message);
|
|
207
|
-
} catch (...) {
|
|
208
|
-
std::string hybridObjectName = derivedInstance->_name;
|
|
209
|
-
std::string errorName = TypeInfo::getCurrentExceptionName();
|
|
210
|
-
std::string suffix = type == MethodType::METHOD ? "(...)" : "";
|
|
211
|
-
throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + " threw an unknown " + errorName + " error.");
|
|
212
|
-
}
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
protected:
|
|
217
|
-
template <typename Derived, typename ReturnType, typename... Args>
|
|
218
|
-
inline void registerHybridMethod(std::string name, ReturnType (Derived::*method)(Args...), Derived* derivedInstance) {
|
|
219
|
-
if (_getters.contains(name) || _setters.contains(name)) [[unlikely]] {
|
|
220
|
-
throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a property with that name already exists!");
|
|
221
|
-
}
|
|
222
|
-
if (_methods.contains(name)) [[unlikely]] {
|
|
223
|
-
throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a method with that name already exists!");
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
_methods[name] = HybridFunction{.function = createHybridMethod(name, method, derivedInstance, MethodType::METHOD),
|
|
227
|
-
.parameterCount = sizeof...(Args)};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
template <typename Derived, typename ReturnType>
|
|
231
|
-
inline void registerHybridGetter(std::string name, ReturnType (Derived::*method)(), Derived* derivedInstance) {
|
|
232
|
-
if (_getters.contains(name)) [[unlikely]] {
|
|
233
|
-
throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a getter with that name already exists!");
|
|
234
|
-
}
|
|
235
|
-
if (_methods.contains(name)) [[unlikely]] {
|
|
236
|
-
throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a method with that name already exists!");
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
_getters[name] = createHybridMethod(name, method, derivedInstance, MethodType::GETTER);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
template <typename Derived, typename ValueType>
|
|
243
|
-
inline void registerHybridSetter(std::string name, void (Derived::*method)(ValueType), Derived* derivedInstance) {
|
|
244
|
-
if (_setters.contains(name)) [[unlikely]] {
|
|
245
|
-
throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a setter with that name already exists!");
|
|
246
|
-
}
|
|
247
|
-
if (_methods.contains(name)) [[unlikely]] {
|
|
248
|
-
throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a method with that name already exists!");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
_setters[name] = createHybridMethod(name, method, derivedInstance, MethodType::SETTER);
|
|
252
|
-
}
|
|
116
|
+
std::unordered_map<jsi::Runtime*, OwningReference<jsi::WeakObject>> _objectCache;
|
|
253
117
|
};
|
|
254
118
|
|
|
255
119
|
} // namespace margelo::nitro
|