Skip to content

Commit 20dc135

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
authored andcommitted
Sync on js thread after animation finished (#54877)
Summary: Pull Request resolved: #54877 This diff adds synchronizing props to react, through a scheduled commit on the js thread. This is used by animated at the end of the animation, and leverages RSNRU to actually push the update to reactjs. Reviewed By: zeyap Differential Revision: D89042949
1 parent 6e6ed05 commit 20dc135

File tree

8 files changed

+91
-74
lines changed

8 files changed

+91
-74
lines changed

packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,6 @@ test('animated opacity', () => {
7070
_opacityAnimation?.stop();
7171
});
7272

73-
// TODO: T246961305 rendered output should be <rn-view opacity="0" /> at this point
74-
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
75-
<rn-view />,
76-
);
77-
78-
// Re-render
79-
Fantom.runTask(() => {
80-
root.render(<MyApp />);
81-
});
82-
8373
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
8474
<rn-view opacity="0" />,
8575
);

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

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,9 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
905905
bool forceFabricCommit,
906906
ShadowNodeFamily::Weak shadowNodeFamily) noexcept {
907907
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
908+
if (forceFabricCommit) {
909+
shouldRequestAsyncFlush_.insert(viewTag);
910+
}
908911
auto& current = layoutStyleUpdated
909912
? updateViewPropsForBackend_[viewTag]
910913
: updateViewPropsDirectForBackend_[viewTag];
@@ -936,6 +939,32 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
936939
}
937940

938941
#ifdef RN_USE_ANIMATION_BACKEND
942+
943+
void NativeAnimatedNodesManager::insertMutations(
944+
std::unordered_map<Tag, std::pair<ShadowNodeFamily::Weak, folly::dynamic>>&
945+
updates,
946+
AnimationMutations& mutations,
947+
AnimatedPropsBuilder& propsBuilder,
948+
bool hasLayoutUpdates) {
949+
for (auto& [tag, update] : updates) {
950+
auto weakFamily = update.first;
951+
952+
if (auto family = weakFamily.lock()) {
953+
propsBuilder.storeDynamic(update.second);
954+
if (shouldRequestAsyncFlush_.contains(tag)) {
955+
mutations.asyncFlushSurfaces.insert(family->getSurfaceId());
956+
}
957+
mutations.batch.push_back(
958+
AnimationMutation{
959+
.tag = tag,
960+
.family = family,
961+
.props = propsBuilder.get(),
962+
.hasLayoutUpdates = hasLayoutUpdates,
963+
});
964+
}
965+
}
966+
}
967+
939968
AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
940969
if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
941970
return {};
@@ -958,7 +987,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
958987
task();
959988
}
960989

961-
AnimationMutations mutations;
990+
AnimationMutations mutations{};
962991

963992
// Step through the animation loop
964993
if (isAnimationUpdateNeeded()) {
@@ -1003,35 +1032,14 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10031032
}
10041033
}
10051034

1006-
for (auto& [tag, update] : updateViewPropsDirectForBackend_) {
1007-
auto weakFamily = update.first;
1008-
1009-
if (auto family = weakFamily.lock()) {
1010-
propsBuilder.storeDynamic(update.second);
1011-
mutations.batch.push_back(
1012-
AnimationMutation{
1013-
.tag = tag,
1014-
.family = family,
1015-
.props = propsBuilder.get(),
1016-
});
1017-
}
1018-
containsChange = true;
1019-
}
1020-
for (auto& [tag, update] : updateViewPropsForBackend_) {
1021-
auto weakFamily = update.first;
1022-
1023-
if (auto family = weakFamily.lock()) {
1024-
propsBuilder.storeDynamic(update.second);
1025-
mutations.batch.push_back(
1026-
AnimationMutation{
1027-
.tag = tag,
1028-
.family = family,
1029-
.props = propsBuilder.get(),
1030-
.hasLayoutUpdates = true,
1031-
});
1032-
}
1033-
containsChange = true;
1034-
}
1035+
insertMutations(
1036+
updateViewPropsDirectForBackend_, mutations, propsBuilder);
1037+
1038+
insertMutations(
1039+
updateViewPropsForBackend_, mutations, propsBuilder, true);
1040+
1041+
containsChange = !updateViewPropsForBackend_.empty() ||
1042+
!updateViewPropsDirectForBackend_.empty();
10351043

