'use strict'; import { setupCallGuard } from './callGuard'; import { registerWorkletsError, WorkletsError } from './debug/WorkletsError'; import { addGuardImplementation } from './guardImplementation'; import { getMemorySafeCapturableConsole, setupConsole, setupSerializer, } from './initializers/initializers'; import { createSerializable, makeShareableCloneOnUIRecursive, } from './memory/serializable'; import { serializableMappingCache } from './memory/serializableMappingCache'; import { setupRunLoop } from './runLoop/workletRuntime'; import { RuntimeKind } from './runtimeKind'; import { scheduleOnRN } from './threads'; import type { WorkletFunction, WorkletRuntime, WorkletRuntimeConfig, } from './types'; import { isWorkletFunction } from './workletFunction'; import { WorkletsModule } from './WorkletsModule/NativeWorklets'; /** * The ID of the [UI Worklet * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime). */ export const UIRuntimeId = RuntimeKind.UI as 2; /** * Lets you create a new JS runtime which can be used to run worklets possibly * on different threads than JS or UI thread. * * @param config - Runtime configuration object - {@link WorkletRuntimeConfig}. * @returns WorkletRuntime which is a * `jsi::HostObject` - {@link WorkletRuntime} * @see https://docs.swmansion.com/react-native-worklets/docs/threading/createWorkletRuntime/ */ // @ts-expect-error Public API overload. export function createWorkletRuntime( config?: WorkletRuntimeConfig ): WorkletRuntime; /** * @deprecated Please use the new config object signature instead: * `createWorkletRuntime({ name, initializer })` * * Lets you create a new JS runtime which can be used to run worklets possibly * on different threads than JS or UI thread. * @param name - A name used to identify the runtime which will appear in * devices list in Chrome DevTools. * @param initializer - An optional worklet that will be run synchronously on * the same thread immediately after the runtime is created. * @returns WorkletRuntime which is a * `jsi::HostObject` - {@link WorkletRuntime} * @see https://docs.swmansion.com/react-native-worklets/docs/threading/createWorkletRuntime/ */ export function createWorkletRuntime( name?: string, initializer?: () => void ): WorkletRuntime; export function createWorkletRuntime( nameOrConfig?: string | WorkletRuntimeConfigInternal, initializer?: WorkletFunction<[], void> ): WorkletRuntime { const runtimeBoundCapturableConsole = getMemorySafeCapturableConsole(); let name: string; let initializerFn: (() => void) | undefined; let useDefaultQueue = true; let customQueue: object | undefined; let animationQueuePollingRate: number; let enableEventLoop = true; if (typeof nameOrConfig === 'string') { name = nameOrConfig; initializerFn = initializer; } else { // TODO: Make anonymous name globally unique. name = nameOrConfig?.name ?? 'anonymous'; initializerFn = nameOrConfig?.initializer; useDefaultQueue = nameOrConfig?.useDefaultQueue ?? true; customQueue = nameOrConfig?.customQueue; animationQueuePollingRate = Math.round( nameOrConfig?.animationQueuePollingRate ?? 16 ); enableEventLoop = nameOrConfig?.enableEventLoop ?? true; } if (initializerFn && !isWorkletFunction(initializerFn)) { throw new WorkletsError( 'The initializer passed to `createWorkletRuntime` is not a worklet.' ); } return WorkletsModule.createWorkletRuntime( name, createSerializable(() => { 'worklet'; setupCallGuard(); setupSerializer(); registerWorkletsError(); setupConsole(runtimeBoundCapturableConsole); if (enableEventLoop) { setupRunLoop(animationQueuePollingRate); } initializerFn?.(); }), useDefaultQueue, customQueue, enableEventLoop ); } /** * Lets you asynchronously run a * [worklet](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worklet) * on a [Worker * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worker-runtime). * * Check * {@link https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds} * for more information about the different runtime kinds. * * - The worklet is scheduled on the Worker Runtime's [Async * Queue](https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-worklets/Common/cpp/worklets/RunLoop/AsyncQueue.h) * * @param workletRuntime - The runtime to schedule the worklet on. * @param worklet - The worklet to schedule. * @param args - The arguments to pass to the worklet. * @returns The return value of the worklet. */ // @ts-expect-error This overload is correct since it's what user sees in their code // before it's transformed by Worklets Babel plugin. export function scheduleOnRuntime( workletRuntime: WorkletRuntime, worklet: (...args: Args) => ReturnValue, ...args: Args ): void; export function scheduleOnRuntime( workletRuntime: WorkletRuntime, worklet: WorkletFunction, ...args: Args ): void { if (__DEV__ && !isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `scheduleOnRuntime` is not a worklet.' ); } WorkletsModule.scheduleOnRuntime( workletRuntime, createSerializable(() => { 'worklet'; worklet(...args); globalThis.__flushMicrotasks?.(); }) ); } if (!globalThis._WORKLETS_BUNDLE_MODE_ENABLED) { function scheduleOnRuntimeWorklet( workletRuntime: WorkletRuntime, worklet: WorkletFunction, ...args: Args ): void { 'worklet'; if (__DEV__ && !isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `scheduleOnRuntime` is not a worklet.' ); } globalThis.__workletsModuleProxy.scheduleOnRuntime( workletRuntime, globalThis.__serializer(() => { 'worklet'; worklet(...args); globalThis.__flushMicrotasks?.(); }) ); } serializableMappingCache.set( scheduleOnRuntime, createSerializable(scheduleOnRuntimeWorklet) ); } /** * Lets you asynchronously run a * [worklet](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worklet) * on a [Worker * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worker-runtime) * identified by the runtime's id. * * Check * {@link https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds} * for more information about the different runtime kinds. * * - The worklet is scheduled on the Worker Runtime's [Async * Queue](https://github.com/software-mansion/react-native-reanimated/blob/main/packages/react-native-worklets/Common/cpp/worklets/RunLoop/AsyncQueue.h) * * @param runtimeId - The id of the runtime to schedule the worklet on. * @param worklet - The worklet to schedule. * @param args - The arguments to pass to the worklet. * @returns The return value of the worklet. */ // @ts-expect-error This overload is correct since it's what user sees in their code // before it's transformed by Worklets Babel plugin. export function scheduleOnRuntimeWithId( runtimeId: number, worklet: (...args: Args) => ReturnValue, ...args: Args ): void; export function scheduleOnRuntimeWithId( runtimeId: number, worklet: WorkletFunction, ...args: Args ): void { if (__DEV__ && !isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `scheduleOnRuntimeWithId` is not a worklet.' ); } WorkletsModule.scheduleOnRuntimeWithId( runtimeId, createSerializable(() => { 'worklet'; worklet(...args); globalThis.__flushMicrotasks?.(); }) ); } if (!globalThis._WORKLETS_BUNDLE_MODE_ENABLED) { function scheduleOnRuntimeWithIdWorklet( runtimeId: number, worklet: WorkletFunction, ...args: Args ): void { 'worklet'; if (__DEV__ && !isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `scheduleOnRuntimeWithId` is not a worklet.' ); } globalThis.__workletsModuleProxy.scheduleOnRuntimeWithId( runtimeId, globalThis.__serializer(() => { 'worklet'; worklet(...args); globalThis.__flushMicrotasks?.(); }) ); } serializableMappingCache.set( scheduleOnRuntimeWithId, createSerializable(scheduleOnRuntimeWithIdWorklet) ); } /** * @deprecated Use `scheduleOnRuntime` instead. * * Schedule a worklet to execute on the background queue. */ // @ts-expect-error This overload is correct since it's what user sees in their code // before it's transformed by Worklets Babel plugin. export function runOnRuntime( workletRuntime: WorkletRuntime, worklet: (...args: Args) => ReturnValue ): WorkletFunction; export function runOnRuntime( workletRuntime: WorkletRuntime, worklet: WorkletFunction ): (...args: Args) => void { 'worklet'; if (__DEV__ && !isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `runOnRuntime` is not a worklet.' ); } return (...args) => scheduleOnRuntime(workletRuntime, worklet, ...args); } 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/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 { 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); }) ); } /** * Lets you run a function synchronously on a [Worklet * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worklet-runtime) * identified by the runtime's id. * * - This function cannot be called from the [UI * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime) * or a [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/bundleMode/) is * enabled. * - You can target the UI Runtime with this function by passing * {@link UIRuntimeId} as the `runtimeId` argument. * * @param runtimeId - The id of 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. */ // @ts-expect-error This overload is correct since it's what user sees in their code // before it's transformed by Worklets Babel plugin. export function runOnRuntimeSyncWithId( runtimeId: number, worklet: (...args: Args) => ReturnValue, ...args: Args ): ReturnValue; export function runOnRuntimeSyncWithId( runtimeId: number, worklet: WorkletFunction, ...args: Args ): ReturnValue { if (__DEV__ && !isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `runOnRuntimeSyncWithId` is not a worklet.' ); } return WorkletsModule.runOnRuntimeSyncWithId( runtimeId, createSerializable(() => { 'worklet'; const result = worklet(...args); return makeShareableCloneOnUIRecursive(result); }) ); } /** * Lets you asynchronously run a * [worklet](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worklet) * on a [Worker * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worker-runtime) * and get the result via a Promise. * * - The worklet is scheduled on the Worker Runtime's Async Queue * - Returns a Promise that resolves with the worklet's return value * - This function can only be called from the [RN * Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#rn-runtime). * * @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 A Promise that resolves to the return value of the worklet. * @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnRuntimeAsync */ // @ts-expect-error This overload is correct since it's what user sees in their code // before it's transformed by Worklets Babel plugin. export function runOnRuntimeAsync( workletRuntime: WorkletRuntime, worklet: (...args: Args) => ReturnValue, ...args: Args ): Promise; export function runOnRuntimeAsync( workletRuntime: WorkletRuntime, worklet: WorkletFunction, ...args: Args ): Promise { if (__DEV__) { if (globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) { throw new WorkletsError( '`runOnRuntimeAsync` can only be called on the RN Runtime.' ); } if (!isWorkletFunction(worklet)) { throw new WorkletsError( 'The function passed to `runOnRuntimeAsync` is not a worklet.' ); } } return new Promise((resolve, reject) => { if (__DEV__) { // in DEV mode we call serializable conversion here because in case the object // can't be converted, we will get a meaningful stack-trace as opposed to the // situation when conversion is only done via microtask queue. This does not // make the app particularily less efficient as converted objects are cached // and for a given worklet the conversion only happens once. createSerializable(worklet); createSerializable(args); } WorkletsModule.scheduleOnRuntime( workletRuntime, createSerializable(() => { 'worklet'; try { const result = worklet(...args); scheduleOnRN(resolve, result); } catch (error) { scheduleOnRN(reject, error); } globalThis.__flushMicrotasks?.(); }) ); }); } if (__DEV__ && !globalThis._WORKLETS_BUNDLE_MODE_ENABLED) { /** * QoL guards to give a meaningful error message when the user tries to call * these functions on Worklet Runtimes outside of the Bundle Mode. */ addGuardImplementation(runOnRuntimeAsync); addGuardImplementation(runOnRuntimeSync); addGuardImplementation(runOnRuntimeSyncWithId); } export function getUIRuntimeHolder(): object { return WorkletsModule.getUIRuntimeHolder(); } export function getUISchedulerHolder(): object { return WorkletsModule.getUISchedulerHolder(); }