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.
Files changed (55) hide show
  1. package/README.md +7 -3
  2. package/android/CMakeLists.txt +1 -0
  3. package/cpp/core/HybridFunction.hpp +167 -0
  4. package/cpp/core/HybridObject.cpp +37 -128
  5. package/cpp/core/HybridObject.hpp +13 -149
  6. package/cpp/jsi/JSICache.cpp +25 -8
  7. package/cpp/jsi/JSICache.hpp +31 -24
  8. package/cpp/jsi/JSIConverter+AnyMap.hpp +22 -0
  9. package/cpp/jsi/JSIConverter+ArrayBuffer.hpp +15 -3
  10. package/cpp/jsi/JSIConverter+Function.hpp +9 -1
  11. package/cpp/jsi/JSIConverter+HybridObject.hpp +31 -76
  12. package/cpp/jsi/JSIConverter+Optional.hpp +9 -0
  13. package/cpp/jsi/JSIConverter+Promise.hpp +4 -0
  14. package/cpp/jsi/JSIConverter+Tuple.hpp +33 -0
  15. package/cpp/jsi/JSIConverter+UnorderedMap.hpp +20 -0
  16. package/cpp/jsi/JSIConverter+Variant.hpp +25 -53
  17. package/cpp/jsi/JSIConverter+Vector.hpp +21 -0
  18. package/cpp/jsi/JSIConverter.hpp +55 -7
  19. package/cpp/jsi/JSIHelpers.hpp +52 -0
  20. package/cpp/jsi/Promise.cpp +2 -2
  21. package/cpp/prototype/HybridObjectPrototype.cpp +87 -0
  22. package/cpp/prototype/HybridObjectPrototype.hpp +92 -0
  23. package/cpp/prototype/Prototype.hpp +163 -0
  24. package/cpp/prototype/PrototypeChain.hpp +78 -0
  25. package/cpp/threading/Dispatcher.cpp +1 -1
  26. package/cpp/turbomodule/NativeNitroModules.cpp +3 -4
  27. package/cpp/turbomodule/NativeNitroModules.hpp +1 -1
  28. package/cpp/utils/BorrowingReference+Owning.hpp +1 -1
  29. package/cpp/utils/BorrowingReference.hpp +1 -1
  30. package/ios/turbomodule/NitroModuleOnLoad.mm +0 -5
  31. package/lib/HybridObject.d.ts +20 -1
  32. package/lib/commonjs/index.js +0 -11
  33. package/lib/commonjs/index.js.map +1 -1
  34. package/lib/index.d.ts +0 -1
  35. package/lib/index.js +0 -1
  36. package/lib/module/index.js +0 -1
  37. package/lib/module/index.js.map +1 -1
  38. package/lib/tsconfig.tsbuildinfo +1 -1
  39. package/lib/typescript/HybridObject.d.ts +77 -0
  40. package/lib/typescript/HybridObject.d.ts.map +1 -0
  41. package/lib/typescript/NitroModules.d.ts.map +1 -0
  42. package/lib/typescript/index.d.ts +4 -0
  43. package/lib/typescript/index.d.ts.map +1 -0
  44. package/package.json +1 -1
  45. package/src/HybridObject.ts +20 -1
  46. package/src/index.ts +0 -1
  47. package/cpp/core/PointerHolder.hpp +0 -94
  48. package/cpp/test-object/TestHybridObject.cpp +0 -37
  49. package/cpp/test-object/TestHybridObject.hpp +0 -87
  50. package/cpp/utils/GetRuntimeID.hpp +0 -28
  51. package/lib/commonjs/createTestObject.js +0 -15
  52. package/lib/commonjs/createTestObject.js.map +0 -1
  53. package/lib/module/createTestObject.js +0 -8
  54. package/lib/module/createTestObject.js.map +0 -1
  55. package/src/createTestObject.ts +0 -40
@@ -25,18 +25,34 @@ using namespace facebook;
25
25
  * Value types, custom types (HostObjects), and even functions with any number of arguments/types are supported.
26
26
  * This type can be extended by just creating a new template for JSIConverter in a header.
