diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx index 2d62efa4fa9d..05d6b893d0e0 100644 --- a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx @@ -60,6 +60,7 @@ export default function RuntimeTestsExample() { require('./tests/runtimes/runOnUISync.test'); require('./tests/runtimes/scheduleOnRuntime.test'); require('./tests/runtimes/scheduleOnUI.test'); + require('./tests/runtimes/runOnRuntimeSync.test'); }, }, { diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnRuntimeSync.test.tsx b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnRuntimeSync.test.tsx new file mode 100644 index 000000000000..afc8993c509e --- /dev/null +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnRuntimeSync.test.tsx @@ -0,0 +1,19 @@ +import { createWorkletRuntime, runOnRuntimeSync } from 'react-native-worklets'; +import { describe, expect, test } from '../../ReJest/RuntimeTestsApi'; +import { ComparisonMode } from '../../ReJest/types'; + +describe('runOnRuntimeSync', () => { + test('use runOnRuntimeSync to run a function on the Worker Runtime from RN Runtime', () => { + // Arrange + const workletRuntime = createWorkletRuntime({ name: 'test' }); + + // Act + const result = runOnRuntimeSync(workletRuntime, () => { + 'worklet'; + return 100; + }); + + // Assert + expect(result).toBe(100, ComparisonMode.NUMBER); + }); +}); diff --git a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnUISync.test.tsx b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnUISync.test.tsx index cf04606aa46c..17a19d026428 100644 --- a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnUISync.test.tsx +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnUISync.test.tsx @@ -1,46 +1,16 @@ import { runOnUISync } from 'react-native-worklets'; -import { - describe, - expect, - getRegisteredValue, - registerValue, - render, - test, - waitForNotification, - notify, -} from '../../ReJest/RuntimeTestsApi'; -import { SharedValue, useSharedValue } from 'react-native-reanimated'; +import { describe, expect, test } from '../../ReJest/RuntimeTestsApi'; import { ComparisonMode } from '../../ReJest/types'; -import { View } from 'react-native'; -import { useEffect } from 'react'; - -const SHARED_VALUE_REF = 'SHARED_VALUE_REF'; -const NOTIFICATION_NAME = 'NOTIFICATION_NAME'; - -const TestComponent = () => { - const sharedValue = useSharedValue(0); - registerValue(SHARED_VALUE_REF, sharedValue as SharedValue); - - useEffect(() => { - const callback = (num: number) => { - 'worklet'; - sharedValue.value = num; - notify(NOTIFICATION_NAME); - }; - runOnUISync(callback, 100); - }, [sharedValue]); - - return ; -}; describe('runOnUISync', () => { - test('use runOnUISync to run a function on the UI runtime', async () => { + test('use runOnUISync to run a function on the UI Runtime from RN Runtime', () => { // Arrange & Act - await render(); + const result = runOnUISync(() => { + 'worklet'; + return 100; + }); // Assert - await waitForNotification(NOTIFICATION_NAME); - const sharedValueOnJS = await getRegisteredValue(SHARED_VALUE_REF); - expect(sharedValueOnJS.onJS).toBe(100, ComparisonMode.NUMBER); + expect(result).toBe(100, ComparisonMode.NUMBER); }); }); diff --git a/docs/docs-worklets/docs/threading/runOnRuntimeSync.mdx b/docs/docs-worklets/docs/threading/runOnRuntimeSync.mdx new file mode 100644 index 000000000000..8e4172c685ac --- /dev/null +++ b/docs/docs-worklets/docs/threading/runOnRuntimeSync.mdx @@ -0,0 +1,79 @@ +--- +title: runOnRuntimeSync +sidebar_position: 42 # Use alphabetical order +--- + +# runOnRuntimeSync + +`runOnRuntimeSync` lets you run a [workletized](/docs/fundamentals/glossary#to-workletize) function synchronously on a [Worker Runtime](/docs/fundamentals/runtimeKinds#worker-runtime). It's blocking - meaning that it can preempt the runtime from a thread that's currently executing it. + +## Reference + +```javascript +import { runOnRuntimeSync, createWorkletRuntime } from 'react-native-worklets'; + +const workletRuntime = createWorkletRuntime({ name: 'background' }); + +const result = runOnRuntimeSync(workletRuntime, () => { + 'worklet'; + return 2 + 2; +}); // This will block the RN Runtime until the worklet is finished + +console.log(result); // 4 +``` + +
+Type definitions + +```typescript +function runOnRuntimeSync( + workletRuntime: WorkletRuntime, + worklet: (...args: Args) => ReturnValue, + ...args: Args +): ReturnValue; +``` + +
+ +## Arguments + +### workletRuntime + +The worklet runtime to run the worklet on. + +### worklet + +A reference to a function you want to execute on the [Worker Runtime](/docs/fundamentals/runtimeKinds#worker-runtime). + +### args + +Arguments to the function you want to execute on the [Worker Runtime](/docs/fundamentals/runtimeKinds#worker-runtime). + +## Remarks + +- `runOnRuntimeSync` can only be called on the [RN Runtime](/docs/fundamentals/glossary#react-native-runtime) unless the [Bundle Mode](/docs/experimental/bundleMode) is enabled. + +```javascript +import { createWorkletRuntime, runOnRuntimeSync } from 'react-native-worklets'; + +const workletRuntime = createWorkletRuntime({ name: 'background' }); + +runOnUI(() => { + runOnRuntimeSync(workletRuntime, (greeting: string) => { + console.log(`${greeting} from the background Worklet Runtime`); + }, 'Hello'); // This will throw an error outside of Bundle Mode 🚨 +}); +``` + +```javascript +import { createWorkletRuntime, scheduleOnRuntime } from 'react-native-worklets'; + +const workletRuntime = createWorkletRuntime({ name: 'background' }); +const anotherWorkletRuntime = createWorkletRuntime({ name: 'anotherBackground' }); + +runOnRuntimeSync(anotherWorkletRuntime, () => { + runOnRuntimeSync(workletRuntime, (greeting: string) => { + console.log(`${greeting} from the background Worklet Runtime`); + }, 'Hello'); // This will throw an error outside of Bundle Mode 🚨 +}); +``` diff --git a/docs/docs-worklets/docs/threading/scheduleOnRuntime.mdx b/docs/docs-worklets/docs/threading/scheduleOnRuntime.mdx index 9e5bada9f5e5..03418dfcb00c 100644 --- a/docs/docs-worklets/docs/threading/scheduleOnRuntime.mdx +++ b/docs/docs-worklets/docs/threading/scheduleOnRuntime.mdx @@ -74,6 +74,6 @@ const anotherWorkletRuntime = createWorkletRuntime({ name: 'anotherBackground' } scheduleOnRuntime(anotherWorkletRuntime, () => { scheduleOnRuntime(workletRuntime, (greeting: string) => { console.log(`${greeting} from the background Worklet Runtime`); - }, 'Hello'); // This will throw an error 🚨 + }, 'Hello'); // This will throw an error outside of Bundle Mode 🚨 }); ``` 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 a7cb25ddf37a..c2c602420d99 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp @@ -54,10 +54,8 @@ inline void scheduleOnUI( }); } -inline jsi::Value executeOnUIRuntimeSync( - const std::weak_ptr &weakUIWorkletRuntime, - jsi::Runtime &rt, - const jsi::Value &worklet) { +inline jsi::Value +runOnUISync(const std::weak_ptr &weakUIWorkletRuntime, jsi::Runtime &rt, const jsi::Value &worklet) { if (auto uiWorkletRuntime = weakUIWorkletRuntime.lock()) { auto serializableWorklet = extractSerializableOrThrow( rt, worklet, "[Worklets] Only worklets can be executed on UI runtime."); @@ -67,6 +65,14 @@ inline jsi::Value executeOnUIRuntimeSync( return jsi::Value::undefined(); } +jsi::Value +runOnRuntimeSync(jsi::Runtime &rt, const jsi::Value &workletRuntimeValue, const jsi::Value &serializableWorkletValue) { + auto workletRuntime = workletRuntimeValue.getObject(rt).getHostObject(rt); + auto worklet = extractSerializableOrThrow( + rt, serializableWorkletValue, "[Worklets] Only worklets can be executed on a worklet runtime."); + return workletRuntime->runSyncSerialized(worklet)->toJSValue(rt); +} + inline jsi::Value createWorkletRuntime( jsi::Runtime &originRuntime, const std::shared_ptr &runtimeManager, @@ -203,7 +209,8 @@ std::vector JSIWorkletsModuleProxy::getPropertyNames(jsi::Runti propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "registerCustomSerializable")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "scheduleOnUI")); - propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "executeOnUIRuntimeSync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "runOnUISync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "runOnRuntimeSync")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "createWorkletRuntime")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "scheduleOnRuntime")); propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "reportFatalErrorOnJS")); @@ -385,14 +392,21 @@ jsi::Value JSIWorkletsModuleProxy::get(jsi::Runtime &rt, const jsi::PropNameID & }); } - if (name == "executeOnUIRuntimeSync") { + if (name == "runOnUISync") { return jsi::Function::createFromHostFunction( rt, propName, 1, [uiWorkletRuntime = uiWorkletRuntime_]( jsi::Runtime &rt, const jsi::Value &thisValue, const jsi::Value *args, size_t count) { - return executeOnUIRuntimeSync(uiWorkletRuntime, rt, args[0]); + return runOnUISync(uiWorkletRuntime, rt, args[0]); + }); + } + + if (name == "runOnRuntimeSync") { + return jsi::Function::createFromHostFunction( + rt, propName, 2, [](jsi::Runtime &rt, const jsi ::Value &thisValue, const jsi::Value *args, size_t count) { + return runOnRuntimeSync(rt, args[0], args[1]); }); } diff --git a/packages/react-native-worklets/__typetests__/runOnRuntimeSync.tsx b/packages/react-native-worklets/__typetests__/runOnRuntimeSync.tsx new file mode 100644 index 000000000000..49cf9ac49482 --- /dev/null +++ b/packages/react-native-worklets/__typetests__/runOnRuntimeSync.tsx @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { createWorkletRuntime, runOnRuntimeSync } from '..'; + +function runOnRuntimeSyncTypeTests() { + const workletRuntime = createWorkletRuntime({ name: 'test' }); + // Correct usage - correct usage + runOnRuntimeSync( + workletRuntime, + (num: number): number => { + return num + 1; + }, + 0 + ); + + // @ts-expect-error - expected no args, but arg is provided + runOnRuntimeSync(workletRuntime, (): void => {}, 0); + + // Wrong args type + runOnRuntimeSync( + workletRuntime, + (num: number): number => { + return num + 1; + }, + // @ts-expect-error - wrong args type + 'tets' + ); + + // Wrong return type + runOnRuntimeSync( + workletRuntime, + (num: number): string => { + // @ts-expect-error - wrong return type + return num + 1; + }, + 0 + ); + + // @ts-expect-error - wrong return type + const result: string = runOnRuntimeSync( + workletRuntime, + (num: number): number => { + return num + 1; + }, + 0 + ); +} diff --git a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts index 9a12fada5772..67ebf32e6008 100644 --- a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts +++ b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.native.ts @@ -177,10 +177,8 @@ See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting return this.#workletsModuleProxy.scheduleOnUI(serializable); } - executeOnUIRuntimeSync( - serializable: SerializableRef - ): TReturn { - return this.#workletsModuleProxy.executeOnUIRuntimeSync(serializable); + runOnUISync(worklet: SerializableRef): TReturn { + return this.#workletsModuleProxy.runOnUISync(worklet); } createWorkletRuntime( @@ -209,6 +207,13 @@ See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting ); } + runOnRuntimeSync( + workletRuntime: WorkletRuntime, + worklet: SerializableRef + ): TReturn { + return this.#workletsModuleProxy.runOnRuntimeSync(workletRuntime, worklet); + } + createSynchronizable(value: TValue): SynchronizableRef { return this.#workletsModuleProxy.createSynchronizable(value); } diff --git a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts index b7d9fb2f110f..ff0f5b71ab24 100644 --- a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts +++ b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts @@ -83,9 +83,7 @@ export interface WorkletsModuleProxy { scheduleOnUI(serializable: SerializableRef): void; - executeOnUIRuntimeSync( - serializable: SerializableRef - ): TReturn; + runOnUISync(serializable: SerializableRef): TReturn; createWorkletRuntime( name: string, @@ -100,6 +98,11 @@ export interface WorkletsModuleProxy { worklet: SerializableRef ): void; + runOnRuntimeSync( + workletRuntime: WorkletRuntime, + worklet: SerializableRef + ): TReturn; + reportFatalErrorOnJS( message: string, stack: string, diff --git a/packages/react-native-worklets/src/index.ts b/packages/react-native-worklets/src/index.ts index 7ce17d8ba5b6..40445e5d3abc 100644 --- a/packages/react-native-worklets/src/index.ts +++ b/packages/react-native-worklets/src/index.ts @@ -44,6 +44,7 @@ export { getRuntimeKind, RuntimeKind } from './runtimeKind'; export { createWorkletRuntime, runOnRuntime, + runOnRuntimeSync, scheduleOnRuntime, } from './runtimes'; export { diff --git a/packages/react-native-worklets/src/runtimes.native.ts b/packages/react-native-worklets/src/runtimes.native.ts index bca57ac5c157..8fda896fdab7 100644 --- a/packages/react-native-worklets/src/runtimes.native.ts +++ b/packages/react-native-worklets/src/runtimes.native.ts @@ -196,3 +196,42 @@ export function runOnRuntime( type WorkletRuntimeConfigInternal = WorkletRuntimeConfig & { initializer?: WorkletFunction<[], void>; }; + +/** + * Lets you run a function synchronously on a [Worker + * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worker-runtime). + * + * - This function cannot be called from the [UI + * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime). + * or another [Worker + * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worker-runtime), + * unless the [Bundle + * Mode](https://docs.swmansion.com/react-native-worklets/docs/experimental/bundleMode) + * is enabled. + * + * @param workletRuntime - The runtime to run the worklet on. + * @param worklet - The worklet to run. + * @param args - The arguments to pass to the worklet. + * @returns The return value of the worklet. + */ +export function runOnRuntimeSync( + workletRuntime: WorkletRuntime, + worklet: (...args: Args) => ReturnValue, + ...args: Args +): ReturnValue { + 'worklet'; + if (__DEV__ && !isWorkletFunction(worklet)) { + throw new WorkletsError( + 'The function passed to `runOnRuntimeSync` is not a worklet.' + ); + } + + return WorkletsModule.runOnRuntimeSync( + workletRuntime, + createSerializable(() => { + 'worklet'; + const result = worklet(...args); + return makeShareableCloneOnUIRecursive(result); + }) + ); +} diff --git a/packages/react-native-worklets/src/runtimes.ts b/packages/react-native-worklets/src/runtimes.ts index 428b5e07772e..53a4205d1dd8 100644 --- a/packages/react-native-worklets/src/runtimes.ts +++ b/packages/react-native-worklets/src/runtimes.ts @@ -38,3 +38,13 @@ export function scheduleOnRuntime( export function scheduleOnRuntime(): never { throw new WorkletsError('`scheduleOnRuntime` is not supported on web.'); } + +export function runOnRuntimeSync( + workletRuntime: WorkletRuntime, + worklet: (...args: Args) => ReturnValue, + ...args: Args +): ReturnValue; + +export function runOnRuntimeSync(): never { + throw new WorkletsError('`runOnRuntimeSync` is not supported on web.'); +} diff --git a/packages/react-native-worklets/src/threads.native.ts b/packages/react-native-worklets/src/threads.native.ts index 5e59a65f6672..3a9a667c6b85 100644 --- a/packages/react-native-worklets/src/threads.native.ts +++ b/packages/react-native-worklets/src/threads.native.ts @@ -187,7 +187,7 @@ export function runOnUISync( worklet: WorkletFunction, ...args: Args ): ReturnValue { - return WorkletsModule.executeOnUIRuntimeSync( + return WorkletsModule.runOnUISync( createSerializable(() => { 'worklet'; const result = worklet(...args);