react-native-webgpu 0.19.2 → 0.20.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/Package.swift +19 -7
- package/android/build.gradle +7 -0
- package/apple/ApplePlatformContext.h +6 -1
- package/apple/ApplePlatformContext.mm +7 -8
- package/apple/MetalLayerColorSpace.mm +17 -1
- package/apple/WebGPUModule.mm +15 -1
- package/cpp/jsi/Promise.cpp +4 -0
- package/cpp/jsi/Promise.h +3 -0
- package/cpp/rnwgpu/CanvasColorSpace.h +5 -0
- package/cpp/rnwgpu/RNWebGPUManager.cpp +2 -4
- package/cpp/rnwgpu/SurfaceRegistry.h +16 -7
- package/cpp/rnwgpu/WebGPURuntime.cpp +2 -1
- package/cpp/rnwgpu/api/GPU.cpp +6 -6
- package/cpp/rnwgpu/api/GPU.h +1 -1
- package/cpp/rnwgpu/api/GPUAdapter.cpp +28 -2
- package/cpp/rnwgpu/api/GPUBuffer.cpp +5 -6
- package/cpp/rnwgpu/api/GPUCanvasContext.cpp +2 -1
- package/cpp/rnwgpu/api/GPUDevice.cpp +38 -47
- package/cpp/rnwgpu/api/GPUDevice.h +4 -5
- package/cpp/rnwgpu/api/GPUQueue.cpp +4 -2
- package/cpp/rnwgpu/api/GPUShaderModule.cpp +2 -2
- package/cpp/rnwgpu/api/RNWebGPU.h +9 -9
- package/cpp/rnwgpu/api/descriptors/GPUCanvasConfiguration.h +24 -0
- package/cpp/rnwgpu/async/AsyncTaskHandle.cpp +33 -45
- package/cpp/rnwgpu/async/AsyncTaskHandle.h +5 -6
- package/cpp/rnwgpu/async/RuntimeContext.cpp +321 -112
- package/cpp/rnwgpu/async/RuntimeContext.h +90 -82
- package/lib/commonjs/Canvas.js +1 -0
- package/lib/commonjs/Canvas.js.map +1 -1
- package/lib/commonjs/WebGPUViewNativeComponent.js.map +1 -1
- package/lib/commonjs/WebGPUViewNativeComponent.web.js.map +1 -1
- package/lib/commonjs/external/reanimated/registerWebGPUForReanimated.js +19 -1
- package/lib/commonjs/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
- package/lib/commonjs/install.js +6 -5
- package/lib/commonjs/install.js.map +1 -1
- package/lib/module/Canvas.js +1 -0
- package/lib/module/Canvas.js.map +1 -1
- package/lib/module/WebGPUViewNativeComponent.js.map +1 -1
- package/lib/module/WebGPUViewNativeComponent.web.js.map +1 -1
- package/lib/module/external/reanimated/registerWebGPUForReanimated.js +19 -1
- package/lib/module/external/reanimated/registerWebGPUForReanimated.js.map +1 -1
- package/lib/module/install.js +6 -5
- package/lib/module/install.js.map +1 -1
- package/lib/typescript/lib/commonjs/Canvas.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
- package/lib/typescript/lib/commonjs/install.d.ts +6 -5
- package/lib/typescript/lib/commonjs/install.d.ts.map +1 -1
- package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
- package/lib/typescript/lib/module/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
- package/lib/typescript/lib/module/install.d.ts.map +1 -1
- package/lib/typescript/src/Canvas.d.ts.map +1 -1
- package/lib/typescript/src/WebGPUViewNativeComponent.d.ts +6 -7
- package/lib/typescript/src/WebGPUViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/WebGPUViewNativeComponent.web.d.ts +2 -3
- package/lib/typescript/src/WebGPUViewNativeComponent.web.d.ts.map +1 -1
- package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts +1 -0
- package/lib/typescript/src/external/reanimated/registerWebGPUForReanimated.d.ts.map +1 -1
- package/lib/typescript/src/install.d.ts +6 -5
- package/lib/typescript/src/install.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/Canvas.tsx +4 -2
- package/src/WebGPUViewNativeComponent.ts +6 -10
- package/src/WebGPUViewNativeComponent.web.ts +2 -3
- package/src/external/reanimated/registerWebGPUForReanimated.ts +21 -1
- package/src/install.ts +6 -5
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
#include "RuntimeContext.h"
|
|
2
2
|
|
|
3
|
+
#include <condition_variable>
|
|
4
|
+
#include <cstdint>
|
|
5
|
+
#include <deque>
|
|
3
6
|
#include <memory>
|
|
4
7
|
#include <stdexcept>
|
|
8
|
+
#include <thread>
|
|
5
9
|
#include <unordered_map>
|
|
6
10
|
#include <utility>
|
|
7
11
|
#include <vector>
|
|
8
12
|
|
|
13
|
+
#if defined(__APPLE__) || defined(__ANDROID__)
|
|
14
|
+
#include <pthread.h>
|
|
15
|
+
#endif
|
|
16
|
+
|
|
9
17
|
#include <ReactCommon/CallInvoker.h>
|
|
10
18
|
|
|
11
19
|
#include "AsyncTaskHandle.h"
|
|
@@ -19,47 +27,130 @@ struct RuntimeData {
|
|
|
19
27
|
};
|
|
20
28
|
constexpr const char *TAG = "RuntimeContext";
|
|
21
29
|
|
|
22
|
-
// The main JS runtime and its CallInvoker, registered
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
30
|
+
// The main JS runtime and its CallInvoker, registered on install (again
|
|
31
|
+
// after a reload) on the JS thread, and read from any thread: the waiter
|
|
32
|
+
// thread posts through them.
|
|
33
|
+
std::mutex sMainMutex;
|
|
26
34
|
jsi::Runtime *sMainRuntime = nullptr;
|
|
27
35
|
std::shared_ptr<facebook::react::CallInvoker> sMainInvoker;
|
|
36
|
+
// Whether a wake that could not run has been reported: once per process.
|
|
37
|
+
std::atomic<bool> sWakeLogged{false};
|
|
38
|
+
|
|
39
|
+
jsi::Runtime *mainRuntime() {
|
|
40
|
+
std::lock_guard<std::mutex> lock(sMainMutex);
|
|
41
|
+
return sMainRuntime;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
std::shared_ptr<facebook::react::CallInvoker> mainInvoker() {
|
|
45
|
+
std::lock_guard<std::mutex> lock(sMainMutex);
|
|
46
|
+
return sMainInvoker;
|
|
47
|
+
}
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
static std::mutex registryMutex;
|
|
38
|
-
static std::unordered_map<WGPUInstance, std::weak_ptr<std::mutex>> registry;
|
|
39
|
-
std::lock_guard<std::mutex> lock(registryMutex);
|
|
40
|
-
auto &slot = registry[instance];
|
|
41
|
-
if (auto existing = slot.lock()) {
|
|
42
|
-
return existing;
|
|
43
|
-
}
|
|
44
|
-
// Contexts retain their instance, so its handle cannot be reused while one
|
|
45
|
-
// of them is alive: an expired slot belonged to an instance whose runtimes
|
|
46
|
-
// are all gone, and an instance reusing that address starts afresh.
|
|
47
|
-
auto created = std::make_shared<std::mutex>();
|
|
48
|
-
slot = created;
|
|
49
|
-
return created;
|
|
49
|
+
void logException(const char *what, const std::exception_ptr &error) {
|
|
50
|
+
try {
|
|
51
|
+
std::rethrow_exception(error);
|
|
52
|
+
} catch (const std::exception &exception) {
|
|
53
|
+
Logger::logToConsole("[%s] %s: %s", TAG, what, exception.what());
|
|
54
|
+
} catch (...) {
|
|
55
|
+
Logger::logToConsole("[%s] %s.", TAG, what);
|
|
56
|
+
}
|
|
50
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* The thread waiting on the futures of one instance: one future at a time,
|
|
61
|
+
* in the order they were handed over, in Instance::WaitAny, which sleeps
|
|
62
|
+
* until the GPU or Dawn's worker is done and then runs the callback. Never
|
|
63
|
+
* destroyed: the instance it retains is shared by every runtime, and a
|
|
64
|
+
* future may be outstanding during process teardown.
|
|
65
|
+
*/
|
|
66
|
+
class Waiter {
|
|
67
|
+
public:
|
|
68
|
+
static Waiter &forInstance(const wgpu::Instance &instance) {
|
|
69
|
+
static std::mutex registryMutex;
|
|
70
|
+
static std::unordered_map<WGPUInstance, Waiter *> registry;
|
|
71
|
+
std::lock_guard<std::mutex> lock(registryMutex);
|
|
72
|
+
auto &slot = registry[instance.Get()];
|
|
73
|
+
if (slot == nullptr) {
|
|
74
|
+
slot = new Waiter(instance);
|
|
75
|
+
}
|
|
76
|
+
return *slot;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
void enqueue(wgpu::Future future) {
|
|
80
|
+
{
|
|
81
|
+
std::lock_guard<std::mutex> lock(_mutex);
|
|
82
|
+
_futures.push_back(future);
|
|
83
|
+
}
|
|
84
|
+
_condition.notify_one();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private:
|
|
88
|
+
explicit Waiter(wgpu::Instance instance) : _instance(std::move(instance)) {
|
|
89
|
+
std::thread([this]() { run(); }).detach();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
void run() {
|
|
93
|
+
#if defined(__APPLE__)
|
|
94
|
+
pthread_setname_np("RNWebGPU Waiter");
|
|
95
|
+
#elif defined(__ANDROID__)
|
|
96
|
+
pthread_setname_np(pthread_self(), "RNWebGPU Waiter");
|
|
97
|
+
#endif
|
|
98
|
+
for (;;) {
|
|
99
|
+
wgpu::Future future;
|
|
100
|
+
{
|
|
101
|
+
std::unique_lock<std::mutex> lock(_mutex);
|
|
102
|
+
_condition.wait(lock, [this]() { return !_futures.empty(); });
|
|
103
|
+
future = _futures.front();
|
|
104
|
+
_futures.pop_front();
|
|
105
|
+
}
|
|
106
|
+
auto status = _instance.WaitAny(future, UINT64_MAX);
|
|
107
|
+
if (status != wgpu::WaitStatus::Success) {
|
|
108
|
+
Logger::logToConsole("[%s] WaitAny failed with status %d", TAG,
|
|
109
|
+
static_cast<int>(status));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
wgpu::Instance _instance;
|
|
115
|
+
std::mutex _mutex;
|
|
116
|
+
std::condition_variable _condition;
|
|
117
|
+
std::deque<wgpu::Future> _futures;
|
|
118
|
+
};
|
|
51
119
|
} // namespace
|
|
52
120
|
|
|
53
121
|
void RuntimeContext::registerMainRuntime(
|
|
54
122
|
jsi::Runtime *runtime,
|
|
55
123
|
std::shared_ptr<facebook::react::CallInvoker> invoker) {
|
|
124
|
+
std::lock_guard<std::mutex> lock(sMainMutex);
|
|
56
125
|
sMainRuntime = runtime;
|
|
57
126
|
sMainInvoker = std::move(invoker);
|
|
58
127
|
}
|
|
59
128
|
|
|
129
|
+
RuntimeContext::Kind RuntimeContext::kindOf(jsi::Runtime &runtime) {
|
|
130
|
+
auto main = mainRuntime();
|
|
131
|
+
if (&runtime == main) {
|
|
132
|
+
return Kind::Main;
|
|
133
|
+
}
|
|
134
|
+
// react-native-worklets' RuntimeKind: 1 for React Native, 2 for the UI
|
|
135
|
+
// runtime, more for the runtimes of the app.
|
|
136
|
+
auto kind = runtime.global().getProperty(runtime, "__RUNTIME_KIND");
|
|
137
|
+
if (kind.isNumber() && kind.asNumber() == 2) {
|
|
138
|
+
return Kind::UI;
|
|
139
|
+
}
|
|
140
|
+
if (main == nullptr && (!kind.isNumber() || kind.asNumber() == 1)) {
|
|
141
|
+
// No main runtime registered (yet): the React Native runtime, as far as
|
|
142
|
+
// anything can tell.
|
|
143
|
+
return Kind::Main;
|
|
144
|
+
}
|
|
145
|
+
// Another runtime, marked or not: settling it through the main runtime's
|
|
146
|
+
// CallInvoker would settle it with the main runtime while its promise
|
|
147
|
+
// lives on this one. Its own setTimeout reaches it.
|
|
148
|
+
return Kind::Mailbox;
|
|
149
|
+
}
|
|
150
|
+
|
|
60
151
|
RuntimeContext::RuntimeContext(jsi::Runtime &runtime, wgpu::Instance instance)
|
|
61
152
|
: _runtime(runtime), _instance(std::move(instance)),
|
|
62
|
-
|
|
153
|
+
_kind(kindOf(runtime)) {
|
|
63
154
|
Logger::logToConsole("[%s] Created (runtime=%p)", TAG, &runtime);
|
|
64
155
|
}
|
|
65
156
|
|
|
@@ -73,39 +164,51 @@ RuntimeContext::getOrCreate(jsi::Runtime &runtime,
|
|
|
73
164
|
runtime.setRuntimeData(runtimeDataUUID(), data);
|
|
74
165
|
}
|
|
75
166
|
// Contexts retain their instance, so its handle is a stable key for the
|
|
76
|
-
// runtime's lifetime
|
|
77
|
-
//
|
|
167
|
+
// runtime's lifetime. A slot left null by a throwing constructor is simply
|
|
168
|
+
// filled next time.
|
|
78
169
|
auto &context = data->contexts[instance.Get()];
|
|
79
170
|
if (!context) {
|
|
80
171
|
context = std::make_shared<RuntimeContext>(runtime, instance);
|
|
81
|
-
// Only the main JS runtime's context carries the CallInvoker; it is used to
|
|
82
|
-
// deliver spontaneous events (device.lost) without the pump.
|
|
83
|
-
if (&runtime == sMainRuntime) {
|
|
84
|
-
context->_callInvoker = sMainInvoker;
|
|
85
|
-
}
|
|
86
172
|
}
|
|
87
173
|
return context;
|
|
88
174
|
}
|
|
89
175
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
176
|
+
bool RuntimeContext::canSettle() const {
|
|
177
|
+
switch (_kind) {
|
|
178
|
+
case Kind::Main:
|
|
179
|
+
return mainInvoker() != nullptr;
|
|
180
|
+
case Kind::UI:
|
|
181
|
+
case Kind::Mailbox:
|
|
182
|
+
return true;
|
|
95
183
|
}
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
96
186
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
_pumpTasks.fetch_add(1, std::memory_order_acq_rel);
|
|
102
|
-
requestTick();
|
|
187
|
+
AsyncTaskHandle RuntimeContext::postTask(const TaskCallback &callback) {
|
|
188
|
+
if (!canSettle()) {
|
|
189
|
+
throw std::runtime_error("Nothing can settle a promise on this runtime: "
|
|
190
|
+
"react-native-webgpu is not installed on it.");
|
|
103
191
|
}
|
|
192
|
+
// A runtime that polls its mailbox starts doing so now, on its own thread;
|
|
193
|
+
// the settle action ends the operation (onTaskSettled). Without a timer
|
|
194
|
+
// nothing could ever settle the promise: none is handed out.
|
|
195
|
+
if (_kind != Kind::Main && !armTick(_runtime)) {
|
|
196
|
+
throw std::runtime_error("Nothing can settle a promise on this runtime: "
|
|
197
|
+
"it has no setTimeout.");
|
|
198
|
+
}
|
|
199
|
+
auto handle = AsyncTaskHandle::create(shared_from_this());
|
|
200
|
+
if (!handle.valid()) {
|
|
201
|
+
throw std::runtime_error("Failed to create AsyncTaskHandle.");
|
|
202
|
+
}
|
|
203
|
+
_outstanding.fetch_add(1, std::memory_order_acq_rel);
|
|
104
204
|
|
|
105
205
|
auto resolve = handle.createResolveFunction();
|
|
106
206
|
auto reject = handle.createRejectFunction();
|
|
107
207
|
try {
|
|
108
|
-
callback(resolve, reject);
|
|
208
|
+
auto future = callback(resolve, reject);
|
|
209
|
+
if (future.has_value()) {
|
|
210
|
+
awaitFuture(*future);
|
|
211
|
+
}
|
|
109
212
|
} catch (const std::exception &exception) {
|
|
110
213
|
reject(exception.what());
|
|
111
214
|
} catch (...) {
|
|
@@ -114,92 +217,198 @@ AsyncTaskHandle RuntimeContext::postTask(const TaskCallback &callback,
|
|
|
114
217
|
return handle;
|
|
115
218
|
}
|
|
116
219
|
|
|
117
|
-
void RuntimeContext::
|
|
118
|
-
|
|
119
|
-
|
|
220
|
+
void RuntimeContext::awaitFuture(wgpu::Future future) {
|
|
221
|
+
// Complete already (requestAdapter, popErrorScope, a lost device): the
|
|
222
|
+
// callback runs here, and nothing waits.
|
|
223
|
+
if (_instance.WaitAny(future, 0) == wgpu::WaitStatus::Success) {
|
|
224
|
+
return;
|
|
120
225
|
}
|
|
226
|
+
Waiter::forInstance(_instance).enqueue(future);
|
|
121
227
|
}
|
|
122
228
|
|
|
123
|
-
void RuntimeContext::
|
|
124
|
-
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
std::lock_guard<std::mutex> lock(_mailboxMutex);
|
|
128
|
-
_mailbox.push_back(std::move(job));
|
|
229
|
+
void RuntimeContext::onTaskSettled() {
|
|
230
|
+
_outstanding.fetch_sub(1, std::memory_order_acq_rel);
|
|
129
231
|
}
|
|
130
232
|
|
|
131
|
-
void RuntimeContext::
|
|
132
|
-
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
233
|
+
void RuntimeContext::post(Job job) {
|
|
234
|
+
switch (_kind) {
|
|
235
|
+
case Kind::Main: {
|
|
236
|
+
auto invoker = mainInvoker();
|
|
237
|
+
if (invoker == nullptr) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
invoker->invokeAsync(facebook::react::CallFunc(std::move(job)));
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
case Kind::UI:
|
|
244
|
+
case Kind::Mailbox: {
|
|
245
|
+
{
|
|
246
|
+
std::lock_guard<std::mutex> lock(_mailboxMutex);
|
|
247
|
+
_mailbox.push_back(std::move(job));
|
|
248
|
+
}
|
|
249
|
+
if (_kind == Kind::UI) {
|
|
250
|
+
wakeUI();
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
136
253
|
}
|
|
137
|
-
// Run settle-actions on this (the owning) thread, NOT under the ProcessEvents
|
|
138
|
-
// mutex, so JS continuations never execute while the pump lock is held.
|
|
139
|
-
for (auto &job : jobs) {
|
|
140
|
-
job();
|
|
141
254
|
}
|
|
142
255
|
}
|
|
143
256
|
|
|
144
|
-
|
|
257
|
+
// Best effort, and coalesced (one wake in flight at a time): on the main
|
|
258
|
+
// runtime, worklets runs the function handed to __webgpuScheduleOnUI on the
|
|
259
|
+
// UI runtime, where it drains the mailbox ahead of the next tick. The
|
|
260
|
+
// function is a host function, which worklets passes by reference. Without
|
|
261
|
+
// the hook (registerWebGPUForReanimated has not run on this main runtime
|
|
262
|
+
// yet, a reload evaluating its bundle, say) the tick alone settles what is
|
|
263
|
+
// awaited, a spontaneous event waits for the next awaited operation, and
|
|
264
|
+
// the next post tries again. Whatever goes wrong on the way, the wake is
|
|
265
|
+
// given up, never the jobs.
|
|
266
|
+
void RuntimeContext::wakeUI() {
|
|
267
|
+
auto invoker = mainInvoker();
|
|
268
|
+
if (invoker == nullptr) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
145
271
|
bool expected = false;
|
|
146
|
-
if (!
|
|
147
|
-
|
|
272
|
+
if (!_wakePending.compare_exchange_strong(expected, true,
|
|
273
|
+
std::memory_order_acq_rel)) {
|
|
148
274
|
return;
|
|
149
275
|
}
|
|
276
|
+
std::weak_ptr<RuntimeContext> weak = weak_from_this();
|
|
277
|
+
invoker->invokeAsync(
|
|
278
|
+
facebook::react::CallFunc([weak](jsi::Runtime &mainRuntime) {
|
|
279
|
+
auto self = weak.lock();
|
|
280
|
+
if (self == nullptr) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
auto schedule = mainRuntime.global().getProperty(
|
|
285
|
+
mainRuntime, "__webgpuScheduleOnUI");
|
|
286
|
+
if (!schedule.isObject() ||
|
|
287
|
+
!schedule.asObject(mainRuntime).isFunction(mainRuntime)) {
|
|
288
|
+
self->_wakePending.store(false, std::memory_order_release);
|
|
289
|
+
if (!sWakeLogged.exchange(true, std::memory_order_acq_rel)) {
|
|
290
|
+
Logger::logToConsole(
|
|
291
|
+
"[%s] __webgpuScheduleOnUI is not installed on the main "
|
|
292
|
+
"runtime (see registerWebGPUForReanimated): the UI "
|
|
293
|
+
"runtime settles its promises on its own timer for now, "
|
|
294
|
+
"and device.lost and uncapturederror reach it with its "
|
|
295
|
+
"next awaited operation.",
|
|
296
|
+
TAG);
|
|
297
|
+
}
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
auto wake = jsi::Function::createFromHostFunction(
|
|
301
|
+
mainRuntime, jsi::PropNameID::forAscii(mainRuntime, "webgpuWake"),
|
|
302
|
+
0,
|
|
303
|
+
[weak](jsi::Runtime &uiRuntime, const jsi::Value &,
|
|
304
|
+
const jsi::Value *, size_t) -> jsi::Value {
|
|
305
|
+
auto self = weak.lock();
|
|
306
|
+
// The UI runtime of the moment: after a reload, not the one
|
|
307
|
+
// this mailbox belongs to.
|
|
308
|
+
if (self == nullptr || &uiRuntime != &self->_runtime) {
|
|
309
|
+
return jsi::Value::undefined();
|
|
310
|
+
}
|
|
311
|
+
// Cleared before the drain: a job posted meanwhile is either
|
|
312
|
+
// in this drain or gets a wake of its own.
|
|
313
|
+
self->_wakePending.store(false, std::memory_order_release);
|
|
314
|
+
auto error = self->drainMailbox(uiRuntime);
|
|
315
|
+
if (error != nullptr) {
|
|
316
|
+
// Reported, not rethrown: the wake runs among the other
|
|
317
|
+
// jobs of worklets' scheduleOnUI batch, which the
|
|
318
|
+
// exception would cut short. (The tick rethrows its own
|
|
319
|
+
// as the timer's.)
|
|
320
|
+
logException("A job run by the wake on the UI runtime threw",
|
|
321
|
+
error);
|
|
322
|
+
}
|
|
323
|
+
return jsi::Value::undefined();
|
|
324
|
+
});
|
|
325
|
+
schedule.asObject(mainRuntime)
|
|
326
|
+
.asFunction(mainRuntime)
|
|
327
|
+
.call(mainRuntime, std::move(wake));
|
|
328
|
+
} catch (const std::exception &error) {
|
|
329
|
+
self->_wakePending.store(false, std::memory_order_release);
|
|
330
|
+
if (!sWakeLogged.exchange(true, std::memory_order_acq_rel)) {
|
|
331
|
+
Logger::logToConsole(
|
|
332
|
+
"[%s] Waking the UI runtime through __webgpuScheduleOnUI "
|
|
333
|
+
"failed, so it settles its promises on its own timer for "
|
|
334
|
+
"now: %s",
|
|
335
|
+
TAG, error.what());
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}));
|
|
339
|
+
}
|
|
150
340
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
// queueMicrotask are fallbacks. We do NOT use queueMicrotask as the primary
|
|
157
|
-
// mechanism: a self-rescheduling microtask never yields the microtask
|
|
158
|
-
// checkpoint, starving the runtime's task loop.
|
|
159
|
-
auto self = shared_from_this();
|
|
160
|
-
jsi::Runtime &rt = _runtime;
|
|
161
|
-
auto tickCallback = jsi::Function::createFromHostFunction(
|
|
162
|
-
rt, jsi::PropNameID::forAscii(rt, "RNWGPUAsyncTick"), 0,
|
|
163
|
-
[self](jsi::Runtime & /*runtime*/, const jsi::Value & /*thisVal*/,
|
|
164
|
-
const jsi::Value * /*args*/, size_t /*count*/) -> jsi::Value {
|
|
165
|
-
self->tick();
|
|
166
|
-
return jsi::Value::undefined();
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
auto global = rt.global();
|
|
170
|
-
auto setTimeoutValue = global.getProperty(rt, "setTimeout");
|
|
171
|
-
if (setTimeoutValue.isObject() &&
|
|
172
|
-
setTimeoutValue.asObject(rt).isFunction(rt)) {
|
|
173
|
-
setTimeoutValue.asObject(rt).asFunction(rt).call(
|
|
174
|
-
rt, jsi::Value(rt, tickCallback), jsi::Value(0.0));
|
|
175
|
-
return;
|
|
341
|
+
bool RuntimeContext::armTick(jsi::Runtime &runtime) {
|
|
342
|
+
bool expected = false;
|
|
343
|
+
if (!_tickArmed.compare_exchange_strong(expected, true,
|
|
344
|
+
std::memory_order_acq_rel)) {
|
|
345
|
+
return true;
|
|
176
346
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
347
|
+
// The runtime's own timer, on its own thread. Not queueMicrotask: a
|
|
348
|
+
// self-rescheduling microtask never yields the microtask checkpoint, which
|
|
349
|
+
// starves the runtime's task loop.
|
|
350
|
+
try {
|
|
351
|
+
auto setTimeout = runtime.global().getProperty(runtime, "setTimeout");
|
|
352
|
+
if (!setTimeout.isObject() ||
|
|
353
|
+
!setTimeout.asObject(runtime).isFunction(runtime)) {
|
|
354
|
+
_tickArmed.store(false, std::memory_order_release);
|
|
355
|
+
Logger::logToConsole("[%s] This runtime has no setTimeout: nothing "
|
|
356
|
+
"asynchronous can settle on it.",
|
|
357
|
+
TAG);
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
std::weak_ptr<RuntimeContext> weak = weak_from_this();
|
|
361
|
+
auto tick = jsi::Function::createFromHostFunction(
|
|
362
|
+
runtime, jsi::PropNameID::forAscii(runtime, "RNWGPUAsyncTick"), 0,
|
|
363
|
+
[weak](jsi::Runtime &runtime, const jsi::Value &, const jsi::Value *,
|
|
364
|
+
size_t) -> jsi::Value {
|
|
365
|
+
auto self = weak.lock();
|
|
366
|
+
if (self == nullptr) {
|
|
367
|
+
return jsi::Value::undefined();
|
|
368
|
+
}
|
|
369
|
+
self->_tickArmed.store(false, std::memory_order_release);
|
|
370
|
+
auto error = self->drainMailbox(runtime);
|
|
371
|
+
if (self->_outstanding.load(std::memory_order_acquire) > 0) {
|
|
372
|
+
self->armTick(runtime);
|
|
373
|
+
}
|
|
374
|
+
if (error != nullptr) {
|
|
375
|
+
// Thrown by a job, after the others ran and the next tick is
|
|
376
|
+
// armed: the runtime reports it as the timer's.
|
|
377
|
+
std::rethrow_exception(error);
|
|
378
|
+
}
|
|
379
|
+
return jsi::Value::undefined();
|
|
380
|
+
});
|
|
381
|
+
setTimeout.asObject(runtime).asFunction(runtime).call(
|
|
382
|
+
runtime, std::move(tick), jsi::Value(0.0));
|
|
383
|
+
return true;
|
|
384
|
+
} catch (const std::exception &error) {
|
|
385
|
+
_tickArmed.store(false, std::memory_order_release);
|
|
386
|
+
Logger::logToConsole("[%s] This runtime's setTimeout threw, so nothing "
|
|
387
|
+
"asynchronous can settle on it: %s",
|
|
388
|
+
TAG, error.what());
|
|
389
|
+
return false;
|
|
183
390
|
}
|
|
184
|
-
rt.queueMicrotask(std::move(tickCallback));
|
|
185
391
|
}
|
|
186
392
|
|
|
187
|
-
|
|
188
|
-
|
|
393
|
+
// Runs the jobs posted so far, every one of them even when one throws: the
|
|
394
|
+
// first exception is returned for the caller to rethrow once the rest ran.
|
|
395
|
+
std::exception_ptr RuntimeContext::drainMailbox(jsi::Runtime &runtime) {
|
|
396
|
+
std::vector<Job> jobs;
|
|
189
397
|
{
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
398
|
+
std::lock_guard<std::mutex> lock(_mailboxMutex);
|
|
399
|
+
jobs.swap(_mailbox);
|
|
400
|
+
}
|
|
401
|
+
std::exception_ptr first = nullptr;
|
|
402
|
+
for (auto &job : jobs) {
|
|
403
|
+
try {
|
|
404
|
+
job(runtime);
|
|
405
|
+
} catch (...) {
|
|
406
|
+
if (first == nullptr) {
|
|
407
|
+
first = std::current_exception();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
202
410
|
}
|
|
411
|
+
return first;
|
|
203
412
|
}
|
|
204
413
|
|
|
205
414
|
jsi::UUID RuntimeContext::runtimeDataUUID() {
|