Skip to content

Commit e4f0509

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
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. ## Changelog: [General][Added] - Add pushAnimationMutations to AnimationBackend for targeted event-driven animation updates Reviewed By: zeyap Differential Revision: D100164749 fbshipit-source-id: 53d36ed316614baa835707a45361ae8f3b828d26
1 parent 7d51201 commit e4f0509

File tree

12 files changed

+126
-30
lines changed

12 files changed

+126
-30
lines changed

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
@@ -59,10 +59,19 @@ class AnimationBackend : public UIManagerAnimationBackend {
5959

6060
void onAnimationFrame(AnimationTimestamp timestamp) override;
6161
void trigger() override;
62+
void pushAnimationMutations(const Callback &callback) override;
6263
CallbackId start(const Callback &callback) override;
6364
void stop(CallbackId callbackId) override;
6465

6566
private:
67+
void unpackMutations(
68+
AnimationMutations &mutations,
69+
std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates,
70+
std::set<SurfaceId> &asyncFlushSurfaces);
71+
void applySurfaceUpdates(
72+
std::unordered_map<SurfaceId, SurfaceUpdates> &surfaceUpdates,
73+
const std::set<SurfaceId> &asyncFlushSurfaces);
74+
void applyMutations(AnimationMutations mutations);
6675
std::vector<CallbackWithId> callbacks;
6776
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
6877
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

scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima
15461546
public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override;
15471547
public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override;
15481548
public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override;
1549+
public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override;
15491550
public virtual void registerJSInvoker(std::shared_ptr<facebook::react::CallInvoker> jsInvoker) override;
15501551
public virtual void stop(facebook::react::CallbackId callbackId) override;
15511552
public virtual void trigger() override;
@@ -1562,6 +1563,7 @@ class facebook::react::AnimationBackendCommitHook : public facebook::react::UIMa
15621563
}
15631564

15641565
class facebook::react::AnimationChoreographer {
1566+
public virtual facebook::react::AnimationTimestamp now() const;
15651567
public virtual void pause() = 0;
15661568
public virtual void resume() = 0;
15671569
public virtual ~AnimationChoreographer() = default;
@@ -2814,7 +2816,7 @@ class facebook::react::JReactHostInspectorTarget : public jni::HybridClass<faceb
28142816
public static void registerNatives();
28152817
public virtual facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() override;
28162818
public virtual facebook::react::jsinspector_modern::HostTargetTracingDelegate* getTracingDelegate() override;
2817-
public virtual void captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&, const std::function<void(std::optional<std::string> base64Data)>& callback) override;
2819+
public virtual std::optional<std::string> captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&) override;
28182820
public virtual void onReload(const facebook::react::jsinspector_modern::HostTargetDelegate::PageReloadRequest& request) override;
28192821
public virtual void onSetPausedInDebuggerMessage(const facebook::react::jsinspector_modern::HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest& request) override;
28202822
public void loadNetworkResource(const facebook::react::jsinspector_modern::LoadNetworkResourceRequest& params, facebook::react::jsinspector_modern::ScopedExecutor<facebook::react::jsinspector_modern::NetworkRequestListener> executor) override;
@@ -5211,6 +5213,7 @@ class facebook::react::UIManagerAnimationBackend {
52115213
public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0;
52125214
public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0;
52135215
public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0;
5216+
public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0;
52145217
public virtual void registerJSInvoker(std::shared_ptr<facebook::react::CallInvoker> jsInvoker) = 0;
52155218
public virtual void stop(facebook::react::CallbackId callbackId) = 0;
52165219
public virtual void trigger() = 0;
@@ -10379,7 +10382,7 @@ class facebook::react::jsinspector_modern::HostTargetDelegate : public facebook:
1037910382
public facebook::react::jsinspector_modern::HostTargetDelegate& operator=(facebook::react::jsinspector_modern::HostTargetDelegate&&) = delete;
1038010383
public virtual facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() = 0;
1038110384
public virtual facebook::react::jsinspector_modern::HostTargetTracingDelegate* getTracingDelegate();
10382-
public virtual void captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&, const std::function<void(std::optional<std::string> base64Data)>& callback);
10385+
public virtual std::optional<std::string> captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&);
1038310386
public virtual void loadNetworkResource(const facebook::react::jsinspector_modern::LoadNetworkResourceRequest&, facebook::react::jsinspector_modern::ScopedExecutor<facebook::react::jsinspector_modern::NetworkRequestListener>) override;
1038410387
public virtual void onReload(const facebook::react::jsinspector_modern::HostTargetDelegate::PageReloadRequest& request) = 0;
1038510388
public virtual void onSetPausedInDebuggerMessage(const facebook::react::jsinspector_modern::HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest& request) = 0;
@@ -12930,6 +12933,8 @@ class facebook::yoga::StyleSizeLength {
1293012933
class facebook::yoga::StyleValueHandle {
1293112934
public constexpr bool isAuto() const;
1293212935
public constexpr bool isDefined() const;
12936+
public constexpr bool isPercent() const;
12937+
public constexpr bool isPoint() const;
1293312938
public constexpr bool isUndefined() const;
1293412939
public static constexpr facebook::yoga::StyleValueHandle ofAuto();
1293512940
}
@@ -12938,6 +12943,7 @@ class facebook::yoga::StyleValuePool {
1293812943
public facebook::yoga::FloatOptional getNumber(facebook::yoga::StyleValueHandle handle) const;
1293912944
public facebook::yoga::StyleLength getLength(facebook::yoga::StyleValueHandle handle) const;
1294012945
public facebook::yoga::StyleSizeLength getSize(facebook::yoga::StyleValueHandle handle) const;
12946+
public float getStoredValue(facebook::yoga::StyleValueHandle handle) const;
1294112947
public void store(facebook::yoga::StyleValueHandle& handle, facebook::yoga::FloatOptional number);
1294212948
public void store(facebook::yoga::StyleValueHandle& handle, facebook::yoga::StyleLength length);
1294312949
public void store(facebook::yoga::StyleValueHandle& handle, facebook::yoga::StyleSizeLength sizeValue);

scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,7 @@ class facebook::react::AnimationBackend : public facebook::react::UIManagerAnima
15451545
public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) override;
15461546
public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) override;
15471547
public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) override;
1548+
public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) override;
15481549
public virtual void registerJSInvoker(std::shared_ptr<facebook::react::CallInvoker> jsInvoker) override;
15491550
public virtual void stop(facebook::react::CallbackId callbackId) override;
15501551
public virtual void trigger() override;
@@ -1561,6 +1562,7 @@ class facebook::react::AnimationBackendCommitHook : public facebook::react::UIMa
15611562
}
15621563

