react-native-nitro-modules 0.5.0 → 0.7.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 (98) hide show
  1. package/NitroModules.podspec +3 -1
  2. package/README.md +33 -14
  3. package/android/gradle.properties +5 -5
  4. package/android/src/main/cpp/JNIOnLoad.cpp +8 -0
  5. package/android/src/main/cpp/core/ByteBufferArrayBuffer.hpp +47 -0
  6. package/android/src/main/cpp/core/JAnyMap.hpp +193 -0
  7. package/android/src/main/cpp/core/JAnyValue.hpp +191 -0
  8. package/android/src/main/cpp/core/JArrayBuffer.hpp +124 -0
  9. package/android/src/main/cpp/core/JHybridObject.hpp +11 -4
  10. package/android/src/main/cpp/core/JPromise.hpp +88 -0
  11. package/android/src/main/cpp/platform/NitroLogger.cpp +36 -0
  12. package/android/src/main/cpp/registry/JHybridObjectInitializer.hpp +1 -1
  13. package/android/src/main/cpp/registry/JHybridObjectRegistry.cpp +1 -1
  14. package/android/src/main/cpp/registry/JHybridObjectRegistry.hpp +1 -1
  15. package/android/src/main/cpp/utils/{JNISharedPtr.h → JNISharedPtr.hpp} +13 -1
  16. package/android/src/main/java/com/margelo/nitro/JNIOnLoad.java +27 -0
  17. package/android/src/main/java/com/margelo/nitro/NitroModulesPackage.java +1 -9
  18. package/android/src/main/java/com/margelo/nitro/core/AnyMap.kt +80 -0
  19. package/android/src/main/java/com/margelo/nitro/core/AnyValue.kt +164 -0
  20. package/android/src/main/java/com/margelo/nitro/core/ArrayBuffer.kt +115 -0
  21. package/android/src/main/java/com/margelo/nitro/{HybridObject.kt → core/HybridObject.kt} +12 -15
  22. package/android/src/main/java/com/margelo/nitro/{HybridObjectInitializer.java → core/HybridObjectInitializer.java} +1 -1
  23. package/android/src/main/java/com/margelo/nitro/{HybridObjectRegistry.java → core/HybridObjectRegistry.java} +6 -4
  24. package/android/src/main/java/com/margelo/nitro/core/Promise.kt +115 -0
  25. package/cpp/core/AnyMap.hpp +2 -2
  26. package/cpp/core/ArrayBuffer.cpp +5 -5
  27. package/cpp/core/ArrayBuffer.hpp +7 -9
  28. package/cpp/core/HybridFunction.hpp +120 -46
  29. package/cpp/core/HybridObject.cpp +17 -6
  30. package/cpp/core/HybridObject.hpp +32 -8
  31. package/cpp/jsi/JSICache.cpp +5 -5
  32. package/cpp/jsi/JSICache.hpp +1 -3
  33. package/cpp/jsi/JSIConverter+AnyMap.hpp +2 -2
  34. package/cpp/jsi/JSIConverter+ArrayBuffer.hpp +2 -2
  35. package/cpp/jsi/JSIConverter+Function.hpp +7 -6
  36. package/cpp/jsi/JSIConverter+HybridObject.hpp +6 -5
  37. package/cpp/jsi/JSIConverter+Optional.hpp +1 -1
  38. package/cpp/jsi/JSIConverter+Promise.hpp +5 -4
  39. package/cpp/jsi/JSIConverter+Tuple.hpp +1 -1
  40. package/cpp/jsi/JSIConverter+UnorderedMap.hpp +1 -1
  41. package/cpp/jsi/JSIConverter+Variant.hpp +1 -3
  42. package/cpp/jsi/JSIConverter+Vector.hpp +1 -1
  43. package/cpp/jsi/JSIConverter.hpp +11 -11
  44. package/cpp/jsi/JSPromise.cpp +2 -2
  45. package/cpp/platform/NitroLogger.hpp +67 -0
  46. package/cpp/prototype/HybridObjectPrototype.cpp +2 -2
  47. package/cpp/prototype/Prototype.hpp +43 -22
  48. package/cpp/registry/HybridObjectRegistry.cpp +4 -4
  49. package/cpp/threading/Dispatcher.cpp +4 -3
  50. package/cpp/threading/ThreadPool.cpp +2 -2
  51. package/cpp/turbomodule/NativeNitroModules.cpp +26 -8
  52. package/cpp/turbomodule/NativeNitroModules.h +1 -0
  53. package/cpp/turbomodule/NativeNitroModules.hpp +2 -0
  54. package/cpp/utils/TypeInfo.hpp +14 -0
  55. package/ios/core/AnyMapHolder.hpp +91 -0
  56. package/ios/core/AnyMapHolder.swift +316 -0
  57. package/ios/core/ArrayBufferHolder.hpp +7 -5
  58. package/ios/core/ArrayBufferHolder.swift +22 -7
  59. package/ios/core/HybridContext.hpp +3 -3
  60. package/ios/core/Promise.swift +20 -0
  61. package/ios/core/PromiseHolder.hpp +15 -11
  62. package/ios/platform/NitroLogger.mm +36 -0
  63. package/ios/platform/ThreadUtils.cpp +1 -1
  64. package/ios/turbomodule/NitroModuleOnLoad.mm +2 -1
  65. package/ios/utils/SwiftClosure.hpp +63 -0
  66. package/ios/utils/SwiftClosure.swift +58 -0
  67. package/lib/AnyMap.d.ts +3 -0
  68. package/lib/HybridObject.d.ts +15 -0
  69. package/lib/NativeNitroModules.d.ts +2 -0
  70. package/lib/NitroModules.d.ts +18 -0
  71. package/lib/NitroModules.js +24 -0
  72. package/lib/commonjs/NativeNitroModules.js.map +1 -1
  73. package/lib/commonjs/NitroModules.js +24 -0
  74. package/lib/commonjs/NitroModules.js.map +1 -1
  75. package/lib/commonjs/package.json +1 -0
  76. package/lib/module/AnyMap.js +1 -1
  77. package/lib/module/HybridObject.js +1 -1
  78. package/lib/module/ModuleNotFoundError.js +2 -0
  79. package/lib/module/ModuleNotFoundError.js.map +1 -1
  80. package/lib/module/NativeNitroModules.js +2 -0
  81. package/lib/module/NativeNitroModules.js.map +1 -1
  82. package/lib/module/NativeNitroModules.web.js +2 -0
  83. package/lib/module/NativeNitroModules.web.js.map +1 -1
  84. package/lib/module/NitroModules.js +26 -0
  85. package/lib/module/NitroModules.js.map +1 -1
  86. package/lib/module/index.js +2 -0
  87. package/lib/module/index.js.map +1 -1
  88. package/lib/module/package.json +1 -0
  89. package/lib/tsconfig.tsbuildinfo +1 -1
  90. package/package.json +13 -12
  91. package/src/AnyMap.ts +3 -0
  92. package/src/HybridObject.ts +15 -0
  93. package/src/NativeNitroModules.ts +5 -0
  94. package/src/NitroModules.ts +24 -0
  95. package/android/src/main/cpp/core/JHybridObject.cpp +0 -8
  96. package/cpp/templates/IsInPack.hpp +0 -21
  97. package/cpp/utils/NitroLogger.hpp +0 -58
  98. package/ios/utils/ClosureWrapper.swift +0 -45
