Skip to content

Commit fd07ac6

Browse files
authored
refactor(Worklets): always spin AnimationFrameQueue (#8900)
## Summary I've decided to remove the optimization of the UI Runtime to request a native animation frame only when it has JS animation frame queue pending. The optimization itself is marginal and on the other hand it lead to bad patterns of calling `flushMicrotasks` imperatively. Because Shared Value side-effects (running the input-outputs graph) are executed in the microtask queue, changing a shared value via JSI without any animations going wouldn't trigger any visible changes. For this reason i.e. `useAnimatedSensor` called `flushMicrotasks` to make sure that the sensor registers and is not held in the queue forever. ## Test plan - Bokeh example works ✅ - Sensors on physical device work ✅ - Runtime run loop tests pass ✅
1 parent 9e44a0d commit fd07ac6

7 files changed

Lines changed: 34 additions & 46 deletions

File tree

packages/react-native-reanimated/src/hook/useAnimatedSensor.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
'use strict';
22
import { useEffect, useMemo, useRef } from 'react';
3-
import { callMicrotasks } from 'react-native-worklets';
43

54
import type {
65
AnimatedSensor,
@@ -154,7 +153,6 @@ export function useAnimatedSensor(
154153
}
155154
}
156155
sensorData.value = data;
157-
callMicrotasks();
158156
});
159157

160158
if (id !== -1) {

packages/react-native-reanimated/src/valueSetter.ts

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,7 @@ export function valueSetter<Value>(
4545
global.__frameTimestamp || global._getAnimationTimestamp();
4646
initializeAnimation(currentTimestamp);
4747

48-
const step = (newTimestamp: number) => {
49-
// Function `requestAnimationFrame` adds callback to an array, all the callbacks are flushed with function `__flushAnimationFrame`
50-
// Usually we flush them inside function `nativeRequestAnimationFrame` and then the given timestamp is the timestamp of end of the current frame.
51-
// However function `__flushAnimationFrame` may also be called inside `registerEventHandler` - then we get actual timestamp which is earlier than the end of the frame.
52-
53-
const timestamp =
54-
newTimestamp < (animation.timestamp || 0)
55-
? animation.timestamp
56-
: newTimestamp;
57-
48+
const step = (timestamp: number) => {
5849
if (animation.cancelled) {
5950
animation.callback?.(false /* finished */);
6051
return;

packages/react-native-worklets/src/deprecated.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,9 @@ export const isShareableRef = isSerializableRef;
3030

3131
/** @deprecated Use {@link serializableMappingCache} instead. */
3232
export const shareableMappingCache = serializableMappingCache;
33+
34+
/** @deprecated NOOP, don't use. */
35+
export function callMicrotasks(): void {
36+
'worklet';
37+
// NOOP for backwards compatibility.
38+
}

packages/react-native-worklets/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ if (globalThis._ALWAYS_FALSE) {
1414
}
1515

1616
export {
17+
callMicrotasks,
1718
isShareableRef,
1819
makeShareable,
1920
type MakeShareableClone,
@@ -59,7 +60,6 @@ export {
5960
scheduleOnRuntime,
6061
} from './runtimes';
6162
export {
62-
callMicrotasks,
6363
executeOnUIRuntimeSync,
6464
runOnJS,
6565
runOnUI,
Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
'use strict';
22

3-
import { callMicrotasks } from '../../threads';
4-
53
export function setupRequestAnimationFrame() {
64
'worklet';
75
const nativeRequestAnimationFrame = globalThis.requestAnimationFrame;
6+
const callMicrotasks = globalThis.__callMicrotasks;
87

98
let queuedCallbacks: ((timestamp: number) => void)[] = [];
109
let queuedCallbacksBegin = 0;
@@ -14,9 +13,7 @@ export function setupRequestAnimationFrame() {
1413
let flushedCallbacksBegin = 0;
1514
let flushedCallbacksEnd = 0;
1615

17-
let flushRequested = false;
18-
19-
globalThis.__flushAnimationFrame = (timestamp: number) => {
16+
function executeQueue(timestamp: number) {
2017
flushedCallbacks = queuedCallbacks;
2118
queuedCallbacks = [];
2219

@@ -31,28 +28,17 @@ export function setupRequestAnimationFrame() {
3128
flushedCallbacksBegin = flushedCallbacksEnd;
3229

3330
callMicrotasks();
34-
};
31+
}
3532

36-
globalThis.requestAnimationFrame = (
33+
function requestAnimationFrame(
3734
callback: (timestamp: number) => void
38-
): number => {
35+
): number {
3936
const handle = queuedCallbacksEnd++;
40-
4137
queuedCallbacks.push(callback);
42-
if (!flushRequested) {
43-
flushRequested = true;
44-
45-
nativeRequestAnimationFrame((timestamp) => {
46-
flushRequested = false;
47-
globalThis.__frameTimestamp = timestamp;
48-
globalThis.__flushAnimationFrame(timestamp);
49-
globalThis.__frameTimestamp = undefined;
50-
});
51-
}
5238
return handle;
53-
};
39+
}
5440

55-
globalThis.cancelAnimationFrame = (handle: number) => {
41+
function cancelAnimationFrame(handle: number) {
5642
if (handle < flushedCallbacksBegin || handle >= queuedCallbacksEnd) {
5743
return;
5844
}
@@ -62,5 +48,23 @@ export function setupRequestAnimationFrame() {
6248
} else {
6349
queuedCallbacks[handle - queuedCallbacksBegin] = () => {};
6450
}
51+
}
52+
53+
function flushQueue(timestamp: number) {
54+
globalThis.__frameTimestamp = timestamp;
55+
executeQueue(timestamp);
56+
globalThis.__frameTimestamp = undefined;
57+
58+
/* Schedule next frame */
59+
nativeRequestAnimationFrame(flushQueue);
60+
}
61+
62+
globalThis.requestAnimationFrame = requestAnimationFrame;
63+
globalThis.cancelAnimationFrame = cancelAnimationFrame;
64+
globalThis.__flushAnimationFrame = () => {
65+
// NOOP for backwards compatibility
6566
};
67+
68+
/* Start the loop */
69+
nativeRequestAnimationFrame(flushQueue);
6670
}

packages/react-native-worklets/src/threads.native.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,6 @@ export function setupMicrotasks() {
4747
};
4848
}
4949

50-
function callMicrotasksOnUIThread() {
51-
'worklet';
52-
globalThis.__callMicrotasks();
53-
}
54-
55-
export const callMicrotasks = callMicrotasksOnUIThread;
56-
5750
/**
5851
* Lets you schedule a function to be executed on the [UI
5952
* Runtime](https://docs.swmansion.com/react-native-worklets/docs/fundamentals/runtimeKinds#ui-runtime).
@@ -376,7 +369,7 @@ function flushUIQueue(): void {
376369
scheduleOnRN(jobResolve, result);
377370
}
378371
});
379-
callMicrotasks();
372+
globalThis.__callMicrotasks();
380373
})
381374
);
382375
});

packages/react-native-worklets/src/threads.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,6 @@ import { WorkletsError } from './debug/WorkletsError';
44
import { IS_JEST } from './platformChecker';
55
import { mockedRequestAnimationFrame } from './runLoop/uiRuntime/mockedRequestAnimationFrame';
66

7-
export function callMicrotasks(): void {
8-
// on web flushing is a noop as immediates are handled by the browser
9-
}
10-
117
export function scheduleOnUI<Args extends unknown[], ReturnValue>(
128
worklet: (...args: Args) => ReturnValue,
139
...args: Args

0 commit comments

Comments
 (0)