chore: update
This commit is contained in:
Generated
Vendored
-275
@@ -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();
|
||||
Generated
Vendored
+250
-4
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Generated
Vendored
+2
-14
@@ -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>(
|
||||
|
||||
-33
@@ -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;
|
||||
}
|
||||
}
|
||||
Generated
Vendored
-32
@@ -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;
|
||||
-18
@@ -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;
|
||||
-35
@@ -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;
|
||||
}
|
||||
}
|
||||
-80
@@ -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;
|
||||
}
|
||||
-8
@@ -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
@@ -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
@@ -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
@@ -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>;
|
||||
|
||||
Generated
Vendored
-70
@@ -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;
|
||||
}
|
||||
Generated
Vendored
-20
@@ -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;
|
||||
}
|
||||
-29
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
Generated
Vendored
-238
@@ -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();
|
||||
});
|
||||
}
|
||||
Generated
Vendored
-21
@@ -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;
|
||||
}
|
||||
}
|
||||
Generated
Vendored
-39
@@ -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();
|
||||
Generated
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
export function bundleModeInit() {
|
||||
// no-op
|
||||
}
|
||||
Generated
Vendored
-76
@@ -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;
|
||||
}
|
||||
Generated
Vendored
-29
@@ -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;
|
||||
Generated
Vendored
-15
@@ -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
|
||||
);
|
||||
}
|
||||
-10
@@ -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.');
|
||||
}
|
||||
Generated
Vendored
-897
@@ -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;
|
||||
}
|
||||
-36
@@ -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
|
||||
}
|
||||
Generated
Vendored
-31
@@ -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),
|
||||
};
|
||||
Generated
Vendored
-12
@@ -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!;
|
||||
},
|
||||
};
|
||||
Generated
Vendored
-17
@@ -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>;
|
||||
}
|
||||
-10
@@ -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.');
|
||||
}
|
||||
Generated
Vendored
-67
@@ -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
@@ -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>[];
|
||||
Generated
Vendored
-92
@@ -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
@@ -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,
|
||||
};
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
export const IS_JEST = false;
|
||||
-8
@@ -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
@@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
-198
@@ -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
@@ -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>;
|
||||
};
|
||||
|
||||
Generated
Vendored
+2
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user