27
27
  */
28
- template <typename ArgType, typename Enable = void>
28
+ template <typename T, typename Enable = void>
29
29
  struct JSIConverter final {
30
30
  JSIConverter() = delete;
31
31
 
32
- static inline ArgType fromJSI(jsi::Runtime&, const jsi::Value&) {
33
- static_assert(always_false<ArgType>::value, "This type is not supported by the JSIConverter!");
34
- return ArgType();
35
- }
36
- static inline jsi::Value toJSI(jsi::Runtime&, ArgType) {
37
- static_assert(always_false<ArgType>::value, "This type is not supported by the JSIConverter!");
32
+ /**
33
+ * Converts the given `jsi::Value` to type `T`.
34
+ * By default, this static-asserts.
35
+ */
36
+ static inline T fromJSI(jsi::Runtime&, const jsi::Value&) {
37
+ static_assert(always_false<T>::value, "This type is not supported by the JSIConverter!");
38
+ return T();
39
+ }
40
+ /**
41
+ * Converts `T` to a `jsi::Value`.
42
+ * By default, this static-asserts.
43
+ */
44
+ static inline jsi::Value toJSI(jsi::Runtime&, T) {
45
+ static_assert(always_false<T>::value, "This type is not supported by the JSIConverter!");
38
46
  return jsi::Value::undefined();
39
47
  }
48
+ /**
49
+ * Returns whether the given `jsi::Value` can be converted to `T`.
50
+ * This involves runtime type-checks.
51
+ * By default, this returns `false`.
52
+ */
53
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value&) {
54
+ return false;
55
+ }
40
56
 
41
57
  private:
42
58
  template <typename>
@@ -52,6 +68,9 @@ struct JSIConverter<int> {
52
68
  static inline jsi::Value toJSI(jsi::Runtime&, int arg) {
53
69
  return jsi::Value(arg);
54
70
  }
71
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
72
+ return value.isNumber();
73
+ }
55
74
  };
56
75
 
57
76
  // std::monostate <> null
@@ -63,6 +82,9 @@ struct JSIConverter<std::monostate> {
63
82
  static inline jsi::Value toJSI(jsi::Runtime&, std::monostate arg) {
64
83
  return jsi::Value::null();
65
84
  }
85
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
86
+ return value.isNull() || value.isUndefined();
87
+ }
66
88
  };
67
89
 
68
90
  // double <> number
@@ -74,6 +96,9 @@ struct JSIConverter<double> {
74
96
  static inline jsi::Value toJSI(jsi::Runtime&, double arg) {
75
97
  return jsi::Value(arg);
76
98
  }
99
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
100
+ return value.isNumber();
101
+ }
77
102
  };
78
103
 
79
104
  // float <> number
@@ -85,6 +110,9 @@ struct JSIConverter<float> {
85
110
  static inline jsi::Value toJSI(jsi::Runtime&, float arg) {
86
111
  return jsi::Value(static_cast<double>(arg));
87
112
  }
113
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
114
+ return value.isNumber();
115
+ }
88
116
  };
89
117
 
90
118
  // int64_t <> BigInt
@@ -96,6 +124,13 @@ struct JSIConverter<int64_t> {
96
124
  static inline jsi::Value toJSI(jsi::Runtime& runtime, int64_t arg) {
97
125
  return jsi::BigInt::fromInt64(runtime, arg);
98
126
  }
127
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
128
+ if (value.isBigInt()) {
129
+ jsi::BigInt bigint = value.getBigInt(runtime);
130
+ return bigint.isInt64(runtime);
131
+ }
132
+ return false;
133
+ }
99
134
  };
100
135
 
101
136
  // uint64_t <> BigInt
@@ -107,6 +142,13 @@ struct JSIConverter<uint64_t> {
107
142
  static inline jsi::Value toJSI(jsi::Runtime& runtime, uint64_t arg) {
108
143
  return jsi::BigInt::fromUint64(runtime, arg);
109
144
  }
145
+ static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
146
+ if (value.isBigInt()) {
147
+ jsi::BigInt bigint = value.getBigInt(runtime);
148
+ return bigint.isUint64(runtime);
149
+ }
150
+ return false;
151
+ }
110
152
  };
