UNPKG

10.2 kBPlain TextView Raw
1'use strict';
2import NativeReanimatedModule from './NativeReanimated';
3import { isJest, shouldBeUseWeb } from './PlatformChecker';
4import type { WorkletFunction } from './commonTypes';
5import {
6 makeShareableCloneOnUIRecursive,
7 makeShareableCloneRecursive,
8} from './shareables';
9import { isWorkletFunction } from './commonTypes';
10
11const IS_JEST = isJest();
12const SHOULD_BE_USE_WEB = shouldBeUseWeb();
13
14/**
15 * An array of [worklet, args] pairs.
16 * */
17let _runOnUIQueue: Array<[WorkletFunction<unknown[], unknown>, unknown[]]> = [];
18
19export function setupMicrotasks() {
20 'worklet';
21
22 let microtasksQueue: Array<() => void> = [];
23 let isExecutingMicrotasksQueue = false;
24 global.queueMicrotask = (callback: () => void) => {
25 microtasksQueue.push(callback);
26 };
27
28 global.__callMicrotasks = () => {
29 if (isExecutingMicrotasksQueue) {
30 return;
31 }
32 try {
33 isExecutingMicrotasksQueue = true;
34 for (let index = 0; index < microtasksQueue.length; index += 1) {
35 // we use classic 'for' loop because the size of the currentTasks array may change while executing some of the callbacks due to queueMicrotask calls
36 microtasksQueue[index]();
37 }
38 microtasksQueue = [];
39 global._maybeFlushUIUpdatesQueue();
40 } finally {
41 isExecutingMicrotasksQueue = false;
42 }
43 };
44}
45
46function callMicrotasksOnUIThread() {
47 'worklet';
48 global.__callMicrotasks();
49}
50
51export const callMicrotasks = SHOULD_BE_USE_WEB
52 ? () => {
53 // on web flushing is a noop as immediates are handled by the browser
54 }
55 : callMicrotasksOnUIThread;
56
57/**
58 * Lets you asynchronously run [workletized](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#to-workletize) functions on the [UI thread](https://docs.swmansion.com/react-native-reanimated/docs/threading/runOnUI).
59 *
60 * This method does not schedule the work immediately but instead waits for other worklets
61 * to be scheduled within the same JS loop. It uses queueMicrotask to schedule all the worklets
62 * at once making sure they will run within the same frame boundaries on the UI thread.
63 *
64 * @param fun - A reference to a function you want to execute on the [UI thread](https://docs.swmansion.com/react-native-reanimated/docs/threading/runOnUI) from the [JavaScript thread](https://docs.swmansion.com/react-native-reanimated/docs/threading/runOnUI).
65 * @returns A function that accepts arguments for the function passed as the first argument.
66 * @see https://docs.swmansion.com/react-native-reanimated/docs/threading/runOnUI
67 */
68// @ts-expect-error This overload is correct since it's what user sees in his code
69// before it's transformed by Reanimated Babel plugin.
70export function runOnUI<Args extends unknown[], ReturnValue>(
71 worklet: (...args: Args) => ReturnValue
72): (...args: Args) => void;
73
74export function runOnUI<Args extends unknown[], ReturnValue>(
75 worklet: WorkletFunction<Args, ReturnValue>
76): (...args: Args) => void {
77 'worklet';
78 if (__DEV__ && !SHOULD_BE_USE_WEB && _WORKLET) {
79 throw new Error(
80 '[Reanimated] `runOnUI` cannot be called on the UI runtime. Please call the function synchronously or use `queueMicrotask` or `requestAnimationFrame` instead.'
81 );
82 }
83 if (__DEV__ && !SHOULD_BE_USE_WEB && !isWorkletFunction(worklet)) {
84 throw new Error('[Reanimated] `runOnUI` can only be used on worklets.');
85 }
86 return (...args) => {
87 if (IS_JEST) {
88 // Mocking time in Jest is tricky as both requestAnimationFrame and queueMicrotask
89 // callbacks run on the same queue and can be interleaved. There is no way
90 // to flush particular queue in Jest and the only control over mocked timers
91 // is by using jest.advanceTimersByTime() method which advances all types
92 // of timers including immediate and animation callbacks. Ideally we'd like
93 // to have some way here to schedule work along with React updates, but
94 // that's not possible, and hence in Jest environment instead of using scheduling
95 // mechanism we just schedule the work ommiting the queue. This is ok for the
96 // uses that we currently have but may not be ok for future tests that we write.
97 NativeReanimatedModule.scheduleOnUI(
98 makeShareableCloneRecursive(() => {
99 'worklet';
100 worklet(...args);
101 })
102 );
103 return;
104 }
105 if (__DEV__) {
106 // in DEV mode we call shareable conversion here because in case the object
107 // can't be converted, we will get a meaningful stack-trace as opposed to the
108 // situation when conversion is only done via microtask queue. This does not
109 // make the app particularily less efficient as converted objects are cached
110 // and for a given worklet the conversion only happens once.
111 makeShareableCloneRecursive(worklet);
112 makeShareableCloneRecursive(args);
113 }
114 _runOnUIQueue.push([worklet as WorkletFunction, args]);
115 if (_runOnUIQueue.length === 1) {
116 queueMicrotask(() => {
117 const queue = _runOnUIQueue;
118 _runOnUIQueue = [];
119 NativeReanimatedModule.scheduleOnUI(
120 makeShareableCloneRecursive(() => {
121 'worklet';
122 // eslint-disable-next-line @typescript-eslint/no-shadow
123 queue.forEach(([worklet, args]) => {
124 worklet(...args);
125 });
126 callMicrotasks();
127 })
128 );
129 });
130 }
131 };
132}
133
134// @ts-expect-error Check `executeOnUIRuntimeSync` overload above.
135export function executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
136 worklet: (...args: Args) => ReturnValue
137): (...args: Args) => ReturnValue;
138
139export function executeOnUIRuntimeSync<Args extends unknown[], ReturnValue>(
140 worklet: WorkletFunction<Args, ReturnValue>
141): (...args: Args) => ReturnValue {
142 return (...args) => {
143 return NativeReanimatedModule.executeOnUIRuntimeSync(
144 makeShareableCloneRecursive(() => {
145 'worklet';
146 const result = worklet(...args);
147 return makeShareableCloneOnUIRecursive(result);
148 })
149 );
150 };
151}
152
153// @ts-expect-error Check `runOnUI` overload above.
154export function runOnUIImmediately<Args extends unknown[], ReturnValue>(
155 worklet: (...args: Args) => ReturnValue
156): WorkletFunction<Args, ReturnValue>;
157/**
158 * Schedule a worklet to execute on the UI runtime skipping batching mechanism.
159 */
160export function runOnUIImmediately<Args extends unknown[], ReturnValue>(
161 worklet: WorkletFunction<Args, ReturnValue>
162): (...args: Args) => void {
163 'worklet';
164 if (__DEV__ && !SHOULD_BE_USE_WEB && _WORKLET) {
165 throw new Error(
166 '[Reanimated] `runOnUIImmediately` cannot be called on the UI runtime. Please call the function synchronously or use `queueMicrotask` or `requestAnimationFrame` instead.'
167 );
168 }
169 if (__DEV__ && !SHOULD_BE_USE_WEB && !isWorkletFunction(worklet)) {
170 throw new Error(
171 '[Reanimated] `runOnUIImmediately` can only be used on worklets.'
172 );
173 }
174 return (...args) => {
175 NativeReanimatedModule.scheduleOnUI(
176 makeShareableCloneRecursive(() => {
177 'worklet';
178 worklet(...args);
179 })
180 );
181 };
182}
183
184type ReleaseRemoteFunction<Args extends unknown[], ReturnValue> = {
185 (...args: Args): ReturnValue;
186};
187
188type DevRemoteFunction<Args extends unknown[], ReturnValue> = {
189 __remoteFunction: (...args: Args) => ReturnValue;
190};
191
192type RemoteFunction<Args extends unknown[], ReturnValue> =
193 | ReleaseRemoteFunction<Args, ReturnValue>
194 | DevRemoteFunction<Args, ReturnValue>;
195
196function runWorkletOnJS<Args extends unknown[], ReturnValue>(
197 worklet: WorkletFunction<Args, ReturnValue>,
198 ...args: Args
199): void {
200 // remote function that calls a worklet synchronously on the JS runtime
201 worklet(...args);
202}
203
204/**
205 * Lets you asynchronously run non-[workletized](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#to-workletize) functions that couldn't otherwise run on the [UI thread](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/glossary#ui-thread).
206 * This applies to most external libraries as they don't have their functions marked with "worklet"; directive.
207 *
208 * @param fun - A reference to a function you want to execute on the JavaScript thread from the UI thread.
209 * @returns A function that accepts arguments for the function passed as the first argument.
210 * @see https://docs.swmansion.com/react-native-reanimated/docs/threading/runOnJS
211 */
212export function runOnJS<Args extends unknown[], ReturnValue>(
213 fun:
214 | ((...args: Args) => ReturnValue)
215 | RemoteFunction<Args, ReturnValue>
216 | WorkletFunction<Args, ReturnValue>
217): (...args: Args) => void {
218 'worklet';
219 type FunDevRemote = Extract<typeof fun, DevRemoteFunction<Args, ReturnValue>>;
220 if (SHOULD_BE_USE_WEB || !_WORKLET) {
221 // if we are already on the JS thread, we just schedule the worklet on the JS queue
222 return (...args) =>
223 queueMicrotask(
224 args.length
225 ? () => (fun as (...args: Args) => ReturnValue)(...args)
226 : (fun as () => ReturnValue)
227 );
228 }
229 if (isWorkletFunction<Args, ReturnValue>(fun)) {
230 // If `fun` is a worklet, we schedule a call of a remote function `runWorkletOnJS`
231 // and pass the worklet as a first argument followed by original arguments.
232
233 return (...args) =>
234 runOnJS(runWorkletOnJS<Args, ReturnValue>)(
235 fun as WorkletFunction<Args, ReturnValue>,
236 ...args
237 );
238 }
239 if ((fun as FunDevRemote).__remoteFunction) {
240 // In development mode the function provided as `fun` throws an error message
241 // such that when someone accidentally calls it directly on the UI runtime, they
242 // see that they should use `runOnJS` instead. To facilitate that we put the
243 // reference to the original remote function in the `__remoteFunction` property.
244 fun = (fun as FunDevRemote).__remoteFunction;
245 }
246 return (...args) => {
247 global._scheduleOnJS(
248 fun as
249 | ((...args: Args) => ReturnValue)
250 | WorkletFunction<Args, ReturnValue>,
251 args.length > 0
252 ? // TODO TYPESCRIPT this cast is terrible but will be fixed
253 (makeShareableCloneOnUIRecursive(args) as unknown as unknown[])
254 : undefined
255 );
256 };
257}
258
\No newline at end of file