react-native-nitro-modules 0.4.0 → 0.5.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 +3 -1
- package/README.md +32 -1
- package/android/gradle.properties +1 -1
- package/cpp/core/ArrayBuffer.cpp +88 -0
- package/cpp/core/ArrayBuffer.hpp +31 -70
- package/cpp/jsi/JSIConverter+Function.hpp +3 -3
- package/cpp/jsi/JSIConverter+Promise.hpp +2 -1
- package/ios/core/ArrayBufferHolder.hpp +78 -0
- package/ios/core/ArrayBufferHolder.swift +48 -0
- package/ios/core/Promise.swift +162 -0
- package/ios/core/PromiseHolder.hpp +82 -0
- package/ios/turbomodule/NitroModuleOnLoad.mm +5 -0
- package/ios/utils/ClosureWrapper.swift +45 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/ios/core/HybridContext.cpp +0 -8
- package/ios/core/Promise.cpp +0 -10
- package/ios/core/Promise.hpp +0 -43
- package/lib/typescript/AnyMap.d.ts +0 -17
- package/lib/typescript/AnyMap.d.ts.map +0 -1
- package/lib/typescript/HybridObject.d.ts +0 -83
- package/lib/typescript/HybridObject.d.ts.map +0 -1
- package/lib/typescript/ModuleNotFoundError.d.ts +0 -7
- package/lib/typescript/ModuleNotFoundError.d.ts.map +0 -1
- package/lib/typescript/NativeNitroModules.d.ts +0 -15
- package/lib/typescript/NativeNitroModules.d.ts.map +0 -1
- package/lib/typescript/NativeNitroModules.web.d.ts +0 -5
- package/lib/typescript/NativeNitroModules.web.d.ts.map +0 -1
- package/lib/typescript/NitroModules.d.ts +0 -26
- package/lib/typescript/NitroModules.d.ts.map +0 -1
- package/lib/typescript/__tests__/index.test.d.ts +0 -1
- package/lib/typescript/__tests__/index.test.d.ts.map +0 -1
- package/lib/typescript/index.d.ts +0 -4
- package/lib/typescript/index.d.ts.map +0 -1
package/NitroModules.podspec
CHANGED
|
@@ -37,7 +37,9 @@ Pod::Spec.new do |s|
|
|
|
37
37
|
"cpp/utils/NitroHash.hpp",
|
|
38
38
|
"cpp/utils/NitroDefines.hpp",
|
|
39
39
|
# Public iOS-specific headers that will be exposed in modulemap (for Swift)
|
|
40
|
-
"ios/core/
|
|
40
|
+
"ios/core/ArrayBufferHolder.hpp",
|
|
41
|
+
"ios/core/PromiseHolder.hpp",
|
|
42
|
+
"ios/core/HybridContext.hpp",
|
|
41
43
|
]
|
|
42
44
|
|
|
43
45
|
s.pod_target_xcconfig = {
|
package/README.md
CHANGED
|
@@ -111,67 +111,98 @@ The following C++ / JS types are supported out of the box:
|
|
|
111
111
|
<tr>
|
|
112
112
|
<th>JS Type</th>
|
|
113
113
|
<th>C++ Type</th>
|
|
114
|
+
<th>Swift Type</th>
|
|
114
115
|
</tr>
|
|
115
116
|
|
|
116
117
|
<tr>
|
|
117
118
|
<td><code>number</code></td>
|
|
118
119
|
<td><code>double</code> / <code>int</code> / <code>float</code></td>
|
|
120
|
+
<td><code>Double</code> / <code>Int</code> / <code>Float</code></td>
|
|
119
121
|
</tr>
|
|
120
122
|
<tr>
|
|
121
123
|
<td><code>boolean</code></td>
|
|
122
124
|
<td><code>bool</code></td>
|
|
125
|
+
<td><code>Bool</code></td>
|
|
123
126
|
</tr>
|
|
124
127
|
<tr>
|
|
125
128
|
<td><code>string</code></td>
|
|
126
129
|
<td><code>std::string</code></td>
|
|
130
|
+
<td><code>String</code></td>
|
|
127
131
|
</tr>
|
|
128
132
|
<tr>
|
|
129
133
|
<td><code>bigint</code></td>
|
|
130
134
|
<td><code>int64_t</code> / <code>uint64_t</code></td>
|
|
135
|
+
<td><code>Int64</code> / <code>UInt64</code></td>
|
|
131
136
|
</tr>
|
|
132
137
|
<tr>
|
|
133
138
|
<td><code>T[]</code></td>
|
|
134
139
|
<td><code>std::vector<T></code></td>
|
|
140
|
+
<td><code>[T]</code></td>
|
|
135
141
|
</tr>
|
|
136
142
|
<tr>
|
|
137
143
|
<td><code>[A, B, C, ...]</code></td>
|
|
138
144
|
<td><code>std::tuple<A, B, C, ...></code></td>
|
|
145
|
+
<td><code>(A, B, C)</code></td>
|
|
139
146
|
</tr>
|
|
140
147
|
<tr>
|
|
141
148
|
<td><code>A | B | C | ...</code></td>
|
|
142
149
|
<td><code>std::variant<A, B, C, ...></code></td>
|
|
150
|
+
<td><code>Variant_A_B_C</code></td>
|
|
143
151
|
</tr>
|
|
144
152
|
<tr>
|
|
145
153
|
<td><code>Record<string, T></code></td>
|
|
146
154
|
<td><code>std::unordered_map<std::string, T></code></td>
|
|
155
|
+
<td><code>Dictionary<String, T></code></td>
|
|
147
156
|
</tr>
|
|
148
157
|
<tr>
|
|
149
158
|
<td><code>T?</code></td>
|
|
150
159
|
<td><code>std::optional<T></code></td>
|
|
160
|
+
<td><code>T?</code></td>
|
|
151
161
|
</tr>
|
|
152
162
|
<tr>
|
|
153
163
|
<td><code>Promise<T></code></td>
|
|
154
164
|
<td><code>std::future<T></code></td>
|
|
165
|
+
<td><code><a href="./ios/core/Promise.swift">Promise<T></a></code></td>
|
|
155
166
|
</tr>
|
|
156
167
|
<tr>
|
|
157
168
|
<td><code>(TArgs...) => void</code></td>
|
|
158
169
|
<td><code>std::function<void (TArgs...)></code></td>
|
|
170
|
+
<td><code>@escaping (TArgs...) -> Void</code></td>
|
|
159
171
|
</tr>
|
|
160
172
|
<tr>
|
|
161
173
|
<td><code>(TArgs...) => TReturn</code></td>
|
|
162
174
|
<td><code>std::function<std::future<TReturn> (TArgs...)></code></td>
|
|
175
|
+
<td>❌</td>
|
|
163
176
|
</tr>
|
|
164
177
|
<tr>
|
|
165
178
|
<td><code>{ ... }</code></td>
|
|
166
179
|
<td><code>std::shared_ptr<<a href="./cpp/core/AnyMap.hpp">AnyMap</a>></code></td>
|
|
180
|
+
<td>❌</td>
|
|
167
181
|
</tr>
|
|
168
182
|
<tr>
|
|
169
183
|
<td><code>ArrayBuffer</code></td>
|
|
170
184
|
<td><code>std::shared_ptr<<a href="./cpp/core/ArrayBuffer.hpp">ArrayBuffer</a>></code></td>
|
|
185
|
+
<td><code><a href="./ios/core/ArrayBufferHolder.hpp">ArrayBufferHolder</a></code></td>
|
|
171
186
|
</tr>
|
|
172
187
|
<tr>
|
|
173
|
-
<td
|
|
188
|
+
<td>..any <code><a href="./src/HybridObject.ts">HybridObject</a></code></td>
|
|
174
189
|
<td><code>std::shared_ptr<<a href="./cpp/core/HybridObject.hpp">HybridObject</a>></code></td>
|
|
190
|
+
<td><code><a href="./ios/core/HybridObjectSpec.swift">HybridObjectSpec</a></code></td>
|
|
191
|
+
</tr>
|
|
192
|
+
<tr>
|
|
193
|
+
<td>..any <code>interface</code></td>
|
|
194
|
+
<td><code>T</code></td>
|
|
195
|
+
<td><code>T</code></td>
|
|
196
|
+
</tr>
|
|
197
|
+
<tr>
|
|
198
|
+
<td>..any <code>enum</code></td>
|
|
199
|
+
<td><code>T</code></td>
|
|
200
|
+
<td><code>T</code></td>
|
|
201
|
+
</tr>
|
|
202
|
+
<tr>
|
|
203
|
+
<td>..any <code>union</code></td>
|
|
204
|
+
<td><code>T</code></td>
|
|
205
|
+
<td><code>T</code></td>
|
|
175
206
|
</tr>
|
|
176
207
|
</table>
|
|
177
208
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ArrayBuffer.cpp
|
|
3
|
+
// react-native-nitro
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 14.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#include "ArrayBuffer.hpp"
|
|
9
|
+
#include "OwningReference.hpp"
|
|
10
|
+
#include <functional>
|
|
11
|
+
#include <jsi/jsi.h>
|
|
12
|
+
#include <thread>
|
|
13
|
+
|
|
14
|
+
namespace margelo::nitro {
|
|
15
|
+
|
|
16
|
+
using namespace facebook;
|
|
17
|
+
|
|
18
|
+
// 1. ArrayBuffer
|
|
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);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 2. NativeArrayBuffer
|
|
25
|
+
|
|
26
|
+
NativeArrayBuffer::NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext)
|
|
27
|
+
: ArrayBuffer(), _data(data), _size(size), _deleteFunc(deleteFunc), _deleteFuncContext(deleteFuncContext) {}
|
|
28
|
+
|
|
29
|
+
NativeArrayBuffer::~NativeArrayBuffer() {
|
|
30
|
+
if (_deleteFunc != nullptr) {
|
|
31
|
+
_deleteFunc(_deleteFuncContext);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
uint8_t* NativeArrayBuffer::data() {
|
|
36
|
+
return _data;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
size_t NativeArrayBuffer::size() const {
|
|
40
|
+
return _size;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
bool NativeArrayBuffer::isOwner() const noexcept {
|
|
44
|
+
return _deleteFunc != nullptr;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. JSArrayBuffer
|
|
48
|
+
|
|
49
|
+
JSArrayBuffer::JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference)
|
|
50
|
+
: ArrayBuffer(), _runtime(runtime), _jsReference(jsReference), _initialThreadId(std::this_thread::get_id()) {}
|
|
51
|
+
|
|
52
|
+
JSArrayBuffer::~JSArrayBuffer() {}
|
|
53
|
+
|
|
54
|
+
uint8_t* JSArrayBuffer::data() {
|
|
55
|
+
if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
|
|
56
|
+
throw std::runtime_error("`data()` can only be accessed synchronously on the JS Thread! "
|
|
57
|
+
"If you want to access it elsewhere, copy it first.");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
|
|
61
|
+
if (!_jsReference) [[unlikely]] {
|
|
62
|
+
// JS Part has been deleted - data is now nullptr.
|
|
63
|
+
return nullptr;
|
|
64
|
+
}
|
|
65
|
+
// JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
|
|
66
|
+
return _jsReference->data(*_runtime);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
size_t JSArrayBuffer::size() const {
|
|
70
|
+
if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
|
|
71
|
+
throw std::runtime_error("`size()` can only be accessed synchronously on the JS Thread! "
|
|
72
|
+
"If you want to access it elsewhere, copy it first.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
|
|
76
|
+
if (!_jsReference) [[unlikely]] {
|
|
77
|
+
// JS Part has been deleted - size is now 0.
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
// JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
|
|
81
|
+
return _jsReference->size(*_runtime);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
bool JSArrayBuffer::isOwner() const noexcept {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
} // namespace margelo::nitro
|
package/cpp/core/ArrayBuffer.hpp
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
#pragma once
|
|
9
9
|
|
|
10
10
|
#include "OwningReference.hpp"
|
|
11
|
-
#include <functional>
|
|
12
11
|
#include <jsi/jsi.h>
|
|
13
12
|
#include <thread>
|
|
14
13
|
|
|
@@ -16,9 +15,7 @@ namespace margelo::nitro {
|
|
|
16
15
|
|
|
17
16
|
using namespace facebook;
|
|
18
17
|
|
|
19
|
-
using DeleteFn =
|
|
20
|
-
|
|
21
|
-
static DeleteFn defaultDeleteFn = [](uint8_t* buffer) { delete[] buffer; };
|
|
18
|
+
using DeleteFn = void (*)(void* context);
|
|
22
19
|
|
|
23
20
|
/**
|
|
24
21
|
* Represents a raw byte buffer that can be read from-, and
|
|
@@ -41,7 +38,7 @@ public:
|
|
|
41
38
|
ArrayBuffer(const ArrayBuffer&) = delete;
|
|
42
39
|
ArrayBuffer(ArrayBuffer&&) = delete;
|
|
43
40
|
virtual ~ArrayBuffer() = default;
|
|
44
|
-
|
|
41
|
+
|
|
45
42
|
public:
|
|
46
43
|
/**
|
|
47
44
|
* Returns whether this `ArrayBuffer` is actually owning the data,
|
|
@@ -49,6 +46,13 @@ public:
|
|
|
49
46
|
* memory that we didn't allocate, or from JS - which can be deleted at any point).
|
|
50
47
|
*/
|
|
51
48
|
virtual bool isOwner() const noexcept = 0;
|
|
49
|
+
|
|
50
|
+
public:
|
|
51
|
+
/**
|
|
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.
|
|
54
|
+
*/
|
|
55
|
+
static std::shared_ptr<ArrayBuffer> makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext);
|
|
52
56
|
};
|
|
53
57
|
|
|
54
58
|
/**
|
|
@@ -62,46 +66,31 @@ public:
|
|
|
62
66
|
*
|
|
63
67
|
* It is safe to access `data()` and `size()` from any Thread, but there are no synchronization/mutexes implemented by default.
|
|
64
68
|
*/
|
|
65
|
-
class NativeArrayBuffer : public ArrayBuffer {
|
|
69
|
+
class NativeArrayBuffer final : public ArrayBuffer {
|
|
66
70
|
public:
|
|
67
71
|
/**
|
|
68
72
|
* Create a new **owning** `ArrayBuffer`.
|
|
69
73
|
* The `ArrayBuffer` can be kept in memory, as C++ owns the data
|
|
70
|
-
* and will only delete it once this `ArrayBuffer` gets deleted
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Create a new `ArrayBuffer`.
|
|
76
|
-
* If `destroyOnDeletion` is `true`, the `ArrayBuffer` is **owning**, otherwise it is **non-owning**.
|
|
77
|
-
* The `ArrayBuffer` can only be safely kept in memory if it is owning (`isOwning()`).
|
|
74
|
+
* and will only delete it once this `ArrayBuffer` gets deleted.
|
|
75
|
+
*
|
|
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.
|
|
78
78
|
*/
|
|
79
|
-
NativeArrayBuffer(uint8_t* data, size_t size,
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
~NativeArrayBuffer() {
|
|
84
|
-
if (_deleteFunc != nullptr) {
|
|
85
|
-
_deleteFunc(_data);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
79
|
+
NativeArrayBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext);
|
|
80
|
+
~NativeArrayBuffer();
|
|
88
81
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
size_t size() const override {
|
|
94
|
-
return _size;
|
|
95
|
-
}
|
|
82
|
+
public:
|
|
83
|
+
uint8_t* data() override;
|
|
84
|
+
size_t size() const override;
|
|
85
|
+
bool isOwner() const noexcept override;
|
|
96
86
|
|
|
97
|
-
|
|
98
|
-
return _deleteFunc != nullptr;
|
|
99
|
-
}
|
|
87
|
+
double something();
|
|
100
88
|
|
|
101
89
|
private:
|
|
102
90
|
uint8_t* _data;
|
|
103
91
|
size_t _size;
|
|
104
92
|
DeleteFn _deleteFunc;
|
|
93
|
+
void* _deleteFuncContext;
|
|
105
94
|
};
|
|
106
95
|
|
|
107
96
|
/**
|
|
@@ -115,52 +104,24 @@ private:
|
|
|
115
104
|
*
|
|
116
105
|
* If the JS ArrayBuffer (or it's JS Runtime) have already been deleted, `data()` returns `nullptr`.
|
|
117
106
|
*/
|
|
118
|
-
class JSArrayBuffer : public ArrayBuffer {
|
|
107
|
+
class JSArrayBuffer final : public ArrayBuffer {
|
|
119
108
|
public:
|
|
120
|
-
explicit JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference)
|
|
121
|
-
|
|
122
|
-
~JSArrayBuffer() {}
|
|
109
|
+
explicit JSArrayBuffer(jsi::Runtime* runtime, OwningReference<jsi::ArrayBuffer> jsReference);
|
|
110
|
+
~JSArrayBuffer();
|
|
123
111
|
|
|
124
112
|
public:
|
|
125
113
|
/**
|
|
126
114
|
* Gets the data this `ArrayBuffer` points to, or `nullptr` if it has already been deleted.
|
|
127
115
|
*/
|
|
128
|
-
uint8_t* data() override
|
|
129
|
-
if (_initialThreadId != std::this_thread::get_id()) [[unlikely]] {
|
|
130
|
-
throw std::runtime_error("`data()` can only be accessed synchronously on the JS Thread! "
|
|
131
|
-
"If you want to access it elsewhere, copy it first.");
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
|
|
135
|
-
if (!_jsReference) [[unlikely]] {
|
|
136
|
-
// JS Part has been deleted - data is now nullptr.
|
|
137
|
-
return nullptr;
|
|
138
|
-
}
|
|
139
|
-
// JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
|
|
140
|
-
return _jsReference->data(*_runtime);
|
|
141
|
-
}
|
|
142
|
-
|
|
116
|
+
uint8_t* data() override;
|
|
143
117
|
/**
|
|
144
118
|
* Gets the size of the data this `ArrayBuffer` points to, or `0` if it has already been deleted.
|
|
145
119
|
*/
|
|
146
|
-
size_t size() const override
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
OwningLock<jsi::ArrayBuffer> lock = _jsReference.lock();
|
|
153
|
-
if (!_jsReference) [[unlikely]] {
|
|
154
|
-
// JS Part has been deleted - size is now 0.
|
|
155
|
-
return 0;
|
|
156
|
-
}
|
|
157
|
-
// JS Part is still alive - we can assume that the jsi::Runtime is safe to access here too.
|
|
158
|
-
return _jsReference->size(*_runtime);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
bool isOwner() const noexcept override {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
120
|
+
size_t size() const override;
|
|
121
|
+
/**
|
|
122
|
+
* Returns `false` for JS-based ArrayBuffers.
|
|
123
|
+
*/
|
|
124
|
+
bool isOwner() const noexcept override;
|
|
164
125
|
|
|
165
126
|
private:
|
|
166
127
|
jsi::Runtime* _runtime;
|
|
@@ -67,9 +67,9 @@ struct JSIConverter<std::function<ReturnType(Args...)>> {
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
static inline jsi::Value toJSI(jsi::Runtime& runtime, std::function<ReturnType(Args...)
|
|
71
|
-
jsi::HostFunctionType jsFunction = [function
|
|
72
|
-
|
|
70
|
+
static inline jsi::Value toJSI(jsi::Runtime& runtime, const std::function<ReturnType(Args...)>& function) {
|
|
71
|
+
jsi::HostFunctionType jsFunction = [function](jsi::Runtime& runtime, const jsi::Value& thisValue,
|
|
72
|
+
const jsi::Value* args, size_t count) -> jsi::Value {
|
|
73
73
|
if (count != sizeof...(Args)) [[unlikely]] {
|
|
74
74
|
throw jsi::JSError(runtime, "Function expected " + std::to_string(sizeof...(Args)) + " arguments, but received " +
|
|
75
75
|
std::to_string(count) + "!");
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ArrayBufferHolder.hpp
|
|
3
|
+
// react-native-nitro
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 14.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "ArrayBuffer.hpp"
|
|
11
|
+
#include <swift/bridging>
|
|
12
|
+
#include <memory>
|
|
13
|
+
|
|
14
|
+
namespace margelo::nitro {
|
|
15
|
+
|
|
16
|
+
using namespace facebook;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Holds instances of `std::shared_ptr<ArrayBuffer>`.
|
|
20
|
+
* The reason this exists is because we cannot directly use `shared_ptr`,
|
|
21
|
+
* nor virtual functions (`jsi::MutableBuffer`) in Swift.
|
|
22
|
+
*
|
|
23
|
+
* Passing around instances of `ArrayBufferHolder` (or `std::shared_ptr<ArrayBuffer>`)
|
|
24
|
+
* does not involve any data copies and is almost zero-overhead - even when passed to JS.
|
|
25
|
+
*/
|
|
26
|
+
class ArrayBufferHolder {
|
|
27
|
+
public:
|
|
28
|
+
ArrayBufferHolder(const std::shared_ptr<ArrayBuffer>& arrayBuffer) : _arrayBuffer(arrayBuffer) {}
|
|
29
|
+
|
|
30
|
+
public:
|
|
31
|
+
/**
|
|
32
|
+
* Create a new `NativeArrayBuffer` that wraps the given data of the given size, without copying it.
|
|
33
|
+
*
|
|
34
|
+
* Once the `ArrayBuffer` is no longer in use, the given `deleteFunc` will be called with the given `deleteFuncContext`
|
|
35
|
+
* as an argument. The caller is responsible for deleting `data` (and `deleteFuncContext`) once this is called.
|
|
36
|
+
*/
|
|
37
|
+
static ArrayBufferHolder makeBuffer(uint8_t* data, size_t size, DeleteFn deleteFunc, void* deleteFuncContext) {
|
|
38
|
+
auto arrayBuffer = ArrayBuffer::makeBuffer(data, size, deleteFunc, deleteFuncContext);
|
|
39
|
+
return ArrayBufferHolder(arrayBuffer);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public:
|
|
43
|
+
/**
|
|
44
|
+
* Gets the raw bytes the underlying `ArrayBuffer` points to.
|
|
45
|
+
*/
|
|
46
|
+
void* getData() const SWIFT_COMPUTED_PROPERTY {
|
|
47
|
+
return _arrayBuffer->data();
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Gets the size of the raw bytes the underlying `ArrayBuffer` points to.
|
|
51
|
+
*/
|
|
52
|
+
size_t getSize() const SWIFT_COMPUTED_PROPERTY {
|
|
53
|
+
return _arrayBuffer->size();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Whether the underlying `ArrayBuffer` actually owns the data it points to, or not.
|
|
58
|
+
*
|
|
59
|
+
* - If an `ArrayBuffer` owns the data, it is likely an ArrayBuffer created on the native side (C++/Swift).
|
|
60
|
+
* This means the `ArrayBuffer` is safe to access as long as you have a reference to it, and cannot be deleted otherwise.
|
|
61
|
+
* - If an `ArrayBuffer` doesn't own the data, it is likely an ArrayBuffer coming from JS.
|
|
62
|
+
* This means the `ArrayBuffer` is **NOT** safe to access outside of the synchronous function's scope.
|
|
63
|
+
* If you plan on hopping do a different Thread, or storing a long-lived reference to it, make sure to **copy** the data.
|
|
64
|
+
*/
|
|
65
|
+
bool getIsOwner() const SWIFT_COMPUTED_PROPERTY {
|
|
66
|
+
return _arrayBuffer->isOwner();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public:
|
|
70
|
+
inline std::shared_ptr<ArrayBuffer> getArrayBuffer() const {
|
|
71
|
+
return _arrayBuffer;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private:
|
|
75
|
+
std::shared_ptr<ArrayBuffer> _arrayBuffer;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
} // namespace margelo::nitro
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
//
|
|
2
|
+
// ArrayBufferHolder.swift
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 17.07.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Holds instances of `std::shared_ptr<ArrayBuffer>`, which can be passed
|
|
12
|
+
* between native and JS **without copy**.
|
|
13
|
+
*
|
|
14
|
+
* See `data`, `size` and `isOwning`.
|
|
15
|
+
*/
|
|
16
|
+
public typealias ArrayBufferHolder = margelo.nitro.ArrayBufferHolder
|
|
17
|
+
|
|
18
|
+
public extension ArrayBufferHolder {
|
|
19
|
+
/**
|
|
20
|
+
* Create a new `ArrayBufferHolder` that wraps the given `data` of the given `size`
|
|
21
|
+
* without performing a copy.
|
|
22
|
+
* When the `ArrayBuffer` is no longer used, `onDelete` will be called, in which
|
|
23
|
+
* you as a caller are responsible for deleting `data`.
|
|
24
|
+
*/
|
|
25
|
+
static func wrap(dataWithoutCopy data: UnsafeMutablePointer<UInt8>,
|
|
26
|
+
size: Int,
|
|
27
|
+
onDelete delete: @escaping () -> Void) -> ArrayBufferHolder {
|
|
28
|
+
// Convert escaping Swift closure to a `void*`
|
|
29
|
+
let (wrappedClosure, context) = ClosureWrapper.wrap(closure: delete)
|
|
30
|
+
// Create ArrayBufferHolder with our wrapped Swift closure to make it callable as a C-function pointer
|
|
31
|
+
return ArrayBufferHolder.makeBuffer(data, size, wrappedClosure, context)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Allocate a new buffer of the given `size`.
|
|
36
|
+
* If `initializeToZero` is `true`, all bytes are set to `0`, otherwise they are left untouched.
|
|
37
|
+
*/
|
|
38
|
+
static func allocate(size: Int, initializeToZero: Bool = false) -> ArrayBufferHolder {
|
|
39
|
+
let data = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
|
|
40
|
+
if initializeToZero {
|
|
41
|
+
data.initialize(repeating: 0, count: size)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return ArrayBufferHolder.makeBuffer(data, size, { data in
|
|
45
|
+
data?.deallocate()
|
|
46
|
+
}, data)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Promise.swift
|
|
3
|
+
// NitroModules
|
|
4
|
+
//
|
|
5
|
+
// Created by Marc Rousavy on 15.08.24.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Represents a Promise that can be passed to JS.
|
|
12
|
+
*
|
|
13
|
+
* Create a new Promise with the following APIs:
|
|
14
|
+
* - `Promise<T>.async { ... }` - Creates a new Promise that runs the given code in a Swift `async`/`await` Task.
|
|
15
|
+
* - `Promise<T>.parallel { ... }` - Creates a new Promise that runs the given code in a parallel `DispatchQueue`.
|
|
16
|
+
* - `Promise<T>.resolved(withResult:)` - Creates a new already resolved Promise.
|
|
17
|
+
* - `Promise<T>.rejected(withError:)` - Creates a new already rejected Promise.
|
|
18
|
+
* - `Promise<T>()` - Creates a new Promise with fully manual control over the `resolve(..)`/`reject(..)` functions.
|
|
19
|
+
*/
|
|
20
|
+
public class Promise<T> {
|
|
21
|
+
private enum State {
|
|
22
|
+
case result(T)
|
|
23
|
+
case error(Error)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private var state: State?
|
|
27
|
+
private var onResolvedListeners: [(T) -> Void] = []
|
|
28
|
+
private var onRejectedListeners: [(Error) -> Void] = []
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create a new pending Promise.
|
|
32
|
+
* It can (and must) be resolved **or** rejected later.
|
|
33
|
+
*/
|
|
34
|
+
public init() {
|
|
35
|
+
state = nil
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
deinit {
|
|
39
|
+
if state == nil {
|
|
40
|
+
print("⚠️ Promise<\(String(describing: T.self))> got destroyed, but was never resolved or rejected! It is probably left hanging in JS now.")
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolves this `Promise<T>` with the given `T` and notifies all listeners.
|
|
46
|
+
*/
|
|
47
|
+
public func resolve(withResult result: T) {
|
|
48
|
+
guard state == nil else {
|
|
49
|
+
fatalError("Failed to resolve promise with \(result) - it has already been resolved or rejected!")
|
|
50
|
+
}
|
|
51
|
+
state = .result(result)
|
|
52
|
+
onResolvedListeners.forEach { listener in listener(result) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Rejects this `Promise<T>` with the given `Error` and notifies all listeners.
|
|
57
|
+
*/
|
|
58
|
+
public func reject(withError error: Error) {
|
|
59
|
+
guard state == nil else {
|
|
60
|
+
fatalError("Failed to reject promise with \(error) - it has already been resolved or rejected!")
|
|
61
|
+
}
|
|
62
|
+
state = .error(error)
|
|
63
|
+
onRejectedListeners.forEach { listener in listener(error) }
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Extensions to easily create new Promises.
|
|
69
|
+
*/
|
|
70
|
+
extension Promise {
|
|
71
|
+
/**
|
|
72
|
+
* Create a new `Promise<T>` already resolved with the given `T`.
|
|
73
|
+
*/
|
|
74
|
+
public static func resolved(withResult result: T) -> Promise {
|
|
75
|
+
let promise = Promise()
|
|
76
|
+
promise.state = .result(result)
|
|
77
|
+
return promise
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Create a new `Promise<T>` already rejected with the given `Error`.
|
|
82
|
+
*/
|
|
83
|
+
public static func rejected(withError error: Error) -> Promise {
|
|
84
|
+
let promise = Promise()
|
|
85
|
+
promise.state = .error(error)
|
|
86
|
+
return promise
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Create a new `Promise<T>` that runs the given `async` code in a `Task`.
|
|
91
|
+
* This does not necessarily run the code in a different Thread, but supports Swift's `async`/`await`.
|
|
92
|
+
*/
|
|
93
|
+
public static func `async`(_ priority: TaskPriority? = nil,
|
|
94
|
+
_ run: @escaping () async throws -> T) -> Promise {
|
|
95
|
+
let promise = Promise()
|
|
96
|
+
Task(priority: priority) {
|
|
97
|
+
do {
|
|
98
|
+
let result = try await run()
|
|
99
|
+
promise.resolve(withResult: result)
|
|
100
|
+
} catch {
|
|
101
|
+
promise.reject(withError: error)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return promise
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create a new `Promise<T>` that runs the given `run` function on a parallel Thread/`DispatchQueue`.
|
|
109
|
+
*/
|
|
110
|
+
public static func parallel(_ queue: DispatchQueue = .global(),
|
|
111
|
+
_ run: @escaping () throws -> T) -> Promise {
|
|
112
|
+
let promise = Promise()
|
|
113
|
+
queue.async {
|
|
114
|
+
do {
|
|
115
|
+
let result = try run()
|
|
116
|
+
promise.resolve(withResult: result)
|
|
117
|
+
} catch {
|
|
118
|
+
promise.reject(withError: error)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return promise
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Extensions to support then/catch syntax.
|
|
127
|
+
*/
|
|
128
|
+
extension Promise {
|
|
129
|
+
/**
|
|
130
|
+
* Add a continuation listener to this `Promise<T>`.
|
|
131
|
+
* Once the `Promise<T>` resolves, the `onResolvedListener` will be called.
|
|
132
|
+
*/
|
|
133
|
+
@discardableResult
|
|
134
|
+
public func then(_ onResolvedListener: @escaping (T) -> Void) -> Promise {
|
|
135
|
+
switch state {
|
|
136
|
+
case .result(let result):
|
|
137
|
+
onResolvedListener(result)
|
|
138
|
+
break
|
|
139
|
+
default:
|
|
140
|
+
onResolvedListeners.append(onResolvedListener)
|
|
141
|
+
break
|
|
142
|
+
}
|
|
143
|
+
return self
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Add an error continuation listener to this `Promise<T>`.
|
|
148
|
+
* Once the `Promise<T>` rejects, the `onRejectedListener` will be called with the error.
|
|
149
|
+
*/
|
|
150
|
+
@discardableResult
|
|
151
|
+
public func `catch`(_ onRejectedListener: @escaping (Error) -> Void) -> Promise {
|
|
152
|
+
switch state {
|
|
153
|
+
case .error(let error):
|
|
154
|
+
onRejectedListener(error)
|
|
155
|
+
break
|
|
156
|
+
default:
|
|
157
|
+
onRejectedListeners.append(onRejectedListener)
|
|
158
|
+
break
|
|
159
|
+
}
|
|
160
|
+
return self
|
|
161
|
+
}
|
|
162
|
+
}
|