Skip to content

Commit 16e6e32

Browse files
Bartlomiej Bloniarzalanjhughes
authored andcommitted
Add pushAnimationMutations to the AnimationBackend (#56401)
Summary: Pull Request resolved: #56401 Add `pushAnimationMutations(Callback)` to the AnimationBackend as a targeted alternative to `trigger()`. The existing `trigger()` method has two problems: 1. **Blast radius**: It calls `onAnimationFrame()` which invokes ALL registered callbacks. When one animation frontend (e.g. Animated) calls `trigger()` in response to an event, every other frontend (e.g. Reanimated) also spins up unnecessarily. 2. **Broken timestamp on iOS**: `trigger()` uses `std::chrono::steady_clock` which on iOS maps to a different kernel clock than what `CADisplayLink` uses for vsync timestamps. These clocks have different baselines and can diverge over time (e.g. after device sleep), causing animations to see time jumps. `pushAnimationMutations(Callback)` fixes both issues: - Executes only the provided callback, not all registered ones - Uses `AnimationChoreographer::now()` which delegates to `HighResTimeStamp`, providing a timestamp from the same clock as the vsync path on each platform Also refactors `onAnimationFrame` to use `unpackMutations`/`applySurfaceUpdates` helpers, avoiding intermediate vector/set merging when accumulating mutations from multiple callbacks. [General][Added] - Add pushAnimationMutations to AnimationBackend for targeted event-driven animation updates Reviewed By: zeyap Differential Revision: D100164749 fbshipit-source-id: 53d36ed316614baa835707a45361ae8f3b828d26
1 parent c634640 commit 16e6e32

6 files changed

Lines changed: 82 additions & 22 deletions

File tree

packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,10 @@ void NativeAnimatedNodesManager::handleAnimatedEvent(
515515
// frames.
516516
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
517517
if (auto animationBackend = animationBackend_.lock()) {
518-
animationBackend->trigger();
518+
animationBackend->pushAnimationMutations(
519+
[this](AnimationTimestamp timestamp) -> AnimationMutations {
520+
return pullAnimationMutations(timestamp);
521+
});
519522
}
520523
} else {
521524
onRender();

packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,31 +51,27 @@ AnimationBackend::AnimationBackend(
5151
react_native_assert(uiManager_.expired() == false);
5252
}
5353

54-
void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) {
55-
std::vector<CallbackWithId> callbacksCopy;
56-
std::unordered_map<SurfaceId, SurfaceUpdates> surfaceUpdates;
57-
std::set<SurfaceId> asyncFlushSurfaces;
54+
void AnimationBackend::unpackMutations(
55+
AnimationMutations& mutations,
56+
std::unordered_map<SurfaceId, SurfaceUpdates>& surfaceUpdates,
57+
std::set<SurfaceId>& asyncFlushSurfaces) {
58+
for (auto& mutation : mutations.batch) {
59+
const auto family = mutation.family;
60+
react_native_assert(family != nullptr);
5861

59-
{
60-
std::lock_guard lock(mutex_);
61-
callbacksCopy = callbacks;
62+
auto& [families, updates, hasLayoutUpdates] =
63+
surfaceUpdates[family->getSurfaceId()];
64+
hasLayoutUpdates |= mutation.hasLayoutUpdates;
65+
families.insert(family);
66+
updates[mutation.tag] = std::move(mutation.props);
6267
}
6368

64-
for (auto& callbackWithId : callbacksCopy) {
65-
auto mutations = callbackWithId.callback(timestamp);
66-
asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces);
67-
for (auto& mutation : mutations.batch) {
68-
const auto family = mutation.family;
69-
react_native_assert(family != nullptr);
70-
71-
auto& [families, updates, hasLayoutUpdates] =
72-
surfaceUpdates[family->getSurfaceId()];
73-
hasLayoutUpdates |= mutation.hasLayoutUpdates;
74-
families.insert(family);
75-
updates[mutation.tag] = std::move(mutation.props);
76-
}
77-
}
69+
asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces);
70+
}
7871

72+
void AnimationBackend::applySurfaceUpdates(
73+
std::unordered_map<SurfaceId, SurfaceUpdates>& surfaceUpdates,
74+
const std::set<SurfaceId>& asyncFlushSurfaces) {
7975
animatedPropsRegistry_->update(surfaceUpdates);
8076

8177
for (auto& [surfaceId, updates] : surfaceUpdates) {
@@ -89,6 +85,30 @@ void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) {
8985
requestAsyncFlushForSurfaces(asyncFlushSurfaces);
9086
}
9187

88+
void AnimationBackend::applyMutations(AnimationMutations mutations) {
89+
std::unordered_map<SurfaceId, SurfaceUpdates> surfaceUpdates;
90+
std::set<SurfaceId> asyncFlushSurfaces;
91+
unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces);
92+
applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces);
93+
}
94+
95+
void AnimationBackend::onAnimationFrame(AnimationTimestamp timestamp) {
96+
std::vector<CallbackWithId> callbacksCopy;
97+
98+
{
99+
std::lock_guard lock(mutex_);
100+
callbacksCopy = callbacks;
101+
}
102+
103+
std::unordered_map<SurfaceId, SurfaceUpdates> surfaceUpdates;
104+
std::set<SurfaceId> asyncFlushSurfaces;
105+
for (auto& callbackWithId : callbacksCopy) {
106+
auto mutations = callbackWithId.callback(timestamp);
107+
unpackMutations(mutations, surfaceUpdates, asyncFlushSurfaces);
108+
}
109+
applySurfaceUpdates(surfaceUpdates, asyncFlushSurfaces);
110+
}
111+
92112
CallbackId AnimationBackend::start(const Callback& callback) {
93113
std::lock_guard lock(mutex_);
94114

@@ -123,6 +143,12 @@ void AnimationBackend::trigger() {
123143
onAnimationFrame(std::chrono::steady_clock::now().time_since_epoch());
124144
}
125145

146+
void AnimationBackend::pushAnimationMutations(const Callback& callback) {
147+
auto timestamp = animationChoreographer_->now();
148+
auto mutations = callback(timestamp);
149+
applyMutations(std::move(mutations));
150+
}
151+
126152
void AnimationBackend::commitUpdates(
127153
SurfaceId surfaceId,
128154
SurfaceUpdates& surfaceUpdates) {

packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,19 @@ class AnimationBackend : public UIManagerAnimationBackend {
6060

6161
void onAnimationFrame(AnimationTimestamp timestamp) override;
6262
void trigger() override;
63+
void pushAnimationMutations(const Callback &callback) override;
6364
CallbackId start(const Callback &callback) override;
6465
void stop(CallbackId callbackId) override;
6566

6667
private:
68+
void unpackMutations(
69+
AnimationMutations &mutations,
70+
std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates,
71+
std::set<SurfaceId> &asyncFlushSurfaces);
72+
void applySurfaceUpdates(
73+
std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates,
74+
const std::set<SurfaceId> &asyncFlushSurfaces);
75+
void applyMutations(AnimationMutations mutations);
6776
std::vector<CallbackWithId> callbacks;
6877
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
6978
std::shared_ptr<AnimationChoreographer> animationChoreographer_;

packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationChoreographer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#pragma once
99

1010
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
11+
#include <react/timing/primitives.h>
1112

1213
namespace facebook::react {
1314

@@ -21,6 +22,10 @@ class AnimationChoreographer {
2122

2223
virtual void resume() = 0;
2324
virtual void pause() = 0;
25+
virtual AnimationTimestamp now() const
26+
{
27+
return HighResTimeStamp::now().toChronoSteadyClockTimePoint().time_since_epoch();
28+
}
2429
void setAnimationBackend(std::weak_ptr<UIManagerAnimationBackend> animationBackend)
2530
{
2631
animationBackend_ = animationBackend;

packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerAnimationBackend.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class UIManagerAnimationBackend {
3131
virtual void stop(CallbackId callbackId) = 0;
3232
virtual void clearRegistry(SurfaceId surfaceId) = 0;
3333
virtual void trigger() = 0;
34+
virtual void pushAnimationMutations(const Callback &callback) = 0;
3435
virtual void registerJSInvoker(std::shared_ptr<CallInvoker> jsInvoker) = 0;
3536
};
3637

private/react-native-fantom/tester/src/TesterAnimationChoreographer.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#pragma once
99

10+
#include <functional>
11+
1012
#include <react/renderer/animationbackend/AnimationChoreographer.h>
1113
#include <react/renderer/core/ReactPrimitives.h>
1214
#include <react/runtime/ReactInstanceConfig.h>
@@ -19,8 +21,22 @@ class TesterAnimationChoreographer : public AnimationChoreographer {
1921
void pause() override;
2022
void runUITick(AnimationTimestamp timestamp);
2123

24+
AnimationTimestamp now() const override
25+
{
26+
if (clockProvider_) {
27+
return clockProvider_();
28+
}
29+
return AnimationChoreographer::now();
30+
}
31+
32+
void setClockProvider(std::function<AnimationTimestamp()> clockProvider)
33+
{
34+
clockProvider_ = std::move(clockProvider);
35+
}
36+
2237
private:
2338
bool isPaused_{false};
39+
std::function<AnimationTimestamp()> clockProvider_;
2440
};
2541

2642
} // namespace facebook::react

0 commit comments

Comments
 (0)