react-native-nitro-modules 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NitroModules.podspec +4 -1
- package/README.md +35 -16
- package/android/gradle.properties +5 -5
- package/android/src/main/cpp/JNIOnLoad.cpp +8 -0
- package/android/src/main/cpp/core/ByteBufferArrayBuffer.hpp +47 -0
- package/android/src/main/cpp/core/JAnyMap.hpp +193 -0
- package/android/src/main/cpp/core/JAnyValue.hpp +191 -0
- package/android/src/main/cpp/core/JArrayBuffer.hpp +119 -0
- package/android/src/main/cpp/core/JHybridObject.hpp +11 -4
- package/android/src/main/cpp/core/JPromise.hpp +88 -0
- package/android/src/main/cpp/platform/NitroLogger.cpp +36 -0
- package/android/src/main/cpp/registry/JHybridObjectInitializer.hpp +1 -1
- package/android/src/main/cpp/registry/JHybridObjectRegistry.cpp +1 -1
- package/android/src/main/cpp/registry/JHybridObjectRegistry.hpp +1 -1
- package/android/src/main/cpp/utils/{JNISharedPtr.h → JNISharedPtr.hpp} +13 -1
- package/android/src/main/java/com/margelo/nitro/JNIOnLoad.java +27 -0
- package/android/src/main/java/com/margelo/nitro/NitroModulesPackage.java +1 -9
- package/android/src/main/java/com/margelo/nitro/core/AnyMap.kt +61 -0
- package/android/src/main/java/com/margelo/nitro/core/AnyValue.kt +153 -0
- package/android/src/main/java/com/margelo/nitro/core/ArrayBuffer.kt +84 -0
- package/android/src/main/java/com/margelo/nitro/{HybridObject.kt → core/HybridObject.kt} +12 -15
- package/android/src/main/java/com/margelo/nitro/{HybridObjectInitializer.java → core/HybridObjectInitializer.java} +1 -1
- package/android/src/main/java/com/margelo/nitro/{HybridObjectRegistry.java → core/HybridObjectRegistry.java} +6 -4
- package/android/src/main/java/com/margelo/nitro/core/Promise.kt +115 -0
- package/cpp/core/AnyMap.hpp +2 -2
- package/cpp/core/ArrayBuffer.cpp +5 -5
- package/cpp/core/ArrayBuffer.hpp +12 -7
- package/cpp/core/HybridFunction.hpp +119 -45
- package/cpp/core/HybridObject.cpp +17 -6
- package/cpp/core/HybridObject.hpp +32 -8
- package/cpp/jsi/JSICache.cpp +5 -5
- package/cpp/jsi/JSICache.hpp +1 -3
- package/cpp/jsi/JSIConverter+AnyMap.hpp +2 -2
- package/cpp/jsi/JSIConverter+ArrayBuffer.hpp +2 -2
- package/cpp/jsi/JSIConverter+Function.hpp +7 -6
- package/cpp/jsi/JSIConverter+HybridObject.hpp +6 -5
- package/cpp/jsi/JSIConverter+Optional.hpp +1 -1
- package/cpp/jsi/JSIConverter+Promise.hpp +6 -4
- package/cpp/jsi/JSIConverter+Tuple.hpp +1 -1
- package/cpp/jsi/JSIConverter+UnorderedMap.hpp +1 -1
- package/cpp/jsi/JSIConverter+Variant.hpp +1 -3
- package/cpp/jsi/JSIConverter+Vector.hpp +1 -1
- package/cpp/jsi/JSIConverter.hpp +11 -11
- package/cpp/jsi/JSPromise.cpp +2 -2
- package/cpp/platform/NitroLogger.hpp +67 -0
- package/cpp/prototype/HybridObjectPrototype.cpp +2 -2
- package/cpp/prototype/Prototype.hpp +43 -22
- package/cpp/registry/HybridObjectRegistry.cpp +4 -4
- package/cpp/threading/Dispatcher.cpp +4 -3
- package/cpp/threading/ThreadPool.cpp +2 -2
- package/cpp/turbomodule/NativeNitroModules.cpp +26 -8
- package/cpp/turbomodule/NativeNitroModules.h +1 -0
- package/cpp/turbomodule/NativeNitroModules.hpp +2 -0
- package/cpp/utils/TypeInfo.hpp +14 -0
- package/ios/core/AnyMapHolder.hpp +91 -0
- package/ios/core/AnyMapHolder.swift +316 -0
- package/ios/core/ArrayBufferHolder.hpp +16 -5
- package/ios/core/{ArrayBuffer.swift → ArrayBufferHolder.swift} +15 -17
- package/ios/core/HybridContext.hpp +3 -3
- package/ios/core/Promise.swift +182 -0
- package/ios/core/PromiseHolder.hpp +86 -0
- package/ios/platform/NitroLogger.mm +36 -0
- package/ios/platform/ThreadUtils.cpp +1 -1
- package/ios/turbomodule/NitroModuleOnLoad.mm +6 -0
- package/ios/utils/SwiftClosure.hpp +63 -0
- package/ios/utils/SwiftClosure.swift +58 -0
- package/lib/AnyMap.d.ts +3 -0
- package/lib/HybridObject.d.ts +15 -0
- package/lib/NativeNitroModules.d.ts +2 -0
- package/lib/NitroModules.d.ts +18 -0
- package/lib/NitroModules.js +24 -0
- package/lib/commonjs/NativeNitroModules.js.map +1 -1
- package/lib/commonjs/NitroModules.js +24 -0
- package/lib/commonjs/NitroModules.js.map +1 -1
- package/lib/commonjs/package.json +1 -0
- package/lib/module/AnyMap.js +1 -1
- package/lib/module/HybridObject.js +1 -1
- package/lib/module/ModuleNotFoundError.js +2 -0
- package/lib/module/ModuleNotFoundError.js.map +1 -1
- package/lib/module/NativeNitroModules.js +2 -0
- package/lib/module/NativeNitroModules.js.map +1 -1
- package/lib/module/NativeNitroModules.web.js +2 -0
- package/lib/module/NativeNitroModules.web.js.map +1 -1
- package/lib/module/NitroModules.js +26 -0
- package/lib/module/NitroModules.js.map +1 -1
- package/lib/module/index.js +2 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/package.json +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/lib/typescript/AnyMap.d.ts +20 -0
- package/lib/typescript/AnyMap.d.ts.map +1 -1
- package/lib/typescript/HybridObject.d.ts +98 -0
- package/lib/typescript/HybridObject.d.ts.map +1 -1
- package/lib/typescript/NativeNitroModules.d.ts +2 -0
- package/lib/typescript/NativeNitroModules.d.ts.map +1 -1
- package/lib/typescript/NitroModules.d.ts +18 -0
- package/lib/typescript/NitroModules.d.ts.map +1 -1
- package/package.json +13 -12
- package/src/AnyMap.ts +3 -0
- package/src/HybridObject.ts +15 -0
- package/src/NativeNitroModules.ts +5 -0
- package/src/NitroModules.ts +24 -0
- package/android/src/main/cpp/core/JHybridObject.cpp +0 -8
- package/cpp/templates/IsInPack.hpp +0 -21
- package/cpp/utils/NitroLogger.hpp +0 -58
- package/ios/core/ClosureWrapper.swift +0 -25
- package/ios/core/HybridContext.cpp +0 -8
- package/ios/core/Promise.cpp +0 -10
- package/ios/core/Promise.hpp +0 -43
|
@@ -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:
|
|
31
|
+
abstract val memorySize: Long
|
|
33
32
|
|
|
34
33
|
/**
|
|
35
|
-
*
|
|
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
|
-
|
|
38
|
-
@Keep
|
|
39
|
-
val mHybridData: HybridData
|
|
38
|
+
private var mHybridData: HybridData? = null
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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,6 +1,6 @@
|
|
|
1
|
-
package com.margelo.nitro;
|
|
1
|
+
package com.margelo.nitro.core;
|
|
2
2
|
|
|
3
|
-
import
|
|
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
|
+
}
|
package/cpp/core/AnyMap.hpp
CHANGED
|
@@ -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);
|
package/cpp/core/ArrayBuffer.cpp
CHANGED
|
@@ -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
|
|
21
|
-
return std::make_shared<NativeArrayBuffer>(data, size, deleteFunc
|
|
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
|
|
27
|
-
: ArrayBuffer(), _data(data), _size(size), _deleteFunc(deleteFunc)
|
|
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(
|
|
31
|
+
_deleteFunc();
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
package/cpp/core/ArrayBuffer.hpp
CHANGED
|
@@ -15,7 +15,7 @@ namespace margelo::nitro {
|
|
|
15
15
|
|
|
16
16
|
using namespace facebook;
|
|
17
17
|
|
|
18
|
-
using DeleteFn = void
|
|
18
|
+
using DeleteFn = std::function<void()>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Represents a raw byte buffer that can be read from-, and
|
|
@@ -33,6 +33,12 @@ using DeleteFn = void (*)(void* context);
|
|
|
33
33
|
* When this `ArrayBuffer` gets deleted, the memory will not be freed explicitly, as someone else owns it.
|
|
34
34
|
*/
|
|
35
35
|
class ArrayBuffer : public jsi::MutableBuffer {
|
|
36
|
+
public:
|
|
37
|
+
ArrayBuffer() = default;
|
|
38
|
+
ArrayBuffer(const ArrayBuffer&) = delete;
|
|
39
|
+
ArrayBuffer(ArrayBuffer&&) = delete;
|
|
40
|
+
virtual ~ArrayBuffer() = default;
|
|
41
|
+
|
|
36
42
|
public:
|
|
37
43
|
/**
|
|
38
44
|
* Returns whether this `ArrayBuffer` is actually owning the data,
|
|
@@ -41,11 +47,12 @@ public:
|
|
|
41
47
|
*/
|
|
42
48
|
virtual bool isOwner() const noexcept = 0;
|
|
43
49
|
|
|
50
|
+
public:
|
|
44
51
|
/**
|
|
45
52
|
* Create a new `NativeArrayBuffer` that wraps the given data (without copy) of the given size,
|
|
46
|
-
* and calls `deleteFunc`
|
|
53
|
+
* and calls `deleteFunc` in which `data` should be deleted.
|
|
47
54
|
*/
|
|
48
|
-
static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc
|
|
55
|
+
static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc);
|
|
49
56
|
};
|
|
50
57
|
|
|
51
58
|
/**
|
|
@@ -67,9 +74,9 @@ public:
|
|
|
67
74
|
* and will only delete it once this `ArrayBuffer` gets deleted.
|
|
68
75
|
*
|
|
69
76
|
* Once this `ArrayBuffer` goes out of scope, `deleteFunc` will be called.
|
|
70
|
-
* The caller is responsible for deleting the memory (`data`
|
|
77
|
+
* The caller is responsible for deleting the memory (`data`) here.
|
|
71
78
|
*/
|
|
72
|
-
NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc
|
|
79
|
+
NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn&& deleteFunc);
|
|
73
80
|
~NativeArrayBuffer();
|
|
74
81
|
|
|
75
82
|
public:
|
|
@@ -77,8 +84,6 @@ public:
|
|
|
77
84
|
size_t size() const override;
|
|
78
85
|
bool isOwner() const noexcept override;
|
|
79
86
|
|
|
80
|
-
double something();
|
|
81
|
-
|
|
82
87
|
private:
|
|
83
88
|
uint8_t* _data;
|
|
84
89
|
size_t _size;
|
|
@@ -26,19 +26,19 @@ namespace margelo::nitro {
|
|
|
26
26
|
using namespace facebook;
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Represents the
|
|
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
|
|
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
|
|
73
|
-
static inline HybridFunction createHybridFunction(const std::string& name, ReturnType (
|
|
74
|
-
jsi::HostFunctionType hostFunction = [name, method,
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|
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,
|
|
108
|
-
|
|
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,
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
106
|
+
std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
|
|
130
107
|
std::string message = exception.what();
|
|
131
|
-
|
|
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
|
|
111
|
+
std::string funcName = getHybridFuncFullName<THybrid>(kind, name, hybridInstance.get());
|
|
136
112
|
std::string errorName = TypeInfo::getCurrentExceptionName();
|
|
137
|
-
|
|
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.
|
|
@@ -162,6 +161,81 @@ private:
|
|
|
162
161
|
return JSIConverter<std::decay_t<ReturnType>>::toJSI(runtime, std::move(result));
|
|
163
162
|
}
|
|
164
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 + "`";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
165
239
|
};
|
|
166
240
|
|
|
167
241
|
} // namespace margelo::nitro
|
|
@@ -49,8 +49,8 @@ HybridObject::HybridObject(const char* name) : HybridObjectPrototype(), _name(na
|
|
|
49
49
|
_instanceId = getId(name);
|
|
50
50
|
uint32_t alive = incrementAliveInstancesAndGet(_name);
|
|
51
51
|
uint32_t totalObjects = getTotalAliveInstances();
|
|
52
|
-
Logger::log(TAG, "(MEMORY) ✅ Creating %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i)", _name, _instanceId,
|
|
53
|
-
totalObjects);
|
|
52
|
+
Logger::log(LogLevel::Info, TAG, "(MEMORY) ✅ Creating %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i)", _name, _instanceId,
|
|
53
|
+
_name, alive, totalObjects);
|
|
54
54
|
#endif
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -58,8 +58,8 @@ HybridObject::~HybridObject() {
|
|
|
58
58
|
#if LOG_MEMORY_ALLOCATIONS
|
|
59
59
|
uint32_t alive = decrementAliveInstancesAndGet(_name);
|
|
60
60
|
uint32_t totalObjects = getTotalAliveInstances();
|
|
61
|
-
Logger::log(TAG, "(MEMORY) ❌ Deleting %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i) ", _name, _instanceId,
|
|
62
|
-
totalObjects);
|
|
61
|
+
Logger::log(LogLevel::Info, TAG, "(MEMORY) ❌ Deleting %s (#%i)... (Total %s(s): %i | Total HybridObjects: %i) ", _name, _instanceId,
|
|
62
|
+
_name, alive, totalObjects);
|
|
63
63
|
#endif
|
|
64
64
|
}
|
|
65
65
|
|
|
@@ -75,11 +75,22 @@ bool HybridObject::equals(std::shared_ptr<HybridObject> other) {
|
|
|
75
75
|
return this == other.get();
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
jsi::Value HybridObject::disposeRaw(jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count) {
|
|
79
|
+
// 1. Dispose any resources - this might be overridden by child classes to perform manual cleanup.
|
|
80
|
+
dispose();
|
|
81
|
+
// 2. Remove the NativeState from `this`
|
|
82
|
+
jsi::Object thisObject = thisArg.asObject(runtime);
|
|
83
|
+
thisObject.setNativeState(runtime, nullptr);
|
|
84
|
+
|
|
85
|
+
return jsi::Value::undefined();
|
|
86
|
+
}
|
|
87
|
+
|
|
78
88
|
void HybridObject::loadHybridMethods() {
|
|
79
89
|
registerHybrids(this, [](Prototype& prototype) {
|
|
80
90
|
prototype.registerHybridGetter("name", &HybridObject::getName);
|
|
81
|
-
prototype.registerHybridMethod("toString", &HybridObject::toString);
|
|
82
91
|
prototype.registerHybridMethod("equals", &HybridObject::equals);
|
|
92
|
+
prototype.registerHybridMethod("toString", &HybridObject::toString);
|
|
93
|
+
prototype.registerRawHybridMethod("dispose", 0, &HybridObject::disposeRaw);
|
|
83
94
|
});
|
|
84
95
|
}
|
|
85
96
|
|
|
@@ -112,7 +123,7 @@ jsi::Value HybridObject::toObject(jsi::Runtime& runtime) {
|
|
|
112
123
|
// 6. Set memory size so Hermes GC knows about actual memory
|
|
113
124
|
object.setExternalMemoryPressure(runtime, getExternalMemorySize());
|
|
114
125
|
|
|
115
|
-
#
|
|
126
|
+
#ifndef NDEBUG
|
|
116
127
|
// 7. Assign a private __type property for debugging - this will be used so users know it's not just an empty object.
|
|
117
128
|
object.setProperty(runtime, "__type", jsi::String::createFromUtf8(runtime, "NativeState<" + std::string(_name) + ">"));
|
|
118
129
|
#endif
|
|
@@ -23,7 +23,7 @@ using namespace facebook;
|
|
|
23
23
|
*
|
|
24
24
|
* The new class can then be passed to JS using the `JSIConverter<HybridObject>`.
|
|
25
25
|
*/
|
|
26
|
-
class HybridObject : public jsi::NativeState, public HybridObjectPrototype, public std::enable_shared_from_this<HybridObject> {
|
|
26
|
+
class HybridObject : public virtual jsi::NativeState, public HybridObjectPrototype, public std::enable_shared_from_this<HybridObject> {
|
|
27
27
|
public:
|
|
28
28
|
/**
|
|
29
29
|
* Create a new instance of a `HybridObject`.
|
|
@@ -38,11 +38,17 @@ public:
|
|
|
38
38
|
/**
|
|
39
39
|
* HybridObjects cannot be copied.
|
|
40
40
|
*/
|
|
41
|
-
HybridObject(const HybridObject& copy) =
|
|
41
|
+
HybridObject(const HybridObject& copy) = default;
|
|
42
42
|
/**
|
|
43
43
|
* HybridObjects cannot be moved.
|
|
44
44
|
*/
|
|
45
|
-
HybridObject(HybridObject&& move) =
|
|
45
|
+
HybridObject(HybridObject&& move) = default;
|
|
46
|
+
/**
|
|
47
|
+
* HybridObjects cannot be default-constructed!
|
|
48
|
+
*/
|
|
49
|
+
HybridObject() {
|
|
50
|
+
throw std::runtime_error("Cannot default-construct HybridObject!");
|
|
51
|
+
}
|
|
46
52
|
|
|
47
53
|
public:
|
|
48
54
|
/**
|
|
@@ -60,7 +66,7 @@ public:
|
|
|
60
66
|
*/
|
|
61
67
|
template <typename Derived>
|
|
62
68
|
std::shared_ptr<Derived> shared() {
|
|
63
|
-
return std::
|
|
69
|
+
return std::dynamic_pointer_cast<Derived>(shared_from_this());
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
public:
|
|
@@ -68,10 +74,6 @@ public:
|
|
|
68
74
|
* Get the HybridObject's name
|
|
69
75
|
*/
|
|
70
76
|
std::string getName();
|
|
71
|
-
/**
|
|
72
|
-
* Get a string representation of this HostObject, useful for logging or debugging.
|
|
73
|
-
*/
|
|
74
|
-
virtual std::string toString();
|
|
75
77
|
/**
|
|
76
78
|
* Compare this HybridObject for reference equality to the other HybridObject.
|
|
77
79
|
*
|
|
@@ -79,6 +81,28 @@ public:
|
|
|
79
81
|
* they might still be the same `HybridObject` - in this case `equals(other)` will return true.
|
|
80
82
|
*/
|
|
81
83
|
bool equals(std::shared_ptr<HybridObject> other);
|
|
84
|
+
/**
|
|
85
|
+
* Get a string representation of this `HybridObject` - useful for logging or debugging.
|
|
86
|
+
*/
|
|
87
|
+
virtual std::string toString();
|
|
88
|
+
/**
|
|
89
|
+
* Eagerly- (and manually-) dispose all native resources this `HybridObject` holds.
|
|
90
|
+
* This method can only be manually called from JS using `dispose()`.
|
|
91
|
+
*
|
|
92
|
+
* If this method is never manually called, a `HybridObject` is expected to disposes it's
|
|
93
|
+
* resources as usual via the object's destructor (`~HybridObject()`, `deinit` or `finalize()`).
|
|
94
|
+
*
|
|
95
|
+
* By default, this method does nothing. It can be overridden to perform actual disposing/cleanup
|
|
96
|
+
* if required.
|
|
97
|
+
*/
|
|
98
|
+
virtual void dispose() {}
|
|
99
|
+
|
|
100
|
+
private:
|
|
101
|
+
/**
|
|
102
|
+
* The actual `dispose()` function from JS.
|
|
103
|
+
* This needs to be a raw JSI function as we remove the NativeState here.
|
|
104
|
+
*/
|
|
105
|
+
jsi::Value disposeRaw(jsi::Runtime& runtime, const jsi::Value& thisArg, const jsi::Value* args, size_t count);
|
|
82
106
|
|
|
83
107
|
protected:
|
|
84
108
|
/**
|