react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
111 lines (106 loc) • 4.19 kB
text/typescript
/* eslint-disable reanimated/use-reanimated-error */
;
import type { WorkletFunction } from '../commonTypes';
import { isWorkletFunction } from '../commonTypes';
import { shouldBeUseWeb } from '../PlatformChecker';
function valueUnpacker(
objectToUnpack: any,
category?: string,
remoteFunctionName?: string
): any {
'worklet';
let workletsCache = global.__workletsCache;
let handleCache = global.__handleCache;
if (workletsCache === undefined) {
// init
workletsCache = global.__workletsCache = new Map();
handleCache = global.__handleCache = new WeakMap();
}
const workletHash = objectToUnpack.__workletHash;
if (workletHash !== undefined) {
let workletFun = workletsCache.get(workletHash);
if (workletFun === undefined) {
const initData = objectToUnpack.__initData;
if (global.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 = global.evalWithSourceMap(
'(' + initData.code + '\n)',
initData.location,
initData.sourceMap
) as (...args: any[]) => any;
} else if (global.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 = global.evalWithSourceUrl(
'(' + initData.code + '\n)',
`worklet_${workletHash}`
) as (...args: any[]) => any;
} else {
// in release we use the regular eval to save on JSI calls
// eslint-disable-next-line no-eval
workletFun = eval('(' + initData.code + '\n)') as (
...args: any[]
) => any;
}
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';
throw new Error(`[Reanimated] Tried to synchronously call a non-worklet ${label} on the UI thread.
See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#tried-to-synchronously-call-a-non-worklet-function-on-the-ui-thread for more details.`);
};
fun.__remoteFunction = objectToUnpack;
return fun;
} else {
throw new Error(
`[Reanimated] Data type in category "${category}" not recognized by value unpacker: "${_toString(
objectToUnpack
)}".`
);
}
}
type ValueUnpacker = WorkletFunction<
[objectToUnpack: any, category?: string],
any
>;
if (__DEV__ && !shouldBeUseWeb()) {
const testWorklet = (() => {
'worklet';
}) as WorkletFunction<[], void>;
if (!isWorkletFunction(testWorklet)) {
throw new Error(
`[Reanimated] Failed to create a worklet. See https://docs.swmansion.com/react-native-reanimated/docs/guides/troubleshooting#failed-to-create-a-worklet for more details.`
);
}
if (!isWorkletFunction(valueUnpacker)) {
throw new Error('[Reanimated] `valueUnpacker` is not a worklet');
}
const closure = (valueUnpacker as ValueUnpacker).__closure;
if (closure === undefined) {
throw new Error('[Reanimated] `valueUnpacker` closure is undefined');
}
if (Object.keys(closure).length !== 0) {
throw new Error('[Reanimated] `valueUnpacker` must have empty closure');
}
}
export function getValueUnpackerCode() {
return (valueUnpacker as ValueUnpacker).__initData.code;
}