@@ -0,0 +1,115 @@
1
+ package com.margelo.nitro.core
2
+
3
+ import androidx.annotation.Keep
4
+ import com.facebook.jni.HybridData
5
+ import com.facebook.proguard.annotations.DoNotStrip
6
+ import dalvik.annotation.optimization.CriticalNative
7
+ import java.nio.ByteBuffer
8
+
9
+ /**
10
+ * An ArrayBuffer instance shared between native (Kotlin/C++) and JS.
11
+ *
12
+ * A `ByteBuffer` will be used as the underlying `ArrayBuffer`'s data,
13
+ * which has to remain valid for as long as the `ArrayBuffer` is alive.
14
+ */
15
+ @Suppress("KotlinJniMissingFunction")
16
+ @Keep
17
+ @DoNotStrip
18
+ class ArrayBuffer {
19
+ /**
20
+ * Holds the native C++ instance of the `ArrayBuffer`.
21
+ */
22
+ private val mHybridData: HybridData
23
+
24
+ /**
25
+ * Whether this `ArrayBuffer` is an **owning-**, or a **non-owning-** `ArrayBuffer`.
26
+ * - **Owning** ArrayBuffers can safely be held in memory for longer, and accessed at any point.
27
+ * - **Non-owning** ArrayBuffers can not be held in memory for longer, and can only be safely
28
+ * accessed within the synchronous function's scope (aka on the JS Thread). Once you switch Threads,
29
+ * data access is not safe anymore. If you need to access data longer, copy the data.
30
+ */
31
+ val isOwner: Boolean
32
+ get() = getIsOwner()
33
+
34
+ /**
35
+ * Whether this `ArrayBuffer` is holding a `ByteBuffer`, or not.
36
+ * - If the `ArrayBuffer` holds a `ByteBuffer`, `getBuffer(false)` can safely be called to
37
+ * get shared access to the underlying data, without performing any copies.
38
+ * - If the `ArrayBuffer` doesn't hold a `ByteBuffer`, it can still be accessed via `getBuffer(false)`,
39
+ * but the returned `ByteBuffer` is only valid as long as it's parent `ArrayBuffer` is alive.
40
+ */
41
+ val isByteBuffer: Boolean
42
+ get() = getIsByteBuffer()
43
+
44
+ /**
45
+ * Get the size of bytes in this `ArrayBuffer`.
46
+ */
47
+ val size: Int
48
+ get() = getBufferSize()
49
+
50
+ /**
51
+ * Get a `ByteBuffer` that holds- or wraps- the underlying data.
52
+ * - If this `ArrayBuffer` has been created from Kotlin/Java, it is already holding a
53
+ * `ByteBuffer` (`isByteBuffer == true`). In this case, the returned buffer is safe to access,
54
+ * even after the `ArrayBuffer` has already been destroyed in JS.
55
+ * - If this `ArrayBuffer` has been created elsewhere (C++/JS), it is not holding a
56
+ * `ByteBuffer` (`isByteBuffer == false`). In this case, the returned buffer will either be a copy
57
+ * of the data (`copyIfNeeded == true`), or just wrapping the data (`copyIfNeeded == false`).
58
+ *
59
+ * @param copyIfNeeded If this `ArrayBuffer` is not holding a `ByteBuffer` (`isByteBuffer == false`),
60
+ * the foreign data needs to be either _wrapped_, or _copied_ to be represented as a `ByteBuffer`.
61
+ * This flag controls that behaviour.
62
+ */
63
+ fun getBuffer(copyIfNeeded: Boolean): ByteBuffer {
64
+ return getByteBuffer(copyIfNeeded)
65
+ }
66
+
67
+ /**
68
+ * Create a new **owning-** `ArrayBuffer` that holds the given `ByteBuffer`.
69
+ * The `ByteBuffer` needs to remain valid for as long as the `ArrayBuffer` is alive.
70
+ */
71
+ constructor(byteBuffer: ByteBuffer) {
72
+ if (!byteBuffer.isDirect) {
73
+ throw Error("ArrayBuffers can only be created from direct ByteBuffers, " +
74
+ "and the given ByteBuffer is not direct!")
75
+ }
76
+ mHybridData = initHybrid(byteBuffer)
77
+ }
78
+
79
+ /**
80
+ * Create a new **non-owning-** `ArrayBuffer` that holds foreign data, potentially coming from JS.
81
+ */
82
+ @Suppress("unused")
83
+ private constructor(hybridData: HybridData) {
84
+ mHybridData = hybridData
85
+ }
86
+
87
+ private external fun initHybrid(buffer: ByteBuffer): HybridData
88
+ private external fun getByteBuffer(copyIfNeeded: Boolean): ByteBuffer
89
+ @CriticalNative
90
+ private external fun getIsOwner(): Boolean
91
+ @CriticalNative
92
+ private external fun getIsByteBuffer(): Boolean
93
+ @CriticalNative
94
+ private external fun getBufferSize(): Int
95
+
96
+ companion object {
97
+ /**
98
+ * Copy the given `ArrayBuffer` into a new **owning** `ArrayBuffer`.
99
+ */
100
+ fun copyOf(other: ArrayBuffer): ArrayBuffer {
101
+ // 1. Create a new buffer with the same size as the other
102
+ val newBuffer = ByteBuffer.allocateDirect(other.size)
103
+ // 2. Prepare the source buffer
104
+ val originalBuffer = other.getBuffer(false)
105
+ originalBuffer.rewind()
106
+ // 3. Copy over the source buffer into the new buffer
107
+ newBuffer.put(originalBuffer)
108
+ // 4. Rewind both buffers again to index 0
109
+ newBuffer.rewind()
110
+ originalBuffer.rewind()
111
+ // 5. Create a new `ArrayBuffer`
112
+ return ArrayBuffer(newBuffer)
113
+ }
114
+ }
115
+ }
@@ -1,4 +1,4 @@
1
- package com.margelo.nitro
1
+ package com.margelo.nitro.core
2
2
 
