diff --git a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp index 15f5ad2a5aeb..7196ea3d4508 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp @@ -226,6 +226,8 @@ std::vector JSIWorkletsModuleProxy::getPropertyNames( jsi::PropNameID::forAscii(rt, "synchronizableLock")); propertyNames.emplace_back( jsi::PropNameID::forAscii(rt, "synchronizableUnlock")); + propertyNames.emplace_back( + jsi::PropNameID::forAscii(rt, "runOnRuntimeAsync")); #ifdef WORKLETS_BUNDLE_MODE propertyNames.emplace_back( @@ -467,6 +469,22 @@ jsi::Value JSIWorkletsModuleProxy::get( }); } + if (name == "runOnRuntimeAsync") { + return jsi::Function::createFromHostFunction( + rt, + propName, + 4, + [jsScheduler = jsScheduler_]( + jsi::Runtime &rt, + const jsi::Value &thisValue, + const jsi::Value *args, + size_t count) { + runOnRuntimeAsync( + rt, jsScheduler, args[0], args[1], args[2], args[3]); + return jsi::Value::undefined(); + }); + } + if (name == "scheduleOnUI") { return jsi::Function::createFromHostFunction( rt, diff --git a/packages/react-native-worklets/Common/cpp/worklets/Tools/Promise.cpp b/packages/react-native-worklets/Common/cpp/worklets/Tools/Promise.cpp new file mode 100644 index 000000000000..92e308d5fb75 --- /dev/null +++ b/packages/react-native-worklets/Common/cpp/worklets/Tools/Promise.cpp @@ -0,0 +1,36 @@ +#include + +namespace worklets { + +void Promise::resolve(const jsi::Value &result) { + auto resolve = resolve_->toJSValue(rt_).asObject(rt_).asFunction(rt_); + resolve.call(rt_, result); +} + +void Promise::reject( + const std::string &message, + const std::string &stack, + const std::string &name, + const std::string &jsEngine) { + const auto reject = reject_->toJSValue(rt_).asObject(rt_).asFunction(rt_); + const auto errorInstance = rt_.global() + .getPropertyAsFunction(rt_, "Error") + .callAsConstructor(rt_) + .asObject(rt_); + + errorInstance.setProperty( + rt_, "message", jsi::String::createFromUtf8(rt_, message)); + + errorInstance.setProperty( + rt_, "stack", jsi::String::createFromUtf8(rt_, stack)); + + errorInstance.setProperty( + rt_, "name", jsi::String::createFromUtf8(rt_, name)); + + errorInstance.setProperty( + rt_, "jsEngine", jsi::String::createFromUtf8(rt_, jsEngine)); + + reject.call(rt_, errorInstance); +} + +} // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/Tools/Promise.h b/packages/react-native-worklets/Common/cpp/worklets/Tools/Promise.h new file mode 100644 index 000000000000..d370f6e53821 --- /dev/null +++ b/packages/react-native-worklets/Common/cpp/worklets/Tools/Promise.h @@ -0,0 +1,27 @@ +#include +#include + +using namespace facebook; + +namespace worklets { + +struct Promise { + Promise( + jsi::Runtime &rt, + std::shared_ptr resolve, + std::shared_ptr reject) + : resolve_(resolve), reject_(reject), rt_(rt) {} + + void resolve(const jsi::Value &result); + void reject( + const std::string &message, + const std::string &stack, + const std::string &name, + const std::string &jsEngine); + + std::shared_ptr resolve_; + std::shared_ptr reject_; + jsi::Runtime &rt_; +}; + +} // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.cpp b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.cpp index 08b1caf5a6f1..24d963a31a59 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.cpp @@ -22,7 +22,6 @@ std::shared_ptr RuntimeManager::getRuntime( return nullptr; } -#ifdef WORKLETS_BUNDLE_MODE std::shared_ptr RuntimeManager::getRuntime( jsi::Runtime *runtime) { std::shared_lock lock(weakRuntimesMutex_); @@ -31,7 +30,6 @@ std::shared_ptr RuntimeManager::getRuntime( } return nullptr; } -#endif // WORKLETS_BUNDLE_MODE std::vector> RuntimeManager::getAllRuntimes() { std::shared_lock lock(weakRuntimesMutex_); @@ -97,9 +95,7 @@ void RuntimeManager::registerRuntime( std::unique_lock lock(weakRuntimesMutex_); weakRuntimes_[runtimeId] = workletRuntime; nameToRuntimeId_[name] = runtimeId; -#ifdef WORKLETS_BUNDLE_MODE runtimeAddressToRuntimeId_[&workletRuntime->getJSIRuntime()] = runtimeId; -#endif // WORKLETS_BUNDLE_MODE } } // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.h b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.h index c6c493719821..b584e7b32f24 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.h +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/RuntimeManager.h @@ -24,9 +24,7 @@ class RuntimeManager { public: std::shared_ptr getRuntime(uint64_t runtimeId); std::shared_ptr getRuntime(const std::string &name); -#ifdef WORKLETS_BUNDLE_MODE std::shared_ptr getRuntime(jsi::Runtime *runtime); -#endif // WORKLETS_BUNDLE_MODE std::vector> getAllRuntimes(); @@ -55,9 +53,7 @@ class RuntimeManager { std::map> weakRuntimes_; std::shared_mutex weakRuntimesMutex_; std::map nameToRuntimeId_; -#ifdef WORKLETS_BUNDLE_MODE std::map runtimeAddressToRuntimeId_; -#endif // WORKLETS_BUNDLE_MODE }; } // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp index 3ff260e6a680..cadc4d462271 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp @@ -1,9 +1,11 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -239,4 +241,62 @@ void scheduleOnRuntime( workletRuntime->runAsyncGuarded(serializableWorklet); } +void runOnRuntimeAsync( + jsi::Runtime &rt, + const std::shared_ptr &jsScheduler, + const jsi::Value &workletRuntimeValue, + const jsi::Value &serializableWorkletValue, + const jsi::Value &serializableResolveValue, + const jsi::Value &serializableRejectValue) { + auto workletRuntime = extractWorkletRuntime(rt, workletRuntimeValue); + auto serializableWorklet = extractSerializableOrThrow( + rt, + serializableWorkletValue, + "[Worklets] Function `worklet` passed to `runOnRuntimeAsync` is not a serializable worklet."); + auto serializableResolve = extractSerializableOrThrow< + SerializableRemoteFunction>( + rt, + serializableResolveValue, + "[Worklets] Function `resolve` passed to `runOnRuntimeAsync` is not a serializable function."); + auto serializableReject = extractSerializableOrThrow< + SerializableRemoteFunction>( + rt, + serializableRejectValue, + "[Worklets] Function `reject` passed to `runOnRuntimeAsync` is not a serializable function."); + + auto promise = std::make_shared( + rt, serializableResolve, serializableReject); + + auto eventLoop = workletRuntime->getEventLoop(); + + if (!eventLoop) { + throw std::runtime_error( + "[Worklets] Event Loop is not enabled for the (" + + workletRuntime->getRuntimeName() + + ") Worklet Runtime. Please enable it to use `runOnRuntimeAsync`."); + } + + eventLoop->pushTask([serializableWorklet, &jsScheduler, promise]( + jsi::Runtime &workletRt) { + try { + auto result = serializableWorklet->toJSValue(workletRt) + .asObject(workletRt) + .asFunction(workletRt) + .call(workletRt); + auto serializableResult = extractSerializableOrThrow(workletRt, result); + jsScheduler->scheduleOnJS( + [serializableResult, promise](jsi::Runtime &rnRt) { + auto resolveResult = serializableResult->toJSValue(rnRt); + promise->resolve(resolveResult); + }); + } catch (const jsi::JSError &error) { + const auto &message = error.getMessage(); + const auto &stack = error.getStack(); + jsScheduler->scheduleOnJS([promise, message, stack](jsi::Runtime &rnRt) { + promise->reject(message, stack, "WorkletsError", "Worklets"); + }); + } + }); +} + } // namespace worklets diff --git a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h index 3d3d3724e7e2..25f37fdb385d 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -77,6 +78,10 @@ class WorkletRuntime : public jsi::HostObject, return name_; } + [[nodiscard]] auto getEventLoop() const -> std::shared_ptr { + return eventLoop_; + } + private: const uint64_t runtimeId_; const std::shared_ptr runtimeMutex_; @@ -97,4 +102,11 @@ void scheduleOnRuntime( const jsi::Value &workletRuntimeValue, const jsi::Value &serializableWorkletValue); +void runOnRuntimeAsync( + jsi::Runtime &rt, + const std::shared_ptr &jsScheduler, + const jsi::Value &workletRuntimeValue, + const jsi::Value &serializableWorkletValue, + const jsi::Value &serializableResolveValue, + const jsi::Value &serializableRejectValue); } // namespace worklets diff --git a/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts b/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts index 68efbd62898d..03bd575a4bb7 100644 --- a/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts +++ b/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts @@ -79,6 +79,12 @@ class JSWorklets implements IWorkletsModule { ); } + runOnRuntimeAsync(): never { + throw new WorkletsError( + 'runOnRuntimeAsync should never be called in JSWorklets.' + ); + } + createSerializableSet(): never { throw new WorkletsError( 'createSerializableSet should never be called in JSWorklets.' diff --git a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts index 456dc34dd56e..a0766a793348 100644 --- a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts +++ b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts @@ -160,6 +160,20 @@ See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting return this.#workletsModuleProxy.scheduleOnUI(serializable); } + runOnRuntimeAsync( + workletRuntime: WorkletRuntime, + worklet: SerializableRef, + resolve: SerializableRef<(value: TReturn) => void>, + reject: SerializableRef<(error: Error) => void> + ): void { + return this.#workletsModuleProxy.runOnRuntimeAsync( + workletRuntime, + worklet, + resolve, + reject + ); + } + executeOnUIRuntimeSync( serializable: SerializableRef ): TReturn { diff --git a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts index 22dd530c8087..bfe9009d58b7 100644 --- a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts +++ b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts @@ -71,6 +71,13 @@ export interface WorkletsModuleProxy { scheduleOnUI(serializable: SerializableRef): void; + runOnRuntimeAsync( + workletRuntime: WorkletRuntime, + worklet: SerializableRef, + resolve: SerializableRef<(value: TReturn) => void>, + reject: SerializableRef<(error: Error) => void> + ): void; + executeOnUIRuntimeSync( serializable: SerializableRef ): TReturn; diff --git a/packages/react-native-worklets/src/index.ts b/packages/react-native-worklets/src/index.ts index 82a99b25fa4c..14cb95dd9543 100644 --- a/packages/react-native-worklets/src/index.ts +++ b/packages/react-native-worklets/src/index.ts @@ -21,6 +21,7 @@ export { getRuntimeKind, RuntimeKind } from './runtimeKind'; export { createWorkletRuntime, runOnRuntime, + runOnRuntimeAsync, scheduleOnRuntime, } from './runtimes'; export { createSerializable, isSerializableRef } from './serializable'; diff --git a/packages/react-native-worklets/src/runtimes.ts b/packages/react-native-worklets/src/runtimes.ts index fafc128647a5..e57ead676668 100644 --- a/packages/react-native-worklets/src/runtimes.ts +++ b/packages/react-native-worklets/src/runtimes.ts @@ -170,6 +170,32 @@ export function scheduleOnRuntime( runOnRuntime(workletRuntime, worklet)(...args); } +export function runOnRuntimeAsync( + workletRuntime: WorkletRuntime, + worklet: (...args: Args) => ReturnValue, + ...args: Args +): Promise { + 'worklet'; + if (globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) { + throw new WorkletsError( + '`runOnRuntimeAsync` cannot be called on the Worker Runtime.' + ); + } + + return new Promise((resolve, reject) => { + WorkletsModule.runOnRuntimeAsync( + workletRuntime, + createSerializable(() => { + 'worklet'; + const result = worklet(...args); + return makeShareableCloneOnUIRecursive(result); + }), + createSerializable(resolve), + createSerializable(reject) + ); + }); +} + /** Configuration object for creating a worklet runtime. */ export type WorkletRuntimeConfig = { /** The name of the worklet runtime. */ diff --git a/packages/react-native-worklets/src/threads.ts b/packages/react-native-worklets/src/threads.ts index 854a5494a5c8..35a521408918 100644 --- a/packages/react-native-worklets/src/threads.ts +++ b/packages/react-native-worklets/src/threads.ts @@ -427,7 +427,7 @@ function flushUIQueue(): void { queue.forEach(([workletFunction, workletArgs, jobResolve]) => { const result = workletFunction(...workletArgs); if (jobResolve) { - runOnJS(jobResolve)(result); + scheduleOnRN(jobResolve, result); } }); callMicrotasks();