react-native-nitro-modules 0.1.3 → 0.1.5

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 (41) hide show
  1. package/NitroModules.podspec +3 -1
  2. package/android/src/main/cpp/platform/ThreadUtils.cpp +5 -0
  3. package/cpp/core/AnyMap.hpp +2 -1
  4. package/cpp/core/ArrayBuffer.hpp +108 -16
  5. package/cpp/core/HybridObject.hpp +3 -4
  6. package/cpp/jsi/JSIConverter+AnyMap.hpp +64 -0
  7. package/cpp/jsi/JSIConverter+ArrayBuffer.hpp +50 -0
  8. package/cpp/jsi/JSIConverter+Function.hpp +124 -0
  9. package/cpp/jsi/JSIConverter+HybridObject.hpp +130 -0
  10. package/cpp/jsi/JSIConverter+Optional.hpp +41 -0
  11. package/cpp/jsi/JSIConverter+Promise.hpp +87 -0
  12. package/cpp/jsi/JSIConverter+Tuple.hpp +60 -0
  13. package/cpp/jsi/JSIConverter+UnorderedMap.hpp +50 -0
  14. package/cpp/jsi/JSIConverter+Variant.hpp +97 -0
  15. package/cpp/jsi/JSIConverter+Vector.hpp +48 -0
  16. package/cpp/jsi/JSIConverter.hpp +22 -498
  17. package/cpp/platform/ThreadUtils.hpp +6 -0
  18. package/cpp/templates/IsSharedPtrTo.hpp +28 -0
  19. package/cpp/threading/ThreadPool.cpp +84 -0
  20. package/cpp/threading/ThreadPool.hpp +53 -0
  21. package/ios/core/HybridContext.cpp +8 -0
  22. package/{cpp → ios}/core/HybridContext.hpp +6 -3
  23. package/ios/platform/ThreadUtils.cpp +4 -0
  24. package/lib/NativeNitroModules.d.ts +5 -0
  25. package/lib/NativeNitroModules.js +5 -0
  26. package/lib/NativeNitroModules.web.d.ts +4 -0
  27. package/lib/NativeNitroModules.web.js +3 -0
  28. package/lib/commonjs/NativeNitroModules.js +6 -0
  29. package/lib/commonjs/NativeNitroModules.js.map +1 -1
  30. package/lib/commonjs/NativeNitroModules.web.js +10 -0
  31. package/lib/commonjs/NativeNitroModules.web.js.map +1 -0
  32. package/lib/module/NativeNitroModules.js +5 -0
  33. package/lib/module/NativeNitroModules.js.map +1 -1
  34. package/lib/module/NativeNitroModules.web.js +4 -0
  35. package/lib/module/NativeNitroModules.web.js.map +1 -0
  36. package/lib/tsconfig.tsbuildinfo +1 -1
  37. package/package.json +1 -1
  38. package/src/NativeNitroModules.ts +11 -0
  39. package/src/NativeNitroModules.web.ts +9 -0
  40. package/cpp/templates/IsHostObject.hpp +0 -27
  41. package/cpp/templates/IsNativeState.hpp +0 -27
@@ -28,14 +28,16 @@ Pod::Spec.new do |s|
28
28
  ]
29
29
  s.public_header_files = [
30
30
  # Public C++ headers will be exposed in modulemap (for Swift)
31
+ "cpp/core/AnyMap.hpp",
31
32
  "cpp/core/ArrayBuffer.hpp",
32
33
  "cpp/core/HybridObject.hpp",
33
- "cpp/core/HybridContext.hpp",
34
34
  "cpp/registry/HybridObjectRegistry.hpp",
35
35
  "cpp/jsi/JSIConverter.hpp",
36
36
  "cpp/threading/Dispatcher.hpp",
37
37
  "cpp/utils/NitroHash.hpp",
38
38
  "cpp/utils/NitroDefines.hpp",
39
+ # Public iOS-specific headers that will be exposed in modulemap (for Swift)
40
+ "ios/core/HybridContext.hpp"
39
41
  ]
