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 140736ea1913..2a201ab97ff9 100644 --- a/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/RuntimeTestsExample.tsx @@ -58,6 +58,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..5aaf1a6a0cde --- /dev/null +++ b/apps/common-app/src/apps/reanimated/examples/RuntimeTests/tests/runtimes/runOnRuntimeSync.test.tsx @@ -0,0 +1,57 @@ +import { + createSynchronizable, + createWorkletRuntime, + scheduleOnRN, + runOnRuntimeSync, + scheduleOnRuntime, +} from 'react-native-worklets'; +import { describe, expect, test, notify, waitForNotification } from '../../ReJest/RuntimeTestsApi'; +import { ComparisonMode } from '../../ReJest/types'; + +const NOTIFICATION_NAME = 'NOTIFICATION_NAME'; + +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); + }); + + test('keep the correct order of execution runOnRuntimeSync and scheduleOnRuntime', async () => { + // Arrange + const workletRuntime = createWorkletRuntime({ name: 'test' }); + const synchronizable = createSynchronizable(0); + + const onJSCallback = () => { + notify(NOTIFICATION_NAME); + }; + + // Act + // TODO: Replace with `runOnRuntimeAsync`. + scheduleOnRuntime(workletRuntime, () => { + 'worklet'; + // heavy computation + new Array(50_000_000).map((_v, i) => i ** 2); + synchronizable.setBlocking(100); + scheduleOnRN(onJSCallback); + }); + + const result = runOnRuntimeSync(workletRuntime, () => { + 'worklet'; + return 100; + }); + + // Assert + expect(result).toBe(100, ComparisonMode.NUMBER); + await waitForNotification(NOTIFICATION_NAME); + expect(synchronizable.getBlocking()).toBe(100, ComparisonMode.NUMBER); + }); +}); diff --git a/packages/docs-worklets/docs/threading/createWorkletRuntime.mdx b/packages/docs-worklets/docs/threading/createWorkletRuntime.mdx index 3652c0f1d5dd..6876dca92252 100644 --- a/packages/docs-worklets/docs/threading/createWorkletRuntime.mdx +++ b/packages/docs-worklets/docs/threading/createWorkletRuntime.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 6 +sidebar_position: 8 --- # createWorkletRuntime diff --git a/packages/docs-worklets/docs/threading/executeOnUIRuntimeSync.mdx b/packages/docs-worklets/docs/threading/executeOnUIRuntimeSync.mdx index 0577c2b56ecc..65b2368ce011 100644 --- a/packages/docs-worklets/docs/threading/executeOnUIRuntimeSync.mdx +++ b/packages/docs-worklets/docs/threading/executeOnUIRuntimeSync.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 4 +sidebar_position: 3 --- { + '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 [Worklet Runtime](/docs/fundamentals/glossary#worklet-runtime). + +### args + +Arguments to the function you want to execute on the [Worklet Runtime](/docs/fundamentals/glossary#worklet-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 🚨 +}); +``` \ No newline at end of file diff --git a/packages/docs-worklets/docs/threading/runOnUIAsync.mdx b/packages/docs-worklets/docs/threading/runOnUIAsync.mdx index ddf9c582e6db..28820e313fa1 100644 --- a/packages/docs-worklets/docs/threading/runOnUIAsync.mdx +++ b/packages/docs-worklets/docs/threading/runOnUIAsync.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 3 +sidebar_position: 4 --- # runOnUIAsync diff --git a/packages/docs-worklets/docs/threading/runOnUISync.mdx b/packages/docs-worklets/docs/threading/runOnUISync.mdx index 107af1e37a17..00b9d947213e 100644 --- a/packages/docs-worklets/docs/threading/runOnUISync.mdx +++ b/packages/docs-worklets/docs/threading/runOnUISync.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 10 +sidebar_position: 5 --- # runOnUISync diff --git a/packages/docs-worklets/docs/threading/scheduleOnRN.mdx b/packages/docs-worklets/docs/threading/scheduleOnRN.mdx index 2558a70cdd7e..033a1ceea417 100644 --- a/packages/docs-worklets/docs/threading/scheduleOnRN.mdx +++ b/packages/docs-worklets/docs/threading/scheduleOnRN.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 9 +sidebar_position: 7 --- # scheduleOnRN diff --git a/packages/docs-worklets/docs/threading/scheduleOnRuntime.mdx b/packages/docs-worklets/docs/threading/scheduleOnRuntime.mdx index 9303a8c75c80..2e6811324aea 100644 --- a/packages/docs-worklets/docs/threading/scheduleOnRuntime.mdx +++ b/packages/docs-worklets/docs/threading/scheduleOnRuntime.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 12 +sidebar_position: 10 --- # scheduleOnRuntime @@ -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 🚨 }); ``` \ No newline at end of file diff --git a/packages/docs-worklets/docs/threading/scheduleOnUI.mdx b/packages/docs-worklets/docs/threading/scheduleOnUI.mdx index 2bf31b304ded..e44fe9675138 100644 --- a/packages/docs-worklets/docs/threading/scheduleOnUI.mdx +++ b/packages/docs-worklets/docs/threading/scheduleOnUI.mdx @@ -1,5 +1,5 @@ --- -sidebar_position: 11 +sidebar_position: 6 --- # scheduleOnUI 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..f2b0bc000195 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/NativeModules/JSIWorkletsModuleProxy.cpp @@ -61,6 +61,15 @@ inline jsi::Value executeOnUIRuntimeSync( return jsi::Value::undefined(); } +inline jsi::Value runOnRuntimeSync( + jsi::Runtime &rt, + const jsi::Value &workletRuntimeValue, + const jsi::Value &serializableWorkletValue) { + auto workletRuntime = + workletRuntimeValue.getObject(rt).getHostObject(rt); + return workletRuntime->executeSync(rt, serializableWorkletValue); +} + inline jsi::Value createWorkletRuntime( jsi::Runtime &originRuntime, const std::shared_ptr &runtimeManager, @@ -202,6 +211,7 @@ std::vector JSIWorkletsModuleProxy::getPropertyNames( propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "scheduleOnUI")); propertyNames.emplace_back( jsi::PropNameID::forAscii(rt, "executeOnUIRuntimeSync")); + propertyNames.emplace_back(jsi::PropNameID::forAscii(rt, "runOnRuntimeSync")); propertyNames.emplace_back( jsi::PropNameID::forAscii(rt, "createWorkletRuntime")); propertyNames.emplace_back( @@ -496,6 +506,17 @@ jsi::Value JSIWorkletsModuleProxy::get( }); } + 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]); }); + } + if (name == "createWorkletRuntime") { return jsi::Function::createFromHostFunction( rt, 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 5e8635004a4e..47e6e6be2763 100644 --- a/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp +++ b/packages/react-native-worklets/Common/cpp/worklets/WorkletRuntime/WorkletRuntime.cpp @@ -164,11 +164,12 @@ jsi::Value WorkletRuntime::executeSync( auto serializableWorklet = extractSerializableOrThrow( rt, worklet, - "[Worklets] Only worklets can be executed synchronously on UI runtime."); + "[Worklets] Only worklets can be executed synchronously on Worklet (" + + name_ + ") Runtime."); auto lock = std::unique_lock(*runtimeMutex_); - jsi::Runtime &uiRuntime = getJSIRuntime(); + jsi::Runtime &workletRuntime = getJSIRuntime(); auto result = runGuarded(serializableWorklet); - auto serializableResult = extractSerializableOrThrow(uiRuntime, result); + auto serializableResult = extractSerializableOrThrow(workletRuntime, result); lock.unlock(); return serializableResult->toJSValue(rt); } diff --git a/packages/react-native-worklets/__typetests__/runOnRuntimeSync.tsx b/packages/react-native-worklets/__typetests__/runOnRuntimeSync.tsx new file mode 100644 index 000000000000..2dca4fd45fc6 --- /dev/null +++ b/packages/react-native-worklets/__typetests__/runOnRuntimeSync.tsx @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { createWorkletRuntime, runOnRuntimeSync, runOnUISync } 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/JSWorklets.ts b/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts index 68efbd62898d..21e97d6c9368 100644 --- a/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts +++ b/packages/react-native-worklets/src/WorkletsModule/JSWorklets.ts @@ -135,6 +135,12 @@ class JSWorklets implements IWorkletsModule { ); } + runOnRuntimeSync(): never { + throw new WorkletsError( + '`runOnRuntimeSync` is not available in JSWorklets.' + ); + } + createWorkletRuntime(): never { throw new WorkletsError( 'createWorkletRuntime is not available in JSWorklets.' diff --git a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts index 456dc34dd56e..ddfe3403a1b6 100644 --- a/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts +++ b/packages/react-native-worklets/src/WorkletsModule/NativeWorklets.ts @@ -192,6 +192,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 22dd530c8087..b5dea301ff84 100644 --- a/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts +++ b/packages/react-native-worklets/src/WorkletsModule/workletsModuleProxy.ts @@ -88,6 +88,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 82a99b25fa4c..a6826cd9224d 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, + runOnRuntimeSync, 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..ef6ae49da4f4 100644 --- a/packages/react-native-worklets/src/runtimes.ts +++ b/packages/react-native-worklets/src/runtimes.ts @@ -170,6 +170,45 @@ export function scheduleOnRuntime( runOnRuntime(workletRuntime, worklet)(...args); } +/** + * Lets you run a function synchronously on the [Worker + * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worker-worklet-runtime---worker-runtime). + * + * - This function cannot be called from the [UI + * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime). + * or another [Worker + * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worker-worklet-runtime---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__ && !SHOULD_BE_USE_WEB && !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); + }) + ); +} + /** Configuration object for creating a worklet runtime. */ export type WorkletRuntimeConfig = { /** The name of the worklet runtime. */