react-native-nitro-modules 0.0.2 → 0.0.4

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.
Files changed (120) hide show
  1. package/NitroModules.podspec +49 -0
  2. package/android/CMakeLists.txt +44 -7
  3. package/android/build.gradle +28 -24
  4. package/cpp/core/AnyMap.cpp +181 -0
  5. package/cpp/core/AnyMap.hpp +191 -0
  6. package/cpp/core/HybridContext.hpp +51 -0
  7. package/cpp/core/HybridObject.cpp +220 -0
  8. package/cpp/core/HybridObject.hpp +241 -0
  9. package/cpp/core/PointerHolder.hpp +93 -0
  10. package/cpp/jsi/ArrayBuffer.hpp +79 -0
  11. package/cpp/jsi/JSICache.hpp +145 -0
  12. package/cpp/jsi/JSIConverter.hpp +610 -0
  13. package/cpp/jsi/Promise.cpp +54 -0
  14. package/cpp/jsi/Promise.hpp +54 -0
  15. package/cpp/platform/ThreadUtils.hpp +23 -0
  16. package/cpp/registry/HybridObjectRegistry.cpp +57 -0
  17. package/cpp/registry/HybridObjectRegistry.hpp +44 -0
  18. package/cpp/test-object/TestHybridObject.cpp +37 -0
  19. package/cpp/test-object/TestHybridObject.hpp +87 -0
  20. package/cpp/threading/CallInvokerDispatcher.hpp +33 -0
  21. package/cpp/threading/Dispatcher.cpp +56 -0
  22. package/cpp/threading/Dispatcher.hpp +82 -0
  23. package/cpp/turbomodule/NativeNitroModules.cpp +70 -0
  24. package/cpp/turbomodule/NativeNitroModules.h +7 -0
  25. package/cpp/turbomodule/NativeNitroModules.hpp +35 -0
  26. package/cpp/turbomodule/RegisterNativeNitroModules.cpp +33 -0
  27. package/cpp/turbomodule/RegisterNativeNitroModules.hpp +21 -0
  28. package/cpp/utils/BorrowingReference+Owning.hpp +34 -0
  29. package/cpp/utils/BorrowingReference.hpp +115 -0
  30. package/cpp/utils/DoesClassExist.hpp +23 -0
  31. package/cpp/utils/GetRuntimeID.hpp +28 -0
  32. package/cpp/utils/NitroDefines.hpp +32 -0
  33. package/cpp/utils/NitroHash.hpp +42 -0
  34. package/cpp/utils/NitroLogger.hpp +55 -0
  35. package/cpp/utils/OwningLock.hpp +54 -0
  36. package/cpp/utils/OwningReference.hpp +214 -0
  37. package/cpp/utils/TypeInfo.hpp +81 -0
  38. package/ios/core/HybridObjectSpec.swift +52 -0
  39. package/ios/core/RuntimeError.swift +17 -0
  40. package/ios/platform/ThreadUtils.cpp +28 -0
  41. package/ios/turbomodule/NitroModuleOnLoad.mm +31 -0
  42. package/lib/AnyMap.d.ts +16 -0
  43. package/lib/AnyMap.js +1 -0
  44. package/lib/HybridObject.d.ts +57 -0
  45. package/lib/HybridObject.js +1 -0
  46. package/lib/ModuleNotFoundError.d.ts +6 -0
  47. package/lib/ModuleNotFoundError.js +61 -0
  48. package/lib/NativeNitro.d.ts +8 -0
  49. package/lib/NativeNitro.js +3 -0
  50. package/lib/NativeNitroModules.d.ts +7 -0
  51. package/lib/NativeNitroModules.js +17 -0
  52. package/lib/NitroModules.d.ts +17 -0
  53. package/lib/NitroModules.js +21 -0
  54. package/lib/__tests__/index.test.d.ts +0 -0
  55. package/lib/__tests__/index.test.js +2 -0
  56. package/lib/commonjs/AnyMap.js +2 -0
  57. package/lib/commonjs/AnyMap.js.map +1 -0
  58. package/lib/commonjs/HybridObject.js +2 -0
  59. package/lib/commonjs/HybridObject.js.map +1 -0
  60. package/lib/commonjs/ModuleNotFoundError.js +72 -0
  61. package/lib/commonjs/ModuleNotFoundError.js.map +1 -0
  62. package/lib/commonjs/NativeNitroModules.js +24 -0
  63. package/lib/commonjs/NativeNitroModules.js.map +1 -0
  64. package/lib/commonjs/NitroModules.js +32 -0
  65. package/lib/commonjs/NitroModules.js.map +1 -0
  66. package/lib/commonjs/createTestObject.js +15 -0
  67. package/lib/commonjs/createTestObject.js.map +1 -0
  68. package/lib/commonjs/index.js +44 -5
  69. package/lib/commonjs/index.js.map +1 -1
  70. package/lib/createTestObject.d.ts +22 -0
  71. package/lib/createTestObject.js +7 -0
  72. package/lib/index.d.ts +4 -0
  73. package/lib/index.js +4 -0
  74. package/lib/module/AnyMap.js +2 -0
  75. package/lib/module/AnyMap.js.map +1 -0
  76. package/lib/module/HybridObject.js +2 -0
  77. package/lib/module/HybridObject.js.map +1 -0
  78. package/lib/module/ModuleNotFoundError.js +65 -0
  79. package/lib/module/ModuleNotFoundError.js.map +1 -0
  80. package/lib/module/NativeNitroModules.js +18 -0
  81. package/lib/module/NativeNitroModules.js.map +1 -0
  82. package/lib/module/NitroModules.js +27 -0
  83. package/lib/module/NitroModules.js.map +1 -0
  84. package/lib/module/createTestObject.js +8 -0
  85. package/lib/module/createTestObject.js.map +1 -0
  86. package/lib/module/index.js +4 -4
  87. package/lib/module/index.js.map +1 -1
  88. package/lib/tsconfig.tsbuildinfo +1 -0
  89. package/package.json +76 -49
  90. package/react-native.config.js +16 -0
  91. package/src/AnyMap.ts +22 -0
  92. package/src/HybridObject.ts +58 -0
  93. package/src/ModuleNotFoundError.ts +90 -0
  94. package/src/NativeNitroModules.ts +26 -0
  95. package/src/NitroModules.ts +30 -0
  96. package/src/__tests__/index.test.tsx +1 -0
  97. package/src/createTestObject.ts +40 -0
  98. package/src/index.ts +4 -0
  99. package/LICENSE +0 -20
  100. package/README.md +0 -32
  101. package/android/cpp-adapter.cpp +0 -8
  102. package/android/src/main/AndroidManifest.xml +0 -3
  103. package/android/src/main/AndroidManifestNew.xml +0 -2
  104. package/android/src/main/java/com/nitro/NitroModule.java +0 -34
  105. package/android/src/main/java/com/nitro/NitroPackage.java +0 -44
  106. package/cpp/react-native-nitro.cpp +0 -7
  107. package/cpp/react-native-nitro.h +0 -8
  108. package/ios/Nitro.h +0 -15
  109. package/ios/Nitro.mm +0 -21
  110. package/lib/commonjs/NativeNitro.js +0 -9
  111. package/lib/commonjs/NativeNitro.js.map +0 -1
  112. package/lib/module/NativeNitro.js +0 -3
  113. package/lib/module/NativeNitro.js.map +0 -1
  114. package/lib/typescript/src/NativeNitro.d.ts +0 -7
  115. package/lib/typescript/src/NativeNitro.d.ts.map +0 -1
  116. package/lib/typescript/src/index.d.ts +0 -2
  117. package/lib/typescript/src/index.d.ts.map +0 -1
  118. package/react-native-nitro.podspec +0 -41
  119. package/src/NativeNitro.ts +0 -8
  120. package/src/index.tsx +0 -5