3
3
  import androidx.annotation.Keep
4
4
  import com.facebook.jni.HybridData
@@ -9,7 +9,6 @@ import com.facebook.proguard.annotations.DoNotStrip
9
9
  */
10
10
  @Keep
11
11
  @DoNotStrip
12
- @Suppress("KotlinJniMissingFunction")
13
12
  abstract class HybridObject {
14
13
  /**
15
14
  * Get the memory size of the Kotlin instance (plus any external heap allocations),
@@ -29,22 +28,20 @@ abstract class HybridObject {
29
28
  */
30
29
  @get:DoNotStrip
31
30
  @get:Keep
32
- abstract val memorySize: ULong
31
+ abstract val memorySize: Long
33
32
 
34
33
  /**
35
- * Contains the C++ context (Hybrid Data) for the fbjni C++ part.
34
+ * Holds the native C++ instance.
35
+ * In `HybridObject`, the C++ instance is a sub-class of `JHybridObject`, such as one of it's specs.
36
+ * This is `null`, until `updateNative(..)` is called.
36
37
  */
37
- @DoNotStrip
38
- @Keep
39
- val mHybridData: HybridData
38
+ private var mHybridData: HybridData? = null
40
39
 
41
- init {
42
- mHybridData = initHybrid()
43
- }
44
-
45
- private external fun initHybrid(): HybridData
46
-
47
- companion object {
48
- private const val TAG = "HybridObject"
40
+ /**
41
+ * Must be called in the constructor of a subclass of `HybridObject`, to initialize the C++
42
+ * `JHybridObject` with a subclass of it.
43
+ */
44
+ protected fun updateNative(hybridData: HybridData) {
45
+ mHybridData = hybridData
49
46
  }
50
47
  }
@@ -1,4 +1,4 @@
1
- package com.margelo.nitro;
1
+ package com.margelo.nitro.core;
2
2
 
3
3
  import androidx.annotation.Keep;
4
4
 
@@ -1,6 +1,6 @@
1
- package com.margelo.nitro;
1
+ package com.margelo.nitro.core;
2
2
 
3
- import java.util.function.Supplier;
3
+ import com.margelo.nitro.JNIOnLoad;
4
4
 
5
5
  /**
6
6
  * A registry that holds initializers for HybridObjects.
@@ -8,12 +8,14 @@ import java.util.function.Supplier;
8
8
  * @noinspection JavaJniMissingFunction
9
9
  */
10
10
  public class HybridObjectRegistry {
11
+ static {
12
+ JNIOnLoad.initializeNativeNitro();
13
+ }
14
+
11
15
  /**
12
16
  * Registers the given HybridObject in the `HybridObjectRegistry`.
13
17
  * It will be uniquely identified via it's `hybridObjectName`, and can be initialized from
14
18
  * JS using `NitroModules.createHybridObject<T>(name)` - which will call the `constructorFn` here.
15
19
  */
16
20
  public static native void registerHybridObjectConstructor(String hybridObjectName, HybridObjectInitializer initializer);
17
-
18
- private static final String TAG = "HybridObjectRegistry";
19
21
  }
@@ -0,0 +1,115 @@
1
+ package com.margelo.nitro.core
2
+
3
+ import androidx.annotation.Keep
4
+ import com.facebook.jni.HybridData
5
+ import com.facebook.proguard.annotations.DoNotStrip
6
+ import kotlinx.coroutines.CoroutineScope
7
+ import kotlinx.coroutines.Dispatchers
8
+ import kotlinx.coroutines.async
9
+ import kotlinx.coroutines.launch
10
+ import kotlin.concurrent.thread
11
+
12
+ /**
13
+ * Represents a Promise that can be passed to JS.
14
+ *
15
+ * Create a new Promise with the following APIs:
16
+ * - [Promise.async]` { ... }` - Creates a new Promise that runs the given suspending function in a Kotlin coroutine scope.
17
+ * - [Promise.parallel]` { ... }` - Creates a new Promise that runs the given code in a parallel Thread.
18
+ * - [Promise.resolved]`(..)` - Creates a new already resolved Promise.
19
+ * - [Promise.rejected]`(..)` - Creates a new already rejected Promise.
20
+ * - [Promise]`()` - Creates a new Promise with fully manual control over the `resolve(..)`/`reject(..)` functions.
21
+ */
22
+ @Suppress("KotlinJniMissingFunction")
23
+ @Keep
24
+ @DoNotStrip
25
+ class Promise<T> {
26
+ private val mHybridData: HybridData
27
+
28
+ /**
29
+ * Creates a new Promise with fully manual control over the `resolve(..)`/`reject(..)` functions
30
+ */
31
+ init {
32
+ mHybridData = initHybrid()
33
+ }
34
+
35
+ /**
36
+ * Resolves the Promise with the given result.
37
+ * Any `onResolved` listeners will be invoked.
38
+ */
39
+ fun resolve(result: T) {
40
+ nativeResolve(result as Any)
41
+ }
42
+
43
+ /**
44
+ * Rejects the Promise with the given error.
45
+ * Any `onRejected` listeners will be invoked.
46
+ */
47
+ fun reject(error: Error) {
48
+ nativeReject(error.toString())
49
+ }
50
+
51
+ // C++ functions
52
+ private external fun nativeResolve(result: Any)
53
+ private external fun nativeReject(error: String)
54
+ private external fun initHybrid(): HybridData
55
+
56
+ companion object {
57
+ private val defaultScope = CoroutineScope(Dispatchers.Default)
58
+
59
+ /**
60
+ * Creates a new Promise that asynchronously runs the given suspending function [run]
61
+ * on the given coroutine scope [scope].
62
+ *
63
+ * If [scope] is omitted, the [Dispatchers.Default] scope will be used.
64
+ *
65
+ * When the suspending function returns, the Promise gets resolved. If the suspending
66
+ * function throws, the Promise gets rejected.
67
+ */
68
+ fun <T> async(scope: CoroutineScope = defaultScope, run: suspend () -> T): Promise<T> {
69
+ val promise = Promise<T>()
70
+ scope.launch {
71
+ try {
72
+ val result = run()
73
+ promise.resolve(result)
74
+ } catch (e: Error) {
75
+ promise.reject(e)
76
+ }
77
+ }
78
+ return promise
79
+ }
80
+
81
+ /**
82
+ * Creates a new Promise that runs the given function [run] on a separate
83
+ * Thread.
84
+ *
85
+ * When the function returns, the Promise gets resolved. If the
86
+ * function throws, the Promise gets rejected.
87
+ */
88
+ fun <T> parallel(run: () -> T): Promise<T> {
89
+ val promise = Promise<T>()
90
+ thread {
91
+ try {
92
+ val result = run()
93
+ promise.resolve(result)
94
+ } catch (e: Error) {
95
+ promise.reject(e)
96
+ }
97
+ }
98
+ return promise
99
+ }
100
+
101
+ /**
102
+ * Creates a new Promise that is already resolved with the given result.
103
+ */
104
+ fun <T> resolved(result: T): Promise<T> {
105
+ return Promise<T>().apply { resolve(result) }
106
+ }
107
+
108
+ /**
109
+ * Creates a new Promise that is already rejected with the given error.
110
+ */
111
+ fun <T> rejected(error: Error): Promise<T> {
112
+ return Promise<T>().apply { reject(error) }
113
+ }
114
+ }
115
+ }
@@ -49,13 +49,13 @@ public:
49
49
 
50
50
  public:
51
51
  /**
52
- * Create a new shared_ptr instance of AnyMap.
52
+ * Create a new `shared_ptr` instance of AnyMap.
53
53
  */
54
54
  static std::shared_ptr<AnyMap> make() {
55
55
  return std::make_shared<AnyMap>();
56
56
  }
57
57
  /**
58
- * Create a new shared_ptr instance of AnyMap with the given amount of spaces pre-allocated.
58
+ * Create a new `shared_ptr` instance of AnyMap with the given amount of spaces pre-allocated.
59
59
  */
60
60
  static std::shared_ptr<AnyMap> make(size_t size) {
61
61
  return std::make_shared<AnyMap>(size);
@@ -17,18 +17,18 @@ using namespace facebook;
17
17
 
18
18
  // 1. ArrayBuffer
19
19
 
20
- std::shared_ptr<ArrayBuffer> ArrayBuffer::makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext) {
21
- return std::make_shared<NativeArrayBuffer>(data, size, deleteFunc, deleteFuncContext);
20
+ std::shared_ptr<ArrayBuffer> ArrayBuffer::makeBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc) {
21
+ return std::make_shared<NativeArrayBuffer>(data, size, std::move(deleteFunc));
22
22
  }
23
23
 
24
24
  // 2. NativeArrayBuffer
25
25
 
26
- NativeArrayBuffer::NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext)
27
- : ArrayBuffer(), _data(data), _size(size), _deleteFunc(deleteFunc), _deleteFuncContext(deleteFuncContext) {}
26
+ NativeArrayBuffer::NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc)
27
+ : ArrayBuffer(), _data(data), _size(size), _deleteFunc(std::move(deleteFunc)) {}
28
28
 
