Skip to content

Commit f9e94c0

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. # Changelog [General] [Added] - ShadowTreeCommitSource::AnimationEndSync [General] [Changed] - animated can now prompt the backend to push changes to the shadowTree on the JS thread, making RSNRU update the ShadowNode references held by the react renderer. Reviewed By: zeyap Differential Revision: D89042949 fbshipit-source-id: 26be5f81276b140bbc6498177c4dc83c90f1083d
1 parent af96497 commit f9e94c0

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
@@ -908,6 +908,9 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
908908
bool forceFabricCommit,
909909
ShadowNodeFamily::Weak shadowNodeFamily) noexcept {
910910
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
911+
if (forceFabricCommit) {
912+
shouldRequestAsyncFlush_.insert(viewTag);
913+
}
911914
auto& current = layoutStyleUpdated
912915
? updateViewPropsForBackend_[viewTag]
913916
: updateViewPropsDirectForBackend_[viewTag];
@@ -939,6 +942,32 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
939942
}
940943

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

964-
AnimationMutations mutations;
993+
AnimationMutations mutations{};
965994

966995
// Step through the animation loop
967996
if (isAnimationUpdateNeeded()) {
@@ -1006,35 +1035,14 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10061035
}
10071036
}
10081037

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

10391047
if (containsChange) {
10401048
updateViewPropsDirectForBackend_.clear();
@@ -1065,33 +1073,11 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10651073

10661074
isEventAnimationInProgress_ = false;
10671075

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

10961082
updateViewPropsForBackend_.clear();
10971083
updateViewPropsDirectForBackend_.clear();
@@ -1100,6 +1086,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
11001086
// There is no active animation. Stop the render callback.
11011087
stopRenderCallbackIfNeeded(false);
11021088
}
1089+
shouldRequestAsyncFlush_.clear();
11031090
return mutations;
11041091
}
11051092
#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
@@ -65,21 +65,25 @@ AnimationBackend::AnimationBackend(
6565
StopOnRenderCallback&& stopOnRenderCallback,
6666
DirectManipulationCallback&& directManipulationCallback,
6767
FabricCommitCallback&& fabricCommitCallback,
68-
UIManager* uiManager)
68+
UIManager* uiManager,
69+
std::shared_ptr<CallInvoker> jsInvoker)
6970
: startOnRenderCallback_(std::move(startOnRenderCallback)),
7071
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
7172
directManipulationCallback_(std::move(directManipulationCallback)),
7273
fabricCommitCallback_(std::move(fabricCommitCallback)),
7374
animatedPropsRegistry_(std::make_shared<AnimatedPropsRegistry>()),
7475
uiManager_(uiManager),
76+
jsInvoker_(std::move(jsInvoker)),
7577
commitHook_(uiManager, animatedPropsRegistry_) {}
7678

7779
void AnimationBackend::onAnimationFrame(double timestamp) {
7880
std::unordered_map<SurfaceId, SurfaceUpdates> surfaceUpdates;
81+
std::set<SurfaceId> asyncFlushSurfaces;
7982

8083
for (auto& callback : callbacks) {
81-
auto muatations = callback(static_cast<float>(timestamp));
82-
for (auto& mutation : muatations.batch) {
84+
auto mutations = callback(static_cast<float>(timestamp));
85+
asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces);
86+
for (auto& mutation : mutations.batch) {
8387
const auto family = mutation.family;
8488
react_native_assert(family != nullptr);
8589

@@ -100,6 +104,8 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
100104
synchronouslyUpdateProps(updates.propsMap);
101105
}
102106
}
107+
108+
requestAsyncFlushForSurfaces(asyncFlushSurfaces);
103109
}
104110

105111
void AnimationBackend::start(const Callback& callback, bool isAsync) {
@@ -165,6 +171,25 @@ void AnimationBackend::synchronouslyUpdateProps(
165171
}
166172
}
167173

174+
void AnimationBackend::requestAsyncFlushForSurfaces(
175+
const std::set<SurfaceId>& surfaces) {
176+
for (const auto& surfaceId : surfaces) {
177+
// perform an empty commit on the js thread, to force the commit hook to
178+
// push updated shadow nodes to react through RSNRU
179+
jsInvoker_->invokeAsync([this, surfaceId]() {
180+
uiManager_->getShadowTreeRegistry().visit(
181+
surfaceId, [](const ShadowTree& shadowTree) {
182+
shadowTree.commit(
183+
[](const RootShadowNode& oldRootShadowNode) {
184+
return std::static_pointer_cast<RootShadowNode>(
185+
oldRootShadowNode.ShadowNode::clone({}));
186+
},
187+
{.source = ShadowTreeCommitSource::AnimationEndSync});
188+
});
189+
});
190+
}
191+
}
192+
168193
void AnimationBackend::clearRegistry(SurfaceId surfaceId) {
169194
animatedPropsRegistry_->clear(surfaceId);
170195
}

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)