@@ -0,0 +1,220 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #include "HybridObject.hpp"
6
+ #include "HybridContext.hpp"
7
+ #include "JSIConverter.hpp"
8
+ #include "NitroLogger.hpp"
9
+
10
+ #define LOG_MEMORY_ALLOCATIONS true
11
+
12
+ namespace margelo::nitro {
13
+
14
+ #if LOG_MEMORY_ALLOCATIONS
15
+ static std::mutex _instanceCounterMutex;
16
+ static std::unordered_map<const char*, uint32_t> _aliveInstances;
17
+ static std::unordered_map<const char*, int> _instanceIds;
18
+
19
+ static uint32_t incrementAliveInstancesAndGet(const char* name) {
20
+ std::unique_lock lock(_instanceCounterMutex);
21
+ return ++_aliveInstances[name];
22
+ }
23
+
24
+ static uint32_t decrementAliveInstancesAndGet(const char* name) {
25
+ std::unique_lock lock(_instanceCounterMutex);
26
+ return --_aliveInstances[name];
27
+ }
28
+
29
+ static uint32_t getTotalAliveInstances() {
30
+ std::unique_lock lock(_instanceCounterMutex);
31
+ uint32_t total = 0;
32
+ for (const auto& iter : _aliveInstances) {
33
+ total += iter.second;
34
+ }
35
+ return total;
36
+ }
37
+
38
+ static int getId(const char* name) {
39
+ std::unique_lock lock(_instanceCounterMutex);
40
+ if (_instanceIds.find(name) == _instanceIds.end()) {
41
+ _instanceIds.insert({name, 1});
42
+ }
43
+ auto iterator = _instanceIds.find(name);
44
+ return iterator->second++;
45
+ }
46
+ #endif
47
+
48
+ HybridObject::HybridObject(const char* name) : _name(name), _mutex(std::make_unique<std::mutex>()) {
49
+ #if LOG_MEMORY_ALLOCATIONS
50
+ _instanceId = getId(name);
51
+ uint32_t alive = incrementAliveInstancesAndGet(_name);
52
+ uint32_t totalObjects = getTotalAliveInstances();
53
+ Logger::log(TAG, "(MEMORY) ✅ Creating %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i)", _name, _instanceId, _name, alive,
54
+ totalObjects);
55
+ #endif
56
+ }
57
+
58
+ HybridObject::~HybridObject() {
59
+ #if LOG_MEMORY_ALLOCATIONS
60
+ uint32_t alive = decrementAliveInstancesAndGet(_name);
61
+ uint32_t totalObjects = getTotalAliveInstances();
62
+ Logger::log(TAG, "(MEMORY) ❌ Deleting %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i) ", _name, _instanceId, _name, alive,
63
+ totalObjects);
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<jsi::Object>::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(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
+ }
115
+
116
+ std::string HybridObject::toString() {
117
+ return "[HybridObject " + std::string(_name) + "]";
118
+ }
119
+
120
+ std::string HybridObject::getName() {
121
+ return _name;
122
+ }
123
+
124
+ bool HybridObject::equals(std::shared_ptr<HybridObject> other) {
125
+ return this == other.get();
126
+ }
127
+
128
+ void HybridObject::loadHybridMethods() {
129
+ registerHybridGetter("name", &HybridObject::getName, this);
130
+ registerHybridMethod("toString", &HybridObject::toString, this);
131
+ registerHybridMethod("equals", &HybridObject::equals, this);
132
+ }
133
+
134
+ std::vector<jsi::PropNameID> HybridObject::getPropertyNames(facebook::jsi::Runtime& runtime) {
135
+ std::unique_lock lock(*_mutex);
136
+ ensureInitialized(runtime);
137
+
138
+ std::vector<jsi::PropNameID> result;
139
+ size_t totalSize = _methods.size() + _getters.size() + _setters.size() + 1;
140
+ result.reserve(totalSize);
141
+
142
+ for (const auto& item : _methods) {
143
+ result.push_back(jsi::PropNameID::forUtf8(runtime, item.first));
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);
168
+ }
169
+ }
170
+
171
+ if (_getters.contains(name)) {
172
+ // it's a property getter. call it directly
173
+ return _getters[name](runtime, jsi::Value::undefined(), nullptr, 0);
174
+ }
175
+
176
+ if (_methods.contains(name)) {
177
+ // it's a function. we now need to wrap it in a jsi::Function, store it in cache, then return it.
178
+ HybridFunction& hybridFunction = _methods.at(name);
179
+ // get (or create) a runtime-specific function cache
180
+ auto runtimeCache = JSICache<jsi::Function>::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(std::move(function));
186
+ functionCache[name] = globalFunction;
187
+ // copy the reference & return it to JS
188
+ return jsi::Value(runtime, *globalFunction);
189
+ }
190
+
191
+ // this property does not exist. Return undefined
192
+ return jsi::Value::undefined();
193
+ }
194
+
195
+ void HybridObject::set(facebook::jsi::Runtime& runtime, const facebook::jsi::PropNameID& propName, const facebook::jsi::Value& value) {
196
+ std::unique_lock lock(*_mutex);
197
+ ensureInitialized(runtime);
198
+
199
+ std::string name = propName.utf8(runtime);
200
+
201
+ if (_setters.contains(name)) {
202
+ // Call setter
203
+ _setters[name](runtime, jsi::Value::undefined(), &value, 1);
204
+ return;
205
+ }
206
+
207
+ // this property does not exist, and cannot be set. Throw and error!
208
+ throw std::runtime_error("Cannot set property \"" + name + "\" - " + std::string(_name) + " does not have a setter for \"" + name +
209
+ "\"!");
210
+ }
211
+
212
+ void HybridObject::ensureInitialized(facebook::jsi::Runtime& runtime) {
213
+ if (!_didLoadMethods) [[unlikely]] {
214
+ // lazy-load all exposed methods
215
+ loadHybridMethods();
216
+ _didLoadMethods = true;
217
+ }
218
+ }
219
+
220
+ } // namespace margelo::nitro
@@ -0,0 +1,241 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ #include "HybridContext.hpp"
8
+ #include "JSIConverter.hpp"
9
+ #include "NitroLogger.hpp"
10
+ #include "OwningReference.hpp"
11
+ #include <functional>
12
+ #include <jsi/jsi.h>
13
+ #include <memory>
14
+ #include <mutex>
15
+ #include <type_traits>
16
+ #include <unordered_map>
17
+
18
+ namespace margelo::nitro {
19
+
20
+ enum class MethodType { METHOD, GETTER, SETTER };
21
+
22
+ using namespace facebook;
23
+
24
+ /**
25
+ * Represents a C++ object that is exposed to JS.
26
+ * `HybridObject`s can have native getters and setters, and normal methods.
27
+ *
28
+ * To implement a `HybridObject`, simply inherit from this class and override `loadHybridMethods`
29
+ * to register the given getters, setters or methods.
30
+ *
31
+ * The new class can then be passed to JS using the `JSIConverter<HybridObject>`.
32
+ */
33
+ class HybridObject : public jsi::HostObject, public std::enable_shared_from_this<HybridObject> {
34
+ public:
35
+ struct HybridFunction {
36
+ jsi::HostFunctionType function;
37
+ size_t parameterCount;
38
+ };
39
+
40
+ public:
41
+ /**
42
+ * Create a new instance of a `HybridObject`.
43
+ * The given `name` will be used for logging and stringifying.
44
+ */
45
+ explicit HybridObject(const char* name);
46
+ /**
47
+ * Called when no more references to the given `HybridObject` exist in both C++ and JS.
48
+ * JS might keep references for longer, as it is a garbage collected language.
49
+ */
50
+ virtual ~HybridObject();
51
+ /**
52
+ * HybridObjects cannot be copied.
53
+ */
54
+ HybridObject(const HybridObject& copy) = delete;
55
+ /**
56
+ * HybridObjects cannot be moved.
57
+ */
58
+ HybridObject(HybridObject&& move) = delete;
59
+
60
+ public:
61
+ void set(jsi::Runtime&, const jsi::PropNameID& name, const jsi::Value& value) override;
62
+ jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) override;
63
+ std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& runtime) override;
64
+
65
+ public:
66
+ /**
67
+ * Get the `std::shared_ptr` instance of this HybridObject.
68
+ * The HybridObject must be managed inside a `shared_ptr` already, otherwise this will fail.
69
+ */
70
+ template <typename Derived> std::shared_ptr<Derived> shared() {
71
+ return std::static_pointer_cast<Derived>(shared_from_this());
72
+ }
73
+
74
+ public:
75
+ /**
76
+ * Get the total size of any external (heap) allocations this `HybridObject` (or
77
+ * a subclass of it) has made.
78
+ * This includes any base allocations (such as function cache), as well as
79
+ * overridden extra memory size.
80
+ */
81
+ size_t getTotalExternalMemorySize() noexcept;
82
+
83
+ /**
84
+ * Return the `jsi::Object` that holds this `HybridObject` (boxed in a `jsi::Value`)
85
+ * Compared to other `jsi::HostObject`s, the `HybridObject` actually
86
+ * caches the created `jsi::Object` instances for safer memory management.
87
+ */
88
+ jsi::Value toObject(jsi::Runtime& runtime);
89
+
90
+ public:
91
+ /**
92
+ * Get the HybridObject's name
93
+ */
94
+ std::string getName();
95
+ /**
96
+ * Get a string representation of this HostObject, useful for logging or debugging.
97
+ */
98
+ virtual std::string toString();
99
+ /**
100
+ * Compare this HybridObject for reference equality to the other HybridObject.
101
+ *
102
+ * While two `jsi::Object`s of the same `HybridObject` might not be equal when compared with `==`,
103
+ * they might still be the same `HybridObject` - in this case `equals(other)` will return true.
104
+ */
105
+ bool equals(std::shared_ptr<HybridObject> other);
106
+
107
+ protected:
108
+ /**
109
+ * Get the size of any external (heap) allocations this `HybridObject` has made, in bytes.
110
+ * This will be used to notify the JS GC about memory pressure.
111
+ */
112
+ virtual inline size_t getExternalMemorySize() noexcept {
113
+ return 0;
114
+ }
115
+
116
+ protected:
117
+ /**
118
+ * Loads all native methods of this `HybridObject` to be exposed to JavaScript.
119
+ * The base implementation registers a `toString()` method and `name` property.
120
+ *
121
+ * Example:
122
+ *
123
+ * ```cpp
124
+ * int User::getAge() {
125
+ * return 23;
126
+ * }
127
+ *
128
+ * void User::loadHybridMethods() {
129
+ * HybridObject::loadHybridMethods();
130
+ * registerHybridMethod("getAge", &User::getAge, this);
131
+ * }
132
+ * ```
133
+ */
134
+ virtual void loadHybridMethods();
135
+
136
+ private:
137
+ static constexpr auto TAG = "HybridObject";
138
+ const char* _name = TAG;
139
+ int _instanceId = 1;
140
+ bool _didLoadMethods = false;
141
+ std::unique_ptr<std::mutex> _mutex;
142
+ std::unordered_map<std::string, HybridFunction> _methods;
143
+ std::unordered_map<std::string, jsi::HostFunctionType> _getters;
144
+ std::unordered_map<std::string, jsi::HostFunctionType> _setters;
145
+ std::unordered_map<jsi::Runtime*, std::unordered_map<std::string, OwningReference<jsi::Function>>> _functionCache;
146
+ std::unordered_map<jsi::Runtime*, BorrowingReference<jsi::Object>> _jsObjects;
147
+
148
+ private:
149
+ inline void ensureInitialized(jsi::Runtime& runtime);
150
+
151
+ private:
152
+ template <typename Derived, typename ReturnType, typename... Args, size_t... Is>
153
+ static inline jsi::Value callMethod(Derived* obj, ReturnType (Derived::*method)(Args...), jsi::Runtime& runtime, const jsi::Value* args,
154
+ std::index_sequence<Is...>) {
155
+ if constexpr (std::is_void_v<ReturnType>) {
156
+ // It's a void method.
157
+ (obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
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, args[Is])...);
162
+ return JSIConverter<std::decay_t<ReturnType>>::toJSI(runtime, std::move(result));
163
+ }
164
+ }
165
+
166
+ template <typename Derived, typename ReturnType, typename... Args>
167
+ static inline jsi::HostFunctionType createHybridMethod(std::string name, ReturnType (Derived::*method)(Args...), Derived* derivedInstance,
168
+ MethodType type) {
169
+ return [name, derivedInstance, method, type](jsi::Runtime& runtime, const jsi::Value& thisVal, const jsi::Value* args,
170
+ size_t count) -> jsi::Value {
171
+ if (count != sizeof...(Args)) {
172
+ // invalid amount of arguments passed!
173
+ std::string hybridObjectName = derivedInstance->_name;
174
+ throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected " + std::to_string(sizeof...(Args)) +
175
+ " arguments, but received " + std::to_string(count) + "!");
176
+ }
177
+
178
+ try {
179
+ if constexpr (std::is_same_v<ReturnType, jsi::Value>) {
180
+ // If the return type is a jsi::Value, we assume the user wants full JSI code control.
181
+ // The signature must be identical to jsi::HostFunction (jsi::Runtime&, jsi::Value& this, ...)
182
+ return (derivedInstance->*method)(runtime, thisVal, args, count);
183
+ } else {
184
+ // Call the actual method with JSI values as arguments and return a JSI value again.
185
+ // Internally, this method converts the JSI values to C++ values.
186
+ return callMethod(derivedInstance, method, runtime, args, std::index_sequence_for<Args...>{});
187
+ }
188
+ } catch (const std::exception& exception) {
189
+ std::string hybridObjectName = derivedInstance->_name;
190
+ std::string message = exception.what();
191
+ std::string suffix = type == MethodType::METHOD ? "(...)" : "";
192
+ throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + ": " + message);
193
+ } catch (...) {
194
+ std::string hybridObjectName = derivedInstance->_name;
195
+ std::string errorName = TypeInfo::getCurrentExceptionName();
196
+ std::string suffix = type == MethodType::METHOD ? "(...)" : "";
197
+ throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + " threw an unknown " + errorName + " error.");
198
+ }
199
+ };
200
+ }
201
+
202
+ protected:
203
+ template <typename Derived, typename ReturnType, typename... Args>
204
+ inline void registerHybridMethod(std::string name, ReturnType (Derived::*method)(Args...), Derived* derivedInstance) {
205
+ if (_getters.contains(name) || _setters.contains(name)) [[unlikely]] {
206
+ throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a property with that name already exists!");
207
+ }
208
+ if (_methods.contains(name)) [[unlikely]] {
209
+ throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a method with that name already exists!");
210
+ }
211
+
212
+ _methods[name] = HybridFunction{.function = createHybridMethod(name, method, derivedInstance, MethodType::METHOD),
213
+ .parameterCount = sizeof...(Args)};
214
+ }
215
+
216
+ template <typename Derived, typename ReturnType>
217
+ inline void registerHybridGetter(std::string name, ReturnType (Derived::*method)(), Derived* derivedInstance) {
218
+ if (_getters.contains(name)) [[unlikely]] {
219
+ throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a getter with that name already exists!");
220
+ }
221
+ if (_methods.contains(name)) [[unlikely]] {
222
+ throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a method with that name already exists!");
223
+ }
224
+
225
+ _getters[name] = createHybridMethod(name, method, derivedInstance, MethodType::GETTER);
226
+ }
227
+
228
+ template <typename Derived, typename ValueType>
229
+ inline void registerHybridSetter(std::string name, void (Derived::*method)(ValueType), Derived* derivedInstance) {
230
+ if (_setters.contains(name)) [[unlikely]] {
231
+ throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a setter with that name already exists!");
232
+ }
233
+ if (_methods.contains(name)) [[unlikely]] {
234
+ throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a method with that name already exists!");
235
+ }
236
+
237
+ _setters[name] = createHybridMethod(name, method, derivedInstance, MethodType::SETTER);
238
+ }
239
+ };
240
+
241
+ } // namespace margelo::nitro
@@ -0,0 +1,93 @@
1
+ //
2
+ // Created by Marc Rousavy on 16.04.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ #include "HybridObject.hpp"
8
+ #include "NitroLogger.hpp"
9
+ #include <memory>
10
+ #include <mutex>
11
+
12
+ namespace margelo::nitro {
13
+
14
+ using namespace facebook;
15
+
16
+ template <typename T> class PointerHolder final : public HybridObject {
17
+ protected:
18
+ // no default constructor
19
+ PointerHolder() = delete;
20
+
21
+ /**
22
+ * Create a new instance of a pointer holder which holds the given shared_ptr.
23
+ * @param name The name of the implementing class, for example "ViewWrapper".
24
+ * @param pointer The pointer this class will hold. It might be released from JS at any point via `release()`.
25
+ */
26
+ PointerHolder(const char* name, std::shared_ptr<T> pointer) : HybridObject(name), _name(name), _pointer(pointer) {
27
+ // eagerly initialize the release() method instead of putting it in `loadHybridMethods`
28
+ registerHybridMethod("release", &PointerHolder<T>::release, this);
29
+ registerHybridGetter("isValid", &PointerHolder<T>::getIsValid, this);
30
+ }
31
+
32
+ /**
33
+ * Create a new instance of a pointer holder which holds a shared_ptr of the given value.
34
+ * The shared_ptr will be move-constructed.
35
+ * @param name The name of the implementing class, for example "ViewWrapper".
36
+ * @param value The value this class will hold as a shared_ptr. It might be destroyed from JS at any point via `release()`.
37
+ */
38
+ PointerHolder(const char* name, T&& value) : PointerHolder(name, std::make_shared<T>(std::move(value))) {}
39
+
40
+ /**
41
+ * Called when the PointerHolder gets automatically destroyed (e.g. via GC) and the shared_ptr will be destroyed.
42
+ */
43
+ ~PointerHolder() {
44
+ if (_pointer != nullptr) {
45
+ Logger::log(TAG, "Automatically releasing %s... (~PointerHolder())", _name.c_str());
46
+ }
47
+ }
48
+
49
+ protected:
50
+ /**
51
+ * Manually release this reference to the pointer.
52
+ * If there are any other references to this pointer, no memory will be force-deleted.
53
+ */
54
+ virtual void release() {
55
+ std::unique_lock lock(_mutex);
56
+
57
+ if (_pointer == nullptr) {
58
+ throw std::runtime_error("Pointer " + _name + " has already been manually released!");
59
+ }
60
+ Logger::log(TAG, "Manually releasing %s... (PointerHolder::release())", _name.c_str());
61
+ _pointer = nullptr;
62
+ }
63
+
64
+ /**
65
+ * Get the shared_ptr this class is holding.
66
+ * If it has already been manually released from JS, this method will throw a runtime_error.
67
+ */
68
+ std::shared_ptr<T> pointee() {
69
+ std::unique_lock lock(_mutex);
70
+
71
+ if (_pointer == nullptr) {
72
+ throw std::runtime_error("Pointer " + _name + " has already been manually released!");
73
+ }
74
+ return _pointer;
75
+ }
76
+
77
+ /**
78
+ * Get if the pointer is still valid and strong.
79
+ */
80
+ bool getIsValid() {
81
+ std::unique_lock lock(_mutex);
82
+
83
+ return _pointer != nullptr;
84
+ }
85
+
86
+ private:
87
+ std::string _name;
88
+ std::shared_ptr<T> _pointer;
89
+ std::mutex _mutex;
90
+ static constexpr auto TAG = "PointerHolder";
91
+ };
92
+
93
+ } // namespace margelo::nitro
@@ -0,0 +1,79 @@
1
+ //
2
+ // ArrayBuffer.hpp
3
+ // react-native-nitro
4
+ //
5
+ // Created by Marc Rousavy on 14.07.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include <functional>
11
+ #include <jsi/jsi.h>
12
+
13
+ namespace margelo::nitro {
14
+
15
+ using namespace facebook;
16
+
17
+ using DeleteFn = std::function<void(uint8_t*)>;
18
+
19
+ static DeleteFn defaultDeleteFn = [](uint8_t* buffer) { delete[] buffer; };
20
+
21
+ /**
22
+ * Represents a raw byte buffer that can be read from-, and
23
+ * written to- from both JavaScript and C++.
24
+ * `ArrayBuffer` is not thread-safe and does not lock multi-thread access.
25
+ *
26
+ * Also, if `ArrayBuffer` is coming from JS, it is not safe to keep a strong
27
+ * reference on `ArrayBuffer` as C++ does not own the data (`isOwner() == false`) -
28
+ * it can be deleted at any point without C++ knowing about it.
29
+ *
30
+ * Only if C++ creates the `ArrayBuffer` a reference to it can be safely
31
+ * kept in memory, as C++ is then the owner of `ArrayBuffer` (`isOwner() == true`).
32
+ */
33
+ class ArrayBuffer : public jsi::MutableBuffer {
34
+ public:
35
+ /**
36
+ * Create a new **owning** `ArrayBuffer`.
37
+ * The `ArrayBuffer` can be kept in memory, as C++ owns the data
38
+ * and will only delete it once this `ArrayBuffer` gets deleted
39
+ */
40
+ ArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc) : _data(data), _size(size), _deleteFunc(std::move(deleteFunc)) {}
41
+ /**
42
+ * Create a new `ArrayBuffer`.
43
+ * If `destroyOnDeletion` is `true`, the `ArrayBuffer` is **owning**, otherwise it is **non-owning**.
44
+ * The `ArrayBuffer` can only be safely kept in memory if it is owning (`isOwning()`).
45
+ */
46
+ ArrayBuffer(uint8_t* data, size_t size, bool destroyOnDeletion) : _data(data), _size(size) {
47
+ _deleteFunc = destroyOnDeletion ? defaultDeleteFn : nullptr;
48
+ }
49
+
50
+ // ArrayBuffer cannot be copied
51
+ ArrayBuffer(const ArrayBuffer&) = delete;
52
+ // ArrayBuffer cannot be moved
53
+ ArrayBuffer(ArrayBuffer&&) = delete;
54
+
55
+ ~ArrayBuffer() {
56
+ if (_deleteFunc != nullptr) {
57
+ _deleteFunc(_data);
58
+ }
59
+ }
60
+
61
+ uint8_t* data() noexcept override {
62
+ return _data;
63
+ }
64
+
65
+ size_t size() const noexcept override {
66
+ return _size;
67
+ }
68
+
69
+ bool isOwner() const noexcept {
70
+ return _deleteFunc != nullptr;
71
+ }
72
+
73
+ private:
74
+ uint8_t* _data;
75
+ size_t _size;
76
+ DeleteFn _deleteFunc;
77
+ };
78
+
79
+ } // namespace margelo::nitro