10361044
if (containsChange) {
10371045
updateViewPropsDirectForBackend_.clear();
@@ -1062,33 +1070,11 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10621070

10631071
isEventAnimationInProgress_ = false;
10641072

1065-
for (auto& [tag, update] : updateViewPropsDirectForBackend_) {
1066-
auto weakFamily = update.first;
1067-
1068-
if (auto family = weakFamily.lock()) {
1069-
propsBuilder.storeDynamic(update.second);
1070-
mutations.batch.push_back(
1071-
AnimationMutation{
1072-
.tag = tag,
1073-
.family = family,
1074-
.props = propsBuilder.get(),
1075-
});
1076-
}
1077-
}
1078-
for (auto& [tag, update] : updateViewPropsForBackend_) {
1079-
auto weakFamily = update.first;
1080-
1081-
if (auto family = weakFamily.lock()) {
1082-
propsBuilder.storeDynamic(update.second);
1083-
mutations.batch.push_back(
1084-
AnimationMutation{
1085-
.tag = tag,
1086-
.family = family,
1087-
.props = propsBuilder.get(),
1088-
.hasLayoutUpdates = true,
1089-
});
1090-
}
1091-
}
1073+
insertMutations(
1074+
updateViewPropsDirectForBackend_, mutations, propsBuilder);
1075+
1076+
insertMutations(
1077+
updateViewPropsForBackend_, mutations, propsBuilder, true);
10921078

10931079
updateViewPropsForBackend_.clear();
10941080
updateViewPropsDirectForBackend_.clear();
@@ -1097,6 +1083,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10971083
// There is no active animation. Stop the render callback.
10981084
stopRenderCallbackIfNeeded(false);
10991085
}
1086+
shouldRequestAsyncFlush_.clear();
11001087
return mutations;
11011088
}
11021089
#endif

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ class NativeAnimatedNodesManager {
121121
void setAnimatedNodeOffset(Tag tag, double offset);
122122

123123
#ifdef RN_USE_ANIMATION_BACKEND
124+
void insertMutations(
125+
std::unordered_map<Tag, std::pair<ShadowNodeFamily::Weak, folly::dynamic>> &updates,
126+
AnimationMutations &mutations,
127+
AnimatedPropsBuilder &propsBuilder,
128+
bool hasLayoutUpdates = false);
124129
AnimationMutations pullAnimationMutations();
125130
#endif
126131

@@ -263,7 +268,7 @@ class NativeAnimatedNodesManager {
263268
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
264269
std::unordered_map<Tag, std::pair<ShadowNodeFamily::Weak, folly::dynamic>> updateViewPropsForBackend_{};
265270
std::unordered_map<Tag, std::pair<ShadowNodeFamily::Weak, folly::dynamic>> updateViewPropsDirectForBackend_{};
266-
271+
std::unordered_set<Tag> shouldRequestAsyncFlush_{};
267272
/*
268273
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
269274
* NativeAnimated has previously changed the view's props via direct

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
9595
std::move(stopOnRenderCallback_),
9696
std::move(directManipulationCallback),
9797
std::move(fabricCommitCallback),
98-
uiManager);
98+
uiManager,
99+
jsInvoker);
99100

100101
nativeAnimatedNodesManager_ =
101102
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,25 @@ AnimationBackend::AnimationBackend(
5555
StopOnRenderCallback&& stopOnRenderCallback,
5656
DirectManipulationCallback&& directManipulationCallback,
5757
FabricCommitCallback&& fabricCommitCallback,
58-
UIManager* uiManager)
58+
UIManager* uiManager,
59+
std::shared_ptr<CallInvoker> jsInvoker)
5960
: startOnRenderCallback_(std::move(startOnRenderCallback)),
6061
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
6162
directManipulationCallback_(std::move(directManipulationCallback)),
6263
fabricCommitCallback_(std::move(fabricCommitCallback)),
6364
animatedPropsRegistry_(std::make_shared<AnimatedPropsRegistry>()),
6465
uiManager_(uiManager),
66+
jsInvoker_(std::move(jsInvoker)),
6567
commitHook_(uiManager, animatedPropsRegistry_) {}
6668

6769
void AnimationBackend::onAnimationFrame(double timestamp) {
6870
std::unordered_map<SurfaceId, SurfaceUpdates> surfaceUpdates;
71+
std::set<SurfaceId> asyncFlushSurfaces;
6972

7073
for (auto& callback : callbacks) {
71-
auto muatations = callback(static_cast<float>(timestamp));
72-
for (auto& mutation : muatations.batch) {
74+
auto mutations = callback(static_cast<float>(timestamp));
75+
asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces);
76+
for (auto& mutation : mutations.batch) {
7377
const auto family = mutation.family;
7478
react_native_assert(family != nullptr);
7579

@@ -90,6 +94,8 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
9094
synchronouslyUpdateProps(updates.propsMap);
9195
}
9296
}
97+
98+
requestAsyncFlushForSurfaces(asyncFlushSurfaces);
9399
}
94100

95101
void AnimationBackend::start(const Callback& callback, bool isAsync) {
@@ -155,6 +161,25 @@ void AnimationBackend::synchronouslyUpdateProps(
155161
}
156162
}
157163

164+
void AnimationBackend::requestAsyncFlushForSurfaces(
165+
const std::set<SurfaceId>& surfaces) {
166+
for (const auto& surfaceId : surfaces) {
167+
// perform an empty commit on the js thread, to force the commit hook to
168+
// push updated shadow nodes to react through RSNRU
169+
jsInvoker_->invokeAsync([this, surfaceId]() {
170+
uiManager_->getShadowTreeRegistry().visit(
171+
surfaceId, [](const ShadowTree& shadowTree) {
172+
shadowTree.commit(
173+
[](const RootShadowNode& oldRootShadowNode) {
174+
return std::static_pointer_cast<RootShadowNode>(
175+
oldRootShadowNode.ShadowNode::clone({}));
176+
},
177+
{.source = ShadowTreeCommitSource::AnimationEndSync});
178+
});
179+
});
180+
}
181+
}
182+
158183
void AnimationBackend::clearRegistry(SurfaceId surfaceId) {
159184
animatedPropsRegistry_->clear(surfaceId);
160185
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,14 @@
77

88
#pragma once
99

10+
#include <ReactCommon/CallInvoker.h>
1011
#include <folly/dynamic.h>
1112
#include <react/renderer/core/ReactPrimitives.h>
1213
#include <react/renderer/uimanager/UIManager.h>
1314
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
1415
#include <functional>
16+
#include <memory>
17+
#include <set>
1518
#include <vector>
1619
#include "AnimatedProps.h"
1720
#include "AnimatedPropsBuilder.h"
@@ -41,6 +44,7 @@ struct AnimationMutation {
4144

4245
struct AnimationMutations {
4346
std::vector<AnimationMutation> batch;
47+
std::set<SurfaceId> asyncFlushSurfaces;
4448
};
4549

4650
class AnimationBackend : public UIManagerAnimationBackend {
@@ -58,16 +62,19 @@ class AnimationBackend : public UIManagerAnimationBackend {
5862
const FabricCommitCallback fabricCommitCallback_;
5963
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
6064
UIManager *uiManager_;
65+
std::shared_ptr<CallInvoker> jsInvoker_;
6166
AnimationBackendCommitHook commitHook_;
6267

6368
AnimationBackend(
6469
StartOnRenderCallback &&startOnRenderCallback,
6570
StopOnRenderCallback &&stopOnRenderCallback,
6671
DirectManipulationCallback &&directManipulationCallback,
6772
FabricCommitCallback &&fabricCommitCallback,
68-
UIManager *uiManager);
73+
UIManager *uiManager,
74+
std::shared_ptr<CallInvoker> jsInvoker);
6975
void commitUpdates(SurfaceId surfaceId, SurfaceUpdates &surfaceUpdates);
7076
void synchronouslyUpdateProps(const std::unordered_map<Tag, AnimatedProps> &updates);
77+
void requestAsyncFlushForSurfaces(const std::set<SurfaceId> &surfaces);
7178
void clearRegistry(SurfaceId surfaceId) override;
7279

7380
void onAnimationFrame(double timestamp) override;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit(
2121
const RootShadowNode::Shared& oldRootShadowNode,
2222
const RootShadowNode::Unshared& newRootShadowNode,
2323
const ShadowTreeCommitOptions& commitOptions) noexcept {
24-
if (commitOptions.source != ShadowTreeCommitSource::React) {
24+
if (commitOptions.source != ShadowTreeCommitSource::React &&
25+
commitOptions.source != ShadowTreeCommitSource::AnimationEndSync) {
2526
return newRootShadowNode;
2627
}
2728

packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ enum class ShadowTreeCommitMode {
5050
enum class ShadowTreeCommitSource {
5151
Unknown,
5252
React,
53+
AnimationEndSync,
5354
};
5455

5556
struct ShadowTreeCommitOptions {

0 commit comments

Comments
 (0)