react-native-nitro-modules 0.0.6 → 0.0.8
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 +1 -1
- package/README.md +210 -0
- package/android/CMakeLists.txt +1 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/JNIOnLoad.cpp +17 -0
- package/android/src/main/cpp/core/JHybridObject.cpp +8 -0
- package/android/src/main/cpp/core/JHybridObject.hpp +25 -0
- package/android/src/main/cpp/platform/ThreadUtils.cpp +35 -0
- package/android/src/main/cpp/registry/JHybridObjectInitializer.hpp +29 -0
- package/android/src/main/cpp/registry/JHybridObjectRegistry.cpp +35 -0
- package/android/src/main/cpp/registry/JHybridObjectRegistry.hpp +30 -0
- package/android/src/main/cpp/utils/JNISharedPtr.h +36 -0
- package/android/src/main/java/com/margelo/nitro/HybridObject.kt +61 -0
- package/android/src/main/java/com/margelo/nitro/HybridObjectInitializer.java +13 -0
- package/android/src/main/java/com/margelo/nitro/HybridObjectRegistry.java +30 -0
- package/android/src/main/java/com/margelo/nitro/NitroModulesPackage.java +26 -0
- package/cpp/core/HybridObject.hpp +23 -8
- package/cpp/core/PointerHolder.hpp +2 -1
- package/cpp/jsi/JSICache.hpp +6 -3
- package/cpp/jsi/JSIConverter.hpp +50 -39
- package/cpp/templates/CountTrailingOptionals.hpp +64 -0
- package/cpp/templates/FutureType.hpp +28 -0
- package/cpp/templates/IsHostObject.hpp +27 -0
- package/cpp/templates/IsInPack.hpp +21 -0
- package/cpp/templates/IsNativeState.hpp +27 -0
- package/cpp/threading/Dispatcher.hpp +2 -1
- package/cpp/utils/BorrowingReference+Owning.hpp +4 -2
- package/cpp/utils/BorrowingReference.hpp +5 -3
- package/cpp/utils/NitroHash.hpp +2 -1
- package/cpp/utils/NitroLogger.hpp +6 -3
- package/cpp/utils/OwningLock.hpp +4 -2
- package/cpp/utils/OwningReference.hpp +5 -4
- package/cpp/utils/TypeInfo.hpp +4 -2
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -36
- package/cpp/utils/DoesClassExist.hpp +0 -23
- /package/cpp/{jsi → core}/ArrayBuffer.hpp +0 -0
package/NitroModules.podspec
CHANGED
|
@@ -28,11 +28,11 @@ Pod::Spec.new do |s|
|
|
|
28
28
|
]
|
|
29
29
|
s.public_header_files = [
|
|
30
30
|
# Public C++ headers will be exposed in modulemap (for Swift)
|
|
31
|
+
"cpp/core/ArrayBuffer.hpp",
|
|
31
32
|
"cpp/core/HybridObject.hpp",
|
|
32
33
|
"cpp/core/HybridContext.hpp",
|
|
33
34
|
"cpp/registry/HybridObjectRegistry.hpp",
|
|
34
35
|
"cpp/jsi/JSIConverter.hpp",
|
|
35
|
-
"cpp/jsi/ArrayBuffer.hpp",
|
|
36
36
|
"cpp/threading/Dispatcher.hpp",
|
|
37
37
|
"cpp/utils/NitroHash.hpp",
|
|
38
38
|
"cpp/utils/NitroDefines.hpp",
|
package/README.md
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
<a href="https://margelo.io">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="../../docs/img/banner-react-native-nitro-modules-dark.png" />
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="../../docs/img/banner-react-native-nitro-modules-light.png" />
|
|
5
|
+
<img alt="Nitrogen" src="../../docs/img/banner-react-native-nitro-modules-light.png" />
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
|
|
9
|
+
<br />
|
|
10
|
+
|
|
11
|
+
**react-native-nitro-modules** is a core library that contains highly efficient statically compiled JS to C++ bindings.
|
|
12
|
+
|
|
13
|
+
It uses JSI to generate C++ templates that can bridge virtually any JS type to a C++ type with minimal overhead.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### Inside an app
|
|
18
|
+
|
|
19
|
+
Install [react-native-nitro-modules](https://npmjs.org/react-native-nitro-modules) as a `dependency` in your react-native app:
|
|
20
|
+
```sh
|
|
21
|
+
npm i react-native-nitro-modules
|
|
22
|
+
cd ios && pod install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Inside a nitro module library
|
|
26
|
+
|
|
27
|
+
If you are building a nitro module yourself, add [react-native-nitro-modules](https://npmjs.org/react-native-nitro-modules) as a `peerDependency` into your library's `package.json`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
...
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
...
|
|
34
|
+
"react-native-nitro-modules": "*"
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Then install `react-native-nitro-modules` as a normal `dependency` in your library's `example/` app as seen above.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
**react-native-nitro-modules** can either be used with-, or without nitrogen, or mixed (some objects are automatically generated, some manually).
|
|
44
|
+
|
|
45
|
+
### With Nitrogen
|
|
46
|
+
|
|
47
|
+
When using Nitrogen, all the bindings are automatically generated. You only need to implement C++, Swift or Kotlin interfaces inside your codebase.
|
|
48
|
+
|
|
49
|
+
### Without Nitrogen
|
|
50
|
+
|
|
51
|
+
All C++ bindings are bridged to JS using "Hybrid Objects".
|
|
52
|
+
|
|
53
|
+
A Hybrid Object can have both methods and properties (get and set).
|
|
54
|
+
Create a C++ Hybrid Object by inheriting from `HybridObject`:
|
|
55
|
+
|
|
56
|
+
```cpp
|
|
57
|
+
#include <NitroModules/HybridObject.hpp>
|
|
58
|
+
|
|
59
|
+
using namespace margelo::nitro;
|
|
60
|
+
|
|
61
|
+
class MyHybridObject: public HybridObject {
|
|
62
|
+
public:
|
|
63
|
+
explicit MyHybridObject(): HybridObject(TAG) {}
|
|
64
|
+
|
|
65
|
+
public:
|
|
66
|
+
// Property (get)
|
|
67
|
+
double getNumber() { return 13; }
|
|
68
|
+
// Property (set)
|
|
69
|
+
void setNumber(double value) { }
|
|
70
|
+
// Method
|
|
71
|
+
double add(double left, double right) { return left + right; }
|
|
72
|
+
|
|
73
|
+
public:
|
|
74
|
+
void loadHybridMethods() override {
|
|
75
|
+
// Register all methods that need to be exposed to JS
|
|
76
|
+
registerHybridGetter("number", &MyHybridObject::getNumber, this);
|
|
77
|
+
registerHybridSetter("number", &MyHybridObject::setNumber, this);
|
|
78
|
+
registerHybridMethod("add", &MyHybridObject::add, this);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private:
|
|
82
|
+
static constexpr auto TAG = "MyHybrid";
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `MyHybridObject` can then be registered in the [`HybridObjectRegistry`](./cpp/registry/HybridObjectRegistry.hpp) at app startup:
|
|
87
|
+
|
|
88
|
+
```cpp
|
|
89
|
+
#include <NitroModules/HybridObjectRegistry.hpp>
|
|
90
|
+
|
|
91
|
+
// Call this at app startup to register the HybridObjects
|
|
92
|
+
void load() {
|
|
93
|
+
HybridObjectRegistry::registerHybridObjectConstructor(
|
|
94
|
+
"MyHybrid",
|
|
95
|
+
[]() -> std::shared_ptr<HybridObject> {
|
|
96
|
+
return std::make_shared<MyHybridObject>();
|
|
97
|
+
}
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Inside your `MyHybridObject`, you can use standard C++ types which will automatically be converted to JS using Nitro's [`JSIConverter<T>`](./cpp/jsi/JSIConverter.hpp) interface.
|
|
103
|
+
|
|
104
|
+
The following C++ / JS types are supported out of the box:
|
|
105
|
+
|
|
106
|
+
<table>
|
|
107
|
+
<tr>
|
|
108
|
+
<th>JS Type</th>
|
|
109
|
+
<th>C++ Type</th>
|
|
110
|
+
</tr>
|
|
111
|
+
|
|
112
|
+
<tr>
|
|
113
|
+
<td><code>number</code></td>
|
|
114
|
+
<td><code>double</code> / <code>int</code> / <code>float</code></td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td><code>boolean</code></td>
|
|
118
|
+
<td><code>bool</code></td>
|
|
119
|
+
</tr>
|
|
120
|
+
<tr>
|
|
121
|
+
<td><code>string</code></td>
|
|
122
|
+
<td><code>std::string</code></td>
|
|
123
|
+
</tr>
|
|
124
|
+
<tr>
|
|
125
|
+
<td><code>bigint</code></td>
|
|
126
|
+
<td><code>int64_t</code> / <code>uint64_t</code></td>
|
|
127
|
+
</tr>
|
|
128
|
+
<tr>
|
|
129
|
+
<td><code>T[]</code></td>
|
|
130
|
+
<td><code>std::vector<T></code></td>
|
|
131
|
+
</tr>
|
|
132
|
+
<tr>
|
|
133
|
+
<td><code>[A, B, C, ...]</code></td>
|
|
134
|
+
<td><code>std::tuple<A, B, C, ...></code></td>
|
|
135
|
+
</tr>
|
|
136
|
+
<tr>
|
|
137
|
+
<td><code>A | B | C | ...</code></td>
|
|
138
|
+
<td><code>std::variant<A, B, C, ...></code></td>
|
|
139
|
+
</tr>
|
|
140
|
+
<tr>
|
|
141
|
+
<td><code>Record<string, T></code></td>
|
|
142
|
+
<td><code>std::unordered_map<std::string, T></code></td>
|
|
143
|
+
</tr>
|
|
144
|
+
<tr>
|
|
145
|
+
<td><code>T?</code></td>
|
|
146
|
+
<td><code>std::optional<T></code></td>
|
|
147
|
+
</tr>
|
|
148
|
+
<tr>
|
|
149
|
+
<td><code>Promise<T></code></td>
|
|
150
|
+
<td><code>std::future<T></code></td>
|
|
151
|
+
</tr>
|
|
152
|
+
<tr>
|
|
153
|
+
<td><code>(TArgs...) => TReturn</code></td>
|
|
154
|
+
<td><code>std::function<std::future<TReturn> (TArgs...)></code></td>
|
|
155
|
+
</tr>
|
|
156
|
+
<tr>
|
|
157
|
+
<td><code>{ ... }</code></td>
|
|
158
|
+
<td><code>std::shared_ptr<<a href="./cpp/core/AnyMap.hpp">AnyMap</a>></code></td>
|
|
159
|
+
</tr>
|
|
160
|
+
<tr>
|
|
161
|
+
<td><code>ArrayBuffer</code></td>
|
|
162
|
+
<td><code>std::shared_ptr<<a href="./cpp/core/ArrayBuffer.hpp">ArrayBuffer</a>></code></td>
|
|
163
|
+
</tr>
|
|
164
|
+
<tr>
|
|
165
|
+
<td><code><a href="./src/HybridObject.ts">HybridObject</a></code></td>
|
|
166
|
+
<td><code>std::shared_ptr<<a href="./cpp/core/HybridObject.hpp">HybridObject</a>></code></td>
|
|
167
|
+
</tr>
|
|
168
|
+
</table>
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
Since the `JSIConverter<T>` is just a template, you can extend it with any other custom types by overloading the interface.
|
|
172
|
+
|
|
173
|
+
For example, to add support for an enum, overload `JSIConverter<YourEnum>`:
|
|
174
|
+
|
|
175
|
+
```cpp
|
|
176
|
+
#include <NitroModules/JSIConverter.hpp>
|
|
177
|
+
|
|
178
|
+
enum class MyEnum {
|
|
179
|
+
FIRST = 0,
|
|
180
|
+
SECOND = 1
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
namespace margelo::nitro {
|
|
184
|
+
template <>
|
|
185
|
+
struct JSIConverter<MyEnum> {
|
|
186
|
+
static inline MyEnum fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
|
|
187
|
+
int intValue = JSIConverter<int>::fromJSI(runtime, arg);
|
|
188
|
+
return static_cast<MyEnum>(intValue);
|
|
189
|
+
}
|
|
190
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, MyEnum arg) {
|
|
191
|
+
int intValue = static_cast<int>(arg);
|
|
192
|
+
return JSIConverter<int>::toJSI(runtime, intValue);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
..and on the JS side, you can implicitly cast the `number` to an enum as well:
|
|
199
|
+
|
|
200
|
+
```js
|
|
201
|
+
enum MyEnum {
|
|
202
|
+
FIRST = 0,
|
|
203
|
+
SECOND = 1
|
|
204
|
+
}
|
|
205
|
+
const value = myHybridObject.getEnumValue() // <-- typed as `MyEnum` instead of `number`
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Make sure to always include the header that defines the `JSIConverter<MyEnum>` overload inside the `MyHybridObject` file, as this is where the `JSIConverter<T>` overloads are accessed from.
|
|
209
|
+
|
|
210
|
+
[**Nitrogen**](../nitrogen/) can automatically generate such `JSIConverter<T>` extensions for enums, TypeScript unions, and even structs/objects - so it is generally recommended to use nitrogen.
|
package/android/CMakeLists.txt
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// Entry point for JNI.
|
|
2
|
+
|
|
3
|
+
#include "JHybridObjectRegistry.hpp"
|
|
4
|
+
#include "RegisterNativeNitroModules.hpp"
|
|
5
|
+
#include <jni.h>
|
|
6
|
+
|
|
7
|
+
using namespace margelo::nitro;
|
|
8
|
+
|
|
9
|
+
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
|
|
10
|
+
// 1. Initialize the Nitro JSI Turbo Module
|
|
11
|
+
RegisterNativeNitroModules::registerNativeNitroModules();
|
|
12
|
+
|
|
13
|
+
// 2. Initialize all Java bindings
|
|
14
|
+
JHybridObjectRegistry::registerNatives();
|
|
15
|
+
|
|
16
|
+
return JNI_VERSION_1_2;
|
|
17
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
//
|
|
2
|
+
// JHybridObject.hpp
|
|
3
|
+
// react-native-nitro
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 14.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "HybridObject.hpp"
|
|
11
|
+
#include <fbjni/fbjni.h>
|
|
12
|
+
|
|
13
|
+
namespace margelo::nitro {
|
|
14
|
+
|
|
15
|
+
using namespace facebook;
|
|
16
|
+
|
|
17
|
+
struct JHybridObject : public jni::HybridClass<JHybridObject>, public HybridObject {
|
|
18
|
+
public:
|
|
19
|
+
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/HybridObject;";
|
|
20
|
+
|
|
21
|
+
public:
|
|
22
|
+
static void registerNatives();
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ThreadUtils.cpp
|
|
3
|
+
// react-native-nitro
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 14.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "ThreadUtils.hpp"
|
|
9
|
+
#include <pthread.h>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
#include <string>
|
|
12
|
+
#include <thread>
|
|
13
|
+
|
|
14
|
+
namespace margelo::nitro {
|
|
15
|
+
|
|
16
|
+
std::string ThreadUtils::getThreadName() {
|
|
17
|
+
#ifdef HAVE_ANDROID_PTHREAD_SETNAME_NP
|
|
18
|
+
// Try using pthread APIs
|
|
19
|
+
pthread_t this_thread = pthread_self();
|
|
20
|
+
char thread_name[16]; // Thread name length limit in Android is 16 characters
|
|
21
|
+
|
|
22
|
+
int result = pthread_getname_np(this_thread, thread_name, sizeof(thread_name));
|
|
23
|
+
if (result == 0) {
|
|
24
|
+
return std::string(thread_name);
|
|
25
|
+
}
|
|
26
|
+
#endif
|
|
27
|
+
|
|
28
|
+
// Fall back to this_thread ID
|
|
29
|
+
std::stringstream stream;
|
|
30
|
+
stream << std::this_thread::get_id();
|
|
31
|
+
std::string threadId = stream.str();
|
|
32
|
+
return "Thread #" + threadId;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// JHybridObjectInitializer.hpp
|
|
3
|
+
// DoubleConversion
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 22.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "HybridObject.hpp"
|
|
11
|
+
#include "JHybridObject.hpp"
|
|
12
|
+
#include <fbjni/fbjni.h>
|
|
13
|
+
|
|
14
|
+
namespace margelo::nitro {
|
|
15
|
+
|
|
16
|
+
using namespace facebook;
|
|
17
|
+
|
|
18
|
+
struct JHybridObjectInitializer : public jni::JavaClass<JHybridObjectInitializer> {
|
|
19
|
+
public:
|
|
20
|
+
jni::local_ref<JHybridObject::javaobject> call() const {
|
|
21
|
+
const auto method = this->getClass()->getMethod<JHybridObject::javaobject()>("initialize");
|
|
22
|
+
return method(self());
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
public:
|
|
26
|
+
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/HybridObjectInitializer;";
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// JHybridObjectRegistry.cpp
|
|
3
|
+
// DoubleConversion
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 22.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "JHybridObjectRegistry.hpp"
|
|
9
|
+
#include "HybridObjectRegistry.hpp"
|
|
10
|
+
#include "JNISharedPtr.h"
|
|
11
|
+
|
|
12
|
+
namespace margelo::nitro {
|
|
13
|
+
|
|
14
|
+
void JHybridObjectRegistry::registerHybridObjectConstructor(jni::alias_ref<jni::JClass> clazz, std::string hybridObjectName,
|
|
15
|
+
jni::alias_ref<JHybridObjectInitializer> constructorFn) {
|
|
16
|
+
auto sharedInitializer = jni::make_global(constructorFn);
|
|
17
|
+
HybridObjectRegistry::registerHybridObjectConstructor(hybridObjectName, [=]() -> std::shared_ptr<HybridObject> {
|
|
18
|
+
// 1. Call the Java initializer function
|
|
19
|
+
jni::local_ref<JHybridObject::javaobject> hybridObject = sharedInitializer->call();
|
|
20
|
+
// 2. Make the resulting HybridObject a global (shared) reference
|
|
21
|
+
jni::global_ref<JHybridObject::javaobject> globalHybridObject = jni::make_global(hybridObject);
|
|
22
|
+
// 3. Create a shared_ptr from the JNI global reference
|
|
23
|
+
std::shared_ptr<JHybridObject> sharedCppPart = JNISharedPtr::make_shared_from_jni<JHybridObject>(globalHybridObject);
|
|
24
|
+
// 4. Up-cast to a HybridObject (kinda unsafe)
|
|
25
|
+
std::shared_ptr<HybridObject> cast = std::static_pointer_cast<HybridObject>(sharedCppPart);
|
|
26
|
+
return cast;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
void JHybridObjectRegistry::registerNatives() {
|
|
31
|
+
javaClassStatic()->registerNatives({
|
|
32
|
+
makeNativeMethod("registerHybridObjectConstructor", JHybridObjectRegistry::registerHybridObjectConstructor),
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//
|
|
2
|
+
// JHybridObjectRegistry.hpp
|
|
3
|
+
// DoubleConversion
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 22.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "HybridObject.hpp"
|
|
11
|
+
#include "JHybridObjectInitializer.hpp"
|
|
12
|
+
#include <fbjni/fbjni.h>
|
|
13
|
+
|
|
14
|
+
namespace margelo::nitro {
|
|
15
|
+
|
|
16
|
+
using namespace facebook;
|
|
17
|
+
|
|
18
|
+
struct JHybridObjectRegistry : public jni::JavaClass<JHybridObjectRegistry> {
|
|
19
|
+
public:
|
|
20
|
+
static auto constexpr kJavaDescriptor = "Lcom/margelo/nitro/HybridObjectRegistry;";
|
|
21
|
+
|
|
22
|
+
public:
|
|
23
|
+
static void registerHybridObjectConstructor(jni::alias_ref<jni::JClass> clazz, std::string hybridObjectName,
|
|
24
|
+
jni::alias_ref<JHybridObjectInitializer> constructorFn);
|
|
25
|
+
|
|
26
|
+
public:
|
|
27
|
+
static void registerNatives();
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Created by Marc Rousavy on 21.02.24.
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
#pragma once
|
|
6
|
+
|
|
7
|
+
#include <fbjni/fbjni.h>
|
|
8
|
+
#include <memory>
|
|
9
|
+
|
|
10
|
+
namespace margelo::nitro {
|
|
11
|
+
|
|
12
|
+
using namespace facebook;
|
|
13
|
+
|
|
14
|
+
template <typename T>
|
|
15
|
+
struct GlobalRefDeleter {
|
|
16
|
+
explicit GlobalRefDeleter(jni::global_ref<typename T::javaobject> ref) : _ref(ref) {}
|
|
17
|
+
|
|
18
|
+
void operator()(T* ptr) {
|
|
19
|
+
if (_ref) {
|
|
20
|
+
_ref.release();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private:
|
|
25
|
+
jni::global_ref<typename T::javaobject> _ref;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
class JNISharedPtr {
|
|
29
|
+
public:
|
|
30
|
+
template <typename T, typename std::enable_if<std::is_base_of<jni::HybridClass<T>, T>::value>::type* = nullptr>
|
|
31
|
+
static std::shared_ptr<T> make_shared_from_jni(jni::global_ref<typename T::javaobject> ref) {
|
|
32
|
+
return std::shared_ptr<T>(ref->cthis(), GlobalRefDeleter<T>{ref});
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package com.margelo.nitro
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
import androidx.annotation.Keep
|
|
5
|
+
import com.facebook.jni.HybridData
|
|
6
|
+
import com.facebook.proguard.annotations.DoNotStrip
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A base class for all Kotlin-based HybridObjects.
|
|
10
|
+
*/
|
|
11
|
+
@Keep
|
|
12
|
+
@DoNotStrip
|
|
13
|
+
@Suppress("KotlinJniMissingFunction")
|
|
14
|
+
abstract class HybridObject {
|
|
15
|
+
/**
|
|
16
|
+
* Get the memory size of the Kotlin instance (plus any external heap allocations),
|
|
17
|
+
* in bytes.
|
|
18
|
+
*
|
|
19
|
+
* Override this to allow tracking heap allocations such as buffers or images,
|
|
20
|
+
* which will help the JS GC be more efficient in deleting large unused objects.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```kotlin
|
|
24
|
+
* val memorySize: ULong
|
|
25
|
+
* get() {
|
|
26
|
+
* val imageSize = this.bitmap.bytesPerRow * this.bitmap.height
|
|
27
|
+
* return getSizeOf(this) + imageSize
|
|
28
|
+
* }
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
@get:DoNotStrip
|
|
32
|
+
@get:Keep
|
|
33
|
+
abstract val memorySize: ULong
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Contains the C++ context (Hybrid Data) for the fbjni C++ part.
|
|
37
|
+
*/
|
|
38
|
+
@DoNotStrip
|
|
39
|
+
@Keep
|
|
40
|
+
val mHybridData: HybridData
|
|
41
|
+
|
|
42
|
+
init {
|
|
43
|
+
mHybridData = initHybrid()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private external fun initHybrid(): HybridData
|
|
47
|
+
|
|
48
|
+
companion object {
|
|
49
|
+
private const val TAG = "HybridObject"
|
|
50
|
+
init {
|
|
51
|
+
try {
|
|
52
|
+
Log.i(TAG, "Loading NitroModules C++ library...")
|
|
53
|
+
System.loadLibrary("NitroModules")
|
|
54
|
+
Log.i(TAG, "Successfully loaded NitroModules C++ library!")
|
|
55
|
+
} catch (e: Error) {
|
|
56
|
+
Log.e(TAG, "Failed to load NitroModules C++ library! Is it properly installed and linked?", e)
|
|
57
|
+
throw e
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.margelo.nitro;
|
|
2
|
+
|
|
3
|
+
import android.util.Log;
|
|
4
|
+
|
|
5
|
+
import java.util.function.Supplier;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A registry that holds initializers for HybridObjects.
|
|
9
|
+
* This will be used to initialize them from JS using `NitroModules.get<T>(name)`.
|
|
10
|
+
* @noinspection JavaJniMissingFunction
|
|
11
|
+
*/
|
|
12
|
+
public class HybridObjectRegistry {
|
|
13
|
+
/**
|
|
14
|
+
* Registers the given HybridObject in the `HybridObjectRegistry`.
|
|
15
|
+
* It will be uniquely identified via it's `hybridObjectName`, and can be initialized from
|
|
16
|
+
* JS using `NitroModules.get<T>(name)` - which will call the `constructorFn` here.
|
|
17
|
+
*/
|
|
18
|
+
public static native void registerHybridObjectConstructor(String hybridObjectName, HybridObjectInitializer initializer);
|
|
19
|
+
|
|
20
|
+
private static final String TAG = "HybridObjectRegistry";
|
|
21
|
+
static {
|
|
22
|
+
Log.i(TAG, "Loading native NitroModules C++ library...");
|
|
23
|
+
try {
|
|
24
|
+
System.loadLibrary("NitroModules");
|
|
25
|
+
Log.i(TAG, "Successfully loaded native NitroModules C++ library!");
|
|
26
|
+
} catch (Throwable e) {
|
|
27
|
+
Log.e(TAG, "Failed to load native NitroModules C++ library!", e);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
package com.margelo.nitro;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.Nullable;
|
|
4
|
+
|
|
5
|
+
import com.facebook.react.bridge.NativeModule;
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext;
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider;
|
|
8
|
+
import com.facebook.react.TurboReactPackage;
|
|
9
|
+
|
|
10
|
+
import java.util.HashMap;
|
|
11
|
+
|
|
12
|
+
public class NitroModulesPackage extends TurboReactPackage {
|
|
13
|
+
|
|
14
|
+
@Nullable
|
|
15
|
+
@Override
|
|
16
|
+
public NativeModule getModule(String name, ReactApplicationContext reactContext) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Override
|
|
21
|
+
public ReactModuleInfoProvider getReactModuleInfoProvider() {
|
|
22
|
+
return () -> {
|
|
23
|
+
return new HashMap<>();
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
#pragma once
|
|
6
6
|
|
|
7
|
+
#include "CountTrailingOptionals.hpp"
|
|
7
8
|
#include "HybridContext.hpp"
|
|
8
9
|
#include "JSIConverter.hpp"
|
|
9
10
|
#include "NitroLogger.hpp"
|
|
@@ -67,7 +68,8 @@ public:
|
|
|
67
68
|
* Get the `std::shared_ptr` instance of this HybridObject.
|
|
68
69
|
* The HybridObject must be managed inside a `shared_ptr` already, otherwise this will fail.
|
|
69
70
|
*/
|
|
70
|
-
template <typename Derived>
|
|
71
|
+
template <typename Derived>
|
|
72
|
+
std::shared_ptr<Derived> shared() {
|
|
71
73
|
return std::static_pointer_cast<Derived>(shared_from_this());
|
|
72
74
|
}
|
|
73
75
|
|
|
@@ -151,14 +153,16 @@ private:
|
|
|
151
153
|
private:
|
|
152
154
|
template <typename Derived, typename ReturnType, typename... Args, size_t... Is>
|
|
153
155
|
static inline jsi::Value callMethod(Derived* obj, ReturnType (Derived::*method)(Args...), jsi::Runtime& runtime, const jsi::Value* args,
|
|
154
|
-
std::index_sequence<Is...>) {
|
|
156
|
+
size_t argsSize, std::index_sequence<Is...>) {
|
|
157
|
+
jsi::Value defaultValue;
|
|
158
|
+
|
|
155
159
|
if constexpr (std::is_void_v<ReturnType>) {
|
|
156
160
|
// It's a void method.
|
|
157
|
-
(obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
|
|
161
|
+
(obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, Is < argsSize ? args[Is] : defaultValue)...);
|
|
158
162
|
return jsi::Value::undefined();
|
|
159
163
|
} else {
|
|
160
164
|
// It's returning some C++ type, we need to convert that to a JSI value now.
|
|
161
|
-
ReturnType result = (obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, args[Is])...);
|
|
165
|
+
ReturnType result = (obj->*method)(JSIConverter<std::decay_t<Args>>::fromJSI(runtime, Is < argsSize ? args[Is] : defaultValue)...);
|
|
162
166
|
return JSIConverter<std::decay_t<ReturnType>>::toJSI(runtime, std::move(result));
|
|
163
167
|
}
|
|
164
168
|
}
|
|
@@ -168,11 +172,22 @@ private:
|
|
|
168
172
|
MethodType type) {
|
|
169
173
|
return [name, derivedInstance, method, type](jsi::Runtime& runtime, const jsi::Value& thisVal, const jsi::Value* args,
|
|
170
174
|
size_t count) -> jsi::Value {
|
|
171
|
-
|
|
175
|
+
constexpr size_t optionalArgsCount = trailing_optionals_count_v<Args...>;
|
|
176
|
+
constexpr size_t maxArgsCount = sizeof...(Args);
|
|
177
|
+
constexpr size_t minArgsCount = maxArgsCount - optionalArgsCount;
|
|
178
|
+
bool isWithinArgsRange = (count >= minArgsCount && count <= maxArgsCount);
|
|
179
|
+
if (!isWithinArgsRange) {
|
|
172
180
|
// invalid amount of arguments passed!
|
|
173
181
|
std::string hybridObjectName = derivedInstance->_name;
|
|
174
|
-
|
|
175
|
-
|
|
182
|
+
if (minArgsCount == maxArgsCount) {
|
|
183
|
+
// min and max args length is the same, so we don't have any optional parameters. fixed count
|
|
184
|
+
throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected " + std::to_string(maxArgsCount) +
|
|
185
|
+
" arguments, but received " + std::to_string(count) + "!");
|
|
186
|
+
} else {
|
|
187
|
+
// min and max args length are different, so we have optional parameters - variable length arguments.
|
|
188
|
+
throw jsi::JSError(runtime, hybridObjectName + "." + name + "(...) expected between " + std::to_string(minArgsCount) + " and " +
|
|
189
|
+
std::to_string(maxArgsCount) + " arguments, but received " + std::to_string(count) + "!");
|
|
190
|
+
}
|
|
176
191
|
}
|
|
177
192
|
|
|
178
193
|
try {
|
|
@@ -183,7 +198,7 @@ private:
|
|
|
183
198
|
} else {
|
|
184
199
|
// Call the actual method with JSI values as arguments and return a JSI value again.
|
|
185
200
|
// Internally, this method converts the JSI values to C++ values.
|
|
186
|
-
return callMethod(derivedInstance, method, runtime, args, std::index_sequence_for<Args...>{});
|
|
201
|
+
return callMethod(derivedInstance, method, runtime, args, count, std::index_sequence_for<Args...>{});
|
|
187
202
|
}
|
|
188
203
|
} catch (const std::exception& exception) {
|
|
189
204
|
std::string hybridObjectName = derivedInstance->_name;
|
|
@@ -13,7 +13,8 @@ namespace margelo::nitro {
|
|
|
13
13
|
|
|
14
14
|
using namespace facebook;
|
|
15
15
|
|
|
16
|
-
template <typename T>
|
|
16
|
+
template <typename T>
|
|
17
|
+
class PointerHolder final : public HybridObject {
|
|
17
18
|
protected:
|
|
18
19
|
// no default constructor
|
|
19
20
|
PointerHolder() = delete;
|