Skip to content

Commit

Permalink
Skip flushing animation-frame callbacks if update was already run for…
Browse files Browse the repository at this point in the history
… a given frame (software-mansion#4099)

## Summary

This PR fixes a perf regression introduced in software-mansion#3722. The issue was that
we'd schedule a native request animation frame callback several times in
a single frame. As a result we'd run mapper updates more than once per
frame which resulted in UI thread skipping frames. The bug would surface
in examples where we had both timing (active) and gesture driven
animations. In such scenario we'd enqueue native requestAnimationFrame
callback twice – first time for the running timing animation, and the
second time as a result of handling the touch event. These two callbacks
would then run as long as there are update happening and would incur
additional cost to all updates happening on the UI thread. Moreover, for
each new gesture started afterwards we'd add a new call to
requestAnimationFrame and yet another time the updates would be executed
withing a single frame. After several times of launching new stream of
events, we'd end up with a huge number of repeated work being done in a
single frame which was causing significant frame drops.

This PR addresses this problem by preventing requestAnimationFrame
callbacks to be flushed more than once in a single frame. We do this by
remembering if the previous flush was caused by the native rAF callback
or was it forced by other activity (i.e. event handling). Then we only
allow native rAF callback to perform a flush if it doesn't immediately
follow a non-native flush. This way we don't perform additional work in
a frame when the updates are already triggered by the event.

## Test plan

Below is a link to a minimum repro of this issue. The app runs an
infinite animation and renders a draggable circle. After running this
app for a while and pulling the circle several times the performance
starts to drop.
https://gist.github.com/kmagiera/b2df85f9512951f5e6ceee7bc569f5f1
  • Loading branch information
kmagiera authored and fluiddot committed Jun 5, 2023
1 parent a298a57 commit ab02fd8
Showing 1 changed file with 6 additions and 0 deletions.
6 changes: 6 additions & 0 deletions src/reanimated2/initializers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ function setupRequestAnimationFrame() {
const nativeRequestAnimationFrame = global.requestAnimationFrame;

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

global.__flushAnimationFrame = (frameTimestamp: number) => {
const currentCallbacks = animationFrameCallbacks;
Expand All @@ -117,6 +118,11 @@ function setupRequestAnimationFrame() {
// is added and then use it to execute all the enqueued callbacks. Once
// the callbacks are run, we clear the array.
nativeRequestAnimationFrame((timestamp) => {
if (lastNativeAnimationFrameTimestamp >= timestamp) {
// Make sure we only execute the callbacks once for a given frame
return;
}
lastNativeAnimationFrameTimestamp = timestamp;
global.__frameTimestamp = timestamp;
global.__flushAnimationFrame(timestamp);
global.__frameTimestamp = undefined;
Expand Down

0 comments on commit ab02fd8

Please sign in to comment.