Skip to content

Commit

Permalink
Change requestAnimationFrame flush condition (#6442)
Browse files Browse the repository at this point in the history
## Summary
Currently we schedule`rAF` flush when the first callback is added to the
list. However the flush method can also be called by an event, meaning
that sometimes we have a flush scheduled to run on a given frame, but
the callbacks array is empty. If then, another callback is requested,
the array will contain 1 element, triggering another flush request (even
though one is already scheduled). To prevent this, from causing
countless requests on a singe frame we check the frame timestamp and
abort if it's repeated.

This approach unfortunately causes some problems when video is playing
in the app. On iPhone devices sometimes the displayLink can fire its
callback twice in a single frame (with the same timestamp). This leads
to us cancelling the `rAF` flush, which means that until an event
triggers a flush, none updates from reanimated will come through.

This PR changes the way we request a flush. Instead of checking the
callbacks array size, we instead remember whether a flush was requested
and request a new one only when there was no request. This prevents us
from requesting unnecessary flushes when an event has caused the
callbacks array to be emptied, while also allowing for repeated frame
timestamps.

closes #6371 

## Test plan

Check for regressions in the example app and in this example:
https://gist.github.com/kmagiera/b2df85f9512951f5e6ceee7bc569f5f1
  • Loading branch information
bartlomiejbloniarz authored Aug 28, 2024
1 parent a50a41c commit 6933d14
Showing 1 changed file with 4 additions and 10 deletions.
14 changes: 4 additions & 10 deletions packages/react-native-reanimated/src/initializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ function setupRequestAnimationFrame() {
const nativeRequestAnimationFrame = global.requestAnimationFrame;

let animationFrameCallbacks: Array<(timestamp: number) => void> = [];
let lastNativeAnimationFrameTimestamp = -1;
let flushRequested = false;

global.__flushAnimationFrame = (frameTimestamp: number) => {
const currentCallbacks = animationFrameCallbacks;
Expand All @@ -110,16 +110,10 @@ function setupRequestAnimationFrame() {
callback: (timestamp: number) => void
): number => {
animationFrameCallbacks.push(callback);
if (animationFrameCallbacks.length === 1) {
// We schedule native requestAnimationFrame only when the first callback
// is added and then use it to execute all the enqueued callbacks. Once
// the callbacks are run, we clear the array.
if (!flushRequested) {
flushRequested = true;
nativeRequestAnimationFrame((timestamp) => {
if (lastNativeAnimationFrameTimestamp >= timestamp) {
// Make sure we only execute the callbacks once for a given frame
return;
}
lastNativeAnimationFrameTimestamp = timestamp;
flushRequested = false;
global.__frameTimestamp = timestamp;
global.__flushAnimationFrame(timestamp);
global.__frameTimestamp = undefined;
Expand Down

0 comments on commit 6933d14

Please sign in to comment.