40
42
 
41
43
  s.pod_target_xcconfig = {
@@ -9,6 +9,7 @@
9
9
  #include <pthread.h>
10
10
  #include <sstream>
11
11
  #include <string>
12
+ #include <sys/prctl.h>
12
13
  #include <thread>
13
14
 
14
15
  namespace margelo::nitro {
@@ -32,4 +33,8 @@ std::string ThreadUtils::getThreadName() {
32
33
  return "Thread #" + threadId;
33
34
  }
34
35
 
36
+ void ThreadUtils::setThreadName(const std::string& name) {
37
+ prctl(PR_SET_NAME, name.c_str(), 0, 0, 0);
38
+ }
39
+
35
40
  } // namespace margelo::nitro
@@ -9,6 +9,7 @@
9
9
  #include <string>
10
10
  #include <unordered_map>
11
11
  #include <variant>
12
+ #include <vector>
12
13
 
13
14
  namespace margelo::nitro {
14
15
 
@@ -33,7 +34,7 @@ struct AnyValue : VariantType {
33
34
  * 2. Arrays of primitives
34
35
  * 3. Objects of primitives
35
36
  */
36
- class AnyMap {
37
+ class AnyMap final {
37
38
  public:
38
39
  /**
39
40
  * Create a new instance of AnyMap.
@@ -7,8 +7,10 @@
7
7
 
8
8
  #pragma once
9
9
 
10
+ #include "OwningReference.hpp"
10
11
  #include <functional>
11
12
  #include <jsi/jsi.h>
13
+ #include <thread>
12
14
 
13
15
  namespace margelo::nitro {
14
16
 
@@ -23,50 +25,76 @@ static DeleteFn defaultDeleteFn = [](uint8_t* buffer) { delete[] buffer; };
23
25
  * written to- from both JavaScript and C++.
24
26
  * `ArrayBuffer` is not thread-safe and does not lock multi-thread access.
25
27
  *
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.
28
+ * `ArrayBuffer` can either be a `JSArrayBuffer`, or a `NativeArrayBuffer`.
29
+ * - `NativeArrayBuffer`: Created from native (C++), and can either own the memory (`isOwner()`), or borrow it.
30
+ * - `JSArrayBuffer`: Received from JS, and will only be alive for as long as the JS Runtime is actually alive.
29
31
  *
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
+ * Also, an `ArrayBuffer` can either own it's memory, or just borrow it's memory.
33
+ * - Owning = the `ArrayBuffer`'s `data()` is alive as long as the `ArrayBuffer` is alive.
34
+ * When this `ArrayBuffer` gets deleted, it will free the memory.
35
+ * - Borrowed = the `ArrayBuffer`'s `data()` might be deleted at any point from an external source (e.g. the JS garbage collector).
36
+ * When this `ArrayBuffer` gets deleted, the memory will not be freed explicitly, as someone else owns it.
32
37
  */
33
38
  class ArrayBuffer : public jsi::MutableBuffer {
39
+ public:
40
+ ArrayBuffer() = default;
41
+ ArrayBuffer(const ArrayBuffer&) = delete;
42
+ ArrayBuffer(ArrayBuffer&&) = delete;
43
+ virtual ~ArrayBuffer() = default;
44
+
45
+ public:
46
+ /**
47
+ * Returns whether this `ArrayBuffer` is actually owning the data,
48
+ * or if it is just borrowed from an external source (either a native
49
+ * memory that we didn't allocate, or from JS - which can be deleted at any point).
50
+ */
51
+ virtual bool isOwner() const noexcept = 0;
52
+ };
53
+
54
+ /**
55
+ * Represents an `ArrayBuffer` that is allocated on the native (C++) side.
56
+ * It can either be "owning" or "borrowing".
57
+ *
58
+ * - Owning = the `ArrayBuffer`'s `data()` is alive as long as the `ArrayBuffer` is alive.
59
+ * When this `ArrayBuffer` gets deleted, it will free the memory.
60
+ * - Borrowed = the `ArrayBuffer`'s `data()` might be deleted at any point from an external source (e.g. the JS garbage collector).
61
+ * When this `ArrayBuffer` gets deleted, the memory will not be freed explicitly, as someone else owns it.
62
+ *
63
+ * It is safe to access `data()` and `size()` from any Thread, but there are no synchronization/mutexes implemented by default.
64
+ */
65
+ class NativeArrayBuffer : public ArrayBuffer {
34
66
  public:
35
67
  /**
36
68
  * Create a new **owning** `ArrayBuffer`.
37
69
  * The `ArrayBuffer` can be kept in memory, as C++ owns the data
38
70
  * and will only delete it once this `ArrayBuffer` gets deleted
39
71
  */
40
- ArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc) : _data(data), _size(size), _deleteFunc(std::move(deleteFunc)) {}
72
+ NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc)
73
+ : ArrayBuffer(), _data(data), _size(size), _deleteFunc(std::move(deleteFunc)) {}
41
74
  /**
42
75
  * Create a new `ArrayBuffer`.
43
76
  * If `destroyOnDeletion` is `true`, the `ArrayBuffer` is **owning**, otherwise it is **non-owning**.
44
77
  * The `ArrayBuffer` can only be safely kept in memory if it is owning (`isOwning()`).
45
78
  */
46
- ArrayBuffer(uint8_t* data, size_t size, bool destroyOnDeletion) : _data(data), _size(size) {
79
+ NativeArrayBuffer(uint8_t* data, size_t size, bool destroyOnDeletion) : ArrayBuffer(), _data(data), _size(size) {
47
80
  _deleteFunc = destroyOnDeletion ? defaultDeleteFn : nullptr;
48
81
  }
49
82
 
50
- // ArrayBuffer cannot be copied
51
- ArrayBuffer(const ArrayBuffer&) = delete;
52
- // ArrayBuffer cannot be moved
53
- ArrayBuffer(ArrayBuffer&&) = delete;
54
-
55
- ~ArrayBuffer() {
83
+ ~NativeArrayBuffer() {
56
84
  if (_deleteFunc != nullptr) {
57
85
  _deleteFunc(_data);
58
86
  }
59
87
  }
60
88
 
61
- uint8_t* data() noexcept override {
89
+ uint8_t* data() override {
62
90
  return _data;
63
91
  }
64
92
 
65
- size_t size() const noexcept override {
93
+ size_t size() const override {
66
94
  return _size;
67
95
  }
68
96
 
69
- bool isOwner() const noexcept {
97
+ bool isOwner() const noexcept override {
70
98
  return _deleteFunc != nullptr;
71
99
  }
72
100
 
@@ -76,4 +104,68 @@ private:
76
104
  DeleteFn _deleteFunc;
77
105
  };
78
106
 
107
+ /**
108
+ * Represents a JS-based `ArrayBuffer`.
109
+ *
110
+ * While it's underlying data might have been allocated on the native side (`NativeArrayBuffer`),
111
+ * we only have a JS reference to the `ArrayBuffer` object so it is considered a "borrowed"-resource.
112
+ *
113
+ * `data()` and `size()` can only be accessed synchronously on the JS Runtime Thread.
114
+ * If you want to access it elsewhere, copy the buffer first.
115
+ *
116
+ * If the JS ArrayBuffer (or it's JS Runtime) have already been deleted, `data()` returns `nullptr`.
117
+ */
118
+ class JSArrayBuffer : public ArrayBuffer {
119
+ public:
120
+ explicit JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference)
121
+ : ArrayBuffer(), _runtime(runtime), _jsReference(jsReference), _initialThreadId(std::this_thread::get_id()) {}
122
+ ~JSArrayBuffer() {}
123
+
124
+ public:
125
+ /**
126
+ * Gets the data this `ArrayBuffer` points to, or `nullptr` if it has already been deleted.
127
+ */
128
+ uint8_t* data() override {
129
+ if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
130
+ throw std::runtime_error("`data()` can only be accessed synchronously on the JS Thread! "
131
+ "If you want to access it elsewhere, copy it first.");
132
+ }
133
+
134
+ OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
135
+ if (!_jsReference) [[unlikely]] {
136
+ // JS Part has been deleted - data is now nullptr.
137
+ return nullptr;
138
+ }
139
+ // JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
140
+ return _jsReference->data(*_runtime);
141
+ }
142
+
143
+ /**
144
+ * Gets the size of the data this `ArrayBuffer` points to, or `0` if it has already been deleted.
145
+ */
146
+ size_t size() const override {
147
+ if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
148
+ throw std::runtime_error("`size()` can only be accessed synchronously on the JS Thread! "
149
+ "If you want to access it elsewhere, copy it first.");
150
+ }
151
+
152
+ OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
153
+ if (!_jsReference) [[unlikely]] {
154
+ // JS Part has been deleted - size is now 0.
155
+ return 0;
156
+ }
157
+ // JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
158
+ return _jsReference->size(*_runtime);
159
+ }
160
+
161
+ bool isOwner() const noexcept override {
162
+ return false;
163
+ }
164
+
165
+ private:
166
+ jsi::Runtime* _runtime;
167
+ OwningReference<jsi::ArrayBuffer> _jsReference;
168
+ std::thread::id _initialThreadId;
169
+ };
170
+
79
171
  } // namespace margelo::nitro
@@ -5,10 +5,9 @@
5
5
  #pragma once
6
6
 
7
7
  #include "CountTrailingOptionals.hpp"
8
- #include "HybridContext.hpp"
9
8
  #include "JSIConverter.hpp"
10
- #include "NitroLogger.hpp"
11
9
  #include "OwningReference.hpp"
10
+ #include "TypeInfo.hpp"
12
11
  #include <functional>
13
12
  #include <jsi/jsi.h>
14
13
  #include <memory>
@@ -176,10 +175,10 @@ private:
176
175
  constexpr size_t maxArgsCount = sizeof...(Args);
177
176
  constexpr size_t minArgsCount = maxArgsCount - optionalArgsCount;
178
177
  bool isWithinArgsRange = (count >= minArgsCount && count <= maxArgsCount);
179
- if (!isWithinArgsRange) {
178
+ if (!isWithinArgsRange) [[unlikely]] {
180
179
  // invalid amount of arguments passed!
181
180
  std::string hybridObjectName = derivedInstance->_name;
182
- if (minArgsCount == maxArgsCount) {
181
+ if constexpr (minArgsCount == maxArgsCount) {
183
182
  // min and max args length is the same, so we don't have any optional parameters. fixed count
184
183
  throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected " + std::to_string(maxArgsCount) +
185
184
  " arguments, but received " + std::to_string(count) + "!");
@@ -0,0 +1,64 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ // Forward declare a few of the common types that might have cyclic includes.
8
+ namespace margelo::nitro {
9
+ struct AnyValue;
10
+ class AnyMap;
11
+
12
+ template <typename T, typename Enable>
13
+ struct JSIConverter;
14
+ } // namespace margelo::nitro
15
+
16
+ #include "JSIConverter+Variant.hpp"
17
+ #include "JSIConverter.hpp"
18
+
19
+ #include "AnyMap.hpp"
20
+ #include <jsi/jsi.h>
21
+ #include <memory>
22
+
23
+ namespace margelo::nitro {
24
+
25
+ using namespace facebook;
26
+
27
+ // AnyValue <> Record<K, V>
28
+ template <>
29
+ struct JSIConverter<AnyValue> {
30
+ static inline AnyValue fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
31
+ return JSIConverter<AnyValue::variant>::fromJSI(runtime, arg);
32
+ }
33
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const AnyValue& value) {
34
+ return JSIConverter<AnyValue::variant>::toJSI(runtime, value);
35
+ }
36
+ };
37
+
38
+ // AnyMap <> Record<K, V>
39
+ template <>
40
+ struct JSIConverter<std::shared_ptr<AnyMap>> {
41
+ static inline std::shared_ptr<AnyMap> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
42
+ jsi::Object object = arg.asObject(runtime);
43
+ jsi::Array propNames = object.getPropertyNames(runtime);
44
+ size_t size = propNames.size(runtime);
45
+ std::shared_ptr<AnyMap> map = AnyMap::make();
46
+ for (size_t i = 0; i < size; i++) {
47
+ jsi::String jsKey = propNames.getValueAtIndex(runtime, i).getString(runtime);
48
+ jsi::Value jsValue = object.getProperty(runtime, jsKey);
49
+ map->setAny(jsKey.utf8(runtime), JSIConverter<AnyValue>::fromJSI(runtime, jsValue));
50
+ }
51
+ return map;
52
+ }
53
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, std::shared_ptr<AnyMap> map) {
54
+ jsi::Object object(runtime);
55
+ for (const auto& item : map->getMap()) {
56
+ jsi::String key = jsi::String::createFromUtf8(runtime, item.first);
57
+ jsi::Value value = JSIConverter<AnyValue>::toJSI(runtime, item.second);
58
+ object.setProperty(runtime, std::move(key), std::move(value));
59
+ }
60
+ return object;
61
+ }
62
+ };
63
+
64
+ } // namespace margelo::nitro
@@ -0,0 +1,50 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ // Forward declare a few of the common types that might have cyclic includes.
8
+ namespace margelo::nitro {
9
+ class ArrayBuffer;
10
+
11
+ template <typename T>
12
+ struct JSICache;
13
+
14
+ template <typename T, typename Enable>
15
+ struct JSIConverter;
16
+ } // namespace margelo::nitro
17
+
18
+ #include "JSIConverter.hpp"
19
+
20
+ #include "ArrayBuffer.hpp"
21
+ #include "IsSharedPtrTo.hpp"
22
+ #include "JSICache.hpp"
23
+ #include <jsi/jsi.h>
24
+ #include <memory>
25
+ #include <type_traits>
26
+
27
+ namespace margelo::nitro {
28
+
29
+ using namespace facebook;
30
+
31
+ // MutableBuffer <> ArrayBuffer
32
+ template <typename T>
33
+ struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::MutableBuffer>>> {
34
+ static inline std::shared_ptr<ArrayBuffer> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
35
+ jsi::Object object = arg.asObject(runtime);
36
+ if (!object.isArrayBuffer(runtime)) [[unlikely]] {
37
+ throw std::runtime_error("Object \"" + arg.toString(runtime).utf8(runtime) + "\" is not an ArrayBuffer!");
38
+ }
39
+
40
+ JSICacheReference<jsi::ArrayBuffer> cache = JSICache<jsi::ArrayBuffer>::getOrCreateCache(runtime);
41
+ auto owningArrayBuffer = cache.makeGlobal(object.getArrayBuffer(runtime));
42
+
43
+ return std::make_shared<JSArrayBuffer>(&runtime, owningArrayBuffer);
44
+ }
45
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, std::shared_ptr<jsi::MutableBuffer> buffer) {
46
+ return jsi::ArrayBuffer(runtime, buffer);
47
+ }
48
+ };
49
+
50
+ } // namespace margelo::nitro
@@ -0,0 +1,124 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ // Forward declare a few of the common types that might have cyclic includes.
8
+ namespace margelo::nitro {
9
+ template <typename T>
10
+ class JSICache;
11
+
12
+ template <typename T, typename Enable>
13
+ struct JSIConverter;
14
+ } // namespace margelo::nitro
15
+
16
+ #include "JSIConverter.hpp"
17
+
18
+ #include "Dispatcher.hpp"
19
+ #include "FutureType.hpp"
20
+ #include "JSICache.hpp"
21
+ #include <functional>
22
+ #include <future>
23
+ #include <jsi/jsi.h>
24
+
25
+ namespace margelo::nitro {
26
+
27
+ using namespace facebook;
28
+
29
+ // [](Args...) -> T {} <> (Args...) => T
30
+ template <typename ReturnType, typename... Args>
31
+ struct JSIConverter<std::function<ReturnType(Args...)>> {
32
+ // std::future<T> -> T
33
+ using ResultingType = future_type_v<ReturnType>;
34
+
35
+ static inline std::function<ReturnType(Args...)> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
36
+ // Make function global - it'll be managed by the Runtime's memory, and we only have a weak_ref to it.
37
+ auto cache = JSICache<jsi::Function>::getOrCreateCache(runtime);
38
+ jsi::Function function = arg.asObject(runtime).asFunction(runtime);
39
+ OwningReference<jsi::Function> sharedFunction = cache.makeGlobal(std::move(function));
40
+
41
+ std::shared_ptr<Dispatcher> strongDispatcher = Dispatcher::getRuntimeGlobalDispatcher(runtime);
42
+ std::weak_ptr<Dispatcher> weakDispatcher = strongDispatcher;
43
+
44
+ // Create a C++ function that can be called by the consumer.
45
+ // This will call the jsi::Function if it is still alive.
46
+ return [&runtime, weakDispatcher, sharedFunction = std::move(sharedFunction)](Args... args) -> ReturnType {
47
+ // Try to get the JS Dispatcher if the Runtime is still alive
48
+ std::shared_ptr<Dispatcher> dispatcher = weakDispatcher.lock();
49
+ if (!dispatcher) {
50
+ if constexpr (std::is_void_v<ResultingType>) {
51
+ Logger::log("JSIConverter", "Tried calling void(..) function, but the JS Dispatcher has already been deleted by JS!");
52
+ return;
53
+ } else {
54
+ throw std::runtime_error("Cannot call the given Function - the JS Dispatcher has already been destroyed by the JS Runtime!");
55
+ }
56
+ }
57
+
58
+ if constexpr (std::is_void_v<ResultingType>) {
59
+ dispatcher->runAsync([&runtime, sharedFunction = std::move(sharedFunction), ... args = std::move(args)]() {
60
+ callJSFunction(runtime, sharedFunction, args...);
61
+ });
62
+ } else {
63
+ return dispatcher->runAsyncAwaitable<ResultingType>(
64
+ [&runtime, sharedFunction = std::move(sharedFunction), ... args = std::move(args)]() -> ResultingType {
65
+ return callJSFunction(runtime, sharedFunction, args...);
66
+ });
67
+ }
68
+ };
69
+ }
70
+
71
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, std::function<ReturnType(Args...)>&& function) {
72
+ jsi::HostFunctionType jsFunction = [function = std::move(function)](jsi::Runtime& runtime, const jsi::Value& thisValue,
73
+ const jsi::Value* args, size_t count) -> jsi::Value {
74
+ if (count != sizeof...(Args)) [[unlikely]] {
75
+ throw jsi::JSError(runtime, "Function expected " + std::to_string(sizeof...(Args)) + " arguments, but received " +
76
+ std::to_string(count) + "!");
77
+ }
78
+ return callHybridFunction(function, runtime, args, std::index_sequence_for<Args...>{});
79
+ };
80
+ return jsi::Function::createFromHostFunction(runtime, jsi::PropNameID::forUtf8(runtime, "hostFunction"), sizeof...(Args), jsFunction);
81
+ }
82
+
83
+ private:
84
+ static inline ResultingType callJSFunction(jsi::Runtime& runtime, const OwningReference<jsi::Function>& function, const Args&... args) {
85
+ // Throw a lock on the OwningReference<T> so we can guarantee safe access (Hermes GC cannot delete it while `lock` is alive)
86
+ OwningLock<jsi::Function> lock = function.lock();
87
+
88
+ if (!function) {
89
+ if constexpr (std::is_void_v<ResultingType>) {
90
+ // runtime has already been deleted. since this returns void, we can just ignore it being deleted.
91
+ Logger::log("JSIConverter", "Tried calling void(..) function, but it has already been deleted by JS!");
92
+ return;
93
+ } else {
94
+ // runtime has already been deleted, but we are expecting a return value - throw an error in this case.
95
+ throw std::runtime_error("Cannot call the given Function - the JS Dispatcher has already been destroyed by the JS Runtime!");
96
+ }
97
+ }
98
+
99
+ if constexpr (std::is_void_v<ResultingType>) {
100
+ // It returns void. Just call the function
101
+ function->call(runtime, JSIConverter<std::decay_t<Args>>::toJSI(runtime, args)...);
102
+ } else {
103
+ // It returns some kind of value - call the function, and convert the return value.
104
+ jsi::Value result = function->call(runtime, JSIConverter<std::decay_t<Args>>::toJSI(runtime, args)...);
105
+ return JSIConverter<ResultingType>::fromJSI(runtime, std::move(result));
106
+ }
107
+ }
108
+
109
+ template <size_t... Is>
110
+ static inline jsi::Value callHybridFunction(const std::function<ReturnType(Args...)>& function, jsi::Runtime& runtime,
111
+ const jsi::Value* args, std::index_sequence<Is...>) {
112
+ if constexpr (std::is_void_v<ReturnType>) {
113
+ // it is a void function (will return undefined in JS)
114
+ function(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
115
+ return jsi::Value::undefined();
116
+ } else {
117
+ // it is a custom type, parse it to a JS value
118
+ ReturnType result = function(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
119
+ return JSIConverter<ReturnType>::toJSI(runtime, result);
120
+ }
121
+ }
122
+ };
123
+
124
+ } // namespace margelo::nitro
@@ -0,0 +1,130 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ // Forward declare a few of the common types that might have cyclic includes.
8
+ namespace margelo::nitro {
9
+ class HybridObject;
10
+
11
+ template <typename T, typename Enable>
12
+ struct JSIConverter;
13
+ } // namespace margelo::nitro
14
+
15
+ #include "JSIConverter.hpp"
16
+
17
+ #include "HybridObject.hpp"
18
+ #include "IsSharedPtrTo.hpp"
19
+ #include "TypeInfo.hpp"
20
+ #include <jsi/jsi.h>
21
+ #include <memory>
22
+ #include <type_traits>
23
+
24
+ #define DO_NULL_CHECKS true
25
+
26
+ namespace margelo::nitro {
27
+
28
+ using namespace facebook;
29
+
30
+ // HybridObject <> {}
31
+ template <typename T>
32
+ struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::HostObject>>> {
33
+ using TPointee = typename T::element_type;
34
+
35
+ static inline T fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
36
+ #if DO_NULL_CHECKS
37
+ if (arg.isUndefined()) [[unlikely]] {
38
+ throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
39
+ }
40
+ if (!arg.isObject()) [[unlikely]] {
41
+ std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
42
+ throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not an object!"));
43
+ }
44
+ #endif
45
+ jsi::Object object = arg.asObject(runtime);
46
+ #if DO_NULL_CHECKS
47
+ if (!object.isHostObject(runtime)) [[unlikely]] {
48
+ std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
49
+ throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not a HostObject!"));
50
+ }
51
+ if (!object.isHostObject<TPointee>(runtime)) [[unlikely]] {
52
+ std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
53
+ throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is a different HostObject<T>!"));
54
+ }
55
+ #endif
56
+ return object.asHostObject<TPointee>(runtime);
57
+ }
58
+
59
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const T& arg) {
60
+ #if DO_NULL_CHECKS
61
+ if (arg == nullptr) [[unlikely]] {
62
+ std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
63
+ throw jsi::JSError(runtime, "Cannot convert nullptr to HostObject<" + typeName + ">!");
64
+ }
65
+ #endif
66
+ if constexpr (std::is_base_of_v<HybridObject, TPointee>) {
67
+ // It's a HybridObject - use it's internal constructor which caches jsi::Objects for proper memory management!
68
+ return arg->toObject(runtime);
69
+ } else {
70
+ // It's any other kind of jsi::HostObject - just create it as normal.
71
+ return jsi::Object::createFromHostObject(runtime, arg);
72
+ }
73
+ }
74
+
75
+ private:
76
+ static inline std::string invalidTypeErrorMessage(const std::string& typeDescription, const std::string& reason) {
77
+ std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
78
+ return "Cannot convert \"" + typeDescription + "\" to HostObject<" + typeName + ">! " + reason;
79
+ }
80
+ };
81
+
82
+ // NativeState <> {}
83
+ template <typename T>
84
+ struct JSIConverter<T, std::enable_if_t<is_shared_ptr_to_v<T, jsi::NativeState>>> {
85
+ using TPointee = typename T::element_type;
86
+
87
+ static inline T fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
88
+ #if DO_NULL_CHECKS
89
+ if (arg.isUndefined()) [[unlikely]] {
90
+ throw jsi::JSError(runtime, invalidTypeErrorMessage("undefined", "It is undefined!"));
91
+ }
92
+ if (!arg.isObject()) [[unlikely]] {
93
+ std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
94
+ throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not an object!"));
95
+ }
96
+ #endif
97
+ jsi::Object object = arg.asObject(runtime);
98
+ #if DO_NULL_CHECKS
99
+ if (!object.hasNativeState(runtime)) [[unlikely]] {
100
+ std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
101
+ throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is not a NativeState!"));
102
+ }
103
+ if (!object.hasNativeState<TPointee>(runtime)) [[unlikely]] {
104
+ std::string stringRepresentation = arg.toString(runtime).utf8(runtime);
105
+ throw jsi::JSError(runtime, invalidTypeErrorMessage(stringRepresentation, "It is a different NativeState<T>!"));
106
+ }
107
+ #endif
108
+ return object.getNativeState<TPointee>(runtime);
109
+ }
110
+
111
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const T& arg) {
112
+ #if DO_NULL_CHECKS
113
+ if (arg == nullptr) [[unlikely]] {
114
+ std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
115
+ throw jsi::JSError(runtime, "Cannot convert nullptr to NativeState<" + typeName + ">!");
116
+ }
117
+ #endif
118
+ jsi::Object object(runtime);
119
+ object.setNativeState(runtime, arg);
120
+ return object;
121
+ }
122
+
123
+ private:
124
+ static inline std::string invalidTypeErrorMessage(const std::string& typeDescription, const std::string& reason) {
125
+ std::string typeName = TypeInfo::getFriendlyTypename<TPointee>();
126
+ return "Cannot convert \"" + typeDescription + "\" to NativeState<" + typeName + ">! " + reason;
127
+ }
128
+ };
129
+
130
+ } // namespace margelo::nitro
@@ -0,0 +1,41 @@
1
+ //
2
+ // Created by Marc Rousavy on 21.02.24.
3
+ //
4
+
5
+ #pragma once
6
+
7
+ // Forward declare a few of the common types that might have cyclic includes.
8
+ namespace margelo::nitro {
9
+ template <typename T, typename Enable>
10
+ struct JSIConverter;
11
+ } // namespace margelo::nitro
12
+
13
+ #include "JSIConverter.hpp"
14
+
15
+ #include <jsi/jsi.h>
16
+ #include <optional>
17
+
18
+ namespace margelo::nitro {
19
+
20
+ using namespace facebook;
21
+
22
+ // std::optional<T> <> T | undefined
23
+ template <typename TInner>
24
+ struct JSIConverter<std::optional<TInner>> {
25
+ static inline std::optional<TInner> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
26
+ if (arg.isUndefined() || arg.isNull()) {
27
+ return std::nullopt;
28
+ } else {
29
+ return JSIConverter<TInner>::fromJSI(runtime, arg);
30
+ }
31
+ }
32
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, const std::optional<TInner>& arg) {
33
+ if (arg == std::nullopt) {
34
+ return jsi::Value::undefined();
35
+ } else {
36
+ return JSIConverter<TInner>::toJSI(runtime, arg.value());
37
+ }
38
+ }
39
+ };
40
+
41
+ } // namespace margelo::nitro