15631564
class facebook::react::AnimationChoreographer {
1565+
public virtual facebook::react::AnimationTimestamp now() const;
15641566
public virtual void pause() = 0;
15651567
public virtual void resume() = 0;
15661568
public virtual ~AnimationChoreographer() = default;
@@ -2811,7 +2813,7 @@ class facebook::react::JReactHostInspectorTarget : public jni::HybridClass<faceb
28112813
public static void registerNatives();
28122814
public virtual facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() override;
28132815
public virtual facebook::react::jsinspector_modern::HostTargetTracingDelegate* getTracingDelegate() override;
2814-
public virtual void captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&, const std::function<void(std::optional<std::string> base64Data)>& callback) override;
2816+
public virtual std::optional<std::string> captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&) override;
28152817
public virtual void onReload(const facebook::react::jsinspector_modern::HostTargetDelegate::PageReloadRequest& request) override;
28162818
public virtual void onSetPausedInDebuggerMessage(const facebook::react::jsinspector_modern::HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest& request) override;
28172819
public void loadNetworkResource(const facebook::react::jsinspector_modern::LoadNetworkResourceRequest& params, facebook::react::jsinspector_modern::ScopedExecutor<facebook::react::jsinspector_modern::NetworkRequestListener> executor) override;
@@ -5202,6 +5204,7 @@ class facebook::react::UIManagerAnimationBackend {
52025204
public virtual facebook::react::CallbackId start(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0;
52035205
public virtual void clearRegistry(facebook::react::SurfaceId surfaceId) = 0;
52045206
public virtual void onAnimationFrame(facebook::react::AnimationTimestamp timestamp) = 0;
5207+
public virtual void pushAnimationMutations(const facebook::react::UIManagerAnimationBackend::Callback& callback) = 0;
52055208
public virtual void registerJSInvoker(std::shared_ptr<facebook::react::CallInvoker> jsInvoker) = 0;
52065209
public virtual void stop(facebook::react::CallbackId callbackId) = 0;
52075210
public virtual void trigger() = 0;
@@ -10235,7 +10238,7 @@ class facebook::react::jsinspector_modern::HostTargetDelegate : public facebook:
1023510238
public facebook::react::jsinspector_modern::HostTargetDelegate& operator=(facebook::react::jsinspector_modern::HostTargetDelegate&&) = delete;
1023610239
public virtual facebook::react::jsinspector_modern::HostTargetMetadata getMetadata() = 0;
1023710240
public virtual facebook::react::jsinspector_modern::HostTargetTracingDelegate* getTracingDelegate();
10238-
public virtual void captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&, const std::function<void(std::optional<std::string> base64Data)>& callback);
10241+
public virtual std::optional<std::string> captureScreenshot(const facebook::react::jsinspector_modern::HostTargetDelegate::PageCaptureScreenshotRequest&);
1023910242
public virtual void loadNetworkResource(const facebook::react::jsinspector_modern::LoadNetworkResourceRequest&, facebook::react::jsinspector_modern::ScopedExecutor<facebook::react::jsinspector_modern::NetworkRequestListener>) override;
1024010243
public virtual void onReload(const facebook::react::jsinspector_modern::HostTargetDelegate::PageReloadRequest& request) = 0;
1024110244
public virtual void onSetPausedInDebuggerMessage(const facebook::react::jsinspector_modern::HostTargetDelegate::OverlaySetPausedInDebuggerMessageRequest& request) = 0;
@@ -12786,6 +12789,8 @@ class facebook::yoga::StyleSizeLength {
1278612789
class facebook::yoga::StyleValueHandle {
1278712790
public constexpr bool isAuto() const;
1278812791
public constexpr bool isDefined() const;
12792+
public constexpr bool isPercent() const;
12793+
public constexpr bool isPoint() const;
1278912794
public constexpr bool isUndefined() const;
1279012795
public static constexpr facebook::yoga::StyleValueHandle ofAuto();
1279112796
}
@@ -12794,6 +12799,7 @@ class facebook::yoga::StyleValuePool {
1279412799
public facebook::yoga::FloatOptional getNumber(facebook::yoga::StyleValueHandle handle) const;
1279512800
public facebook::yoga::StyleLength getLength(facebook::yoga::StyleValueHandle handle) const;
1279612801
public facebook::yoga::StyleSizeLength getSize(facebook::yoga::StyleValueHandle handle) const;
12802+
public float getStoredValue(facebook::yoga::StyleValueHandle handle) const;
1279712803
public void store(facebook::yoga::StyleValueHandle& handle, facebook::yoga::FloatOptional number);
1279812804
public void store(facebook::yoga::StyleValueHandle& handle, facebook::yoga::StyleLength length);
1279912805
public void store(facebook::yoga::StyleValueHandle& handle, facebook::yoga::StyleSizeLength sizeValue);

0 commit comments

Comments
 (0)