29
29
  NativeArrayBuffer::~NativeArrayBuffer() {
30
30
  if (_deleteFunc != nullptr) {
31
- _deleteFunc(_deleteFuncContext);
31
+ _deleteFunc();
32
32
  }
33
33
  }
34
34
 
@@ -15,7 +15,7 @@ namespace margelo::nitro {
15
15
 
16
16
  using namespace facebook;
17
17
 
18
- using DeleteFn = void (*)(void* context);
18
+ using DeleteFn = std::function<void()>;
19
19
 
20
20
  /**
21
21
  * Represents a raw byte buffer that can be read from-, and
@@ -38,7 +38,7 @@ public:
38
38
  ArrayBuffer(const ArrayBuffer&) = delete;
39
39
  ArrayBuffer(ArrayBuffer&&) = delete;
40
40
  virtual ~ArrayBuffer() = default;
41
-
41
+
42
42
  public:
43
43
  /**
44
44
  * Returns whether this `ArrayBuffer` is actually owning the data,
@@ -46,13 +46,13 @@ public:
46
46
  * memory that we didn't allocate, or from JS - which can be deleted at any point).
47
47
  */
48
48
  virtual bool isOwner() const noexcept = 0;
49
-
49
+
50
50
  public:
51
51
  /**
52
52
  * Create a new `NativeArrayBuffer` that wraps the given data (without copy) of the given size,
53
- * and calls `deleteFunc` with the given `deleteFuncContext` as a parameter in which `data` should be deleted.
53
+ * and calls `deleteFunc` in which `data` should be deleted.
54
54
  */
55
- static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext);
55
+ static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc);
56
56
  };
