chore: update

This commit is contained in:
2026-02-18 21:52:32 +01:00
parent aed786cf2c
commit 1f2e4e96dd
399 changed files with 4173 additions and 10273 deletions
@@ -1,275 +0,0 @@
'use strict';
import { checkCppVersion } from '../debug/checkCppVersion';
import { jsVersion } from '../debug/jsVersion';
import { WorkletsError } from '../debug/WorkletsError';
import type { SerializableRef, SynchronizableRef } from '../memory/types';
import { RuntimeKind } from '../runtimeKind';
import { WorkletsTurboModule } from '../specs';
import type { WorkletRuntime } from '../types';
import type {
IWorkletsModule,
WorkletsModuleProxy,
} from './workletsModuleProxy';
class NativeWorklets implements IWorkletsModule {
#workletsModuleProxy: WorkletsModuleProxy;
#serializableUndefined: SerializableRef<undefined>;
#serializableNull: SerializableRef<null>;
#serializableTrue: SerializableRef<boolean>;
#serializableFalse: SerializableRef<boolean>;
constructor() {
globalThis._WORKLETS_VERSION_JS = jsVersion;
if (
global.__workletsModuleProxy === undefined &&
globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative
) {
WorkletsTurboModule?.installTurboModule();
}
if (global.__workletsModuleProxy === undefined) {
throw new WorkletsError(
`Native part of Worklets doesn't seem to be initialized.
See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#native-part-of-worklets-doesnt-seem-to-be-initialized for more details.`
);
}
if (__DEV__ && globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative) {
checkCppVersion();
}
this.#workletsModuleProxy = global.__workletsModuleProxy;
this.#serializableNull = this.#workletsModuleProxy.createSerializableNull();
this.#serializableUndefined =
this.#workletsModuleProxy.createSerializableUndefined();
this.#serializableTrue =
this.#workletsModuleProxy.createSerializableBoolean(true);
this.#serializableFalse =
this.#workletsModuleProxy.createSerializableBoolean(false);
}
createSerializable<TValue>(
value: TValue,
shouldPersistRemote: boolean,
nativeStateSource?: object
) {
return this.#workletsModuleProxy.createSerializable(
value,
shouldPersistRemote,
nativeStateSource
);
}
createSerializableImport<TValue>(
from: string,
to: string
): SerializableRef<TValue> {
return this.#workletsModuleProxy.createSerializableImport(from, to);
}
createSerializableString(str: string) {
return this.#workletsModuleProxy.createSerializableString(str);
}
createSerializableNumber(num: number) {
return this.#workletsModuleProxy.createSerializableNumber(num);
}
createSerializableBoolean(bool: boolean) {
return bool ? this.#serializableTrue : this.#serializableFalse;
}
createSerializableBigInt(bigInt: bigint) {
return this.#workletsModuleProxy.createSerializableBigInt(bigInt);
}
createSerializableUndefined() {
return this.#serializableUndefined;
}
createSerializableNull() {
return this.#serializableNull;
}
createSerializableTurboModuleLike<
TProps extends object,
TProto extends object,
>(props: TProps, proto: TProto): SerializableRef<TProps> {
return this.#workletsModuleProxy.createSerializableTurboModuleLike(
props,
proto
);
}
createSerializableObject<T extends object>(
obj: T,
shouldRetainRemote: boolean,
nativeStateSource?: object
): SerializableRef<T> {
return this.#workletsModuleProxy.createSerializableObject(
obj,
shouldRetainRemote,
nativeStateSource
);
}
createSerializableHostObject<T extends object>(obj: T) {
return this.#workletsModuleProxy.createSerializableHostObject(obj);
}
createSerializableArray(array: unknown[], shouldRetainRemote: boolean) {
return this.#workletsModuleProxy.createSerializableArray(
array,
shouldRetainRemote
);
}
createSerializableMap<TKey, TValue>(
keys: TKey[],
values: TValue[]
): SerializableRef<Map<TKey, TValue>> {
return this.#workletsModuleProxy.createSerializableMap(keys, values);
}
createSerializableSet<TValues>(
values: TValues[]
): SerializableRef<Set<TValues>> {
return this.#workletsModuleProxy.createSerializableSet(values);
}
createSerializableInitializer(obj: object) {
return this.#workletsModuleProxy.createSerializableInitializer(obj);
}
createSerializableFunction<TArgs extends unknown[], TReturn>(
func: (...args: TArgs) => TReturn
) {
return this.#workletsModuleProxy.createSerializableFunction(func);
}
createSerializableWorklet(worklet: object, shouldPersistRemote: boolean) {
return this.#workletsModuleProxy.createSerializableWorklet(
worklet,
shouldPersistRemote
);
}
createCustomSerializable(
data: SerializableRef<unknown>,
typeId: number
): SerializableRef<unknown> {
return this.#workletsModuleProxy.createCustomSerializable(data, typeId);
}
registerCustomSerializable(
determine: SerializableRef<object>,
pack: SerializableRef<object>,
unpack: SerializableRef<object>,
typeId: number
): void {
this.#workletsModuleProxy.registerCustomSerializable(
determine,
pack,
unpack,
typeId
);
}
scheduleOnUI<TValue>(serializable: SerializableRef<TValue>) {
return this.#workletsModuleProxy.scheduleOnUI(serializable);
}
executeOnUIRuntimeSync<TValue, TReturn>(
serializable: SerializableRef<TValue>
): TReturn {
return this.#workletsModuleProxy.executeOnUIRuntimeSync(serializable);
}
createWorkletRuntime(
name: string,
initializer: SerializableRef<() => void>,
useDefaultQueue: boolean,
customQueue: object | undefined,
enableEventLoop: boolean
) {
return this.#workletsModuleProxy.createWorkletRuntime(
name,
initializer,
useDefaultQueue,
customQueue,
enableEventLoop
);
}
scheduleOnRuntime<T>(
workletRuntime: WorkletRuntime,
serializableWorklet: SerializableRef<T>
) {
return this.#workletsModuleProxy.scheduleOnRuntime(
workletRuntime,
serializableWorklet
);
}
createSynchronizable<TValue>(value: TValue): SynchronizableRef<TValue> {
return this.#workletsModuleProxy.createSynchronizable(value);
}
synchronizableGetDirty<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): TValue {
return this.#workletsModuleProxy.synchronizableGetDirty(synchronizableRef);
}
synchronizableGetBlocking<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): TValue {
return this.#workletsModuleProxy.synchronizableGetBlocking(
synchronizableRef
);
}
synchronizableSetBlocking<TValue>(
synchronizableRef: SynchronizableRef<TValue>,
value: SerializableRef<TValue>
) {
return this.#workletsModuleProxy.synchronizableSetBlocking(
synchronizableRef,
value
);
}
synchronizableLock<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): void {
return this.#workletsModuleProxy.synchronizableLock(synchronizableRef);
}
synchronizableUnlock<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): void {
return this.#workletsModuleProxy.synchronizableUnlock(synchronizableRef);
}
reportFatalErrorOnJS(
message: string,
stack: string,
name: string,
jsEngine: string
) {
return this.#workletsModuleProxy.reportFatalErrorOnJS(
message,
stack,
name,
jsEngine
);
}
getStaticFeatureFlag(name: string): boolean {
return this.#workletsModuleProxy.getStaticFeatureFlag(name);
}
setDynamicFeatureFlag(name: string, value: boolean) {
this.#workletsModuleProxy.setDynamicFeatureFlag(name, value);
}
}
export const WorkletsModule: IWorkletsModule = new NativeWorklets();
@@ -1,10 +1,256 @@
'use strict';
import type { IWorkletsModule } from './workletsModuleProxy';
export type {
import { RuntimeKind } from '../runtimeKind';
import { WorkletsTurboModule } from '../specs';
import type { SynchronizableRef } from '../synchronizable';
import { checkCppVersion } from '../utils/checkCppVersion';
import { jsVersion } from '../utils/jsVersion';
import { WorkletsError } from '../WorkletsError';
import type { SerializableRef, WorkletRuntime } from '../workletTypes';
import type {
IWorkletsModule,
WorkletsModuleProxy,
} from './workletsModuleProxy';
export const WorkletsModule: IWorkletsModule = null!;
export function createNativeWorkletsModule(): IWorkletsModule {
return new NativeWorklets();
}
class NativeWorklets implements IWorkletsModule {
#workletsModuleProxy: WorkletsModuleProxy;
#serializableUndefined: SerializableRef<undefined>;
#serializableNull: SerializableRef<null>;
#serializableTrue: SerializableRef<boolean>;
#serializableFalse: SerializableRef<boolean>;
constructor() {
globalThis._WORKLETS_VERSION_JS = jsVersion;
if (
global.__workletsModuleProxy === undefined &&
globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative
) {
WorkletsTurboModule?.installTurboModule();
}
if (global.__workletsModuleProxy === undefined) {
throw new WorkletsError(
`Native part of Worklets doesn't seem to be initialized.
See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#native-part-of-worklets-doesnt-seem-to-be-initialized for more details.`
);
}
if (__DEV__) {
checkCppVersion();
}
this.#workletsModuleProxy = global.__workletsModuleProxy;
this.#serializableNull = this.#workletsModuleProxy.createSerializableNull();
this.#serializableUndefined =
this.#workletsModuleProxy.createSerializableUndefined();
this.#serializableTrue =
this.#workletsModuleProxy.createSerializableBoolean(true);
this.#serializableFalse =
this.#workletsModuleProxy.createSerializableBoolean(false);
}
createSerializable<TValue>(
value: TValue,
shouldPersistRemote: boolean,
nativeStateSource?: object
) {
return this.#workletsModuleProxy.createSerializable(
value,
shouldPersistRemote,
nativeStateSource
);
}
createSerializableImport<TValue>(
from: string,
to: string
): SerializableRef<TValue> {
return this.#workletsModuleProxy.createSerializableImport(from, to);
}
createSerializableString(str: string) {
return this.#workletsModuleProxy.createSerializableString(str);
}
createSerializableNumber(num: number) {
return this.#workletsModuleProxy.createSerializableNumber(num);
}
createSerializableBoolean(bool: boolean) {
return bool ? this.#serializableTrue : this.#serializableFalse;
}
createSerializableBigInt(bigInt: bigint) {
return this.#workletsModuleProxy.createSerializableBigInt(bigInt);
}
createSerializableUndefined() {
return this.#serializableUndefined;
}
createSerializableNull() {
return this.#serializableNull;
}
createSerializableTurboModuleLike<
TProps extends object,
TProto extends object,
>(props: TProps, proto: TProto): SerializableRef<TProps> {
return this.#workletsModuleProxy.createSerializableTurboModuleLike(
props,
proto
);
}
createSerializableObject<T extends object>(
obj: T,
shouldRetainRemote: boolean,
nativeStateSource?: object
): SerializableRef<T> {
return this.#workletsModuleProxy.createSerializableObject(
obj,
shouldRetainRemote,
nativeStateSource
);
}
createSerializableHostObject<T extends object>(obj: T) {
return this.#workletsModuleProxy.createSerializableHostObject(obj);
}
createSerializableArray(array: unknown[], shouldRetainRemote: boolean) {
return this.#workletsModuleProxy.createSerializableArray(
array,
shouldRetainRemote
);
}
createSerializableMap<TKey, TValue>(
keys: TKey[],
values: TValue[]
): SerializableRef<Map<TKey, TValue>> {
return this.#workletsModuleProxy.createSerializableMap(keys, values);
}
createSerializableSet<TValues>(
values: TValues[]
): SerializableRef<Set<TValues>> {
return this.#workletsModuleProxy.createSerializableSet(values);
}
createSerializableInitializer(obj: object) {
return this.#workletsModuleProxy.createSerializableInitializer(obj);
}
createSerializableFunction<TArgs extends unknown[], TReturn>(
func: (...args: TArgs) => TReturn
) {
return this.#workletsModuleProxy.createSerializableFunction(func);
}
createSerializableWorklet(worklet: object, shouldPersistRemote: boolean) {
return this.#workletsModuleProxy.createSerializableWorklet(
worklet,
shouldPersistRemote
);
}
scheduleOnUI<TValue>(serializable: SerializableRef<TValue>) {
return this.#workletsModuleProxy.scheduleOnUI(serializable);
}
executeOnUIRuntimeSync<TValue, TReturn>(
serializable: SerializableRef<TValue>
): TReturn {
return this.#workletsModuleProxy.executeOnUIRuntimeSync(serializable);
}
createWorkletRuntime(
name: string,
initializer: SerializableRef<() => void>,
useDefaultQueue: boolean,
customQueue: object | undefined,
enableEventLoop: boolean
) {
return this.#workletsModuleProxy.createWorkletRuntime(
name,
initializer,
useDefaultQueue,
customQueue,
enableEventLoop
);
}
scheduleOnRuntime<T>(
workletRuntime: WorkletRuntime,
serializableWorklet: SerializableRef<T>
) {
return this.#workletsModuleProxy.scheduleOnRuntime(
workletRuntime,
serializableWorklet
);
}
createSynchronizable<TValue>(value: TValue): SynchronizableRef<TValue> {
return this.#workletsModuleProxy.createSynchronizable(value);
}
synchronizableGetDirty<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): TValue {
return this.#workletsModuleProxy.synchronizableGetDirty(synchronizableRef);
}
synchronizableGetBlocking<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): TValue {
return this.#workletsModuleProxy.synchronizableGetBlocking(
synchronizableRef
);
}
synchronizableSetBlocking<TValue>(
synchronizableRef: SynchronizableRef<TValue>,
value: SerializableRef<TValue>
) {
return this.#workletsModuleProxy.synchronizableSetBlocking(
synchronizableRef,
value
);
}
synchronizableLock<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): void {
return this.#workletsModuleProxy.synchronizableLock(synchronizableRef);
}
synchronizableUnlock<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): void {
return this.#workletsModuleProxy.synchronizableUnlock(synchronizableRef);
}
reportFatalErrorOnJS(
message: string,
stack: string,
name: string,
jsEngine: string
) {
return this.#workletsModuleProxy.reportFatalErrorOnJS(
message,
stack,
name,
jsEngine
);
}
getStaticFeatureFlag(name: string): boolean {
return this.#workletsModuleProxy.getStaticFeatureFlag(name);
}
setDynamicFeatureFlag(name: string, value: boolean) {
this.#workletsModuleProxy.setDynamicFeatureFlag(name, value);
}
}
@@ -1,7 +1,7 @@
'use strict';
import type { SerializableRef, SynchronizableRef } from '../memory/types';
import type { WorkletRuntime } from '../types';
import type { SynchronizableRef } from '../synchronizable';
import type { SerializableRef, WorkletRuntime } from '../workletTypes';
/** Type of `__workletsModuleProxy` injected with JSI. */
export interface WorkletsModuleProxy {
@@ -69,18 +69,6 @@ export interface WorkletsModuleProxy {
shouldPersistRemote: boolean
): SerializableRef<object>;
createCustomSerializable(
data: SerializableRef<unknown>,
typeId: number
): SerializableRef<unknown>;
registerCustomSerializable(
determine: SerializableRef<object>,
pack: SerializableRef<object>,
unpack: SerializableRef<object>,
typeId: number
): void;
scheduleOnUI<TValue>(serializable: SerializableRef<TValue>): void;
executeOnUIRuntimeSync<TValue, TReturn>(
@@ -1,33 +0,0 @@
'use strict';
import type { RNError } from './debug/errors';
/** Used only with debug builds. */
export function callGuardDEV<Args extends unknown[], ReturnValue>(
fn: (...args: Args) => ReturnValue,
...args: Args
): ReturnValue | void {
'worklet';
try {
return fn(...args);
} catch (error) {
if (globalThis.__workletsModuleProxy) {
const { message, stack, name, jsEngine } = error as RNError;
globalThis.__workletsModuleProxy.reportFatalErrorOnJS(
message,
stack ?? '',
name ?? 'WorkletsError',
jsEngine ?? 'Worklets'
);
} else {
throw error;
}
}
}
export function setupCallGuard() {
'worklet';
if (!globalThis.__callGuardDEV) {
globalThis.__callGuardDEV = callGuardDEV;
}
}
@@ -1,32 +0,0 @@
'use strict';
import { RuntimeKind } from '../runtimeKind';
import type {
IWorkletsErrorConstructor,
WorkletsError as IWorkletsError,
} from './types';
function WorkletsErrorConstructor(message?: string): IWorkletsError {
'worklet';
const prefix = '[Worklets]';
// eslint-disable-next-line reanimated/use-worklets-error
const errorInstance = new Error(message ? `${prefix} ${message}` : prefix);
errorInstance.name = `WorkletsError`;
return errorInstance as IWorkletsError;
}
/**
* Registers WorkletsError in the global scope. Register only for Worklet
* runtimes.
*/
export function registerWorkletsError() {
'worklet';
if ((globalThis.__RUNTIME_KIND as RuntimeKind) !== RuntimeKind.ReactNative) {
(globalThis as Record<string, unknown>).WorkletsError =
WorkletsErrorConstructor;
}
}
export const WorkletsError =
WorkletsErrorConstructor as IWorkletsErrorConstructor;
@@ -1,18 +0,0 @@
'use strict';
import type {
IWorkletsErrorConstructor,
WorkletsError as IWorkletsError,
} from './types';
function WorkletsErrorConstructor(message?: string): IWorkletsError {
const prefix = '[Worklets]';
// eslint-disable-next-line reanimated/use-worklets-error
const errorInstance = new Error(message ? `${prefix} ${message}` : prefix);
errorInstance.name = `WorkletsError`;
return errorInstance as IWorkletsError;
}
export const WorkletsError =
WorkletsErrorConstructor as IWorkletsErrorConstructor;
@@ -1,35 +0,0 @@
'use strict';
import { jsVersion } from './jsVersion';
import { logger } from './logger';
import { WorkletsError } from './WorkletsError';
export function checkCppVersion() {
const cppVersion = global._WORKLETS_VERSION_CPP;
if (cppVersion === undefined) {
logger.warn(
`Couldn't determine the version of the native part of Worklets.
See \`https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#couldnt-determine-the-version-of-the-native-part-of-worklets\` for more details.`
);
return;
}
const ok = matchVersion(jsVersion, cppVersion);
if (!ok) {
throw new WorkletsError(
`Mismatch between JavaScript part and native part of Worklets (${jsVersion} vs ${cppVersion}).
See \`https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#mismatch-between-javascript-part-and-native-part-of-worklets\` for more details.`
);
}
}
export function matchVersion(version1: string, version2: string) {
if (version1.match(/^\d+\.\d+\.\d+$/) && version2.match(/^\d+\.\d+\.\d+$/)) {
// x.y.z, compare only major and minor, skip patch
const [major1, minor1] = version1.split('.');
const [major2, minor2] = version2.split('.');
return major1 === major2 && minor1 === minor2;
} else {
// alpha, beta or rc, compare everything
return version1 === version2;
}
}
@@ -1,80 +0,0 @@
'use strict';
import type { WorkletStackDetails } from '../types';
import { WorkletsError } from './WorkletsError';
const _workletStackDetails = new Map<number, WorkletStackDetails>();
export function registerWorkletStackDetails(
hash: number,
stackDetails: WorkletStackDetails
) {
_workletStackDetails.set(hash, stackDetails);
}
function getBundleOffset(error: Error): [string, number, number] {
const frame = error.stack?.split('\n')?.[0];
if (frame) {
const parsedFrame = /@([^@]+):(\d+):(\d+)/.exec(frame);
if (parsedFrame) {
const [, file, line, col] = parsedFrame;
return [file, Number(line), Number(col)];
}
}
return ['unknown', 0, 0];
}
function processStack(stack?: string): string | undefined {
if (stack === '' || stack === undefined) {
return undefined;
}
const workletStackEntries = stack.match(/worklet_(\d+):(\d+):(\d+)/g);
let result = stack;
workletStackEntries?.forEach((match) => {
const [, hash, origLine, origCol] = match.split(/:|_/).map(Number);
const errorDetails = _workletStackDetails.get(hash);
if (!errorDetails) {
return;
}
const [error, lineOffset, colOffset] = errorDetails;
const [bundleFile, bundleLine, bundleCol] = getBundleOffset(error);
const line = origLine + bundleLine + lineOffset;
const col = origCol + bundleCol + colOffset;
result = result.replace(match, `${bundleFile}:${line}:${col}`);
});
return result;
}
export interface RNError extends Error {
jsEngine: string;
}
/**
* Remote error is an error coming from a Worklet Runtime that we bubble up to
* the RN Runtime.
*/
export function reportFatalRemoteError(
{ message, stack, name, jsEngine }: RNError,
force: boolean
): void {
const error = new WorkletsError() as RNError;
error.message = message;
error.stack = processStack(stack);
error.name = name;
error.jsEngine = jsEngine;
if (force) {
throw error;
} else {
// @ts-expect-error React Native's `ErrorUtils` are hidden from the global scope.
globalThis.ErrorUtils.reportFatalError(error);
}
}
/**
* Registers `reportFatalRemoteError` function in global scope to allow to
* invoke it from C++.
*/
export function registerReportFatalRemoteError() {
globalThis.__reportFatalRemoteError = reportFatalRemoteError;
}
@@ -1,8 +0,0 @@
'use strict';
/**
* We hardcode the version of Worklets here in order to compare it with the
* version used to build the native part of the library in runtime. Remember to
* keep this in sync with the version declared in `package.json`
*/
export const jsVersion = '0.7.2';
-16
View File
@@ -1,16 +0,0 @@
'use strict';
const PREFIX = '[Worklets]';
function formatMessage(message: string) {
return `${PREFIX} ${message}`;
}
export const logger = {
warn(message: string) {
console.warn(formatMessage(message));
},
error(message: string) {
console.error(formatMessage(message));
},
};
-9
View File
@@ -1,9 +0,0 @@
'use strict';
export type WorkletsError = Error & { name: 'Worklets' }; // signed type
export interface IWorkletsErrorConstructor extends Error {
new (message?: string): WorkletsError;
(message?: string): WorkletsError;
readonly prototype: WorkletsError;
}
+3 -3
View File
@@ -5,9 +5,9 @@ import {
isSerializableRef,
makeShareable,
makeShareableCloneOnUIRecursive,
} from './memory/serializable';
import { serializableMappingCache } from './memory/serializableMappingCache';
import type { SerializableRef } from './memory/types';
} from './serializable';
import { serializableMappingCache } from './serializableMappingCache';
import type { SerializableRef } from './workletTypes';
/** @deprecated Use {@link SerializableRef} instead. */
export type ShareableRef<T> = SerializableRef<T>;
@@ -1,70 +0,0 @@
'use strict';
import { logger } from '../debug/logger';
import { WorkletsModule } from '../WorkletsModule/NativeWorklets';
import type {
DynamicFlagName,
DynamicFlagsType,
StaticFeatureFlagsSchema,
} from './types';
export const DynamicFlags: DynamicFlagsType = {
EXAMPLE_DYNAMIC_FLAG: true,
init() {
Object.keys(DynamicFlags).forEach((key) => {
if (key !== 'init' && key !== 'setFlag' && key !== 'getFlag') {
WorkletsModule.setDynamicFeatureFlag(
key,
DynamicFlags[key as DynamicFlagName]
);
}
});
},
setFlag(name, value) {
if (name in DynamicFlags) {
DynamicFlags[name] = value;
WorkletsModule.setDynamicFeatureFlag(name, value);
} else {
logger.warn(
`The feature flag: '${name}' no longer exists, you can safely remove invocation of \`setDynamicFeatureFlag('${name}')\` from your code.`
);
}
},
getFlag(name) {
if (name in DynamicFlags) {
return DynamicFlags[name];
} else {
logger.warn(
`The feature flag: '${name}' no longer exists, you can safely remove invocation of \`getDynamicFeatureFlag('${name}')\` from your code.`
);
return false;
}
},
};
DynamicFlags.init();
// Public API function to update a feature flag
export function setDynamicFeatureFlag(
name: DynamicFlagName,
value: boolean
): void {
DynamicFlags.setFlag(name, value);
}
// Public API function to read a feature flag
export function getDynamicFeatureFlag(name: DynamicFlagName): boolean {
return DynamicFlags.getFlag(name);
}
const staticFeatureFlags: Partial<StaticFeatureFlagsSchema> = {};
export function getStaticFeatureFlag(
name: keyof StaticFeatureFlagsSchema
): boolean {
if (name in staticFeatureFlags) {
return staticFeatureFlags[name]!;
}
const featureFlagValue = WorkletsModule.getStaticFeatureFlag(name);
staticFeatureFlags[name] = featureFlagValue;
return featureFlagValue;
}
@@ -1,20 +0,0 @@
'use strict';
import type { DynamicFlagName, StaticFeatureFlagsSchema } from './types';
export function getStaticFeatureFlag(
_name: keyof StaticFeatureFlagsSchema
): boolean {
return false;
}
export function setDynamicFeatureFlag(
_name: DynamicFlagName,
_value: boolean
): void {
// no-op
}
export function getDynamicFeatureFlag(_name: DynamicFlagName): boolean {
return false;
}
@@ -1,29 +0,0 @@
'use strict';
import type StaticFeatureFlagsJSON from './staticFlags.json';
export type DynamicFlagsType = {
EXAMPLE_DYNAMIC_FLAG: boolean;
init(): void;
setFlag(name: DynamicFlagName, value: boolean): void;
getFlag(name: DynamicFlagName): boolean;
};
export type DynamicFlagName = keyof Omit<
Omit<DynamicFlagsType, 'setFlag' | 'getFlag'>,
'init'
>;
/**
* This constant is needed for typechecking and preserving static typechecks in
* generated .d.ts files. Without it, the static flags resolve to an object
* without specific keys.
*/
export const DefaultStaticFeatureFlags = {
RUNTIME_TEST_FLAG: false,
IOS_DYNAMIC_FRAMERATE_ENABLED: false,
} as const satisfies typeof StaticFeatureFlagsJSON;
export type StaticFeatureFlagsSchema = {
-readonly [K in keyof typeof DefaultStaticFeatureFlags]: boolean;
};
+24 -42
View File
@@ -1,51 +1,28 @@
'use strict';
import { init } from './initializers/initializers';
import { bundleModeInit } from './initializers/workletRuntimeEntry';
import './publicGlobals';
import { init } from './initializers';
import { bundleModeInit } from './workletRuntimeEntry';
init();
// @ts-expect-error We must trick the bundler to include
// the `workletRuntimeEntry` file the way it cannot optimize it out.
if (globalThis._ALWAYS_FALSE) {
// Bundle mode.
bundleModeInit();
}
export type { MakeShareableClone, ShareableRef } from './deprecated';
export {
isShareableRef,
makeShareable,
type MakeShareableClone,
makeShareableCloneOnUIRecursive,
makeShareableCloneRecursive,
shareableMappingCache,
type ShareableRef,
} from './deprecated';
export {
getDynamicFeatureFlag,
getStaticFeatureFlag,
setDynamicFeatureFlag,
} from './featureFlags/featureFlags';
export { isSynchronizable } from './memory/isSynchronizable';
export {
createSerializable,
isSerializableRef,
registerCustomSerializable,
} from './memory/serializable';
export { serializableMappingCache } from './memory/serializableMappingCache';
export { createSynchronizable } from './memory/synchronizable';
export type {
RegistrationData,
SerializableRef,
Synchronizable,
SynchronizableRef,
} from './memory/types';
export { getStaticFeatureFlag, setDynamicFeatureFlag } from './featureFlags';
export { isSynchronizable } from './isSynchronizable';
export { getRuntimeKind, RuntimeKind } from './runtimeKind';
export {
createWorkletRuntime,
runOnRuntime,
scheduleOnRuntime,
} from './runtimes';
export { createWorkletRuntime, runOnRuntime } from './runtimes';
export { createSerializable, isSerializableRef } from './serializable';
export { serializableMappingCache } from './serializableMappingCache';
export type { Synchronizable } from './synchronizable';
export { createSynchronizable } from './synchronizable';
export {
callMicrotasks,
executeOnUIRuntimeSync,
@@ -58,14 +35,19 @@ export {
// eslint-disable-next-line camelcase
unstable_eventLoopTask,
} from './threads';
export { isWorkletFunction } from './workletFunction';
export type { IWorkletsModule, WorkletsModuleProxy } from './WorkletsModule';
export { WorkletsModule } from './WorkletsModule';
export type {
SerializableRef,
WorkletFunction,
WorkletRuntime,
WorkletStackDetails,
} from './types';
export { isWorkletFunction } from './workletFunction';
export { WorkletsModule } from './WorkletsModule/NativeWorklets';
export type {
IWorkletsModule,
WorkletsModuleProxy,
} from './WorkletsModule/workletsModuleProxy';
} from './workletTypes';
// @ts-expect-error We must trick the bundler to include
// the `workletRuntimeEntry` file the way it cannot optimize it out.
if (globalThis._ALWAYS_FALSE) {
// Bundle mode.
bundleModeInit();
}
@@ -1,238 +0,0 @@
'use strict';
import { setupCallGuard } from '../callGuard';
import { registerReportFatalRemoteError } from '../debug/errors';
import { registerWorkletsError, WorkletsError } from '../debug/WorkletsError';
import { bundleValueUnpacker } from '../memory/bundleUnpacker';
import { __installUnpacker as installCustomSerializableUnpacker } from '../memory/customSerializableUnpacker';
import { __installUnpacker as installSynchronizableUnpacker } from '../memory/synchronizableUnpacker';
import { setupSetImmediate } from '../runLoop/common/setImmediatePolyfill';
import { setupSetInterval } from '../runLoop/common/setIntervalPolyfill';
import { setupRequestAnimationFrame } from '../runLoop/uiRuntime/requestAnimationFrame';
import { setupSetTimeout } from '../runLoop/uiRuntime/setTimeoutPolyfill';
import { RuntimeKind } from '../runtimeKind';
import { runOnUISync, scheduleOnRN, setupMicrotasks } from '../threads';
import type { ValueUnpacker } from '../types';
import { isWorkletFunction } from '../workletFunction';
import { WorkletsModule } from '../WorkletsModule/NativeWorklets';
if (globalThis.__RUNTIME_KIND === undefined) {
// The only runtime that doesn't have `__RUNTIME_KIND` preconfigured
// is the RN Runtime. We must set it as soon as possible.
globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
}
let capturableConsole: typeof console;
/**
* Currently there seems to be a bug in the JSI layer which causes a crash when
* we try to copy some of the console methods, i.e. `clear` or `dirxml`.
*
* The crash happens only in React Native 0.75. It's not reproducible in neither
* 0.76 nor 0.74. It also happens only in the configuration of a debug app and
* production bundle.
*
* I haven't yet discovered what exactly causes the crash. It's tied to the
* console methods sometimes being `HostFunction`s. Therefore, as a workaround
* we don't copy the methods as they are in the original console object, we copy
* JavaScript wrappers instead.
*/
export function getMemorySafeCapturableConsole(): typeof console {
if (capturableConsole) {
return capturableConsole;
}
const consoleCopy = Object.fromEntries(
Object.entries(console).map(([methodName, method]) => {
const methodWrapper = function methodWrapper(...args: unknown[]) {
return method(...args);
};
if (method.name) {
/**
* Set the original method name as the wrapper name if available.
*
* It might be unnecessary but if we want to fully mimic the console
* object we should take into the account the fact some code might rely
* on the method name.
*/
Object.defineProperty(methodWrapper, 'name', {
value: method.name,
writable: false,
});
}
return [methodName, methodWrapper];
})
);
capturableConsole = consoleCopy as unknown as typeof console;
return consoleCopy as unknown as typeof console;
}
export function setupConsole(boundCapturableConsole: typeof console) {
'worklet';
// @ts-ignore TypeScript doesn't like that there are missing methods in console object, but we don't provide all the methods for the UI runtime console version
globalThis.console = {
assert: (...args) => scheduleOnRN(boundCapturableConsole.assert, ...args),
debug: (...args) => scheduleOnRN(boundCapturableConsole.debug, ...args),
log: (...args) => scheduleOnRN(boundCapturableConsole.log, ...args),
warn: (...args) => scheduleOnRN(boundCapturableConsole.warn, ...args),
error: (...args) => scheduleOnRN(boundCapturableConsole.error, ...args),
info: (...args) => scheduleOnRN(boundCapturableConsole.info, ...args),
};
}
let initialized = false;
export function init() {
if (initialized) {
return;
}
initialized = true;
initializeRuntime();
if (globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) {
initializeWorkletRuntime();
} else {
initializeRNRuntime();
installRNBindingsOnUIRuntime();
}
}
/** A function that should be run on any kind of runtime. */
function initializeRuntime() {
if (globalThis._WORKLETS_BUNDLE_MODE) {
globalThis.__valueUnpacker = bundleValueUnpacker as ValueUnpacker;
}
installSynchronizableUnpacker();
installCustomSerializableUnpacker();
}
/** A function that should be run only on React Native runtime. */
function initializeRNRuntime() {
if (__DEV__) {
const testWorklet = () => {
'worklet';
};
if (!isWorkletFunction(testWorklet)) {
throw new WorkletsError(
`Failed to create a worklet. See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#failed-to-create-a-worklet for more details.`
);
}
}
registerReportFatalRemoteError();
}
/** A function that should be run only on Worklet runtimes. */
function initializeWorkletRuntime() {
if (globalThis._WORKLETS_BUNDLE_MODE) {
setupCallGuard();
if (__DEV__) {
/*
* Temporary workaround for Metro bundler. We must implement a dummy
* Refresh module to prevent Metro from throwing irrelevant errors.
*/
const Refresh = new Proxy(
{},
{
get() {
return () => {};
},
}
);
globalThis.__r.Refresh = Refresh;
/* Gracefully handle unwanted imports from React Native. */
const modules = require.getModules();
const ReactNativeModuleId = require.resolveWeak('react-native');
const factory = function (
_global: unknown,
_require: unknown,
_importDefault: unknown,
_importAll: unknown,
module: Record<string, unknown>,
_exports: unknown,
_dependencyMap: unknown
) {
module.exports = new Proxy(
{},
{
get: function get(_target, prop) {
globalThis.console.warn(
`You tried to import '${String(prop)}' from 'react-native' module on a Worklet Runtime. Using 'react-native' module on a Worklet Runtime is not allowed.`
);
return {
get() {
return undefined;
},
};
},
}
);
};
const mod = {
dependencyMap: [],
factory,
hasError: false,
importedAll: {},
importedDefault: {},
isInitialized: false,
publicModule: {
exports: {},
},
};
modules.set(ReactNativeModuleId, mod);
}
}
}
/**
* A function that should be run on the RN Runtime to configure the UI Runtime
* with callback bindings.
*/
function installRNBindingsOnUIRuntime() {
if (!WorkletsModule) {
throw new WorkletsError(
'Worklets are trying to initialize the UI runtime without a valid WorkletsModule'
);
}
const runtimeBoundCapturableConsole = getMemorySafeCapturableConsole();
if (!globalThis._WORKLETS_BUNDLE_MODE) {
/** In bundle mode Runtimes setup their callGuard themselves. */
runOnUISync(setupCallGuard);
/**
* Register WorkletsError in the UI runtime global scope. (we are using
* `executeOnUIRuntimeSync` here to make sure that the changes are applied
* before any async operations are executed on the UI runtime).
*
* There's no need to register the error in bundle mode.
*/
runOnUISync(registerWorkletsError);
}
runOnUISync(() => {
'worklet';
setupConsole(runtimeBoundCapturableConsole);
/**
* TODO: Move `setupMicrotasks` and `setupRequestAnimationFrame` to a
* separate function once we have a better way to distinguish between
* Worklet Runtimes.
*/
setupMicrotasks();
setupRequestAnimationFrame();
setupSetTimeout();
setupSetImmediate();
setupSetInterval();
});
}
@@ -1,21 +0,0 @@
'use strict';
import { IS_JEST } from '../platformChecker';
import { mockedRequestAnimationFrame } from '../runLoop/uiRuntime/mockedRequestAnimationFrame';
import { RuntimeKind } from '../runtimeKind';
export function init() {
globalThis._WORKLET = false;
globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
globalThis._log = console.log;
globalThis._getAnimationTimestamp = () => performance.now();
if (IS_JEST) {
// requestAnimationFrame react-native jest's setup is incorrect as it polyfills
// the method directly using setTimeout, therefore the callback doesn't get the
// expected timestamp as the only argument: https://github.com/facebook/react-native/blob/main/packages/react-native/jest/setup.js#L28
// We override this setup here to make sure that callbacks get the proper timestamps
// when executed. For non-jest environments we define requestAnimationFrame in setupRequestAnimationFrame
// @ts-ignore TypeScript uses Node definition for rAF, setTimeout, etc which returns a Timeout object rather than a number
globalThis.requestAnimationFrame = mockedRequestAnimationFrame;
}
}
@@ -1,39 +0,0 @@
'use strict';
import { WorkletsError } from '../debug/WorkletsError';
import { RuntimeKind } from '../runtimeKind';
import { init } from './initializers';
/**
* This function is an entry point for Worklet Runtimes. We can use it to setup
* necessary tools, like the ValueUnpacker.
*
* We must throw an error at the end of this function to prevent the bundle to
* continue executing. This is because the next module to be ran would be the
* React Native one, and it would break the Worklet Runtime if initialized. The
* error is caught in C++ code.
*
* This function has no effect on the RN Runtime beside setting the
* `_WORKLETS_BUNDLE_MODE` flag.
*/
export function bundleModeInit() {
// Worklets Babel Plugin replaces `false` with `true` here
// when Bundle Mode is enabled.
globalThis._WORKLETS_BUNDLE_MODE = false;
if (!globalThis._WORKLETS_BUNDLE_MODE) {
return;
}
const runtimeKind = globalThis.__RUNTIME_KIND;
if (runtimeKind && runtimeKind !== RuntimeKind.ReactNative) {
/**
* We shouldn't call `init()` on RN Runtime here, as it would initialize our
* module before React Native has configured the RN Runtime.
*/
init();
throw new WorkletsError('Worklets initialized successfully');
}
}
bundleModeInit();
@@ -1,5 +0,0 @@
'use strict';
export function bundleModeInit() {
// no-op
}
@@ -1,76 +0,0 @@
'use strict';
import { logger } from '../debug/logger';
import { WorkletsError } from '../debug/WorkletsError';
import type { WorkletFactory, WorkletFunction } from '../types';
const handleCache = new WeakMap<WorkletFunction, unknown>();
export function bundleValueUnpacker(
objectToUnpack: ObjectToUnpack,
category?: string,
remoteFunctionName?: string
): unknown {
const workletHash = objectToUnpack.__workletHash;
if (workletHash !== undefined) {
return getWorklet(workletHash, objectToUnpack.__closure);
} else if (objectToUnpack.__init !== undefined) {
let value = handleCache.get(objectToUnpack);
if (value === undefined) {
value = objectToUnpack.__init();
handleCache.set(objectToUnpack, value);
}
return value;
} else if (category === 'RemoteFunction') {
const remoteFunctionHolder = () => {
const label = remoteFunctionName
? `function \`${remoteFunctionName}\``
: 'anonymous function';
throw new WorkletsError(`Tried to synchronously call a non-worklet ${label} on the UI thread.
See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#tried-to-synchronously-call-a-non-worklet-function-on-the-ui-thread for more details.`);
};
remoteFunctionHolder.__remoteFunction = objectToUnpack;
return remoteFunctionHolder;
} else {
throw new WorkletsError(
`Data type in category "${category}" not recognized by value unpacker: "${globalThis._toString(
objectToUnpack
)}".`
);
}
}
function getWorklet(
workletHash: number,
closureVariables: Record<string, unknown>
): WorkletFunction | undefined {
let worklet;
if (__DEV__) {
try {
worklet = getWorkletFromMetroRequire(workletHash, closureVariables);
} catch (_e) {
logger.error(
'Unable to resolve worklet with hash ' +
workletHash +
'. Try reloading the app.'
);
}
} else {
worklet = getWorkletFromMetroRequire(workletHash, closureVariables);
}
return worklet;
}
const metroRequire = globalThis.__r;
function getWorkletFromMetroRequire(
workletHash: number,
closureVariables: Record<string, unknown>
): WorkletFunction {
const factory = metroRequire(workletHash).default as WorkletFactory;
return factory(closureVariables);
}
interface ObjectToUnpack extends WorkletFunction {
_recur: unknown;
}
@@ -1,29 +0,0 @@
/* eslint-disable reanimated/use-worklets-error */
'use strict';
export function __installUnpacker() {
if (!globalThis.__customSerializationRegistry) {
globalThis.__customSerializationRegistry =
[] as typeof globalThis.__customSerializationRegistry;
}
const registry = globalThis.__customSerializationRegistry;
function customSerializableUnpacker<TValue>(value: TValue, typeId: number) {
const data = registry[typeId];
if (!data) {
throw new Error(
`[Worklets] No custom serializable registered for type ID ${typeId}.`
);
}
return data.unpack(value as object);
}
globalThis.__customSerializableUnpacker =
customSerializableUnpacker as CustomSerializableUnpacker;
}
export type CustomSerializableUnpacker = (
object: unknown,
typeId: number
) => unknown;
@@ -1,15 +0,0 @@
'use strict';
import type { Synchronizable } from './types';
export function isSynchronizable<TValue>(
value: unknown
): value is Synchronizable<TValue> {
'worklet';
return (
typeof value === 'object' &&
value !== null &&
'__synchronizableRef' in value &&
value.__synchronizableRef === true
);
}
@@ -1,10 +0,0 @@
'use strict';
import { WorkletsError } from '../debug/WorkletsError';
import type { Synchronizable } from './types';
export function isSynchronizable<TValue>(
_value: unknown
): _value is Synchronizable<TValue> {
throw new WorkletsError('`isSynchronizable` is not supported on web.');
}
@@ -1,897 +0,0 @@
'use strict';
import { registerWorkletStackDetails } from '../debug/errors';
import { jsVersion } from '../debug/jsVersion';
import { logger } from '../debug/logger';
import { WorkletsError } from '../debug/WorkletsError';
import { getRuntimeKind, RuntimeKind } from '../runtimeKind';
import type { WorkletFunction, WorkletImport } from '../types';
import { isWorkletFunction } from '../workletFunction';
import { WorkletsModule } from '../WorkletsModule/NativeWorklets';
import { isSynchronizable } from './isSynchronizable';
import {
serializableMappingCache,
serializableMappingFlag,
} from './serializableMappingCache';
import type {
FlatSerializableRef,
RegistrationData,
SerializableRef,
SerializationData,
Synchronizable,
} from './types';
const MAGIC_KEY = 'REANIMATED_MAGIC_KEY';
function isHostObject(value: NonNullable<object>) {
'worklet';
// We could use JSI to determine whether an object is a host object, however
// the below workaround works well and is way faster than an additional JSI call.
// We use the fact that host objects have broken implementation of `hasOwnProperty`
// and hence return true for all `in` checks regardless of the key we ask for.
return MAGIC_KEY in value;
}
export function isSerializableRef<TValue = unknown>(
value: unknown
): value is SerializableRef<TValue> {
'worklet';
return (
typeof value === 'object' &&
value !== null &&
'__serializableRef' in value &&
value.__serializableRef === true
);
}
function isPlainJSObject(object: object): object is Record<string, unknown> {
'worklet';
return Object.getPrototypeOf(object) === Object.prototype;
}
function isTurboModuleLike(object: object): object is Record<string, unknown> {
return isHostObject(Object.getPrototypeOf(object));
}
function getFromCache(value: object) {
const cached = serializableMappingCache.get(value);
if (cached === serializableMappingFlag) {
// This means that `value` was already a clone and we should return it as is.
return value;
}
return cached;
}
// The below object is used as a replacement for objects that cannot be transferred
// as serializable values. In createSerializable we detect if an object is of
// a plain Object.prototype and only allow such objects to be transferred. This lets
// us avoid all sorts of react internals from leaking into the UI runtime. To make it
// possible to catch errors when someone actually tries to access such object on the UI
// runtime, we use the below Proxy object which is instantiated on the UI runtime and
// throws whenever someone tries to access its fields.
const INACCESSIBLE_OBJECT = {
__init: () => {
'worklet';
return new Proxy(
{},
{
get: (_: unknown, prop: string | symbol) => {
if (
prop === '_isReanimatedSharedValue' ||
prop === '__remoteFunction' ||
prop === '__synchronizableRef'
) {
// not very happy about this check here, but we need to allow for
// "inaccessible" objects to be tested with isSerializableRef check
// as it is being used in the mappers when extracting inputs recursively
// as well as with isRemoteFunction when cloning objects recursively.
// Apparently we can't check if a key exists there as HostObjects always
// return true for such tests, so the only possibility for us is to
// actually access that key and see if it is set to true. We therefore
// need to allow for this key to be accessed here.
return false;
}
throw new WorkletsError(
`Trying to access property \`${String(
prop
)}\` of an object which cannot be sent to the UI runtime.`
);
},
set: () => {
throw new WorkletsError(
'Trying to write to an object which cannot be sent to the UI runtime.'
);
},
}
);
},
};
const VALID_ARRAY_VIEWS_NAMES = [
'Int8Array',
'Uint8Array',
'Uint8ClampedArray',
'Int16Array',
'Uint16Array',
'Int32Array',
'Uint32Array',
'Float32Array',
'Float64Array',
'BigInt64Array',
'BigUint64Array',
'DataView',
];
const DETECT_CYCLIC_OBJECT_DEPTH_THRESHOLD = 30;
// Below variable stores object that we process in createSerializable at the specified depth.
// We use it to check if later on the function reenters with the same object
let processedObjectAtThresholdDepth: unknown;
export function createSerializable<TValue>(
value: TValue,
shouldPersistRemote = false,
depth = 0
): SerializableRef<TValue> {
detectCyclicObject(value, depth);
const isObject = typeof value === 'object';
const isFunction = typeof value === 'function';
if (typeof value === 'string') {
return cloneString(value) as SerializableRef<TValue>;
}
if (typeof value === 'number') {
return cloneNumber(value) as SerializableRef<TValue>;
}
if (typeof value === 'boolean') {
return cloneBoolean(value) as SerializableRef<TValue>;
}
if (typeof value === 'bigint') {
return cloneBigInt(value) as SerializableRef<TValue>;
}
if (value === undefined) {
return cloneUndefined() as SerializableRef<TValue>;
}
if (value === null) {
return cloneNull() as SerializableRef<TValue>;
}
if ((!isObject && !isFunction) || value === null) {
return clonePrimitive(value, shouldPersistRemote);
}
const cached = getFromCache(value);
if (cached !== undefined) {
return cached as SerializableRef<TValue>;
}
if (Array.isArray(value)) {
return cloneArray(value, shouldPersistRemote, depth);
}
if (
globalThis._WORKLETS_BUNDLE_MODE &&
isFunction &&
(value as WorkletImport).__bundleData
) {
return cloneImport(value as WorkletImport) as SerializableRef<TValue>;
}
if (isFunction && !isWorkletFunction(value)) {
return cloneRemoteFunction(value);
}
// RN has introduced a new representation of TurboModules as a JS object whose prototype is the host object
// More details: https://github.com/facebook/react-native/blob/main/packages/react-native/ReactCommon/react/nativemodule/core/ReactCommon/TurboModuleBinding.cpp#L182
if (isTurboModuleLike(value)) {
return cloneTurboModuleLike(value, shouldPersistRemote, depth);
}
if (isHostObject(value)) {
return cloneHostObject(value);
}
if (isPlainJSObject(value) && value.__init) {
return cloneInitializer(
value,
shouldPersistRemote,
depth
) as SerializableRef<TValue>;
}
if (isPlainJSObject(value) && value.__workletContextObjectFactory) {
return cloneContextObject(value);
}
if ((isPlainJSObject(value) || isFunction) && isWorkletFunction(value)) {
return cloneWorklet(value, shouldPersistRemote, depth);
}
if (isSynchronizable(value)) {
return cloneSynchronizable(value) as SerializableRef<TValue>;
}
if (isPlainJSObject(value) || isFunction) {
return clonePlainJSObject(value, shouldPersistRemote, depth);
}
if (value instanceof Set) {
return cloneSet(value);
}
if (value instanceof Map) {
return cloneMap(value);
}
if (value instanceof RegExp) {
return cloneRegExp(value);
}
if (value instanceof Error) {
return cloneError(value);
}
if (value instanceof ArrayBuffer) {
return cloneArrayBuffer(value, shouldPersistRemote);
}
if (ArrayBuffer.isView(value)) {
// typed array (e.g. Int32Array, Uint8ClampedArray) or DataView
return cloneArrayBufferView(value);
}
for (let i = 0; i < customSerializationRegistry.length; i++) {
const { determine, pack } = customSerializationRegistry[i];
if (determine(value)) {
return cloneCustom(value, pack, i) as SerializableRef<TValue>;
}
}
return inaccessibleObject(value);
}
if (globalThis._WORKLETS_BUNDLE_MODE) {
// TODO: Do it programmatically.
createSerializable.__bundleData = {
imported: 'createSerializable',
source: require.resolveWeak('react-native-worklets'),
};
}
if (!globalThis.__customSerializationRegistry) {
globalThis.__customSerializationRegistry =
[] as typeof globalThis.__customSerializationRegistry;
}
const customSerializationRegistry = globalThis.__customSerializationRegistry;
/**
* `registerCustomSerializable` lets you register your own pre-serialization and
* post-deserialization logic. This is necessary for objects with prototypes
* different than just `Object.prototype` or some other built-in prototypes like
* `Map` etc. Worklets can't handle such objects by default to convert into
* [Serializables](https://docs.swmansion.com/react-native-worklets/docs/memory/serializable)
* hence you need to register them as **Custom Serializables**. This way you can
* tell Worklets how to transfer your custom data structures between different
* Runtimes without manually serializing and deserializing them every time.
*
* @param registrationData - The registration data for the custom serializable -
* {@link RegistrationData}
* @see https://docs.swmansion.com/react-native-worklets/docs/memory/registerCustomSerializable/
*/
export function registerCustomSerializable<
TValue extends object,
TPacked extends object,
>(registrationData: RegistrationData<TValue, TPacked>) {
if (__DEV__ && getRuntimeKind() !== RuntimeKind.ReactNative) {
throw new WorkletsError(
'registerCustomSerializable can be used only on React Native runtime.'
);
}
const { name, determine, pack, unpack } = registrationData;
if (__DEV__) {
verifyRegistrationData(determine, pack, unpack);
}
if (customSerializationRegistry.some((data) => data.name === name)) {
if (__DEV__) {
console.warn(
`Custom serializable with name "${name}" is already registered. Duplicate registration is ignored.`
);
}
return;
}
customSerializationRegistry.push(
registrationData as unknown as SerializationData<object, unknown>
);
WorkletsModule.registerCustomSerializable(
createSerializable(determine),
createSerializable(pack),
createSerializable(unpack),
customSerializationRegistry.length - 1
);
}
function verifyRegistrationData(
determine: unknown,
pack: unknown,
unpack: unknown
) {
if (!isWorkletFunction(determine)) {
throw new WorkletsError(
'The "determine" function provided to registerCustomSerializable must be a worklet.'
);
}
if (!isWorkletFunction(pack)) {
throw new WorkletsError(
'The "pack" function provided to registerCustomSerializable must be a worklet.'
);
}
if (!isWorkletFunction(unpack)) {
throw new WorkletsError(
'The "unpack" function provided to registerCustomSerializable must be a worklet.'
);
}
}
function detectCyclicObject(value: unknown, depth: number) {
if (depth >= DETECT_CYCLIC_OBJECT_DEPTH_THRESHOLD) {
// if we reach certain recursion depth we suspect that we are dealing with a cyclic object.
// this type of objects are not supported and cannot be transferred as serializable, so we
// implement a simple detection mechanism that remembers the value at a given depth and
// tests whether we try reenter this method later on with the same value. If that happens
// we throw an appropriate error.
if (depth === DETECT_CYCLIC_OBJECT_DEPTH_THRESHOLD) {
processedObjectAtThresholdDepth = value;
} else if (value === processedObjectAtThresholdDepth) {
throw new WorkletsError(
'Trying to convert a cyclic object to a serializable. This is not supported.'
);
}
} else {
processedObjectAtThresholdDepth = undefined;
}
}
function clonePrimitive<T>(
value: T,
shouldPersistRemote: boolean
): SerializableRef<T> {
return WorkletsModule.createSerializable(value, shouldPersistRemote);
}
function cloneString(value: string): SerializableRef<string> {
return WorkletsModule.createSerializableString(value);
}
function cloneNumber(value: number): SerializableRef<number> {
return WorkletsModule.createSerializableNumber(value);
}
function cloneBoolean(value: boolean): SerializableRef<boolean> {
return WorkletsModule.createSerializableBoolean(value);
}
function cloneBigInt(value: bigint): SerializableRef<bigint> {
return WorkletsModule.createSerializableBigInt(value);
}
function cloneUndefined(): SerializableRef<undefined> {
return WorkletsModule.createSerializableUndefined();
}
function cloneNull(): SerializableRef<null> {
return WorkletsModule.createSerializableNull();
}
function cloneObjectProperties<T extends object>(
value: T,
shouldPersistRemote: boolean,
depth: number
): Record<string, unknown> {
const clonedProps: Record<string, unknown> = {};
for (const [key, element] of Object.entries(value)) {
// We don't need to clone __initData field as it contains long strings
// representing the worklet code, source map, and location, and we will
// serialize/deserialize it once.
if (key === '__initData' && clonedProps.__initData !== undefined) {
continue;
}
clonedProps[key] = createSerializable(
element,
shouldPersistRemote,
depth + 1
);
}
return clonedProps;
}
function cloneInitializer(
value: object,
shouldPersistRemote = false,
depth = 0
): SerializableRef<object> {
const clonedProps: Record<string, unknown> = cloneObjectProperties(
value,
shouldPersistRemote,
depth
);
return WorkletsModule.createSerializableInitializer(clonedProps);
}
function cloneArray<T extends unknown[]>(
value: T,
shouldPersistRemote: boolean,
depth: number
): SerializableRef<T> {
const clonedElements = value.map((element) =>
createSerializable(element, shouldPersistRemote, depth + 1)
);
const clone = WorkletsModule.createSerializableArray(
clonedElements,
shouldPersistRemote
) as SerializableRef<T>;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
freezeObjectInDev(value);
return clone;
}
function cloneRemoteFunction<TArgs extends unknown[], TReturn>(
value: (...args: TArgs) => TReturn
): SerializableRef<TReturn> {
const clone = WorkletsModule.createSerializableFunction(value);
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
freezeObjectInDev(value);
return clone;
}
function cloneHostObject<T extends object>(value: T): SerializableRef<T> {
// for host objects we pass the reference to the object as serializable and
// then recreate new host object wrapping the same instance on the UI thread.
// there is no point of iterating over keys as we do for regular objects.
const clone = WorkletsModule.createSerializableHostObject(value);
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
return clone;
}
function cloneWorklet<TValue extends WorkletFunction>(
value: TValue,
shouldPersistRemote: boolean,
depth: number
): SerializableRef<TValue> {
if (__DEV__) {
const babelVersion = (value as WorkletFunction).__pluginVersion;
if (babelVersion !== undefined && babelVersion !== jsVersion) {
throw new WorkletsError(
`Mismatch between JavaScript code version and Worklets Babel plugin version (${jsVersion} vs. ${babelVersion}).
See \`https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#mismatch-between-javascript-code-version-and-worklets-babel-plugin-version\` for more details.
Offending code was: \`${getWorkletCode(value)}\``
);
}
registerWorkletStackDetails(
value.__workletHash,
(value as WorkletFunction).__stackDetails!
);
}
if ((value as WorkletFunction).__stackDetails) {
// `Error` type of value cannot be copied to the UI thread, so we
// remove it after we handled it in dev mode or delete it to ignore it in production mode.
// Not removing this would cause an infinite loop in production mode and it just
// seems more elegant to handle it this way.
delete (value as WorkletFunction).__stackDetails;
}
const clonedProps: Record<string, unknown> = cloneObjectProperties(
value,
true,
depth
);
// to save on transferring static __initData field of worklet structure
// we request serializable value to persist its UI counterpart. This means
// that the __initData field that contains long strings representing the
// worklet code, source map, and location, will always be
// serialized/deserialized once.
clonedProps.__initData = createSerializable(
value.__initData,
true,
depth + 1
);
const clone = WorkletsModule.createSerializableWorklet(
clonedProps,
// TODO: Check after refactor if we can remove shouldPersistRemote parameter (imho it's redundant here since worklets are always persistent)
// retain all worklets
true
) as SerializableRef<TValue>;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
freezeObjectInDev(value);
return clone;
}
/**
* TurboModuleLike objects are JS objects that have a TurboModule as their
* prototype.
*/
function cloneTurboModuleLike<TValue extends object>(
value: TValue,
shouldPersistRemote: boolean,
depth: number
): SerializableRef<TValue> {
const proto = Object.getPrototypeOf(value);
const clonedProps = cloneObjectProperties(value, shouldPersistRemote, depth);
const clone = WorkletsModule.createSerializableTurboModuleLike(
clonedProps,
proto
) as SerializableRef<TValue>;
return clone;
}
function cloneContextObject<TValue extends object>(
value: TValue
): SerializableRef<TValue> {
const workletContextObjectFactory = (value as Record<string, unknown>)
.__workletContextObjectFactory as () => TValue;
const handle = cloneInitializer({
__init: () => {
'worklet';
return workletContextObjectFactory();
},
});
serializableMappingCache.set(value, handle);
return handle as SerializableRef<TValue>;
}
function clonePlainJSObject<TValue extends object>(
value: TValue,
shouldPersistRemote: boolean,
depth: number
): SerializableRef<TValue> {
const clonedProps: Record<string, unknown> = cloneObjectProperties(
value,
shouldPersistRemote,
depth
);
const clone = WorkletsModule.createSerializableObject(
clonedProps,
shouldPersistRemote,
value
) as SerializableRef<TValue>;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
freezeObjectInDev(value);
return clone;
}
function cloneMap<TValue extends Map<unknown, unknown>>(
value: TValue
): SerializableRef<TValue> {
const clonedKeys: unknown[] = [];
const clonedValues: unknown[] = [];
for (const [key, element] of value.entries()) {
clonedKeys.push(createSerializable(key));
clonedValues.push(createSerializable(element));
}
const clone = WorkletsModule.createSerializableMap(
clonedKeys,
clonedValues
) as SerializableRef<TValue>;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
freezeObjectInDev(value);
return clone;
}
function cloneSet<TValue extends Set<unknown>>(
value: TValue
): SerializableRef<TValue> {
const clonedElements: unknown[] = [];
for (const element of value) {
clonedElements.push(createSerializable(element));
}
const clone = WorkletsModule.createSerializableSet(
clonedElements
) as SerializableRef<TValue>;
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
freezeObjectInDev(value);
return clone;
}
function cloneRegExp<TValue extends RegExp>(
value: TValue
): SerializableRef<TValue> {
const pattern = value.source;
const flags = value.flags;
const handle = cloneInitializer({
__init: () => {
'worklet';
return new RegExp(pattern, flags);
},
}) as unknown as SerializableRef<TValue>;
serializableMappingCache.set(value, handle);
return handle;
}
function cloneError<TValue extends Error>(
value: TValue
): SerializableRef<TValue> {
const { name, message, stack } = value;
const handle = cloneInitializer({
__init: () => {
'worklet';
// eslint-disable-next-line reanimated/use-worklets-error
const error = new Error();
error.name = name;
error.message = message;
error.stack = stack;
return error;
},
});
serializableMappingCache.set(value, handle);
return handle as unknown as SerializableRef<TValue>;
}
function cloneArrayBuffer<T extends ArrayBuffer>(
value: T,
shouldPersistRemote: boolean
): SerializableRef<T> {
const clone = WorkletsModule.createSerializable(
value,
shouldPersistRemote,
value
);
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
return clone;
}
function cloneArrayBufferView<TValue extends ArrayBufferView>(
value: TValue
): SerializableRef<TValue> {
const buffer = value.buffer;
const typeName = value.constructor.name;
const handle = cloneInitializer({
__init: () => {
'worklet';
if (!VALID_ARRAY_VIEWS_NAMES.includes(typeName)) {
throw new WorkletsError(`Invalid array view name \`${typeName}\`.`);
}
const constructor = global[typeName as keyof typeof global];
if (constructor === undefined) {
throw new WorkletsError(`Constructor for \`${typeName}\` not found.`);
}
return new constructor(buffer);
},
}) as unknown as SerializableRef<TValue>;
serializableMappingCache.set(value, handle);
return handle;
}
function cloneSynchronizable<TValue>(
value: Synchronizable<TValue>
): SerializableRef<TValue> {
serializableMappingCache.set(value);
return value;
}
function cloneImport<TValue extends WorkletImport>(
value: TValue
): SerializableRef<TValue> {
const { source, imported } = value.__bundleData;
const clone = WorkletsModule.createSerializableImport(source, imported);
serializableMappingCache.set(value, clone);
serializableMappingCache.set(clone);
return clone as SerializableRef<TValue>;
}
function cloneCustom<TValue extends object, TPacked = unknown>(
data: TValue,
pack: (data: TValue) => TPacked,
typeId: number
): SerializableRef<TValue> {
const packedData = pack(data);
const serialized = createSerializable(packedData);
return WorkletsModule.createCustomSerializable(
serialized,
typeId
) as SerializableRef<TValue>;
}
function inaccessibleObject<TValue extends object>(
value: TValue
): SerializableRef<TValue> {
// This is reached for object types that are not of plain Object.prototype.
// We don't support such objects from being transferred as serializables to
// the UI runtime and hence we replace them with "inaccessible object"
// which is implemented as a Proxy object that throws on any attempt
// of accessing its fields. We argue that such objects can sometimes leak
// as attributes of objects being captured by worklets but should never
// be used on the UI runtime regardless. If they are being accessed, the user
// will get an appropriate error message.
const clone = createSerializable<TValue>(INACCESSIBLE_OBJECT as TValue);
serializableMappingCache.set(value, clone);
return clone;
}
const WORKLET_CODE_THRESHOLD = 255;
function getWorkletCode(value: WorkletFunction) {
const code = value?.__initData?.code;
if (!code) {
return 'unknown';
}
if (code.length > WORKLET_CODE_THRESHOLD) {
return `${code.substring(0, WORKLET_CODE_THRESHOLD)}...`;
}
return code;
}
type RemoteFunction<TValue> = {
__remoteFunction: FlatSerializableRef<TValue>;
};
function isRemoteFunction<TValue>(value: {
__remoteFunction?: unknown;
}): value is RemoteFunction<TValue> {
'worklet';
return !!value.__remoteFunction;
}
/**
* We freeze
*
* - Arrays,
* - Remote functions,
* - Plain JS objects,
*
* That are transformed to a serializable with a meaningful warning. This should
* help detect issues when someone modifies data after it's been converted.
* Meaning that they may be doing a faulty assumption in their code expecting
* that the updates are going to automatically propagate to the object sent to
* the UI thread. If the user really wants some objects to be mutable they
* should use shared values instead.
*/
function freezeObjectInDev<TValue extends object>(value: TValue) {
if (!__DEV__ || globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) {
return;
}
Object.entries(value).forEach(([key, element]) => {
const descriptor = Object.getOwnPropertyDescriptor(value, key)!;
if (!descriptor.configurable) {
return;
}
Object.defineProperty(value, key, {
get() {
return element;
},
set() {
logger.warn(
`Tried to modify key \`${key}\` of an object which has been already passed to a worklet. See
https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#tried-to-modify-key-of-an-object-which-has-been-converted-to-a-serializable
for more details.`
);
},
});
});
Object.preventExtensions(value);
}
function makeShareableCloneOnUIRecursiveLEGACY<TValue>(
value: TValue
): FlatSerializableRef<TValue> {
'worklet';
// eslint-disable-next-line @typescript-eslint/no-shadow
function cloneRecursive(value: TValue): FlatSerializableRef<TValue> {
if (
(typeof value === 'object' && value !== null) ||
typeof value === 'function'
) {
if (isHostObject(value)) {
// We call `_createSerializableClone` to wrap the provided HostObject
// inside SerializableJSRef.
return global._createSerializableHostObject(
value
) as FlatSerializableRef<TValue>;
}
if (isRemoteFunction<TValue>(value)) {
// RemoteFunctions are created by us therefore they are
// a Serializable out of the box and there is no need to
// call `_createSerializableClone`.
return value.__remoteFunction;
}
if (Array.isArray(value)) {
return global._createSerializableArray(
value.map(cloneRecursive)
) as FlatSerializableRef<TValue>;
}
if ((value as Record<string, unknown>).__synchronizableRef) {
return global._createSerializableSynchronizable(
value
) as FlatSerializableRef<TValue>;
}
if (Object.getPrototypeOf(value) !== Object.prototype) {
const length = globalThis.__customSerializationRegistry.length;
for (let i = 0; i < length; i++) {
const { determine, pack } =
globalThis.__customSerializationRegistry[i];
if (determine(value)) {
const packedData = pack(value);
return globalThis.__workletsModuleProxy?.createCustomSerializable(
cloneRecursive(packedData as TValue) as SerializableRef<object>,
i
) as FlatSerializableRef<TValue>;
}
}
}
const toAdapt: Record<string, FlatSerializableRef<TValue>> = {};
for (const [key, element] of Object.entries(value)) {
toAdapt[key] = cloneRecursive(element);
}
return global._createSerializable(
toAdapt,
value
) as FlatSerializableRef<TValue>;
}
if (typeof value === 'string') {
return global._createSerializableString(value);
}
if (typeof value === 'number') {
return global._createSerializableNumber(value);
}
if (typeof value === 'boolean') {
return global._createSerializableBoolean(value);
}
if (typeof value === 'bigint') {
return global._createSerializableBigInt(value);
}
if (value === undefined) {
return global._createSerializableUndefined();
}
if (value === null) {
return global._createSerializableNull();
}
return global._createSerializable(value, undefined);
}
return cloneRecursive(value);
}
/** @deprecated This function is no longer supported. */
export const makeShareableCloneOnUIRecursive = (
globalThis._WORKLETS_BUNDLE_MODE
? createSerializable
: makeShareableCloneOnUIRecursiveLEGACY
) as typeof makeShareableCloneOnUIRecursiveLEGACY;
/**
* This function creates a value on UI with persistent state - changes to it on
* the UI thread will be seen by all worklets. Use it when you want to create a
* value that is read and written only on the UI thread.
*
* @deprecated This function is no longer supported.
*/
export function makeShareable<TValue extends object>(value: TValue): TValue {
if (serializableMappingCache.get(value)) {
return value;
}
const handle = createSerializable({
__init: () => {
'worklet';
return value;
},
});
serializableMappingCache.set(value, handle);
return value;
}
@@ -1,36 +0,0 @@
'use strict';
import type {
FlatSerializableRef,
RegistrationData,
SerializableRef,
} from './types';
export function isSerializableRef<TValue = unknown>(
value: unknown
): value is SerializableRef<TValue> {
return true;
}
export function createSerializable<TValue>(
value: TValue
): SerializableRef<TValue> {
return value as SerializableRef<TValue>;
}
export function makeShareableCloneOnUIRecursive<TValue>(
value: TValue
): FlatSerializableRef<TValue> {
return value as FlatSerializableRef<TValue>;
}
export function makeShareable<TValue>(value: TValue): TValue {
return value;
}
export function registerCustomSerializable<
TValue extends object,
TPacked extends object,
>(_registrationData: RegistrationData<TValue, TPacked>) {
// noop
}
@@ -1,31 +0,0 @@
'use strict';
import type { SerializableRef } from './types';
/**
* This symbol is used to represent a mapping from the value to itself.
*
* It's used to prevent converting a serializable that's already converted - for
* example a Shared Value that's in worklet's closure.
*/
export const serializableMappingFlag = Symbol('serializable flag');
/*
During a fast refresh, React holds the same instance of a Mutable
(that's guaranteed by `useRef`) but `serializableCache` gets regenerated and thus
becoming empty. This happens when editing the file that contains the definition of this cache.
Because of it, `createSerializable` can't find given mapping
in `serializableCache` for the Mutable and tries to clone it as if it was a regular JS object.
During cloning we use `Object.entries` to iterate over the keys which throws an error on accessing `_value`.
For convenience we moved this cache to a separate file so it doesn't scare us with red squiggles.
*/
const cache = new WeakMap<object, SerializableRef | symbol>();
export const serializableMappingCache = {
set(serializable: object, serializableRef?: SerializableRef): void {
cache.set(serializable, serializableRef || serializableMappingFlag);
},
get: cache.get.bind(cache),
};
@@ -1,12 +0,0 @@
'use strict';
import type { SerializableRef } from './types';
export const serializableMappingCache = {
set(_serializable: object, _serializableRef?: SerializableRef): void {
// NOOP
},
get(_key: object): object | symbol | SerializableRef {
return null!;
},
};
@@ -1,17 +0,0 @@
'use strict';
import { WorkletsModule } from '../WorkletsModule/NativeWorklets';
import { createSerializable } from './serializable';
import type { Synchronizable } from './types';
export function createSynchronizable<TValue = unknown>(
initialValue: TValue
): Synchronizable<TValue> {
const synchronizableRef = WorkletsModule.createSynchronizable(
createSerializable(initialValue)
);
return globalThis.__synchronizableUnpacker(
synchronizableRef
) as unknown as Synchronizable<TValue>;
}
@@ -1,10 +0,0 @@
'use strict';
import { WorkletsError } from '../debug/WorkletsError';
import type { Synchronizable } from './types';
export function createSynchronizable<TValue = unknown>(
_value: TValue
): Synchronizable<TValue> {
throw new WorkletsError('`createSynchronizable` is not supported on web.');
}
@@ -1,67 +0,0 @@
'use strict';
import { createSerializable } from './serializable';
import { type Synchronizable, type SynchronizableRef } from './types';
export function __installUnpacker() {
// TODO: Add cache for synchronizables.
const serializer =
!globalThis._WORKLET || globalThis._WORKLETS_BUNDLE_MODE
? (value: unknown, _: unknown) => createSerializable(value)
: globalThis._createSerializable;
function synchronizableUnpacker<TValue>(
synchronizableRef: SynchronizableRef<TValue>
): Synchronizable<TValue> {
const synchronizable =
synchronizableRef as unknown as Synchronizable<TValue>;
const proxy = globalThis.__workletsModuleProxy!;
synchronizable.__synchronizableRef = true;
synchronizable.getDirty = () => {
return proxy.synchronizableGetDirty(synchronizable);
};
synchronizable.getBlocking = () => {
return proxy.synchronizableGetBlocking(synchronizable);
};
synchronizable.setBlocking = (
valueOrFunction: TValue | ((prev: TValue) => TValue)
) => {
let newValue: TValue;
if (typeof valueOrFunction === 'function') {
const func = valueOrFunction as (prev: TValue) => TValue;
synchronizable.lock();
const prev = synchronizable.getBlocking();
newValue = func(prev);
proxy.synchronizableSetBlocking(
synchronizable,
serializer(newValue, undefined)
);
synchronizable.unlock();
} else {
const value = valueOrFunction;
newValue = value;
proxy.synchronizableSetBlocking(
synchronizable,
serializer(newValue, undefined)
);
}
};
synchronizable.lock = () => {
proxy.synchronizableLock(synchronizable);
};
synchronizable.unlock = () => {
proxy.synchronizableUnlock(synchronizable);
};
return synchronizable;
}
globalThis.__synchronizableUnpacker = synchronizableUnpacker;
}
export type SynchronizableUnpacker = <TValue>(
synchronizableRef: SynchronizableRef<TValue>
) => Synchronizable<TValue>;
-79
View File
@@ -1,79 +0,0 @@
'use strict';
/**
* The below type is used for HostObjects returned by the JSI API that don't
* have any accessible fields or methods but can carry data that is accessed
* from the c++ side. We add a field to the type to make it possible for
* typescript to recognize which JSI methods accept those types as arguments and
* to be able to correctly type check other methods that may use them. However,
* this field is not actually defined nor should be used for anything else as
* assigning any data to those objects will throw an error.
*/
export type SerializableRef<TValue = unknown> = {
__serializableRef: true;
__nativeStateSerializableJSRef: TValue;
};
// In case of objects with depth or arrays of objects or arrays of arrays etc.
// we add this utility type that makes it a `SharaebleRef` of the outermost type.
export type FlatSerializableRef<TValue> =
TValue extends SerializableRef<infer TRecursive>
? SerializableRef<TRecursive>
: SerializableRef<TValue>;
export type SynchronizableRef<TValue = unknown> = {
__synchronizableRef: true;
__nativeStateSynchronizableJSRef: TValue;
};
export type Synchronizable<TValue = unknown> = SerializableRef<TValue> &
SynchronizableRef<TValue> & {
__synchronizableRef: true;
getDirty(): TValue;
getBlocking(): TValue;
setBlocking(value: TValue | ((prev: TValue) => TValue)): void;
lock(): void;
unlock(): void;
};
/**
* Registration data for
* [registerCustomSerializable](https://docs.swmansion.com/react-native-reanimated/docs/memory/registerCustomSerializable)
* function.
*/
export type RegistrationData<TValue extends object, TPacked = unknown> = {
/**
* A unique name for the Custom Serializable. It's used to prevent duplicate
* registrations of the same Custom Serializable. You will get warned if you
* attempt to register a Custom Serializable with a name that has already been
* used.
*/
name: string;
/**
* A worklet that checks whether a given JavaScript value is of the type
* handled by this Custom Serializable.
*/
determine: (value: object) => value is TValue;
/**
* A worklet that packs the JavaScript value of type `TValue` into a value
* that can be serialized by default as Serializable. The function must return
* a [supported type for
* Serialization](https://docs.swmansion.com/react-native-reanimated/docs/memory/Serializable#supported-types).
*/
pack: (value: TValue) => TPacked;
/**
* A worklet that unpacks the packed value, after it's been deserialized from
* it's packed form, back into the JavaScript value of type `TValue`.
*/
unpack: (value: TPacked) => TValue;
};
export type SerializationData<TValue extends object, TPacked = unknown> = Omit<
RegistrationData<TValue, TPacked>,
'name'
> & {
/** Only defined on the RN Runtime. */
name?: string;
};
export type CustomSerializationRegistry = SerializationData<object, unknown>[];
@@ -1,92 +0,0 @@
'use strict';
import type { ValueUnpacker, WorkletFunction } from '../types';
declare global {
var evalWithSourceMap:
| ((js: string, sourceURL: string, sourceMap: string) => () => unknown)
| undefined;
var evalWithSourceUrl:
| ((js: string, sourceURL: string) => () => unknown)
| undefined;
}
function __installUnpacker() {
const workletsCache = new Map<number, () => unknown>();
const handleCache = new WeakMap<object, unknown>();
function valueUnpacker(
objectToUnpack: ObjectToUnpack,
category?: string,
remoteFunctionName?: string
): unknown {
// eslint-disable-next-line strict
'use strict';
const workletHash = objectToUnpack.__workletHash;
if (workletHash !== undefined) {
let workletFun = workletsCache.get(workletHash);
if (workletFun === undefined) {
const initData = objectToUnpack.__initData;
if (globalThis.evalWithSourceMap) {
// if the runtime (hermes only for now) supports loading source maps
// we want to use the proper filename for the location as it guarantees
// that debugger understands and loads the source code of the file where
// the worklet is defined.
workletFun = globalThis.evalWithSourceMap(
'(' + initData!.code + '\n)',
initData!.location!,
initData!.sourceMap!
);
} else if (globalThis.evalWithSourceUrl) {
// if the runtime doesn't support loading source maps, in dev mode we
// can pass source url when evaluating the worklet. Now, instead of using
// the actual file location we use worklet hash, as it the allows us to
// properly symbolicate traces (see errors.ts for details)
workletFun = globalThis.evalWithSourceUrl(
'(' + initData!.code + '\n)',
`worklet_${workletHash}`
);
} else {
// in release we use the regular eval to save on JSI calls
// eslint-disable-next-line no-eval
workletFun = eval('(' + initData!.code + '\n)');
}
workletsCache.set(workletHash, workletFun!);
}
const functionInstance = workletFun!.bind(objectToUnpack);
objectToUnpack._recur = functionInstance;
return functionInstance;
} else if (objectToUnpack.__init !== undefined) {
let value = handleCache.get(objectToUnpack);
if (value === undefined) {
value = objectToUnpack.__init();
handleCache.set(objectToUnpack, value);
}
return value;
} else if (category === 'RemoteFunction') {
const fun = () => {
const label = remoteFunctionName
? `function \`${remoteFunctionName}\``
: 'anonymous function';
// eslint-disable-next-line reanimated/use-worklets-error
throw new Error(`[Worklets] Tried to synchronously call a non-worklet ${label} on the UI thread.
See https://docs.swmansion.com/react-native-worklets/docs/guides/troubleshooting#tried-to-synchronously-call-a-non-worklet-function-on-the-ui-thread for more details.`);
};
fun.__remoteFunction = objectToUnpack;
return fun;
} else {
// eslint-disable-next-line reanimated/use-worklets-error
throw new Error(
`[Worklets] Data type in category "${category}" not recognized by value unpacker: "${globalThis._toString(
objectToUnpack
)}".`
);
}
}
globalThis.__valueUnpacker = valueUnpacker as ValueUnpacker;
}
interface ObjectToUnpack extends WorkletFunction {
_recur: unknown;
}
-107
View File
@@ -1,107 +0,0 @@
'use strict';
import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
import { RuntimeKind } from './runtimeKind';
import { isWorkletFunction } from './workletFunction';
const NOOP = () => {};
const NOOP_FACTORY = () => NOOP;
const ID = <TValue>(value: TValue) => value;
const IMMEDIATE_CALLBACK_INVOCATION = <TCallback>(callback: () => TCallback) =>
callback();
globalThis._WORKLET = false;
globalThis.__RUNTIME_KIND = RuntimeKind.ReactNative;
globalThis._log = console.log;
globalThis._getAnimationTimestamp = () => performance.now();
// requestAnimationFrame react-native jest's setup is incorrect as it polyfills
// the method directly using setTimeout, therefore the callback doesn't get the
// expected timestamp as the only argument: https://github.com/facebook/react-native/blob/main/packages/react-native/jest/setup.js#L28
// We override this setup here to make sure that callbacks get the proper timestamps
// when executed. For non-jest environments we define requestAnimationFrame in setupRequestAnimationFrame
// @ts-ignore TypeScript uses Node definition for rAF, setTimeout, etc which returns a Timeout object rather than a number
globalThis.requestAnimationFrame = mockedRequestAnimationFrame;
const WorkletAPI = {
isShareableRef: () => true,
makeShareable: ID,
makeShareableCloneOnUIRecursive: ID,
makeShareableCloneRecursive: ID,
shareableMappingCache: new Map(),
getStaticFeatureFlag: () => false,
setDynamicFeatureFlag: NOOP,
isSynchronizable: () => false,
getRuntimeKind: () => RuntimeKind.ReactNative,
RuntimeKind: RuntimeKind,
createWorkletRuntime: NOOP_FACTORY,
runOnRuntime: ID,
scheduleOnRuntime: IMMEDIATE_CALLBACK_INVOCATION,
createSerializable: ID,
isSerializableRef: ID,
serializableMappingCache: new Map(),
createSynchronizable: ID,
callMicrotasks: NOOP,
executeOnUIRuntimeSync: ID,
runOnJS<Args extends unknown[], ReturnValue>(
fun: (...args: Args) => ReturnValue
): (...args: Args) => void {
return (...args) =>
queueMicrotask(
args.length
? () => (fun as (...args: Args) => ReturnValue)(...args)
: (fun as () => ReturnValue)
);
},
runOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => void {
return (...args) => {
// Mocking time in Jest is tricky as both requestAnimationFrame and queueMicrotask
// callbacks run on the same queue and can be interleaved. There is no way
// to flush particular queue in Jest and the only control over mocked timers
// is by using jest.advanceTimersByTime() method which advances all types
// of timers including immediate and animation callbacks. Ideally we'd like
// to have some way here to schedule work along with React updates, but
// that's not possible, and hence in Jest environment instead of using scheduling
// mechanism we just schedule the work ommiting the queue. This is ok for the
// uses that we currently have but may not be ok for future tests that we write.
mockedRequestAnimationFrame(() => {
worklet(...args);
});
};
},
runOnUIAsync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => Promise<ReturnValue> {
return (...args: Args) => {
return new Promise<ReturnValue>((resolve) => {
mockedRequestAnimationFrame(() => {
const result = worklet(...args);
resolve(result);
});
});
};
},
runOnUISync: IMMEDIATE_CALLBACK_INVOCATION,
scheduleOnRN<Args extends unknown[], ReturnValue>(
fun: (...args: Args) => ReturnValue,
...args: Args
): void {
WorkletAPI.runOnJS(fun)(...args);
},
scheduleOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): void {
WorkletAPI.runOnUI(worklet)(...args);
},
// eslint-disable-next-line camelcase
unstable_eventLoopTask: NOOP_FACTORY,
isWorkletFunction: isWorkletFunction,
WorkletsModule: {},
};
module.exports = {
__esModule: true,
...WorkletAPI,
};
@@ -1,3 +0,0 @@
'use strict';
export const IS_JEST = false;
@@ -1,8 +0,0 @@
'use strict';
import { Platform } from 'react-native';
export const IS_JEST: boolean = !!process.env.JEST_WORKER_ID;
export const IS_WEB: boolean = Platform.OS === 'web';
export const IS_WINDOWS: boolean = Platform.OS === 'windows';
export const SHOULD_BE_USE_WEB: boolean = IS_JEST || IS_WEB || IS_WINDOWS;
+7 -16
View File
@@ -1,15 +1,15 @@
/* eslint-disable reanimated/use-global-this */
'use strict';
// This file works by accident - currently Builder Bob doesn't move `.d.ts` files to output types.
// If it ever breaks, we should address it so we'd not pollute the user's global namespace.
import type { callGuardDEV } from './callGuard';
import type { reportFatalRemoteError } from './debug/errors';
import type { CustomSerializableUnpacker } from './memory/customSerializableUnpacker';
import type { SynchronizableUnpacker } from './memory/synchronizableUnpacker';
import type { CustomSerializationRegistry } from './memory/types';
import type { reportFatalRemoteError } from './errors';
import type { Queue } from './runLoop/workletRuntime/taskQueue';
import type { ValueUnpacker } from './types';
import type { WorkletsModuleProxy } from './WorkletsModule/workletsModuleProxy';
import type { SynchronizableUnpacker } from './synchronizableUnpacker';
import type { IWorkletsErrorConstructor } from './WorkletsError';
import type { WorkletsModuleProxy } from './WorkletsModule';
import type { ValueUnpacker } from './workletTypes';
declare global {
/** The only runtime-available require method is `__r` defined by Metro. */
@@ -61,8 +61,6 @@ declare global {
var __reportFatalRemoteError: typeof reportFatalRemoteError | undefined;
var __valueUnpacker: ValueUnpacker;
var __synchronizableUnpacker: SynchronizableUnpacker;
var __customSerializationRegistry: CustomSerializationRegistry;
var __customSerializableUnpacker: CustomSerializableUnpacker;
var __callGuardDEV: typeof callGuardDEV | undefined;
var __flushAnimationFrame: (timestamp: number) => void;
var __frameTimestamp: number | undefined;
@@ -73,16 +71,9 @@ declare global {
worklet: SerializableRef<() => void>
) => void;
var _microtaskQueueFinalizers: (() => void)[];
var WorkletsError: IWorkletsErrorConstructor;
var _scheduleTimeoutCallback: (delay: number, handlerId: number) => void;
var __runTimeoutCallback: (handlerId: number) => void;
var __flushMicrotasks: () => void;
var _taskQueue: Queue;
/** Only in Debug builds. */
var __hasNativeState: (value: object) => boolean;
/** Only in Debug builds. */
var __isHostObject: (value: object) => boolean;
interface NodeRequire {
resolveWeak(id: string): number;
getModules(): Map<number, unknown>;
}
}
@@ -1,198 +0,0 @@
'use strict';
import { setupCallGuard } from './callGuard';
import { registerWorkletsError, WorkletsError } from './debug/WorkletsError';
import {
getMemorySafeCapturableConsole,
setupConsole,
} from './initializers/initializers';
import {
createSerializable,
makeShareableCloneOnUIRecursive,
} from './memory/serializable';
import { setupRunLoop } from './runLoop/workletRuntime';
import { RuntimeKind } from './runtimeKind';
import type {
WorkletFunction,
WorkletRuntime,
WorkletRuntimeConfig,
} from './types';
import { isWorkletFunction } from './workletFunction';
import { WorkletsModule } from './WorkletsModule/NativeWorklets';
/**
* 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<worklets::WorkletRuntime>` - {@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<worklets::WorkletRuntime>` - {@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();
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)
* - The function cannot be scheduled on the Worker Runtime from [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 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<Args extends unknown[], ReturnValue>(
workletRuntime: WorkletRuntime,
worklet: (...args: Args) => ReturnValue,
...args: Args
): void;
export function scheduleOnRuntime<Args extends unknown[], ReturnValue>(
workletRuntime: WorkletRuntime,
worklet: WorkletFunction<Args, ReturnValue>,
...args: Args
): void {
'worklet';
if (__DEV__ && !isWorkletFunction(worklet)) {
throw new WorkletsError(
'The function passed to `scheduleOnRuntime` is not a worklet.'
);
}
if (globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) {
globalThis._scheduleOnRuntime(
workletRuntime,
makeShareableCloneOnUIRecursive(() => {
'worklet';
worklet(...args);
})
);
}
WorkletsModule.scheduleOnRuntime(
workletRuntime,
createSerializable(() => {
'worklet';
worklet(...args);
globalThis.__flushMicrotasks();
})
);
}
/**
* @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<Args extends unknown[], ReturnValue>(
workletRuntime: WorkletRuntime,
worklet: (...args: Args) => ReturnValue
): WorkletFunction<Args, ReturnValue>;
export function runOnRuntime<Args extends unknown[], ReturnValue>(
workletRuntime: WorkletRuntime,
worklet: WorkletFunction<Args, ReturnValue>
): (...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>;
};
+173 -20
View File
@@ -1,40 +1,193 @@
'use strict';
import { WorkletsError } from './debug/WorkletsError';
import type {
WorkletFunction,
WorkletRuntime,
WorkletRuntimeConfig,
} from './types';
import { setupCallGuard } from './callGuard';
import { getMemorySafeCapturableConsole, setupConsole } from './initializers';
import { SHOULD_BE_USE_WEB } from './PlatformChecker';
import { setupRunLoop } from './runLoop/workletRuntime';
import { RuntimeKind } from './runtimeKind';
import {
createSerializable,
makeShareableCloneOnUIRecursive,
} from './serializable';
import { isWorkletFunction } from './workletFunction';
import { registerWorkletsError, WorkletsError } from './WorkletsError';
import { WorkletsModule } from './WorkletsModule';
import type { WorkletFunction, WorkletRuntime } from './workletTypes';
/**
* 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<worklets::WorkletRuntime>` - {@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<worklets::WorkletRuntime>` - {@link WorkletRuntime}
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/createWorkletRuntime/
*/
export function createWorkletRuntime(
name?: string,
initializer?: () => void
): WorkletRuntime;
export function createWorkletRuntime(): never {
throw new WorkletsError('`createWorkletRuntime` is not supported on web.');
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();
registerWorkletsError();
setupConsole(runtimeBoundCapturableConsole);
if (enableEventLoop) {
setupRunLoop(animationQueuePollingRate);
}
initializerFn?.();
}),
useDefaultQueue,
customQueue,
enableEventLoop
);
}
// @ts-expect-error Check `runOnUI` overload.
export function runOnRuntime<Args extends unknown[], ReturnValue>(
workletRuntime: WorkletRuntime,
worklet: (...args: Args) => ReturnValue
): WorkletFunction<Args, ReturnValue>;
export function runOnRuntime(): never {
throw new WorkletsError('`runOnRuntime` is not supported on web.');
}
export function scheduleOnRuntime<Args extends unknown[], ReturnValue>(
/** Schedule a worklet to execute on the background queue. */
export function runOnRuntime<Args extends unknown[], ReturnValue>(
workletRuntime: WorkletRuntime,
worklet: (...args: Args) => ReturnValue,
...args: Args
): void;
export function scheduleOnRuntime(): never {
throw new WorkletsError('`scheduleOnRuntime` is not supported on web.');
worklet: WorkletFunction<Args, ReturnValue>
): (...args: Args) => void {
'worklet';
if (__DEV__ && !SHOULD_BE_USE_WEB && !isWorkletFunction(worklet)) {
throw new WorkletsError(
'The function passed to `runOnRuntime` is not a worklet.'
);
}
if (globalThis.__RUNTIME_KIND !== RuntimeKind.ReactNative) {
return (...args) =>
globalThis._scheduleOnRuntime(
workletRuntime,
makeShareableCloneOnUIRecursive(() => {
'worklet';
worklet(...args);
})
);
}
return (...args) =>
WorkletsModule.scheduleOnRuntime(
workletRuntime,
createSerializable(() => {
'worklet';
worklet(...args);
globalThis.__flushMicrotasks();
})
);
}
/** Configuration object for creating a worklet runtime. */
export type WorkletRuntimeConfig = {
/** The name of the worklet runtime. */
name?: string;
/**
* A worklet that will be run immediately after the runtime is created and
* before any other worklets.
*/
initializer?: () => void;
/**
* Time interval in milliseconds between polling of frame callbacks scheduled
* by requestAnimationFrame. If not specified, it defaults to 16 ms.
*/
animationQueuePollingRate?: number;
/**
* Determines whether to enable the default Event Loop or not. The Event Loop
* provides implementations for `setTimeout`, `setImmediate`, `setInterval`,
* `requestAnimationFrame`, `queueMicrotask`, `clearTimeout`, `clearInterval`,
* `clearImmediate`, and `cancelAnimationFrame` methods. If not specified, it
* defaults to `true`.
*/
enableEventLoop?: true;
} & (
| {
/**
* If true, the runtime will use the default queue implementation for
* scheduling worklets. Defaults to true.
*/
useDefaultQueue?: true;
/**
* An optional custom queue to be used for scheduling worklets.
*
* The queue has to implement the C++ `AsyncQueue` interface from
* `<worklets/Public/AsyncQueue.h>`.
*/
customQueue?: never;
}
| {
/**
* If true, the runtime will use the default queue implementation for
* scheduling worklets. Defaults to true.
*/
useDefaultQueue: false;
/**
* An optional custom queue to be used for scheduling worklets.
*
* The queue has to implement the C++ `AsyncQueue` interface from
* `<worklets/Public/AsyncQueue.h>`.
*/
customQueue?: object;
}
);
type WorkletRuntimeConfigInternal = WorkletRuntimeConfig & {
initializer?: WorkletFunction<[], void>;
};
@@ -1,6 +1,6 @@
'use strict';
import { type TurboModule, TurboModuleRegistry } from 'react-native';
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
export interface Spec extends TurboModule {
installTurboModule: () => boolean;
+1 -1
View File
@@ -4,7 +4,7 @@ import { RuntimeKind } from '../runtimeKind';
import type { Spec } from './NativeWorkletsModule';
import RNWorkletsTurboModule from './NativeWorkletsModule';
export const WorkletsTurboModule: Spec | null | undefined =
export const WorkletsTurboModule: Spec | null =
globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative
? RNWorkletsTurboModule
: // In Bundle Mode, on Worklet Runtimes `RNWorkletsTurboModule` isn't
-423
View File
@@ -1,423 +0,0 @@
'use strict';
import { WorkletsError } from './debug/WorkletsError';
import {
createSerializable,
makeShareableCloneOnUIRecursive,
} from './memory/serializable';
import { serializableMappingCache } from './memory/serializableMappingCache';
import { RuntimeKind } from './runtimeKind';
import type { WorkletFunction, WorkletImport } from './types';
import { isWorkletFunction } from './workletFunction';
import { WorkletsModule } from './WorkletsModule/NativeWorklets';
type UIJob<Args extends unknown[] = unknown[], ReturnValue = unknown> = [
worklet: WorkletFunction<Args, ReturnValue>,
args: Args,
resolve?: (value: ReturnValue) => void,
];
let runOnUIQueue: UIJob[] = [];
export function setupMicrotasks() {
'worklet';
let microtasksQueue: Array<() => void> = [];
let isExecutingMicrotasksQueue = false;
globalThis.queueMicrotask = (callback: () => void) => {
microtasksQueue.push(callback);
};
globalThis._microtaskQueueFinalizers = [];
globalThis.__callMicrotasks = () => {
if (isExecutingMicrotasksQueue) {
return;
}
try {
isExecutingMicrotasksQueue = true;
for (let index = 0; index < microtasksQueue.length; index += 1) {
// we use classic 'for' loop because the size of the currentTasks array may change while executing some of the callbacks due to queueMicrotask calls
microtasksQueue[index]();
}
microtasksQueue = [];
globalThis._microtaskQueueFinalizers.forEach((finalizer) => finalizer());
} finally {
isExecutingMicrotasksQueue = false;
}
};
}
function callMicrotasksOnUIThread() {
'worklet';
globalThis.__callMicrotasks();
}
export const callMicrotasks = callMicrotasksOnUIThread;
/**
* Lets you schedule a function to be executed on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
*
* - The callback executes asynchronously and doesn't return a value.
* - Passed function and args are automatically
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* and serialized.
* - 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 you have the [Bundle Mode](/docs/experimental/bundleMode) enabled.
*
* @param fun - A reference to a function you want to schedule on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
* @param args - Arguments to pass to the function.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/scheduleOnUI
*/
// @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 scheduleOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): void;
export function scheduleOnUI<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>,
...args: Args
): void {
if (
__DEV__ &&
!isWorkletFunction(worklet) &&
!(worklet as unknown as WorkletImport).__bundleData
) {
throw new WorkletsError('`scheduleOnUI` can only be used with worklets.');
}
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);
}
enqueueUI(worklet, args);
}
/**
* Lets you asynchronously run
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* functions on the [UI
* thread](https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI/).
*
* This method does not schedule the work immediately but instead waits for
* other worklets to be scheduled within the same JS loop. It uses
* queueMicrotask to schedule all the worklets at once making sure they will run
* within the same frame boundaries on the UI thread.
*
* @param fun - A reference to a function you want to execute on the [UI
* thread](https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI/)
* from the [JavaScript
* thread](https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI/).
* @returns A function that accepts arguments for the function passed as the
* first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI @deprecated Use `scheduleOnUI` instead.
*/
// @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 runOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => void;
export function runOnUI<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>
): (...args: Args) => void {
if (
__DEV__ &&
!isWorkletFunction(worklet) &&
!(worklet as unknown as WorkletImport).__bundleData
) {
throw new WorkletsError('`runOnUI` can only be used with worklets.');
}
return (...args: Args) => {
scheduleOnUI(worklet, ...args);
};
}
if (__DEV__) {
function runOnUIWorklet(): void {
'worklet';
throw new WorkletsError(
'`runOnUI` cannot be called on the UI runtime. Please call the function synchronously or use `queueMicrotask` or `requestAnimationFrame` instead.'
);
}
const serializableRunOnUIWorklet = createSerializable(runOnUIWorklet);
serializableMappingCache.set(runOnUI, serializableRunOnUIWorklet);
}
/**
* Lets you run a function synchronously on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime)
* from the [RN
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#rn-runtime).
* Passed function and args are automatically
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* and serialized.
*
* - This function cannot be called from the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
* - This function cannot be called from a [Worker
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#worker-runtime).
*
* @param fun - A reference to a function you want to execute on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
* @param args - Arguments to pass to the function.
* @returns The return value of the function passed as the first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUISync
*/
// @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 runOnUISync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): ReturnValue;
export function runOnUISync<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>,
...args: Args
): ReturnValue {
return WorkletsModule.executeOnUIRuntimeSync(
createSerializable(() => {
'worklet';
const result = worklet(...args);
return makeShareableCloneOnUIRecursive(result);
})
);
}
// @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 executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => ReturnValue;
export function executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>
): (...args: Args) => ReturnValue {
return (...args) => {
return runOnUISync(worklet, ...args);
};
}
type ReleaseRemoteFunction<Args extends unknown[], ReturnValue> = {
(...args: Args): ReturnValue;
};
type DevRemoteFunction<Args extends unknown[], ReturnValue> = {
__remoteFunction: (...args: Args) => ReturnValue;
};
type RemoteFunction<Args extends unknown[], ReturnValue> =
| ReleaseRemoteFunction<Args, ReturnValue>
| DevRemoteFunction<Args, ReturnValue>;
function runWorkletOnJS<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>,
...args: Args
): void {
// remote function that calls a worklet synchronously on the JS runtime
worklet(...args);
}
/**
* Lets you schedule a function to be executed on the RN runtime from any
* runtime. Check
* {@link https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds}
* for more information about the different runtime kinds.
*
* Scheduling function from the RN Runtime (we are already on RN Runtime) simply
* uses `queueMicrotask`.
*
* When functions need to be scheduled from the UI Runtime, first function and
* args are serialized and then the system passes the scheduling responsibility
* to the JSScheduler. The JSScheduler then uses the RN CallInvoker to schedule
* the function asynchronously on the JavaScript thread by calling
* `jsCallInvoker_->invokeAsync()`.
*
* When called from a Worker Runtime, it uses the same JSScheduler mechanism.
*
* @param fun - A function you want to schedule on the RN runtime. A function
* can be a worklet, a remote function or a regular function.
* @param args - Arguments to pass to the function.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/scheduleOnRN
*/
export function scheduleOnRN<Args extends unknown[], ReturnValue>(
fun:
| ((...args: Args) => ReturnValue)
| RemoteFunction<Args, ReturnValue>
| WorkletFunction<Args, ReturnValue>,
...args: Args
): void {
'worklet';
type FunDevRemote = Extract<typeof fun, DevRemoteFunction<Args, ReturnValue>>;
if (globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative) {
// if we are already on the JS thread, we just schedule the worklet on the JS queue
queueMicrotask(
args.length
? () => (fun as (...args: Args) => ReturnValue)(...args)
: (fun as () => ReturnValue)
);
return;
}
if (isWorkletFunction<Args, ReturnValue>(fun)) {
// If `fun` is a worklet, we schedule a call of a remote function `runWorkletOnJS`
// and pass the worklet as a first argument followed by original arguments.
scheduleOnRN(runWorkletOnJS<Args, ReturnValue>, fun, ...args);
return;
}
if ((fun as FunDevRemote).__remoteFunction) {
// In development mode the function provided as `fun` throws an error message
// such that when someone accidentally calls it directly on the UI runtime, they
// see that they should use `runOnJS` instead. To facilitate that we put the
// reference to the original remote function in the `__remoteFunction` property.
fun = (fun as FunDevRemote).__remoteFunction;
}
const scheduleOnRNImpl =
typeof fun === 'function'
? globalThis._scheduleHostFunctionOnJS
: globalThis._scheduleRemoteFunctionOnJS;
scheduleOnRNImpl(
fun as (...args: Args) => ReturnValue,
args.length > 0 ? makeShareableCloneOnUIRecursive(args) : undefined
);
}
/**
* Lets you asynchronously run
* non-[workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* functions that couldn't otherwise run on the [UI
* thread](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-thread).
* This applies to most external libraries as they don't have their functions
* marked with "worklet"; directive.
*
* @param fun - A reference to a function you want to execute on the JavaScript
* thread from the UI thread.
* @returns A function that accepts arguments for the function passed as the
* first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnJS
*/
/** @deprecated Use `scheduleOnRN` instead. */
export function runOnJS<Args extends unknown[], ReturnValue>(
fun:
| ((...args: Args) => ReturnValue)
| RemoteFunction<Args, ReturnValue>
| WorkletFunction<Args, ReturnValue>
): (...args: Args) => void {
'worklet';
return (...args: Args) => {
scheduleOnRN(fun, ...args);
};
}
/**
* Lets you asynchronously run
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* functions on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
*
* This method does not schedule the work immediately but instead waits for
* other worklets to be scheduled within the same JS loop. It uses
* queueMicrotask to schedule all the worklets at once making sure they will run
* within the same frame boundaries on the UI thread.
*
* @param fun - A reference to a function you want to execute on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
* from the [JavaScript
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#javascript-runtime).
* @returns A promise that resolves to the return value of the function passed
* as the first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUIAsync
*/
export function runOnUIAsync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): Promise<ReturnValue> {
if (__DEV__ && !isWorkletFunction(worklet)) {
throw new WorkletsError('`runOnUIAsync` can only be used with worklets.');
}
return new Promise<ReturnValue>((resolve) => {
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);
}
enqueueUI(worklet as WorkletFunction<Args, ReturnValue>, args, resolve);
});
}
if (__DEV__) {
function runOnUIAsyncWorklet(): void {
'worklet';
throw new WorkletsError(
'`runOnUIAsync` cannot be called on the UI runtime. Please call the function synchronously or use `queueMicrotask` or `requestAnimationFrame` instead.'
);
}
const serializableRunOnUIAsyncWorklet =
createSerializable(runOnUIAsyncWorklet);
serializableMappingCache.set(runOnUIAsync, serializableRunOnUIAsyncWorklet);
}
function enqueueUI<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>,
args: Args,
resolve?: (value: ReturnValue) => void
): void {
const job = [worklet, args, resolve] as UIJob<Args, ReturnValue>;
runOnUIQueue.push(job as unknown as UIJob);
if (runOnUIQueue.length === 1) {
flushUIQueue();
}
}
function flushUIQueue(): void {
queueMicrotask(() => {
const queue = runOnUIQueue;
runOnUIQueue = [];
WorkletsModule.scheduleOnUI(
createSerializable(() => {
'worklet';
queue.forEach(([workletFunction, workletArgs, jobResolve]) => {
const result = workletFunction(...workletArgs);
if (jobResolve) {
runOnJS(jobResolve)(result);
}
});
callMicrotasks();
})
);
});
}
/**
* Added temporarily for integration with `react-native-audio-api`. Don't depend
* on this API as it may change without notice.
*/
// eslint-disable-next-line camelcase
export function unstable_eventLoopTask<TArgs extends unknown[], TRet>(
worklet: (...args: TArgs) => TRet
) {
return (...args: TArgs) => {
'worklet';
worklet(...args);
callMicrotasks();
};
}
+423 -81
View File
@@ -1,80 +1,412 @@
'use strict';
import { WorkletsError } from './debug/WorkletsError';
import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
export function callMicrotasks(): void {
// on web flushing is a noop as immediates are handled by the browser
}
export function scheduleOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): void {
enqueueUI(worklet, args);
}
export function runOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => void {
return (...args) => {
scheduleOnUI(worklet, ...args);
};
}
export function runOnUISync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): ReturnValue;
export function runOnUISync(): never {
throw new WorkletsError('`runOnUISync` is not supported on web.');
}
export function executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => ReturnValue;
export function executeOnUIRuntimeSync(): never {
throw new WorkletsError('`executeOnUIRuntimeSync` is not supported on web.');
}
export function runOnJS<Args extends unknown[], ReturnValue>(
fun: (...args: Args) => ReturnValue
): (...args: Args) => void {
return (...args) => scheduleOnRN(fun, ...args);
}
export function scheduleOnRN<Args extends unknown[], ReturnValue>(
fun: (...args: Args) => ReturnValue,
...args: Args
): void {
queueMicrotask(
args.length
? () => (fun as (...args: Args) => ReturnValue)(...args)
: (fun as () => ReturnValue)
);
}
export function runOnUIAsync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): Promise<ReturnValue> {
return new Promise<ReturnValue>((resolve) => {
enqueueUI(worklet, args, resolve);
});
}
import { IS_JEST, SHOULD_BE_USE_WEB } from './PlatformChecker';
import { RuntimeKind } from './runtimeKind';
import {
createSerializable,
makeShareableCloneOnUIRecursive,
} from './serializable';
import { serializableMappingCache } from './serializableMappingCache';
import { isWorkletFunction } from './workletFunction';
import { WorkletsError } from './WorkletsError';
import { WorkletsModule } from './WorkletsModule';
import type { WorkletFunction, WorkletImport } from './workletTypes';
type UIJob<Args extends unknown[] = unknown[], ReturnValue = unknown> = [
worklet: (...args: Args) => ReturnValue,
worklet: WorkletFunction<Args, ReturnValue>,
args: Args,
resolve?: (value: ReturnValue) => void,
];
let runOnUIQueue: UIJob[] = [];
function enqueueUI<Args extends unknown[], ReturnValue>(
export function setupMicrotasks() {
'worklet';
let microtasksQueue: Array<() => void> = [];
let isExecutingMicrotasksQueue = false;
global.queueMicrotask = (callback: () => void) => {
microtasksQueue.push(callback);
};
global._microtaskQueueFinalizers = [];
global.__callMicrotasks = () => {
if (isExecutingMicrotasksQueue) {
return;
}
try {
isExecutingMicrotasksQueue = true;
for (let index = 0; index < microtasksQueue.length; index += 1) {
// we use classic 'for' loop because the size of the currentTasks array may change while executing some of the callbacks due to queueMicrotask calls
microtasksQueue[index]();
}
microtasksQueue = [];
global._microtaskQueueFinalizers.forEach((finalizer) => finalizer());
} finally {
isExecutingMicrotasksQueue = false;
}
};
}
function callMicrotasksOnUIThread() {
'worklet';
global.__callMicrotasks();
}
export const callMicrotasks = SHOULD_BE_USE_WEB
? () => {
// on web flushing is a noop as immediates are handled by the browser
}
: callMicrotasksOnUIThread;
/**
* Lets you schedule a function to be executed on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime).
*
* - The callback executes asynchronously and doesn't return a value.
* - Passed function and args are automatically
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* and serialized.
* - This function cannot be called from the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime)
* or [Worker
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worker-worklet-runtime---worker-runtime),
* unless you have the [Bundle Mode](/docs/experimental/bundleMode) enabled.
*
* @param fun - A reference to a function you want to schedule on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime).
* @param args - Arguments to pass to the function.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/scheduleOnUI
*/
export function scheduleOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): void {
runOnUI(worklet)(...args);
}
/**
* Lets you asynchronously run
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* functions on the [UI
* thread](https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI/).
*
* This method does not schedule the work immediately but instead waits for
* other worklets to be scheduled within the same JS loop. It uses
* queueMicrotask to schedule all the worklets at once making sure they will run
* within the same frame boundaries on the UI thread.
*
* @param fun - A reference to a function you want to execute on the [UI
* thread](https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI/)
* from the [JavaScript
* thread](https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI/).
* @returns A function that accepts arguments for the function passed as the
* first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUI @deprecated Use `scheduleOnUI` instead.
*/
// @ts-expect-error This overload is correct since it's what user sees in his code
// before it's transformed by Reanimated Babel plugin.
export function runOnUI<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => void;
export function runOnUI<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>
): (...args: Args) => void {
if (
__DEV__ &&
!SHOULD_BE_USE_WEB &&
!isWorkletFunction(worklet) &&
!(worklet as unknown as WorkletImport).__bundleData
) {
throw new WorkletsError('`runOnUI` can only be used with worklets.');
}
return (...args) => {
if (IS_JEST) {
// Mocking time in Jest is tricky as both requestAnimationFrame and queueMicrotask
// callbacks run on the same queue and can be interleaved. There is no way
// to flush particular queue in Jest and the only control over mocked timers
// is by using jest.advanceTimersByTime() method which advances all types
// of timers including immediate and animation callbacks. Ideally we'd like
// to have some way here to schedule work along with React updates, but
// that's not possible, and hence in Jest environment instead of using scheduling
// mechanism we just schedule the work ommiting the queue. This is ok for the
// uses that we currently have but may not be ok for future tests that we write.
WorkletsModule.scheduleOnUI(
createSerializable(() => {
'worklet';
worklet(...args);
})
);
return;
}
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);
}
enqueueUI(worklet, args);
};
}
if (__DEV__) {
function runOnUIWorklet(): void {
'worklet';
throw new WorkletsError(
'`runOnUI` cannot be called on the UI runtime. Please call the function synchronously or use `queueMicrotask` or `requestAnimationFrame` instead.'
);
}
const serializableRunOnUIWorklet = createSerializable(runOnUIWorklet);
serializableMappingCache.set(runOnUI, serializableRunOnUIWorklet);
}
/**
* Lets you run a function synchronously on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime)
* from the [RN
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#react-native-runtime-rn-runtime).
* Passed function and args are automatically
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* and serialized.
*
* - This function cannot be called from the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime).
* - This function cannot be called from a [Worker
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#worker-worklet-runtime---worker-runtime).
*
* @param fun - A reference to a function you want to execute on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime).
* @param args - Arguments to pass to the function.
* @returns The return value of the function passed as the first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUISync
*/
export function runOnUISync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue,
...args: Args
): ReturnValue {
return executeOnUIRuntimeSync(worklet)(...args);
}
// @ts-expect-error Check `executeOnUIRuntimeSync` overload above.
export function executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => ReturnValue;
export function executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>
): (...args: Args) => ReturnValue {
return (...args) => {
return WorkletsModule.executeOnUIRuntimeSync(
createSerializable(() => {
'worklet';
const result = worklet(...args);
return makeShareableCloneOnUIRecursive(result);
})
);
};
}
type ReleaseRemoteFunction<Args extends unknown[], ReturnValue> = {
(...args: Args): ReturnValue;
};
type DevRemoteFunction<Args extends unknown[], ReturnValue> = {
__remoteFunction: (...args: Args) => ReturnValue;
};
type RemoteFunction<Args extends unknown[], ReturnValue> =
| ReleaseRemoteFunction<Args, ReturnValue>
| DevRemoteFunction<Args, ReturnValue>;
function runWorkletOnJS<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>,
...args: Args
): void {
// remote function that calls a worklet synchronously on the JS runtime
worklet(...args);
}
/**
* Lets you asynchronously run
* non-[workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* functions that couldn't otherwise run on the [UI
* thread](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-thread).
* This applies to most external libraries as they don't have their functions
* marked with "worklet"; directive.
*
* @param fun - A reference to a function you want to execute on the JavaScript
* thread from the UI thread.
* @returns A function that accepts arguments for the function passed as the
* first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnJS
*/
/** @deprecated Use `scheduleOnRN` instead. */
export function runOnJS<Args extends unknown[], ReturnValue>(
fun:
| ((...args: Args) => ReturnValue)
| RemoteFunction<Args, ReturnValue>
| WorkletFunction<Args, ReturnValue>
): (...args: Args) => void {
'worklet';
type FunDevRemote = Extract<typeof fun, DevRemoteFunction<Args, ReturnValue>>;
if (
SHOULD_BE_USE_WEB ||
globalThis.__RUNTIME_KIND === RuntimeKind.ReactNative
) {
// if we are already on the JS thread, we just schedule the worklet on the JS queue
return (...args) =>
queueMicrotask(
args.length
? () => (fun as (...args: Args) => ReturnValue)(...args)
: (fun as () => ReturnValue)
);
}
if (isWorkletFunction<Args, ReturnValue>(fun)) {
// If `fun` is a worklet, we schedule a call of a remote function `runWorkletOnJS`
// and pass the worklet as a first argument followed by original arguments.
return (...args) =>
runOnJS(runWorkletOnJS<Args, ReturnValue>)(
fun as WorkletFunction<Args, ReturnValue>,
...args
);
}
if ((fun as FunDevRemote).__remoteFunction) {
// In development mode the function provided as `fun` throws an error message
// such that when someone accidentally calls it directly on the UI runtime, they
// see that they should use `runOnJS` instead. To facilitate that we put the
// reference to the original remote function in the `__remoteFunction` property.
fun = (fun as FunDevRemote).__remoteFunction;
}
const scheduleOnJS =
typeof fun === 'function'
? global._scheduleHostFunctionOnJS
: global._scheduleRemoteFunctionOnJS;
return (...args) => {
scheduleOnJS(
fun as
| ((...args: Args) => ReturnValue)
| WorkletFunction<Args, ReturnValue>,
args.length > 0 ? makeShareableCloneOnUIRecursive(args) : undefined
);
};
}
/**
* Lets you schedule a function to be executed on the RN runtime from any
* runtime. Check
* {@link https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds}
* for more information about the different runtime kinds.
*
* Scheduling function from the RN Runtime (we are already on RN Runtime) simply
* uses `queueMicrotask`.
*
* When functions need to be scheduled from the UI Runtime, first function and
* args are serialized and then the system passes the scheduling responsibility
* to the JSScheduler. The JSScheduler then uses the RN CallInvoker to schedule
* the function asynchronously on the JavaScript thread by calling
* `jsCallInvoker_->invokeAsync()`.
*
* When called from a Worker Runtime, it uses the same JSScheduler mechanism.
*
* @param fun - A function you want to schedule on the RN runtime. A function
* can be a worklet, a remote function or a regular function.
* @param args - Arguments to pass to the function.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/scheduleOnRN
*/
export function scheduleOnRN<Args extends unknown[], ReturnValue>(
fun:
| ((...args: Args) => ReturnValue)
| RemoteFunction<Args, ReturnValue>
| WorkletFunction<Args, ReturnValue>,
...args: Args
): void {
'worklet';
runOnJS(fun)(...args);
}
/**
* Lets you asynchronously run
* [workletized](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#to-workletize)
* functions on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime).
*
* This method does not schedule the work immediately but instead waits for
* other worklets to be scheduled within the same JS loop. It uses
* queueMicrotask to schedule all the worklets at once making sure they will run
* within the same frame boundaries on the UI thread.
*
* @param fun - A reference to a function you want to execute on the [UI
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#ui-runtime).
* from the [JavaScript
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/glossary#javascript-runtime).
* @returns A promise that resolves to the return value of the function passed
* as the first argument.
* @see https://docs.swmansion.com/react-native-worklets/docs/threading/runOnUIAsync
*/
export function runOnUIAsync<Args extends unknown[], ReturnValue>(
worklet: (...args: Args) => ReturnValue
): (...args: Args) => Promise<ReturnValue> {
if (__DEV__ && !SHOULD_BE_USE_WEB && !isWorkletFunction(worklet)) {
throw new WorkletsError('`runOnUIAsync` can only be used with worklets.');
}
return (...args: Args) => {
return new Promise<ReturnValue>((resolve) => {
if (IS_JEST) {
// Mocking time in Jest is tricky as both requestAnimationFrame and queueMicrotask
// callbacks run on the same queue and can be interleaved. There is no way
// to flush particular queue in Jest and the only control over mocked timers
// is by using jest.advanceTimersByTime() method which advances all types
// of timers including immediate and animation callbacks. Ideally we'd like
// to have some way here to schedule work along with React updates, but
// that's not possible, and hence in Jest environment instead of using scheduling
// mechanism we just schedule the work ommiting the queue. This is ok for the
// uses that we currently have but may not be ok for future tests that we write.
WorkletsModule.scheduleOnUI(
createSerializable(() => {
'worklet';
worklet(...args);
})
);
return;
}
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);
}
enqueueUI(worklet as WorkletFunction<Args, ReturnValue>, args, resolve);
});
};
}
if (__DEV__) {
function runOnUIAsyncWorklet(): void {
'worklet';
throw new WorkletsError(
'`runOnUIAsync` cannot be called on the UI runtime. Please call the function synchronously or use `queueMicrotask` or `requestAnimationFrame` instead.'
);
}
const serializableRunOnUIAsyncWorklet =
createSerializable(runOnUIAsyncWorklet);
serializableMappingCache.set(runOnUIAsync, serializableRunOnUIAsyncWorklet);
}
function enqueueUI<Args extends unknown[], ReturnValue>(
worklet: WorkletFunction<Args, ReturnValue>,
args: Args,
resolve?: (value: ReturnValue) => void
): void {
@@ -89,22 +421,32 @@ function flushUIQueue(): void {
queueMicrotask(() => {
const queue = runOnUIQueue;
runOnUIQueue = [];
requestAnimationFrameImpl(() => {
queue.forEach(([workletFunction, workletArgs, jobResolve]) => {
const result = workletFunction(...workletArgs);
if (jobResolve) {
jobResolve(result);
}
});
});
WorkletsModule.scheduleOnUI(
createSerializable(() => {
'worklet';
queue.forEach(([workletFunction, workletArgs, jobResolve]) => {
const result = workletFunction(...workletArgs);
if (jobResolve) {
runOnJS(jobResolve)(result);
}
});
callMicrotasks();
})
);
});
}
/**
* Added temporarily for integration with `react-native-audio-api`. Don't depend
* on this API as it may change without notice.
*/
// eslint-disable-next-line camelcase
export function unstable_eventLoopTask(): never {
throw new WorkletsError('`unstable_eventLoopTask` is not supported on web.');
export function unstable_eventLoopTask<TArgs extends unknown[], TRet>(
worklet: (...args: TArgs) => TRet
) {
return (...args: TArgs) => {
'worklet';
worklet(...args);
callMicrotasks();
};
}
const requestAnimationFrameImpl = !globalThis.requestAnimationFrame
? mockedRequestAnimationFrame
: globalThis.requestAnimationFrame;
-151
View File
@@ -1,151 +0,0 @@
/* eslint-disable reanimated/use-global-this */
'use strict';
import type { RuntimeKind } from './runtimeKind';
/** Public globals to be exposed to the user. */
declare global {
/**
* @deprecated Use {@link __RUNTIME_KIND} instead.
*
* This global variable is a diagnostic/development tool.
*
* It's `true` on Worklet Runtimes and `false` on React Native Runtime.
*/
var _WORKLET: boolean | undefined;
/**
* This ArrayBuffer contains the memory address of `jsi::Runtime` which is the
* Reanimated UI runtime.
*/
var _WORKLET_RUNTIME: ArrayBuffer;
/** @deprecated Don't use. */
var _IS_FABRIC: boolean | undefined;
/**
* This global variable is used to determine the kind of the current runtime.
* You can use it directly to differentiate between runtimes. However, the
* recommended way for differentiating is to use the {@link getRuntimeKind}
* function.
*
* - Value _1_: React Native Runtime
* - Value _2_: UI Worklet Runtime
* - Value _3_: Worker Worklet Runtime
*/
var __RUNTIME_KIND: RuntimeKind | 1 | 2 | 3;
}
export type WorkletRuntime = {
__hostObjectWorkletRuntime: never;
readonly name: string;
};
export type WorkletStackDetails = [
error: Error,
lineOffset: number,
columnOffset: number,
];
export type WorkletClosure = Record<string, unknown>;
interface WorkletInitData {
code: string;
/** Only in dev builds. */
location?: string;
/** Only in dev builds. */
sourceMap?: string;
}
interface WorkletProps {
__closure: WorkletClosure;
__workletHash: number;
/** Only in Legacy Bundling. */
__initData?: WorkletInitData;
/** Only for Handles. */
__init?: () => unknown;
/** `__stackDetails` is removed after parsing. */
__stackDetails?: WorkletStackDetails;
/** Only in dev builds. */
__pluginVersion?: string;
}
export type WorkletFunction<
TArgs extends unknown[] = unknown[],
TReturn = unknown,
> = ((...args: TArgs) => TReturn) & WorkletProps;
export interface WorkletFactory<
TArgs extends unknown[] = unknown[],
TReturn = unknown,
TClosureVariables extends Record<string, unknown> = Record<string, unknown>,
> {
(closureVariables: TClosureVariables): WorkletFunction<TArgs, TReturn>;
}
export type ValueUnpacker = WorkletFunction<
[objectToUnpack: unknown, category?: string],
unknown
>;
export interface WorkletImport {
__bundleData: {
/** Name of the module which is the source of the import. */
source: string;
/** The name of the imported value. */
imported: string;
};
}
/** Configuration object for creating a worklet runtime. */
export type WorkletRuntimeConfig = {
/** The name of the worklet runtime. */
name?: string;
/**
* A worklet that will be run immediately after the runtime is created and
* before any other worklets.
*/
initializer?: () => void;
/**
* Time interval in milliseconds between polling of frame callbacks scheduled
* by requestAnimationFrame. If not specified, it defaults to 16 ms.
*/
animationQueuePollingRate?: number;
/**
* Determines whether to enable the default Event Loop or not. The Event Loop
* provides implementations for `setTimeout`, `setImmediate`, `setInterval`,
* `requestAnimationFrame`, `queueMicrotask`, `clearTimeout`, `clearInterval`,
* `clearImmediate`, and `cancelAnimationFrame` methods. If not specified, it
* defaults to `true`.
*/
enableEventLoop?: true;
} & (
| {
/**
* If true, the runtime will use the default queue implementation for
* scheduling worklets. Defaults to true.
*/
useDefaultQueue?: true;
/**
* An optional custom queue to be used for scheduling worklets.
*
* The queue has to implement the C++ `AsyncQueue` interface from
* `<worklets/RunLoop/AsyncQueue.h>`.
*/
customQueue?: never;
}
| {
/**
* If true, the runtime will use the default queue implementation for
* scheduling worklets. Defaults to true.
*/
useDefaultQueue: false;
/**
* An optional custom queue to be used for scheduling worklets.
*
* The queue has to implement the C++ `AsyncQueue` interface from
* `<worklets/RunLoop/AsyncQueue.h>`.
*/
customQueue?: object;
}
);
+4 -3
View File
@@ -1,11 +1,12 @@
'use strict';
import type { WorkletFunction } from './types';
import type { WorkletFunction } from './workletTypes';
/**
* This function allows you to determine if a given function is a worklet. It
* only works with Worklets Babel plugin enabled. Unless you are doing something
* with internals of Worklets you shouldn't need to use this function.
* only works with Reanimated Babel plugin enabled. Unless you are doing
* something with internals of Reanimated you shouldn't need to use this
* function.
*
* ### Note
*