@react-native-gpui/worklets 0.0.0-stage → 0.13.0-alpha.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/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +24 -2
- package/desktop/CMakeLists.txt +52 -0
- package/desktop/cpp/GPUIPlatformLogger.cpp +27 -0
- package/desktop/cpp/GPUIWorkletsModule.cpp +336 -0
- package/desktop/cpp/GPUIWorkletsModule.h +60 -0
- package/desktop/cpp/GpuiWorkletsFrameModule.cpp +13 -0
- package/desktop/patch-sources.mjs +149 -0
- package/desktop/patches/react-native-worklets-0.13.0.patch +107 -0
- package/package.json +36 -4
- package/react-native.config.js +10 -0
- package/src/NativeWorkletsModule.ts +10 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@react-native-gpui/worklets` are listed here. Each version's section becomes the notes of its GitHub release.
|
|
4
|
+
|
|
5
|
+
## 0.13.0-alpha.0 - 2026-11-01
|
|
6
|
+
|
|
7
|
+
- First release, for `react-native-worklets` 0.13.x.
|
|
8
|
+
- Runs worklets on desktop, driven by the renderer's frame clock.
|
|
9
|
+
- Works with the npm release of `react-native-worklets`: the C++ fixes it needs for MSVC and Linux are applied to a copy in the build directory.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Streetwriters
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @react-native-gpui/worklets
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Desktop support for [`react-native-worklets`](https://github.com/software-mansion/react-native-reanimated/tree/main/packages/react-native-worklets): worklets run on desktop, driven by the renderer's frame clock.
|
|
4
|
+
|
|
5
|
+
Part of [react-native-gpui](https://github.com/streetwriters/react-native-gpui), React Native for macOS, Windows and Linux.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Install it next to `react-native-worklets`:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm install react-native-worklets@0.13.x @react-native-gpui/worklets
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Install the regular npm release of `react-native-worklets`. Its C++ needs small fixes to build with MSVC and on Linux; this package applies them to a copy in the desktop build directory and never changes `node_modules`.
|
|
16
|
+
|
|
17
|
+
Keep using `react-native-worklets` as you do on iOS and Android; its JavaScript API is unchanged. Autolinking builds the desktop side when the app is built.
|
|
18
|
+
|
|
19
|
+
## Documentation
|
|
20
|
+
|
|
21
|
+
[Supported libraries](https://streetwriters.github.io/react-native-gpui/docs/supported-libraries)
|
|
22
|
+
|
|
23
|
+
## License
|
|
24
|
+
|
|
25
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
if(NOT RN_GPUI_PROJECT_ROOT)
|
|
2
|
+
message(FATAL_ERROR
|
|
3
|
+
"@react-native-gpui/worklets was added by the platform; RN_GPUI_PROJECT_ROOT must be set by its build.rs")
|
|
4
|
+
endif()
|
|
5
|
+
set(_gpui_app_root "${RN_GPUI_PROJECT_ROOT}")
|
|
6
|
+
find_program(_gpui_node node REQUIRED)
|
|
7
|
+
execute_process(
|
|
8
|
+
COMMAND "${_gpui_node}" -e "process.stdout.write(require.resolve('react-native-worklets/package.json', {paths: [process.argv[1]]}))" "${_gpui_app_root}"
|
|
9
|
+
RESULT_VARIABLE _worklets_resolve_result
|
|
10
|
+
OUTPUT_VARIABLE _worklets_package_json
|
|
11
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
12
|
+
if(NOT _worklets_resolve_result EQUAL 0 OR NOT EXISTS "${_worklets_package_json}")
|
|
13
|
+
message(FATAL_ERROR "react-native-worklets could not be resolved from ${_gpui_app_root}")
|
|
14
|
+
endif()
|
|
15
|
+
get_filename_component(_worklets_root "${_worklets_package_json}" DIRECTORY)
|
|
16
|
+
set(_worklets_common "${_worklets_root}/Common/cpp")
|
|
17
|
+
if(NOT EXISTS "${_worklets_common}/worklets/NativeModules/WorkletsModuleProxy.h")
|
|
18
|
+
message(FATAL_ERROR "react-native-worklets 0.13.x was not found in ${_gpui_app_root} or any ancestor node_modules directory")
|
|
19
|
+
endif()
|
|
20
|
+
|
|
21
|
+
# Upstream's C++ does not build with MSVC or on Linux as published; compile a
|
|
22
|
+
# patched copy in the build tree and leave node_modules untouched.
|
|
23
|
+
set(_worklets_patch "${CMAKE_CURRENT_LIST_DIR}/patches/react-native-worklets-0.13.0.patch")
|
|
24
|
+
execute_process(
|
|
25
|
+
COMMAND "${_gpui_node}" "${CMAKE_CURRENT_LIST_DIR}/patch-sources.mjs"
|
|
26
|
+
"${_worklets_root}" "${_worklets_patch}"
|
|
27
|
+
"${CMAKE_BINARY_DIR}/rn-gpui-patched/react-native-worklets"
|
|
28
|
+
RESULT_VARIABLE _worklets_patch_result
|
|
29
|
+
OUTPUT_VARIABLE _worklets_common
|
|
30
|
+
ERROR_VARIABLE _worklets_patch_error
|
|
31
|
+
OUTPUT_STRIP_TRAILING_WHITESPACE)
|
|
32
|
+
if(NOT _worklets_patch_result EQUAL 0)
|
|
33
|
+
message(FATAL_ERROR "${_worklets_patch_error}")
|
|
34
|
+
endif()
|
|
35
|
+
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
|
36
|
+
"${_worklets_patch}" "${_worklets_package_json}")
|
|
37
|
+
|
|
38
|
+
file(GLOB_RECURSE _worklets_sources CONFIGURE_DEPENDS "${_worklets_common}/worklets/*.cpp")
|
|
39
|
+
set_property(GLOBAL APPEND PROPERTY RN_GPUI_APP_CPP_SOURCES
|
|
40
|
+
${_worklets_sources}
|
|
41
|
+
"${CMAKE_CURRENT_LIST_DIR}/cpp/GPUIWorkletsModule.cpp"
|
|
42
|
+
"${CMAKE_CURRENT_LIST_DIR}/cpp/GPUIPlatformLogger.cpp")
|
|
43
|
+
set_property(GLOBAL APPEND PROPERTY RN_GPUI_APP_CPP_INCLUDE_DIRS
|
|
44
|
+
"${_worklets_common}"
|
|
45
|
+
"${_worklets_root}")
|
|
46
|
+
set_property(GLOBAL APPEND PROPERTY RN_GPUI_APP_CPP_DEFINITIONS
|
|
47
|
+
"WORKLETS_VERSION=0.13.0")
|
|
48
|
+
# The frame hook is how worklets see every frame; the platform compiles this
|
|
49
|
+
# glue only because the package is installed.
|
|
50
|
+
set_property(GLOBAL APPEND PROPERTY RN_GPUI_PACKAGE_MODULE_SOURCES
|
|
51
|
+
"${CMAKE_CURRENT_LIST_DIR}/cpp/GpuiWorkletsFrameModule.cpp")
|
|
52
|
+
set_property(GLOBAL APPEND PROPERTY RN_GPUI_PACKAGE_MODULE_INSTALLERS worklets)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#include <worklets/Tools/PlatformLogger.h>
|
|
2
|
+
|
|
3
|
+
#include <glog/logging.h>
|
|
4
|
+
|
|
5
|
+
namespace worklets {
|
|
6
|
+
|
|
7
|
+
void PlatformLogger::log(const char* str) {
|
|
8
|
+
LOG(INFO) << str;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
void PlatformLogger::log(const std::string& str) {
|
|
12
|
+
LOG(INFO) << str;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
void PlatformLogger::log(const double value) {
|
|
16
|
+
LOG(INFO) << value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void PlatformLogger::log(const int value) {
|
|
20
|
+
LOG(INFO) << value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void PlatformLogger::log(const bool value) {
|
|
24
|
+
LOG(INFO) << (value ? "true" : "false");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
} // namespace worklets
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
#include "GPUIWorkletsModule.h"
|
|
2
|
+
|
|
3
|
+
#include "rn-gpui-sys/src/lib.rs.h"
|
|
4
|
+
|
|
5
|
+
#include <glog/logging.h>
|
|
6
|
+
#include <worklets/Tools/JSLogger.h>
|
|
7
|
+
#include <worklets/WorkletRuntime/RuntimeBindings.h>
|
|
8
|
+
#include <worklets/WorkletRuntime/WorkletRuntime.h>
|
|
9
|
+
|
|
10
|
+
#include <algorithm>
|
|
11
|
+
#include <atomic>
|
|
12
|
+
#include <chrono>
|
|
13
|
+
#include <cstdlib>
|
|
14
|
+
#include <stdexcept>
|
|
15
|
+
#include <thread>
|
|
16
|
+
#include <utility>
|
|
17
|
+
|
|
18
|
+
#include "GpuiModules.h"
|
|
19
|
+
|
|
20
|
+
namespace {
|
|
21
|
+
bool animationTracingEnabled() {
|
|
22
|
+
static const bool enabled = std::getenv("RN_GPUI_DEBUG_ANIMATION") != nullptr;
|
|
23
|
+
return enabled;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
std::mutex moduleMutex;
|
|
27
|
+
std::weak_ptr<gpui_worklets::Module> moduleInstance;
|
|
28
|
+
std::atomic<uint64_t> workletFrames{0};
|
|
29
|
+
std::atomic<uint64_t> workletRafRequests{0};
|
|
30
|
+
|
|
31
|
+
class GPUIScheduler final : public worklets::UIScheduler {
|
|
32
|
+
protected:
|
|
33
|
+
bool queryIsOnUIThread() const override {
|
|
34
|
+
return gpui_worklets::isOnMainThread();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public:
|
|
38
|
+
void scheduleOnUI(std::function<void()> job) override {
|
|
39
|
+
static std::atomic<uint64_t> scheduled{0};
|
|
40
|
+
const auto count = ++scheduled;
|
|
41
|
+
if (animationTracingEnabled() && count <= 5) {
|
|
42
|
+
LOG(INFO) << "[gpui-worklets] scheduleOnUI count=" << count
|
|
43
|
+
<< " onUI=" << isOnUIThread();
|
|
44
|
+
}
|
|
45
|
+
if (isOnUIThread()) {
|
|
46
|
+
job();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
UIScheduler::scheduleOnUI(std::move(job));
|
|
50
|
+
rngpui::requestFrames("worklets", true);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
worklets::RuntimeBindings::NativeLoggingHook nativeLoggingHook() {
|
|
55
|
+
return [](facebook::jsi::Runtime& runtime,
|
|
56
|
+
const facebook::jsi::Value&,
|
|
57
|
+
const facebook::jsi::Value* args,
|
|
58
|
+
size_t count) -> facebook::jsi::Value {
|
|
59
|
+
if (count != 2) {
|
|
60
|
+
throw std::invalid_argument("nativeLoggingHook takes 2 arguments");
|
|
61
|
+
}
|
|
62
|
+
const auto message = args[0].asString(runtime).utf8(runtime);
|
|
63
|
+
LOG(INFO) << "[worklet] " << message;
|
|
64
|
+
return facebook::jsi::Value::undefined();
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
} // namespace
|
|
68
|
+
|
|
69
|
+
namespace gpui_worklets {
|
|
70
|
+
namespace {
|
|
71
|
+
std::mutex uiThreadMutex;
|
|
72
|
+
std::thread::id uiThreadId;
|
|
73
|
+
} // namespace
|
|
74
|
+
|
|
75
|
+
bool isOnMainThread() {
|
|
76
|
+
// The runtime records the main thread before its loop runs. `onFrame` only
|
|
77
|
+
// notes it once a frame arrives, and `UIScheduler::isOnUIThread` caches the
|
|
78
|
+
// first answer per thread: a query that comes first, such as a layout
|
|
79
|
+
// animation flush, would pin "not the UI thread" on the main thread for good.
|
|
80
|
+
if (rngpui::rn_gpui_is_main_thread()) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
std::lock_guard<std::mutex> lock(uiThreadMutex);
|
|
84
|
+
return uiThreadId != std::thread::id{} &&
|
|
85
|
+
uiThreadId == std::this_thread::get_id();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void noteMainThread() {
|
|
89
|
+
std::lock_guard<std::mutex> lock(uiThreadMutex);
|
|
90
|
+
if (uiThreadId == std::thread::id{}) {
|
|
91
|
+
uiThreadId = std::this_thread::get_id();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
Module::Module(std::shared_ptr<facebook::react::CallInvoker> jsInvoker)
|
|
96
|
+
: NativeWorkletsModuleSpec(std::move(jsInvoker)) {}
|
|
97
|
+
|
|
98
|
+
Module::~Module() {
|
|
99
|
+
rngpui::reportPendingWork("worklets", 0);
|
|
100
|
+
rngpui::requestFrames("worklets", false);
|
|
101
|
+
if (initializer_) {
|
|
102
|
+
initializer_->invalidate();
|
|
103
|
+
}
|
|
104
|
+
std::lock_guard<std::mutex> lock(moduleMutex);
|
|
105
|
+
if (moduleInstance.lock().get() == this) {
|
|
106
|
+
moduleInstance.reset();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
void Module::initialize(facebook::jsi::Runtime& runtime) {
|
|
111
|
+
if (initializer_)
|
|
112
|
+
return;
|
|
113
|
+
uiScheduler_ = std::make_shared<GPUIScheduler>();
|
|
114
|
+
runtimeStatus_ = std::make_shared<worklets::RNRuntimeStatus>();
|
|
115
|
+
jsScheduler_ = std::make_shared<worklets::JSScheduler>(
|
|
116
|
+
runtime, jsInvoker_, [] { return true; });
|
|
117
|
+
std::weak_ptr<Module> weakModule = shared_from_this();
|
|
118
|
+
auto runtimeBindings =
|
|
119
|
+
std::make_shared<worklets::RuntimeBindings>(worklets::RuntimeBindings{
|
|
120
|
+
.requestAnimationFrame =
|
|
121
|
+
[weakModule](std::function<void(double)> callback) {
|
|
122
|
+
if (auto module = weakModule.lock()) {
|
|
123
|
+
module->requestAnimationFrame(std::move(callback));
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
.nativeLoggingHook = nativeLoggingHook(),
|
|
127
|
+
.networkingBackend = nullptr,
|
|
128
|
+
});
|
|
129
|
+
initializer_ = std::make_shared<worklets::WorkletsModuleProxyInitializer>(
|
|
130
|
+
jsScheduler_, uiScheduler_, runtimeBindings, runtimeStatus_);
|
|
131
|
+
LOG(INFO) << "[gpui-worklets] initialized runtime bindings";
|
|
132
|
+
auto initializer = initializer_;
|
|
133
|
+
std::thread([initializer] { initializer->prepareProxy(); }).detach();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
bool Module::installTurboModule(
|
|
137
|
+
facebook::jsi::Runtime& runtime,
|
|
138
|
+
bool bundleModeEnabled) {
|
|
139
|
+
initialize(runtime);
|
|
140
|
+
if (bundleModeEnabled) {
|
|
141
|
+
throw facebook::jsi::JSError(
|
|
142
|
+
runtime, "GPUI Worklets does not support Bundle Mode yet");
|
|
143
|
+
}
|
|
144
|
+
proxy_ = initializer_->finalize(runtime, false, {});
|
|
145
|
+
LOG(INFO) << "[gpui-worklets] WorkletsModule installed";
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
bool Module::prepareBundleMode(facebook::jsi::Runtime& runtime) {
|
|
150
|
+
throw facebook::jsi::JSError(
|
|
151
|
+
runtime, "GPUI Worklets does not support Bundle Mode yet");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
bool Module::start(facebook::jsi::Runtime& runtime) {
|
|
155
|
+
if (!proxy_)
|
|
156
|
+
throw facebook::jsi::JSError(
|
|
157
|
+
runtime, "WorkletsModule.installTurboModule must run before start");
|
|
158
|
+
proxy_->start();
|
|
159
|
+
LOG(INFO) << "[gpui-worklets] UI runtime started";
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
bool Module::toggleSlowAnimationsOnUIRuntime(facebook::jsi::Runtime&) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
void Module::requestAnimationFrame(std::function<void(double)> callback) {
|
|
168
|
+
const auto count = ++workletRafRequests;
|
|
169
|
+
if (animationTracingEnabled() && count <= 5) {
|
|
170
|
+
LOG(INFO) << "[gpui-worklets] requestAnimationFrame count=" << count;
|
|
171
|
+
}
|
|
172
|
+
{
|
|
173
|
+
std::lock_guard<std::mutex> lock(callbacksMutex_);
|
|
174
|
+
frameCallbacks_.push_back(std::move(callback));
|
|
175
|
+
}
|
|
176
|
+
rngpui::requestFrames("worklets", true);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
void Module::onFrame(double timestampMs) {
|
|
180
|
+
const auto count = ++workletFrames;
|
|
181
|
+
if (animationTracingEnabled()) {
|
|
182
|
+
static double windowStartMs = timestampMs;
|
|
183
|
+
static double previousFrameMs = timestampMs;
|
|
184
|
+
static double maximumGapMs = 0;
|
|
185
|
+
maximumGapMs = std::max(maximumGapMs, timestampMs - previousFrameMs);
|
|
186
|
+
previousFrameMs = timestampMs;
|
|
187
|
+
if (count % 60 == 0) {
|
|
188
|
+
LOG(INFO) << "[gpui-worklets] native frame cadence frames=60 elapsed_ms="
|
|
189
|
+
<< timestampMs - windowStartMs
|
|
190
|
+
<< " max_frame_gap_ms=" << maximumGapMs;
|
|
191
|
+
windowStartMs = timestampMs;
|
|
192
|
+
maximumGapMs = 0;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (animationTracingEnabled() && (count <= 5 || count % 600 == 0)) {
|
|
196
|
+
LOG(INFO) << "[gpui-worklets] GPUI frame count=" << count;
|
|
197
|
+
}
|
|
198
|
+
// Frame callbacks are platform entry points, outside Worklets' guarded
|
|
199
|
+
// invocation paths. Report JS exceptions on RN rather than unwinding across
|
|
200
|
+
// the CXX boundary and terminating the desktop process.
|
|
201
|
+
const auto reportError = [this](const facebook::jsi::JSError& error) {
|
|
202
|
+
LOG(ERROR) << "[gpui-worklets] " << error.what();
|
|
203
|
+
worklets::JSLogger::reportFatalErrorOnJS(
|
|
204
|
+
jsScheduler_,
|
|
205
|
+
{.message = error.getMessage(),
|
|
206
|
+
.stack = error.getStack(),
|
|
207
|
+
.name = "WorkletsError"});
|
|
208
|
+
};
|
|
209
|
+
try {
|
|
210
|
+
if (uiScheduler_)
|
|
211
|
+
uiScheduler_->triggerUI();
|
|
212
|
+
} catch (const facebook::jsi::JSError& error) {
|
|
213
|
+
reportError(error);
|
|
214
|
+
}
|
|
215
|
+
// Worklets has a perpetual native heartbeat even when its JS RAF queue is
|
|
216
|
+
// empty. Test synchronization observes application callbacks, not that beat.
|
|
217
|
+
if (std::getenv("RN_GPUI_TEST_DRIVER")) {
|
|
218
|
+
if (auto runtime = uiRuntime()) {
|
|
219
|
+
runtime->runSync([](facebook::jsi::Runtime& rt) {
|
|
220
|
+
auto global = rt.global();
|
|
221
|
+
if (!global.hasProperty(rt, "requestAnimationFrame") ||
|
|
222
|
+
(global.hasProperty(rt, "__gpuiTestRaf") &&
|
|
223
|
+
facebook::jsi::Value::strictEquals(
|
|
224
|
+
rt,
|
|
225
|
+
global.getProperty(rt, "requestAnimationFrame"),
|
|
226
|
+
global.getProperty(rt, "__gpuiTestRaf"))))
|
|
227
|
+
return;
|
|
228
|
+
global.setProperty(
|
|
229
|
+
rt,
|
|
230
|
+
"__gpuiReportPendingRaf",
|
|
231
|
+
facebook::jsi::Function::createFromHostFunction(
|
|
232
|
+
rt,
|
|
233
|
+
facebook::jsi::PropNameID::forAscii(
|
|
234
|
+
rt, "__gpuiReportPendingRaf"),
|
|
235
|
+
1,
|
|
236
|
+
[](facebook::jsi::Runtime&,
|
|
237
|
+
const facebook::jsi::Value&,
|
|
238
|
+
const facebook::jsi::Value* args,
|
|
239
|
+
size_t count) {
|
|
240
|
+
if (count == 1 && args[0].isNumber())
|
|
241
|
+
rngpui::reportPendingWork(
|
|
242
|
+
"worklets", static_cast<uint64_t>(args[0].getNumber()));
|
|
243
|
+
return facebook::jsi::Value::undefined();
|
|
244
|
+
}));
|
|
245
|
+
rt.evaluateJavaScript(
|
|
246
|
+
std::make_shared<facebook::jsi::StringBuffer>(R"JS(
|
|
247
|
+
(() => {
|
|
248
|
+
const raf = globalThis.requestAnimationFrame;
|
|
249
|
+
const cancel = globalThis.cancelAnimationFrame;
|
|
250
|
+
const pending = new Set();
|
|
251
|
+
const report = () => globalThis.__gpuiReportPendingRaf(pending.size);
|
|
252
|
+
globalThis.requestAnimationFrame = callback => {
|
|
253
|
+
let id;
|
|
254
|
+
id = raf(timestamp => {
|
|
255
|
+
pending.delete(id); report(); callback(timestamp);
|
|
256
|
+
});
|
|
257
|
+
pending.add(id); report(); return id;
|
|
258
|
+
};
|
|
259
|
+
globalThis.cancelAnimationFrame = id => {
|
|
260
|
+
pending.delete(id); report(); cancel(id);
|
|
261
|
+
};
|
|
262
|
+
globalThis.__gpuiTestRaf = globalThis.requestAnimationFrame;
|
|
263
|
+
report();
|
|
264
|
+
})();
|
|
265
|
+
)JS"),
|
|
266
|
+
"gpui-test-raf.js");
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
std::vector<std::function<void(double)>> callbacks;
|
|
271
|
+
{
|
|
272
|
+
std::lock_guard<std::mutex> lock(callbacksMutex_);
|
|
273
|
+
callbacks.swap(frameCallbacks_);
|
|
274
|
+
}
|
|
275
|
+
if (animationTracingEnabled() && !callbacks.empty() &&
|
|
276
|
+
(count <= 5 || count % 60 == 0)) {
|
|
277
|
+
LOG(INFO) << "[gpui-worklets] callbacks on frame=" << callbacks.size();
|
|
278
|
+
}
|
|
279
|
+
for (auto& callback : callbacks) {
|
|
280
|
+
try {
|
|
281
|
+
callback(timestampMs);
|
|
282
|
+
} catch (const facebook::jsi::JSError& error) {
|
|
283
|
+
reportError(error);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
{
|
|
287
|
+
std::lock_guard<std::mutex> lock(callbacksMutex_);
|
|
288
|
+
if (frameCallbacks_.empty()) {
|
|
289
|
+
rngpui::requestFrames("worklets", false);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
std::shared_ptr<worklets::WorkletRuntime> Module::uiRuntime() const {
|
|
295
|
+
return proxy_ ? proxy_->getUIWorkletRuntime() : nullptr;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
std::shared_ptr<worklets::UIScheduler> Module::uiScheduler() const {
|
|
299
|
+
return uiScheduler_;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
void onFrame(double timestampMs) {
|
|
303
|
+
noteMainThread();
|
|
304
|
+
std::shared_ptr<Module> module;
|
|
305
|
+
{
|
|
306
|
+
std::lock_guard<std::mutex> lock(moduleMutex);
|
|
307
|
+
module = moduleInstance.lock();
|
|
308
|
+
}
|
|
309
|
+
if (module)
|
|
310
|
+
module->onFrame(timestampMs);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
std::shared_ptr<Module> currentModule() {
|
|
314
|
+
std::lock_guard<std::mutex> lock(moduleMutex);
|
|
315
|
+
return moduleInstance.lock();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
std::shared_ptr<facebook::react::TurboModule> createModule(
|
|
319
|
+
const std::string& name,
|
|
320
|
+
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
|
321
|
+
if (name != Module::kModuleName)
|
|
322
|
+
return nullptr;
|
|
323
|
+
auto module = std::make_shared<Module>(std::move(jsInvoker));
|
|
324
|
+
{
|
|
325
|
+
std::lock_guard<std::mutex> lock(moduleMutex);
|
|
326
|
+
moduleInstance = module;
|
|
327
|
+
}
|
|
328
|
+
return module;
|
|
329
|
+
}
|
|
330
|
+
} // namespace gpui_worklets
|
|
331
|
+
|
|
332
|
+
std::shared_ptr<facebook::react::TurboModule> create_gpui_worklets_module(
|
|
333
|
+
const std::string& name,
|
|
334
|
+
std::shared_ptr<facebook::react::CallInvoker> jsInvoker) {
|
|
335
|
+
return gpui_worklets::createModule(name, std::move(jsInvoker));
|
|
336
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <GPUIWorkletsSpec.h>
|
|
4
|
+
#include <ReactCommon/CallInvoker.h>
|
|
5
|
+
#include <ReactCommon/TurboModule.h>
|
|
6
|
+
#include <worklets/NativeModules/WorkletsModuleProxy.h>
|
|
7
|
+
#include <worklets/NativeModules/WorkletsModuleProxyInitializer.h>
|
|
8
|
+
#include <worklets/Tools/JSScheduler.h>
|
|
9
|
+
#include <worklets/Tools/RNRuntimeStatus.h>
|
|
10
|
+
#include <worklets/Tools/UIScheduler.h>
|
|
11
|
+
|
|
12
|
+
#include <functional>
|
|
13
|
+
#include <memory>
|
|
14
|
+
#include <mutex>
|
|
15
|
+
#include <vector>
|
|
16
|
+
|
|
17
|
+
namespace gpui_worklets {
|
|
18
|
+
bool isOnMainThread();
|
|
19
|
+
void noteMainThread();
|
|
20
|
+
|
|
21
|
+
class Module final : public facebook::react::NativeWorkletsModuleSpec<Module> {
|
|
22
|
+
public:
|
|
23
|
+
explicit Module(std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
|
|
24
|
+
~Module() override;
|
|
25
|
+
|
|
26
|
+
void onFrame(double timestampMs);
|
|
27
|
+
void requestAnimationFrame(std::function<void(double)> callback);
|
|
28
|
+
std::shared_ptr<worklets::WorkletRuntime> uiRuntime() const;
|
|
29
|
+
std::shared_ptr<worklets::UIScheduler> uiScheduler() const;
|
|
30
|
+
|
|
31
|
+
bool installTurboModule(
|
|
32
|
+
facebook::jsi::Runtime& runtime,
|
|
33
|
+
bool bundleModeEnabled);
|
|
34
|
+
bool prepareBundleMode(facebook::jsi::Runtime& runtime);
|
|
35
|
+
bool start(facebook::jsi::Runtime& runtime);
|
|
36
|
+
bool toggleSlowAnimationsOnUIRuntime(facebook::jsi::Runtime& runtime);
|
|
37
|
+
|
|
38
|
+
private:
|
|
39
|
+
void initialize(facebook::jsi::Runtime& runtime);
|
|
40
|
+
|
|
41
|
+
std::shared_ptr<worklets::UIScheduler> uiScheduler_;
|
|
42
|
+
std::shared_ptr<worklets::JSScheduler> jsScheduler_;
|
|
43
|
+
std::shared_ptr<worklets::RNRuntimeStatus> runtimeStatus_;
|
|
44
|
+
std::shared_ptr<worklets::WorkletsModuleProxyInitializer> initializer_;
|
|
45
|
+
std::shared_ptr<worklets::WorkletsModuleProxy> proxy_;
|
|
46
|
+
std::mutex callbacksMutex_;
|
|
47
|
+
std::vector<std::function<void(double)>> frameCallbacks_;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
void onFrame(double timestampMs);
|
|
51
|
+
std::shared_ptr<Module> currentModule();
|
|
52
|
+
std::shared_ptr<facebook::react::TurboModule> createModule(
|
|
53
|
+
const std::string& name,
|
|
54
|
+
std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
|
|
55
|
+
|
|
56
|
+
} // namespace gpui_worklets
|
|
57
|
+
|
|
58
|
+
std::shared_ptr<facebook::react::TurboModule> create_gpui_worklets_module(
|
|
59
|
+
const std::string& name,
|
|
60
|
+
std::shared_ptr<facebook::react::CallInvoker> jsInvoker);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#include "GpuiModules.h"
|
|
2
|
+
|
|
3
|
+
namespace gpui_worklets {
|
|
4
|
+
void onFrame(double timestampMs);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
namespace rngpui {
|
|
8
|
+
void rn_gpui_install_worklets_module() {
|
|
9
|
+
registerFrameCallback(
|
|
10
|
+
[](double timestampMs) { gpui_worklets::onFrame(timestampMs); });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
} // namespace rngpui
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Copies an upstream package's `Common/cpp` into a build directory and applies a
|
|
3
|
+
// unified diff to the copy, so the app's node_modules is never modified.
|
|
4
|
+
//
|
|
5
|
+
// node patch-sources.mjs <package root> <patch file> <output dir>
|
|
6
|
+
//
|
|
7
|
+
// Prints the patched `Common/cpp` directory. The copy is reused while the
|
|
8
|
+
// package version and the patch are unchanged.
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
/** Parses a unified diff into files, each with its hunks. */
|
|
15
|
+
export function parsePatch(text) {
|
|
16
|
+
const files = [];
|
|
17
|
+
let file = null;
|
|
18
|
+
let hunk = null;
|
|
19
|
+
// Lines still expected in the current hunk, from its header counts.
|
|
20
|
+
let oldLeft = 0;
|
|
21
|
+
let newLeft = 0;
|
|
22
|
+
for (const line of text.replace(/\r\n/gu, '\n').split('\n')) {
|
|
23
|
+
if (hunk !== null && (oldLeft > 0 || newLeft > 0)) {
|
|
24
|
+
const kind = line.length === 0 ? ' ' : line[0];
|
|
25
|
+
const body = line.slice(1);
|
|
26
|
+
if (kind === ' ' || kind === '-') {
|
|
27
|
+
hunk.old.push(body);
|
|
28
|
+
oldLeft--;
|
|
29
|
+
}
|
|
30
|
+
if (kind === ' ' || kind === '+') {
|
|
31
|
+
hunk.new.push(body);
|
|
32
|
+
newLeft--;
|
|
33
|
+
}
|
|
34
|
+
if (kind === '\\') continue;
|
|
35
|
+
} else if (line.startsWith('--- ')) {
|
|
36
|
+
file = { path: null, hunks: [] };
|
|
37
|
+
files.push(file);
|
|
38
|
+
hunk = null;
|
|
39
|
+
} else if (line.startsWith('+++ ') && file !== null) {
|
|
40
|
+
file.path = line.slice(4).split('\t')[0].replace(/^b\//u, '');
|
|
41
|
+
} else if (line.startsWith('@@ ') && file !== null) {
|
|
42
|
+
const match = /^@@ -(\d+)(?:,(\d+))? \+\d+(?:,(\d+))? @@/u.exec(line);
|
|
43
|
+
if (match === null) throw new Error(`bad hunk header: ${line}`);
|
|
44
|
+
hunk = { start: Number(match[1]), old: [], new: [] };
|
|
45
|
+
oldLeft = match[2] === undefined ? 1 : Number(match[2]);
|
|
46
|
+
newLeft = match[3] === undefined ? 1 : Number(match[3]);
|
|
47
|
+
file.hunks.push(hunk);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return files;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function matchesAt(lines, block, at) {
|
|
54
|
+
if (at < 0 || at + block.length > lines.length) return false;
|
|
55
|
+
return block.every((line, i) => lines[at + i] === line);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The line nearest `expected` where `block` starts, or -1. */
|
|
59
|
+
function find(lines, block, expected) {
|
|
60
|
+
for (let delta = 0; delta <= lines.length; delta++) {
|
|
61
|
+
if (matchesAt(lines, block, expected - delta)) return expected - delta;
|
|
62
|
+
if (matchesAt(lines, block, expected + delta)) return expected + delta;
|
|
63
|
+
}
|
|
64
|
+
return -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Applies one file's hunks to its text, keeping its line endings. A hunk whose
|
|
69
|
+
* result is already present
|
|
70
|
+
* is skipped, so sources that carry the fix upstream still build; any other
|
|
71
|
+
* mismatch throws.
|
|
72
|
+
*/
|
|
73
|
+
export function applyHunks(text, hunks, name) {
|
|
74
|
+
const crlf = text.includes('\r\n');
|
|
75
|
+
const lines = text.replace(/\r\n/gu, '\n').split('\n');
|
|
76
|
+
let offset = 0;
|
|
77
|
+
for (const hunk of hunks) {
|
|
78
|
+
const expected = Math.max(hunk.start - 1, 0) + offset;
|
|
79
|
+
const at = find(lines, hunk.old, expected);
|
|
80
|
+
if (at !== -1) {
|
|
81
|
+
lines.splice(at, hunk.old.length, ...hunk.new);
|
|
82
|
+
offset = at - (hunk.start - 1) + hunk.new.length - hunk.old.length;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const applied = find(lines, hunk.new, expected);
|
|
86
|
+
if (applied === -1) {
|
|
87
|
+
throw new Error(`${name}: hunk at line ${hunk.start} does not apply`);
|
|
88
|
+
}
|
|
89
|
+
offset = applied - (hunk.start - 1);
|
|
90
|
+
}
|
|
91
|
+
return lines.join(crlf ? '\r\n' : '\n');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function main([packageRoot, patchFile, outputDir]) {
|
|
95
|
+
if (!packageRoot || !patchFile || !outputDir) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
'usage: patch-sources.mjs <package root> <patch file> <output dir>',
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
const manifest = JSON.parse(
|
|
101
|
+
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
102
|
+
);
|
|
103
|
+
const patch = fs.readFileSync(patchFile, 'utf8');
|
|
104
|
+
const stamp = createHash('sha256')
|
|
105
|
+
.update(`${manifest.name}@${manifest.version}\n`)
|
|
106
|
+
.update(patch)
|
|
107
|
+
.digest('hex');
|
|
108
|
+
const target = path.join(outputDir, 'Common', 'cpp');
|
|
109
|
+
const stampFile = path.join(outputDir, '.patched');
|
|
110
|
+
const current = fs.existsSync(stampFile)
|
|
111
|
+
? fs.readFileSync(stampFile, 'utf8')
|
|
112
|
+
: '';
|
|
113
|
+
if (current !== stamp) {
|
|
114
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
115
|
+
fs.cpSync(path.join(packageRoot, 'Common', 'cpp'), target, {
|
|
116
|
+
recursive: true,
|
|
117
|
+
});
|
|
118
|
+
for (const file of parsePatch(patch)) {
|
|
119
|
+
const full = path.join(target, file.path);
|
|
120
|
+
const original = fs.existsSync(full) ? fs.readFileSync(full, 'utf8') : '';
|
|
121
|
+
let patched;
|
|
122
|
+
try {
|
|
123
|
+
patched = applyHunks(original, file.hunks, file.path);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
126
|
+
throw new Error(
|
|
127
|
+
`${path.basename(patchFile)} does not apply to ${manifest.name}@${manifest.version}: ${error.message}`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
fs.mkdirSync(path.dirname(full), { recursive: true });
|
|
131
|
+
fs.writeFileSync(full, patched);
|
|
132
|
+
}
|
|
133
|
+
fs.writeFileSync(stampFile, stamp);
|
|
134
|
+
}
|
|
135
|
+
// CMake reads backslashes as escapes.
|
|
136
|
+
process.stdout.write(target.split(path.sep).join('/'));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (
|
|
140
|
+
process.argv[1] &&
|
|
141
|
+
path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
142
|
+
) {
|
|
143
|
+
try {
|
|
144
|
+
main(process.argv.slice(2));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
process.stderr.write(`${error.message}\n`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
--- a/worklets/RunLoop/AsyncQueueImpl.cpp
|
|
2
|
+
+++ b/worklets/RunLoop/AsyncQueueImpl.cpp
|
|
3
|
+
@@ -4,6 +4,10 @@
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
#endif // ANDROID
|
|
6
|
+
|
|
7
|
+
+#if defined(__APPLE__) || defined(__linux__)
|
|
8
|
+
+#include <pthread.h>
|
|
9
|
+
+#endif
|
|
10
|
+
+
|
|
11
|
+
#include <memory>
|
|
12
|
+
#include <queue>
|
|
13
|
+
#include <string>
|
|
14
|
+
@@ -69,7 +73,11 @@
|
|
15
|
+
pthread_setname_np(pthread_self(), name.c_str());
|
|
16
|
+
jni::ThreadScope::WithClassLoader([state]() { AsyncQueueImpl::runLoop(state); });
|
|
17
|
+
#else
|
|
18
|
+
+#if defined(__APPLE__)
|
|
19
|
+
pthread_setname_np(name.c_str());
|
|
20
|
+
+#elif defined(__linux__)
|
|
21
|
+
+ pthread_setname_np(pthread_self(), name.substr(0, 15).c_str());
|
|
22
|
+
+#endif
|
|
23
|
+
AsyncQueueImpl::runLoop(state);
|
|
24
|
+
#endif // ANDROID
|
|
25
|
+
});
|
|
26
|
+
--- a/worklets/RunLoop/EventLoop.cpp
|
|
27
|
+
+++ b/worklets/RunLoop/EventLoop.cpp
|
|
28
|
+
@@ -1,5 +1,6 @@
|
|
29
|
+
#include <worklets/RunLoop/EventLoop.h>
|
|
30
|
+
|
|
31
|
+
+#include <algorithm>
|
|
32
|
+
#include <memory>
|
|
33
|
+
#include <string>
|
|
34
|
+
#include <thread>
|
|
35
|
+
--- a/worklets/Tools/JSISerializer.cpp
|
|
36
|
+
+++ b/worklets/Tools/JSISerializer.cpp
|
|
37
|
+
@@ -1,7 +1,10 @@
|
|
38
|
+
#include <worklets/Tools/JSISerializer.h>
|
|
39
|
+
|
|
40
|
+
+#ifndef _MSC_VER
|
|
41
|
+
#include <cxxabi.h>
|
|
42
|
+
+#endif
|
|
43
|
+
|
|
44
|
+
+#include <algorithm>
|
|
45
|
+
#include <memory>
|
|
46
|
+
#include <sstream>
|
|
47
|
+
#include <string>
|
|
48
|
+
@@ -116,15 +119,20 @@
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
std::string JSISerializer::stringifyHostObject(jsi::HostObject &hostObject) {
|
|
52
|
+
+#ifdef _MSC_VER
|
|
53
|
+
+ const char *hostObjClassName = typeid(hostObject).name();
|
|
54
|
+
+#else
|
|
55
|
+
int status = -1;
|
|
56
|
+
- std::unique_ptr<char, decltype(&std::free)> hostObjClassName(
|
|
57
|
+
+ std::unique_ptr<char, decltype(&std::free)> demangled(
|
|
58
|
+
abi::__cxa_demangle(typeid(hostObject).name(), nullptr, nullptr, &status), std::free);
|
|
59
|
+
if (status != 0) {
|
|
60
|
+
return "[jsi::HostObject]";
|
|
61
|
+
}
|
|
62
|
+
+ const char *hostObjClassName = demangled.get();
|
|
63
|
+
+#endif
|
|
64
|
+
|
|
65
|
+
std::stringstream ss;
|
|
66
|
+
- ss << "[jsi::HostObject(" << hostObjClassName.get() << ")";
|
|
67
|
+
+ ss << "[jsi::HostObject(" << hostObjClassName << ")";
|
|
68
|
+
|
|
69
|
+
auto props = hostObject.getPropertyNames(rt_);
|
|
70
|
+
auto propsCount = props.size();
|
|
71
|
+
--- a/worklets/Tools/SingleInstanceChecker.h
|
|
72
|
+
+++ b/worklets/Tools/SingleInstanceChecker.h
|
|
73
|
+
@@ -4,7 +4,9 @@
|
|
74
|
+
|
|
75
|
+
#include <react/debug/react_native_assert.h>
|
|
76
|
+
|
|
77
|
+
+#ifndef _MSC_VER
|
|
78
|
+
#include <cxxabi.h>
|
|
79
|
+
+#endif
|
|
80
|
+
|
|
81
|
+
#include <atomic>
|
|
82
|
+
#include <cassert>
|
|
83
|
+
@@ -50,10 +52,14 @@
|
|
84
|
+
|
|
85
|
+
template <class T>
|
|
86
|
+
SingleInstanceChecker<T>::SingleInstanceChecker() {
|
|
87
|
+
+#ifdef _MSC_VER
|
|
88
|
+
+ std::string className = typeid(T).name();
|
|
89
|
+
+#else
|
|
90
|
+
int status = 0;
|
|
91
|
+
char *demangled = __cxxabiv1::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status);
|
|
92
|
+
std::string className = (status == 0 && demangled != nullptr) ? demangled : typeid(T).name();
|
|
93
|
+
std::free(demangled);
|
|
94
|
+
+#endif
|
|
95
|
+
|
|
96
|
+
// React Native can spawn up to two instances of a Native Module at the same
|
|
97
|
+
// time. This happens during a reload when a new instance of React Native is
|
|
98
|
+
--- a/worklets/Tools/WorkletsJSIUtils.cpp
|
|
99
|
+
+++ b/worklets/Tools/WorkletsJSIUtils.cpp
|
|
100
|
+
@@ -2,6 +2,7 @@
|
|
101
|
+
|
|
102
|
+
#include <react/debug/react_native_assert.h>
|
|
103
|
+
|
|
104
|
+
+#include <iterator>
|
|
105
|
+
#include <memory>
|
|
106
|
+
#include <sstream>
|
|
107
|
+
#include <string>
|
package/package.json
CHANGED
|
@@ -1,6 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-gpui/worklets",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.13.0-alpha.0",
|
|
4
|
+
"description": "GPUI platform bindings for react-native-worklets",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/streetwriters/react-native-gpui.git",
|
|
9
|
+
"directory": "modules/worklets"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://streetwriters.github.io/react-native-gpui/",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/streetwriters/react-native-gpui/issues"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"desktop",
|
|
17
|
+
"react-native.config.js",
|
|
18
|
+
"src",
|
|
19
|
+
"CHANGELOG.md",
|
|
20
|
+
"!**/*.test.js",
|
|
21
|
+
"!**/*.test.mjs",
|
|
22
|
+
"!**/*.test.ts",
|
|
23
|
+
"!**/*.test.cjs"
|
|
24
|
+
],
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"react-native": "0.87.x",
|
|
27
|
+
"react-native-worklets": "0.13.x"
|
|
28
|
+
},
|
|
29
|
+
"codegenConfig": {
|
|
30
|
+
"name": "GPUIWorkletsSpec",
|
|
31
|
+
"type": "modules",
|
|
32
|
+
"jsSrcsDir": "src"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public",
|
|
36
|
+
"tag": "latest"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { TurboModuleRegistry, type TurboModule } from 'react-native';
|
|
2
|
+
|
|
3
|
+
export interface Spec extends TurboModule {
|
|
4
|
+
installTurboModule(bundleModeEnabled: boolean): boolean;
|
|
5
|
+
prepareBundleMode(): boolean;
|
|
6
|
+
toggleSlowAnimationsOnUIRuntime(): boolean;
|
|
7
|
+
start(): boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default TurboModuleRegistry.get<Spec>('WorkletsModule');
|