57
57
 
58
58
  /**
@@ -74,9 +74,9 @@ public:
74
74
  * and will only delete it once this `ArrayBuffer` gets deleted.
75
75
  *
76
76
  * Once this `ArrayBuffer` goes out of scope, `deleteFunc` will be called.
77
- * The caller is responsible for deleting the memory (`data` and `deleteFuncContext`) here.
77
+ * The caller is responsible for deleting the memory (`data`) here.
78
78
  */
79
- NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext);
79
+ NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc);
80
80
  ~NativeArrayBuffer();
81
81
 
82
82
  public:
@@ -84,8 +84,6 @@ public:
84
84
  size_t size() const override;
85
85
  bool isOwner() const noexcept override;
86
86
 
87
- double something();
88
-
89
87
  private:
90
88
  uint8_t* _data;
91
89
  size_t _size;
@@ -26,19 +26,19 @@ namespace margelo::nitro {
26
26
  using namespace facebook;
27
27
 
28
28
  /**
29
- * Represents the type of a function - it can be either a normal function ("METHOD"),
29
+ * Represents the kind of a function - it can be either a normal function ("METHOD"),
30
30
  * or a property ("GETTER" + "SETTER")
31
31
  */
32
- enum class FunctionType { METHOD, GETTER, SETTER };
32
+ enum class FunctionKind { METHOD, GETTER, SETTER };
33
33
 
34
34
  /**
35
35
  * Represents a Hybrid Function.
36
36
  */
37
37
  class HybridFunction final {
38
38
  private:
39
- std::string _name;
40
- size_t _paramCount;
41
39
  jsi::HostFunctionType _function;
40
+ size_t _paramCount;
41
+ std::string _name;
42
42
 
43
43
  public:
44
44
  // getters
@@ -69,30 +69,14 @@ public:
69
69
  * and assumes that the object this is called on has a proper `this` configured.
70
70
  * The object's `this` needs to be a `NativeState`.
71
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,
72
+ template <typename THybrid, typename ReturnType, typename... Args>
73
+ static inline HybridFunction createHybridFunction(const std::string& name, ReturnType (THybrid::*method)(Args...), FunctionKind kind) {
74
+ jsi::HostFunctionType hostFunction = [name, method, kind](/* JS Runtime */ jsi::Runtime& runtime,
75
75
  /* HybridObject */ const jsi::Value& thisValue,
76
76
  /* JS arguments */ const jsi::Value* args,
77
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);
78
+ // 1. Get actual `HybridObject` instance from `thisValue` (it's stored as `NativeState`)
79
+ std::shared_ptr<THybrid> hybridInstance = getHybridObjectNativeState<THybrid>(runtime, thisValue, kind, name);
96
80
 
97
81
  // 2. Make sure the given arguments match, either with a static size, or with potentially optional arguments size.
98
82
  constexpr size_t optionalArgsCount = trailing_optionals_count_v<Args...>;
@@ -101,47 +85,62 @@ public:
101
85
  bool isWithinArgsRange = (count >= minArgsCount && count <= maxArgsCount);
102
86
  if (!isWithinArgsRange) [[unlikely]] {
103
87
  // invalid amount of arguments passed!
104
- std::string hybridObjectName = hybridInstance->getName();
88
+ std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
105
89
  if constexpr (minArgsCount == maxArgsCount) {
106
90
  // 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) + "!");
91
+ throw jsi::JSError(runtime, "`" + funcName + "` expected " + std::to_string(maxArgsCount) + " arguments, but received " +
92
+ std::to_string(count) + "!");
109
93
  } else {
110
94
  // 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 " +
95
+ throw jsi::JSError(runtime, "`" + funcName + "` expected between " + std::to_string(minArgsCount) + " and " +
112
96
  std::to_string(maxArgsCount) + " arguments, but received " + std::to_string(count) + "!");
113
97
  }
114
98
  }
115
99
 
116
- // 3. Actually call method - either raw JSI method, or by going through `JSIConverter<T>` first.
117
100
  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
- }
101
+ // 3. Actually call the method with JSI values as arguments and return a JSI value again.
102
+ // Internally, this method converts the JSI values to C++ values using `JSIConverter<T>`.
103
+ return callMethod(hybridInstance.get(), method, runtime, args, count, std::index_sequence_for<Args...>{});
127
104
  } catch (const std::exception& exception) {
128
105
  // Some exception was thrown - add method name information and re-throw as `JSError`.
129
- std::string hybridObjectName = hybridInstance->getName();
106
+ std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
130
107
  std::string message = exception.what();
131
- std::string suffix = type == FunctionType::METHOD ? "(...)" : "";
132
- throw jsi::JSError(runtime, hybridObjectName + "." + name + suffix + ": " + message);
108
+ throw jsi::JSError(runtime, funcName + ": " + message);
133
109
  } catch (...) {
134
110
  // Some unknown exception was thrown - add method name information and re-throw as `JSError`.
135
- std::string hybridObjectName = hybridInstance->getName();
111
+ std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
136
112
  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.");
113
+ throw jsi::JSError(runtime, "`" + funcName + "` threw an unknown " + errorName + " error.");
139
114
  }
140
115
  };