111
153
 
112
154
  // bool <> boolean
@@ -118,6 +160,9 @@ struct JSIConverter<bool> {
118
160
  static inline jsi::Value toJSI(jsi::Runtime&, bool arg) {
119
161
  return jsi::Value(arg);
120
162
  }
163
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
164
+ return value.isBool();
165
+ }
121
166
  };
122
167
 
123
168
  // std::string <> string
@@ -129,6 +174,9 @@ struct JSIConverter<std::string> {
129
174
  static inline jsi::Value toJSI(jsi::Runtime& runtime, const std::string& arg) {
130
175
  return jsi::String::createFromUtf8(runtime, arg);
131
176
  }
177
+ static inline bool canConvert(jsi::Runtime&, const jsi::Value& value) {
178
+ return value.isString();
179
+ }
132
180
  };
133
181
 
134
182
  } // namespace margelo::nitro
@@ -0,0 +1,52 @@
1
+ //
2
+ // JSIHelpers.hpp
3
+ // Pods
4
+ //
5
+ // Created by Marc Rousavy on 07.08.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "ThreadUtils.hpp"
11
+ #include <jsi/jsi.h>
12
+
13
+ namespace margelo::nitro {
14
+
15
+ using namespace facebook;
16
+
17
+ /**
18
+ * Returns whether the given `jsi::Object` is a plain-JS object, or not.
19
+ * If it is not a plain-JS object, it could be an Array, ArrayBuffer, Function,
20
+ * HostObject or NativeState.
21
+ */
22
+ static inline bool isPlainObject(jsi::Runtime& runtime, const jsi::Object& object) {
23
+ if (object.isArray(runtime)) {
24
+ return false;
25
+ }
26
+ if (object.isArrayBuffer(runtime)) {
27
+ return false;
28
+ }
29
+ if (object.isFunction(runtime)) {
30
+ return false;
31
+ }
32
+ if (object.isHostObject(runtime)) {
33
+ return false;
34
+ }
35
+ if (object.hasNativeState(runtime)) {
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+
41
+ /**
42
+ * Get an ID for the given Runtime.
43
+ *
44
+ * The ID usually consists of a Runtime description (e.g. "HermesRuntime"),
45
+ * and it's Thread (e.g. "com.facebook.react.runtime.JavaScript")
46
+ */
47
+ static inline std::string getRuntimeId(jsi::Runtime& runtime) {
48
+ std::string threadName = ThreadUtils::getThreadName();
49
+ return runtime.description() + std::string(" (") + threadName + std::string(")");
50
+ }
51
+
52
+ } // namespace margelo::nitro
@@ -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.makeGlobal<jsi::Function>(std::move(resolver));
13
- _rejecter = functionCache.makeGlobal<jsi::Function>(std::move(rejecter));
12
+ _resolver = functionCache.makeShared(std::move(resolver));
13
+ _rejecter = functionCache.makeShared(std::move(rejecter));
14
14
  }
15
15
 
16
16
  jsi::Value Promise::createPromise(jsi::Runtime& runtime, RunPromise&& run) {
@@ -0,0 +1,87 @@
1
+ //
2
+ // HybridObjectPrototype.cpp
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 07.08.24.
6
+ //
7
+
8
+ #include "HybridObjectPrototype.hpp"
9
+ #include "NitroLogger.hpp"
10
+
11
+ namespace margelo::nitro {
12
+
13
+ std::unordered_map<jsi::Runtime*, HybridObjectPrototype::PrototypeCache> HybridObjectPrototype::_prototypeCache;
14
+
15
+ jsi::Value HybridObjectPrototype::createPrototype(jsi::Runtime& runtime, const std::shared_ptr<Prototype>& prototype) {
16
+ // 0. Check if we're at the highest level of our prototype chain
17
+ if (prototype == nullptr) {
18
+ // There is no prototype - we just have an empty Object base - so `Object.create({})`
19
+ return jsi::Object(runtime);
20
+ }
21
+
22
+ // 1. Try looking for the given prototype in cache.
23
+ // If we find it in cache, we can create instances faster and skip creating the prototype from scratch!
24
+ auto& prototypeCache = _prototypeCache[&runtime];
25
+ auto cachedPrototype = prototypeCache.find(prototype->getNativeInstanceId());
26
+ if (cachedPrototype != prototypeCache.end()) {
27
+ const OwningReference<jsi::Object>& cachedObject = cachedPrototype->second;
28
+ return jsi::Value(runtime, *cachedObject).getObject(runtime);
29
+ }
30
+
31
+ // 2. We didn't find the given prototype in cache (either it's a new prototype, or a new runtime),
32
+ // so we need to create it. First, we need some helper methods from JS
33
+ Logger::log(TAG, "Creating new JS prototype for C++ instance type \"%s\"...", prototype->getNativeInstanceId().name());
34
+ jsi::Object objectConstructor = runtime.global().getPropertyAsObject(runtime, "Object");
35
+ jsi::Function objectCreate = objectConstructor.getPropertyAsFunction(runtime, "create");
36
+ jsi::Function objectDefineProperty = objectConstructor.getPropertyAsFunction(runtime, "defineProperty");
37
+
38
+ // 3. Create an empty JS Object, inheriting from the base prototype (recursively!)
39
+ jsi::Object object = objectCreate.call(runtime, createPrototype(runtime, prototype->getBase())).getObject(runtime);
40
+
41
+ // 4. Add all Hybrid Methods to it
42
+ for (const auto& method : prototype->getMethods()) {
43
+ object.setProperty(runtime, method.first.c_str(), method.second.toJSFunction(runtime));
44
+ }
45
+
46
+ // 5. Add all properties (getter + setter) to it using defineProperty
47
+ for (const auto& getter : prototype->getGetters()) {
48
+ jsi::Object property(runtime);
49
+ property.setProperty(runtime, "configurable", false);
50
+ property.setProperty(runtime, "enumerable", true);
51
+ property.setProperty(runtime, "get", getter.second.toJSFunction(runtime));
52
+
53
+ const auto& setter = prototype->getSetters().find(getter.first);
54
+ if (setter != prototype->getSetters().end()) {
55
+ // there also is a setter for this property!
56
+ property.setProperty(runtime, "set", setter->second.toJSFunction(runtime));
57
+ }
58
+
59
+ property.setProperty(runtime, "name", jsi::String::createFromUtf8(runtime, getter.first.c_str()));
60
+ objectDefineProperty.call(runtime,
61
+ /* obj */ object,
62
+ /* propName */ jsi::String::createFromUtf8(runtime, getter.first.c_str()),
63
+ /* descriptorObj */ property);
64
+ }
65
+
66
+ // 6. Throw it into our cache so the next lookup can be cached and therefore faster
67
+ JSICacheReference jsiCache = JSICache::getOrCreateCache(runtime);
68
+ OwningReference<jsi::Object> cachedObject = jsiCache.makeShared(std::move(object));
69
+ prototypeCache.emplace(prototype->getNativeInstanceId(), cachedObject);
70
+
71
+ // 7. In DEBUG, add a __type info to the prototype object.
72
+ #if DEBUG
73
+ auto typeName = "Prototype<" + std::string(prototype->getNativeInstanceId().name()) + ">";
74
+ cachedObject->setProperty(runtime, "__type", jsi::String::createFromUtf8(runtime, typeName));
75
+ #endif
76
+
77
+ // 8. Return it!
78
+ return jsi::Value(runtime, *cachedObject);
79
+ }
80
+
81
+ jsi::Value HybridObjectPrototype::getPrototype(jsi::Runtime& runtime) {
82
+ ensureInitialized();
83
+
84
+ return createPrototype(runtime, _prototypeChain.getPrototype());
85
+ }
86
+
87
+ } // namespace margelo::nitro
@@ -0,0 +1,92 @@
1
+ //
2
+ // HybridObjectPrototype.hpp
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 07.08.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "HybridFunction.hpp"
11
+ #include "OwningReference.hpp"
12
+ #include "Prototype.hpp"
13
+ #include "PrototypeChain.hpp"
14
+ #include <functional>
15
+ #include <jsi/jsi.h>
16
+ #include <memory>
17
+ #include <mutex>
18
+ #include <string>
19
+ #include <type_traits>
20
+
21
+ namespace margelo::nitro {
22
+
23
+ using namespace facebook;
24
+
25
+ /**
26
+ * Represents a Hybrid Object's prototype.
27
+ * The prototype should be cached per Runtime, and can be assigned to multiple jsi::Objects.
28
+ * When assigned to a jsi::Object, all methods of this prototype can be called on that jsi::Object,
29
+ * as long as it has a valid NativeState (`this`).
30
+ */
31
+ class HybridObjectPrototype {
32
+ private:
33
+ PrototypeChain _prototypeChain;
34
+ bool _didLoadMethods = false;
35
+ static constexpr auto TAG = "HybridObjectPrototype";
36
+
37
+ public:
38
+ HybridObjectPrototype() {}
39
+
40
+ public:
41
+ /**
42
+ * Get a fully initialized jsi::Object that represents this prototype to JS.
43
+ * The result of this value will be cached per Runtime, so it's safe to call this often.
44
+ */
45
+ jsi::Value getPrototype(jsi::Runtime& runtime);
46
+
47
+ private:
48
+ static jsi::Value createPrototype(jsi::Runtime& runtime, const std::shared_ptr<Prototype>& prototype);
49
+ using PrototypeCache = std::unordered_map<NativeInstanceId, OwningReference<jsi::Object>>;
50
+ static std::unordered_map<jsi::Runtime*, PrototypeCache> _prototypeCache;
51
+
52
+ protected:
53
+ /**
54
+ * Loads all Hybrid Methods that will be initialized in this Prototype.
55
+ * This will only be called once for the first time the Prototype will be created,
56
+ * so don't conditionally register methods.
57
+ */
58
+ virtual void loadHybridMethods() = 0;
59
+
60
+ private:
61
+ /**
62
+ * Ensures that all Hybrid Methods, Getters and Setters are initialized by calling loadHybridMethods().
63
+ */
64
+ inline void ensureInitialized() {
65
+ if (!_didLoadMethods) [[unlikely]] {
66
+ // lazy-load all exposed methods
67
+ loadHybridMethods();
68
+ _didLoadMethods = true;
69
+ }
70
+ }
71
+
72
+ protected:
73
+ using RegisterFn = void (*)(Prototype&);
74
+ /**
75
+ * Registers the given methods inside the Hybrid Object's prototype.
76
+ *
77
+ * For subsequent HybridObjects of the same type, `registerFunc` will not be called again, as the
78
+ * prototype will already be known and cached.
79
+ * **Do not conditionally register hybrid methods, getters or setter!**
80
+ */
81
+ template <typename Derived>
82
+ inline void registerHybrids(Derived* thisInstance, RegisterFn registerFunc) {
83
+ const std::shared_ptr<Prototype>& prototype = _prototypeChain.extendPrototype<Derived>();
84
+
85
+ if (!prototype->hasHybrids()) {
86
+ // The `Prototype` does not have any methods or properties registered yet - so do it now
87
+ registerFunc(*prototype);
88
+ }
89
+ }
90
+ };
91
+
92
+ } // namespace margelo::nitro
@@ -0,0 +1,163 @@
1
+ //
2
+ // PrototypeChain.hpp
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 07.08.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "HybridFunction.hpp"
11
+ #include <memory>
12
+ #include <string>
13
+ #include <typeindex>
14
+ #include <unordered_map>
15
+
16
+ namespace margelo::nitro {
17
+
18
+ /**
19
+ * Represents a Prototype's native C++ type ID.
20
+ * This can be used to identify a prototype against a C++ instance,
21
+ * or used as a cache-key.
22
+ */
23
+ using NativeInstanceId = std::type_index;
24
+
25
+ /**
26
+ * Represents a JS `Prototype`'s structure.
27
+ * Every prototype has a related C++ type ID (`instanceTypeId`).
28
+ * Prototypes can be sub-classes, in which case they have a `base` prototype.
29
+ * Each prototype has a list of methods, and properties (getters + setters).
30
+ *
31
+ * By using this `Prototype` structure, we can create JS objects that act
32
+ * as prototypes for `HybridObject`s.
33
+ *
34
+ * While a `Prototype` actually holds all the methods, a `HybridObject` only
35
+ * contains state and memory.
36
+ * This way the JS VM doesn't need to re-create methods for each `HybridObject`,
37
+ * they are only initialized once on the shared `Prototype`.
38
+ */
39
+ struct Prototype final {
40
+ private:
41
+ std::shared_ptr<Prototype> _base = nullptr;
42
+ NativeInstanceId _instanceTypeId;
43
+ std::unordered_map<std::string, HybridFunction> _methods;
44
+ std::unordered_map<std::string, HybridFunction> _getters;
45
+ std::unordered_map<std::string, HybridFunction> _setters;
46
+
47
+ private:
48
+ Prototype(const NativeInstanceId& typeId, const std::shared_ptr<Prototype>& base) : _instanceTypeId(typeId), _base(base) {}
49
+
50
+ public:
51
+ /**
52
+ * Gets a `Prototype` specification/node for the given native C++ type ID.
53
+ *
54
+ * If the given C++ type ID is unknown, a new `Prototype` node is created,
55
+ * which has to be initialized with methods, getters and setters first.
56
+ *
57
+ * If the given C++ type ID is already known in the static `Prototype` tree,
58
+ * a shared reference to it is returned.
59
+ */
60
+ static std::shared_ptr<Prototype> get(const NativeInstanceId& typeId, const std::shared_ptr<Prototype>& base = nullptr) {
61
+ static std::unordered_map<NativeInstanceId, std::shared_ptr<Prototype>> _prototypesCache;
62
+
63
+ const auto& found = _prototypesCache.find(typeId);
64
+ if (found != _prototypesCache.end()) {
65
+ // We know this C++ type ID / Prototype - return it!
66
+ return found->second;
67
+ } else {
68
+ // This is the first time we see this C++ type ID - create a new base Prototype for this.
69
+ auto prototype = std::shared_ptr<Prototype>(new Prototype(typeId, base));
70
+ _prototypesCache.emplace(typeId, prototype);
71
+ return prototype;
72
+ }
73
+ }
74
+
75
+ public:
76
+ template <typename T>
77
+ inline bool isNativeInstance() const noexcept {
78
+ return _instanceTypeId == std::type_index(typeid(T));
79
+ }
80
+
81
+ inline bool hasHybrids() const {
82
+ return !_methods.empty() || !_getters.empty() || !_setters.empty();
83
+ }
84
+
85
+ inline bool hasBase() const noexcept {
86
+ return _base != nullptr;
87
+ }
88
+ inline const std::shared_ptr<Prototype>& getBase() const noexcept {
89
+ return _base;
90
+ }
91
+ inline const NativeInstanceId& getNativeInstanceId() const noexcept {
92
+ return _instanceTypeId;
93
+ }
94
+ inline const std::unordered_map<std::string, HybridFunction>& getMethods() const noexcept {
95
+ return _methods;
96
+ }
97
+ inline const std::unordered_map<std::string, HybridFunction>& getGetters() const noexcept {
98
+ return _getters;
99
+ }
100
+ inline const std::unordered_map<std::string, HybridFunction>& getSetters() const noexcept {
101
+ return _setters;
102
+ }
103
+
104
+ public:
105
+ /**
106
+ * Registers the given C++ method as a Hybrid Method that can be called from JS, through the object's Prototype.
107
+ * Example:
108
+ * ```cpp
109
+ * registerHybridMethod("sayHello", &MyObject::sayHello);
110
+ * ```
111
+ */
112
+ template <typename Derived, typename ReturnType, typename... Args>
113
+ inline void registerHybridMethod(std::string name, ReturnType (Derived::*method)(Args...)) {
114
+ if (_getters.contains(name) || _setters.contains(name)) [[unlikely]] {
115
+ throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a property with that name already exists!");
116
+ }
117
+ if (_methods.contains(name)) [[unlikely]] {
118
+ throw std::runtime_error("Cannot add Hybrid Method \"" + name + "\" - a method with that name already exists!");
119
+ }
120
+
121
+ _methods.emplace(name, HybridFunction::createHybridFunction(name, method, FunctionType::METHOD));
122
+ }
123
+
124
+ /**
125
+ * Registers the given C++ method as a property getter that can be called from JS, through the object's Prototype.
126
+ * Example:
127
+ * ```cpp
128
+ * registerHybridGetter("foo", &MyObject::getFoo);
129
+ * ```
130
+ */
131
+ template <typename Derived, typename ReturnType>
132
+ inline void registerHybridGetter(std::string name, ReturnType (Derived::*method)()) {
133
+ if (_getters.contains(name)) [[unlikely]] {
134
+ throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a getter with that name already exists!");
135
+ }
136
+ if (_methods.contains(name)) [[unlikely]] {
137
+ throw std::runtime_error("Cannot add Hybrid Property Getter \"" + name + "\" - a method with that name already exists!");
138
+ }
139
+
140
+ _getters.emplace(name, HybridFunction::createHybridFunction(name, method, FunctionType::GETTER));
141
+ }
142
+
143
+ /**
144
+ * Registers the given C++ method as a property setter that can be called from JS, through the object's Prototype.
145
+ * Example:
146
+ * ```cpp
147
+ * registerHybridSetter("foo", &MyObject::setFoo);
148
+ * ```
149
+ */
150
+ template <typename Derived, typename ValueType>
151
+ inline void registerHybridSetter(std::string name, void (Derived::*method)(ValueType)) {
152
+ if (_setters.contains(name)) [[unlikely]] {
153
+ throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a setter with that name already exists!");
154
+ }
155
+ if (_methods.contains(name)) [[unlikely]] {
156
+ throw std::runtime_error("Cannot add Hybrid Property Setter \"" + name + "\" - a method with that name already exists!");
157
+ }
158
+
159
+ _setters.emplace(name, HybridFunction::createHybridFunction(name, method, FunctionType::SETTER));
160
+ }
161
+ };
162
+
163
+ } // namespace margelo::nitro
@@ -0,0 +1,78 @@
1
+ //
2
+ // PrototypeChain.hpp
3
+ // NitroModules
4
+ //
5
+ // Created by Marc Rousavy on 07.08.24.
6
+ //
7
+
8
+ #pragma once
9
+
10
+ #include "Prototype.hpp"
11
+
12
+ namespace margelo::nitro {
13
+
14
+ /**
15
+ * Represents a mutable chain of prototypes.
16
+ * Callers can use this class to incrementally add sub-classes to prototypes and build
17
+ * prototype chains/trees using C++ type information.
18
+ *
19
+ * The template methods can be used to find a specific C++ instance in the prototype tree,
20
+ * or create a new sub-class if it cannot be found.
21
+ */
22
+ class PrototypeChain final {
23
+ private:
24
+ std::shared_ptr<Prototype> _prototype;
25
+
26
+ public:
27
+ PrototypeChain() {}
28
+
29
+ public:
30
+ /**
31
+ * Gets the current `Prototype` as a whole.
32
+ * This does not do any modifications to the Prototype tree.
33
+ */
34
+ inline const std::shared_ptr<Prototype>& getPrototype() const {
35
+ return _prototype;
36
+ }
37
+
38
+ public:
39
+ /**
40
+ * Extends the Prototype with the given type `Derived`.
41
+ * If the Prototype already extended `Derived`, this returns the current state.
42
+ */
43
+ template <typename Derived>
44
+ inline const std::shared_ptr<Prototype>& extendPrototype() {
45
+ if (_prototype == nullptr) {
46
+ _prototype = Prototype::get(typeid(Derived));
47
+ }
48
+
49
+ return getOrExtendPrototype<Derived>(_prototype);
50
+ }
51
+
52
+ private:
53
+ /**
54
+ * Perform a bottom-down search of the given `Derived` C++ type info.
55
+ * If the current prototype tree does not have a Prototype that represents the
56
+ * C++ type `Derived`, it will extend it at the bottom and shift the `Prototype` tree
57
+ * up by one.
58
+ */
59
+ template <typename Derived>
60
+ inline const std::shared_ptr<Prototype>& getOrExtendPrototype(const std::shared_ptr<Prototype>& node) {
61
+ if (node->isNativeInstance<Derived>()) {
62
+ // If the Prototype represents the caller type (`Derived`), we work with this Prototype.
63
+ return node;
64
+ } else {
65
+ if (node->hasBase()) {
66
+ // We didn't find a match in this prototype, let's recursively try it's parent!
67
+ return getOrExtendPrototype<Derived>(node->getBase());
68
+ } else {
69
+ // We didn't find `Derived` and we don't have a base- add a child and shift the tree by one.
70
+ auto newBase = _prototype;
71
+ _prototype = Prototype::get(typeid(Derived), newBase);
72
+ return _prototype;
73
+ }
74
+ }
75
+ }
76
+ };
77
+
78
+ } // namespace margelo::nitro
@@ -1,5 +1,5 @@
1
1
  #include "Dispatcher.hpp"
2
- #include "GetRuntimeID.hpp"
2
+ #include "JSIHelpers.hpp"
3
3
  #include "NitroLogger.hpp"
4
4
 
5
5
  namespace margelo::nitro {
@@ -9,7 +9,6 @@
9
9
  #include "CallInvokerDispatcher.hpp"
10
10
  #include "Dispatcher.hpp"
11
11
  #include "HybridObjectRegistry.hpp"
12
- #include "TestHybridObject.hpp"
13
12
 
14
13
  namespace facebook::react {
15
14
 
@@ -59,12 +58,12 @@ void NativeNitroModules::install(jsi::Runtime& runtime) {
59
58
  Dispatcher::installRuntimeGlobalDispatcher(runtime, dispatcher);
60
59
  }
61
60
 
62
- jsi::Object NativeNitroModules::createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName,
63
- const std::optional<jsi::Object>& args) {
61
+ jsi::Value NativeNitroModules::createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName,
62
+ const std::optional<jsi::Object>& args) {
64
63
  auto name = hybridObjectName.utf8(runtime);
65
64
  // TODO: Pass args? Do we need that?
66
65
  auto hybridObject = HybridObjectRegistry::createHybridObject(name.c_str());
67
- return jsi::Object::createFromHostObject(runtime, hybridObject);
66
+ return hybridObject->toObject(runtime);
68
67
  }
69
68
 
70
69
  } // namespace facebook::react
@@ -23,7 +23,7 @@ public:
23
23
  jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& propName) override;
24
24
 
25
25
  void install(jsi::Runtime& runtime);
26
- jsi::Object createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName, const std::optional<jsi::Object>& args);
26
+ jsi::Value createHybridObject(jsi::Runtime& runtime, const jsi::String& hybridObjectName, const std::optional<jsi::Object>& args);
27
27
 
28
28
  public:
29
29
  constexpr static auto kModuleName = "NitroModulesCxx";
@@ -22,7 +22,7 @@ BorrowingReference<T>::BorrowingReference(const OwningReference<T>& ref) {
22
22
  }
23
23
 
24
24
  template <typename T>
25
- OwningReference<T> BorrowingReference<T>::lock() {
25
+ OwningReference<T> BorrowingReference<T>::lock() const {
26
26
  std::unique_lock lock(*_mutex);
27
27
 
28
28
  if (*_isDeleted) {
@@ -82,7 +82,7 @@ public:
82
82
  Try to lock the borrowing reference to an owning reference, or `nullptr` if it has already been deleted.
83
83
  */
84
84
  [[nodiscard]]
85
- OwningReference<T> lock();
85
+ OwningReference<T> lock() const;
86
86
 
87
87
  public:
88
88
  friend class OwningReference<T>;