141
116
 
142
117
  return HybridFunction(std::move(hostFunction), sizeof...(Args), name);
143
118
  }
144
119
 
120
+ /**
121
+ * Create a new `HybridFunction` that can be called from JS.
122
+ * Unlike `createHybridFunction(...)`, this method does **not** perform any argument parsing or size checking.
123
+ * It is a raw-, untyped JSI method, and the user is expected to manually handle arguments and return values.
124
+ */
125
+ template <typename Derived>
126
+ static inline HybridFunction createRawHybridFunction(const std::string& name, size_t expectedArgumentsCount,
127
+ jsi::Value (Derived::*method)(jsi::Runtime& runtime, const jsi::Value& thisArg,
128
+ const jsi::Value* args, size_t count)) {
129
+ jsi::HostFunctionType hostFunction = [name, method](/* JS Runtime */ jsi::Runtime& runtime,
130
+ /* HybridObject */ const jsi::Value& thisValue,
131
+ /* JS arguments */ const jsi::Value* args,
132
+ /* argument size */ size_t count) -> jsi::Value {
133
+ // 1. Get actual `HybridObject` instance from `thisValue` (it's stored as `NativeState`)
134
+ std::shared_ptr<Derived> hybridInstance = getHybridObjectNativeState<Derived>(runtime, thisValue, FunctionKind::METHOD, name);
135
+
136
+ // 2. Call the raw JSI method using raw JSI Values. Exceptions are also expected to be handled by the user.
137
+ Derived* pointer = hybridInstance.get();
138
+ return (pointer->*method)(runtime, thisValue, args, count);
139
+ };
140
+
141
+ return HybridFunction(std::move(hostFunction), expectedArgumentsCount, name);
142
+ }
143
+
145
144
  private:
146
145
  /**
147
146
  * Calls the given method on the given instance with the given `jsi::Value` arguments by converting them to the desired target types.
@@ -159,7 +158,82 @@ private:
159
158
  } else {
160
159
  // It's returning some C++ type, we need to convert that to a JSI value now.
161
160
  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));
161
+ return JSIConverter<ReturnType>::toJSI(runtime, std::forward<ReturnType>(result));
162
+ }
163
+ }
164
+
165
+ private:
166
+ /**
167
+ * Get the `NativeState` of the given `value`.
168
+ */
169
+ template <typename THybrid>
170
+ static inline std::shared_ptr<THybrid> getHybridObjectNativeState(jsi::Runtime& runtime, const jsi::Value& value, FunctionKind funcKind,
171
+ const std::string& funcName) {
172
+ // 1. Convert jsi::Value to jsi::Object
173
+ #ifndef NDEBUG
174
+ if (!value.isObject()) [[unlikely]] {
175
+ throw jsi::JSError(runtime, "Cannot " + getHybridFuncDebugInfo<THybrid>(funcKind, funcName) + " - `this` is not bound!");
176
+ }
177
+ #endif
178
+ jsi::Object object = value.getObject(runtime);
179
+
180
+ // 2. Check if it even has any kind of `NativeState`
181
+ #ifndef NDEBUG
182
+ if (!object.hasNativeState(runtime)) [[unlikely]] {
183
+ throw jsi::JSError(runtime, "Cannot " + getHybridFuncDebugInfo<THybrid>(funcKind, funcName) +
184
+ " - `this` does not have a NativeState! Suggestions:\n"
185
+ "- Did you accidentally destructure the `HybridObject` and lose a reference to the original object?\n"
186
+ "- Did you call `dispose()` on the `HybridObject` before?"
187
+ "- Did you accidentally call `" +
188
+ funcName + "` on the prototype directly?\n");
189
+ }
190
+ #endif
191
+
192
+ // 3. Get `NativeState` from the jsi::Object and check if it is non-null
193
+ std::shared_ptr<jsi::NativeState> nativeState = object.getNativeState(runtime);
194
+ #ifndef NDEBUG
195
+ if (nativeState == nullptr) [[unlikely]] {
196
+ throw jsi::JSError(runtime, "Cannot " + getHybridFuncDebugInfo<THybrid>(funcKind, funcName) +
197
+ " - `this`'s `NativeState` is `nullptr`, "
198
+ "did you accidentally call `dispose()` on this object?");
199
+ }
200
+ #endif
201
+
202
+ // 4. Try casting it to our desired target type.
203
+ std::shared_ptr<THybrid> hybridInstance = std::dynamic_pointer_cast<THybrid>(nativeState);
204
+ #ifndef NDEBUG
205
+ if (hybridInstance == nullptr) [[unlikely]] {
206
+ throw jsi::JSError(runtime, "Cannot " + getHybridFuncDebugInfo<THybrid>(funcKind, funcName) +
207
+ " - `this` has a NativeState, but it's the wrong type!");
208
+ }
209
+ #endif
210
+ return hybridInstance;
211
+ }
212
+
213
+ private:
214
+ template <typename THybrid>
215
+ static inline std::string getHybridFuncFullName(FunctionKind kind, const std::string& registrationName,
216
+ THybrid* hybridInstance = nullptr) {
217
+ std::string typeName = hybridInstance != nullptr ? hybridInstance->getName() : TypeInfo::getFriendlyTypenameNoNamespace<THybrid>();
218
+ switch (kind) {
219
+ case FunctionKind::METHOD:
220
+ return typeName + "." + registrationName + "(...)";
221
+ case FunctionKind::GETTER:
222
+ case FunctionKind::SETTER:
223
+ return typeName + "." + registrationName;
224
+ }
225
+ }
226
+ template <typename THybrid>
227
+ static inline std::string getHybridFuncDebugInfo(FunctionKind kind, const std::string& registrationName,
228
+ THybrid* hybridInstance = nullptr) {
229
+ auto funcName = getHybridFuncFullName<THybrid>(kind, registrationName, hybridInstance);
230
+ switch (kind) {
231
+ case FunctionKind::METHOD:
232
+ return "call hybrid function `" + funcName + "`";
233
+ case FunctionKind::GETTER:
234
+ return "get hybrid property `" + funcName + "`";
235
+ case FunctionKind::SETTER:
236
+ return "set hybrid property `" + funcName + "`";
163
237
  }
164
238
  }
165
239
  };