Skip to content

Commit 30a7cf0

Browse files
Bartlomiej Bloniarzmeta-codesync[bot]
authored andcommitted
Reduce animation registry copying and allocation (#58775)
Summary: Pull Request resolved: #58775 For every raw update, `AnimatedPropsRegistry::update` copied the incoming props before merging them, and it built a full typed view-props snapshot even when no typed prop was animated. It now merges from the incoming `folly::dynamic` without copying it and allocates typed props only when a typed update arrives. The merge semantics and the caller's input are unchanged. On a low-end Android phone (4× Cortex-A53) with 12 views animating opacity, the registry update per frame dropped from about 0.22 ms to 0.13 ms. ## Changelog: [Internal] Reviewed By: zeyap Differential Revision: D122570619
1 parent aa62cbf commit 30a7cf0

12 files changed

Lines changed: 331 additions & 97 deletions

‎packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,27 @@ void AnimatedPropsRegistry::update(
4646
.first;
4747
}
4848
auto& snapshot = it->second;
49-
auto& viewProps = snapshot->props;
50-
5149
if (animatedProps.rawProps) {
5250
const auto& newRawProps = *animatedProps.rawProps;
5351
auto& currentRawProps = snapshot->rawProps;
5452

5553
if (currentRawProps) {
56-
mergeAnimatedRawProps(*currentRawProps, newRawProps.toDynamic());
54+
if (const auto* dynamic = newRawProps.getDynamic()) {
55+
mergeAnimatedRawProps(*currentRawProps, *dynamic);
56+
} else {
57+
mergeAnimatedRawProps(*currentRawProps, newRawProps.toDynamic());
58+
}
5759
} else {
5860
currentRawProps =
5961
std::make_unique<folly::dynamic>(newRawProps.toDynamic());
6062
}
6163
}
64+
if (!animatedProps.props.empty() && !snapshot->props) {
65+
snapshot->props = std::make_unique<BaseViewProps>();
66+
}
6267
for (const auto& animatedProp : animatedProps.props) {
6368
snapshot->propNames.insert(animatedProp->propName);
64-
cloneProp(viewProps, *animatedProp);
69+
cloneProp(*snapshot->props, *animatedProp);
6570
}
6671
}
6772
}
@@ -97,9 +102,14 @@ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) {
97102
currentSnapshot->rawProps = std::move(propsSnapshot->rawProps);
98103
}
99104
}
100-
for (auto& propName : propsSnapshot->propNames) {
101-
currentSnapshot->propNames.insert(propName);
102-
updateProp(propName, currentSnapshot->props, *propsSnapshot);
105+
if (!currentSnapshot->props) {
106+
currentSnapshot->props = std::move(propsSnapshot->props);
107+
currentSnapshot->propNames = std::move(propsSnapshot->propNames);
108+
} else {
109+
for (auto& propName : propsSnapshot->propNames) {
110+
currentSnapshot->propNames.insert(propName);
111+
updateProp(propName, *currentSnapshot->props, *propsSnapshot);
112+
}
103113
}
104114
}
105115
}

‎packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h‎

Lines changed: 86 additions & 81 deletions
Large diffs are not rendered by default.
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <gtest/gtest.h>
9+
#include <hermes/hermes.h>
10+
#include <react/renderer/animationbackend/AnimatedPropsBuilder.h>
11+
#include <react/renderer/animationbackend/AnimatedPropsRegistry.h>
12+
#include <react/renderer/components/view/ViewComponentDescriptor.h>
13+
#include <react/renderer/element/ComponentBuilder.h>
14+
#include <react/renderer/element/Element.h>
15+
#include <react/renderer/element/testUtils.h>
16+
#include <react/utils/ContextContainer.h>
17+
18+
namespace facebook::react {
19+
20+
class AnimatedPropsRegistryTest : public ::testing::Test {
21+
protected:
22+
ComponentBuilder builder =
23+
simpleComponentBuilder(std::make_shared<ContextContainer>());
24+
std::shared_ptr<ViewShadowNode> node =
25+
builder.build(Element<ViewShadowNode>().surfaceId(1).tag(10));
26+
AnimatedPropsRegistry registry;
27+
28+
void SetUp() override {
29+
registry.initializeSurface(1);
30+
}
31+
32+
void update(AnimatedProps props) {
33+
std::vector<AnimationMutations> batches(1);
34+
batches[0].batch.push_back(
35+
AnimationMutation{
36+
.tag = 10,
37+
.family = node->getFamilyShared(),
38+
.props = std::move(props)});
39+
registry.update(batches);
40+
}
41+
42+
void updateRaw(folly::dynamic props) {
43+
update(
44+
{.props = {},
45+
.rawProps = std::make_unique<RawProps>(std::move(props))});
46+
}
47+
48+
void expectRawProps(const folly::dynamic& expected) {
49+
EXPECT_EQ(*snapshot().rawProps, expected);
50+
}
51+
52+
PropsSnapshot& snapshot() {
53+
return *registry.getMap(1).second.at(10);
54+
}
55+
};
56+
57+
TEST_F(AnimatedPropsRegistryTest, rawUpdatesDoNotAllocateTypedProps) {
58+
for (int frame = 0; frame < 10; frame++) {
59+
updateRaw(folly::dynamic::object("opacity", frame / 10.0));
60+
auto& props = snapshot();
61+
EXPECT_EQ(props.props, nullptr);
62+
EXPECT_TRUE(props.propNames.empty());
63+
EXPECT_EQ((*props.rawProps)["opacity"], frame / 10.0);
64+
}
65+
}
66+
67+
TEST_F(AnimatedPropsRegistryTest, updatePreservesInputForSynchronousMount) {
68+
folly::dynamic rawProps = folly::dynamic::object("opacity", 0.25)(
69+
"transform",
70+
folly::dynamic::array(folly::dynamic::object("translateX", 10)));
71+
std::vector<AnimationMutations> batches(1);
72+
batches[0].batch.push_back(
73+
AnimationMutation{
74+
.tag = 10,
75+
.family = node->getFamilyShared(),
76+
.props = AnimatedProps{
77+
.props = {}, .rawProps = std::make_unique<RawProps>(rawProps)}});
78+
registry.update(batches);
79+
registry.update(batches);
80+
EXPECT_EQ(batches[0].batch[0].props.rawProps->toDynamic(), rawProps);
81+
EXPECT_EQ(*snapshot().rawProps, rawProps);
82+
}
83+
84+
TEST_F(AnimatedPropsRegistryTest, mergesPendingUpdatesPerKey) {
85+
updateRaw(
86+
folly::dynamic::object("opacity", 0.25)(
87+
"shadowOffset", folly::dynamic::object("width", 2)("height", 3)));
88+
updateRaw(
89+
folly::dynamic::object(
90+
"shadowOffset", folly::dynamic::object("width", 7)));
91+
expectRawProps(
92+
folly::dynamic::object("opacity", 0.25)(
93+
"shadowOffset", folly::dynamic::object("width", 7)));
94+
}
95+
96+
TEST_F(AnimatedPropsRegistryTest, mergesCommittedUpdatesPerKey) {
97+
updateRaw(
98+
folly::dynamic::object("opacity", 0.25)(
99+
"shadowOffset", folly::dynamic::object("width", 2)("height", 3)));
100+
snapshot();
101+
updateRaw(
102+
folly::dynamic::object(
103+
"shadowOffset", folly::dynamic::object("width", 7)));
104+
expectRawProps(
105+
folly::dynamic::object("opacity", 0.25)(
106+
"shadowOffset", folly::dynamic::object("width", 7)));
107+
}
108+
109+
TEST_F(AnimatedPropsRegistryTest, nullResetsArePreservedInPendingProps) {
110+
updateRaw(folly::dynamic::object("opacity", 0.25)("borderRadius", 4));
111+
updateRaw(folly::dynamic::object("opacity", nullptr));
112+
expectRawProps(folly::dynamic::object("opacity", nullptr)("borderRadius", 4));
113+
}
114+
115+
TEST_F(AnimatedPropsRegistryTest, nullResetsArePreservedInCommittedProps) {
116+
updateRaw(folly::dynamic::object("opacity", 0.25)("borderRadius", 4));
117+
snapshot();
118+
updateRaw(folly::dynamic::object("opacity", nullptr));
119+
expectRawProps(folly::dynamic::object("opacity", nullptr)("borderRadius", 4));
120+
updateRaw(folly::dynamic::object("opacity", 0.5));
121+
expectRawProps(folly::dynamic::object("opacity", 0.5)("borderRadius", 4));
122+
}
123+
124+
TEST_F(AnimatedPropsRegistryTest, arraysReplaceInsteadOfMerging) {
125+
updateRaw(
126+
folly::dynamic::object(
127+
"transform",
128+
folly::dynamic::array(
129+
folly::dynamic::object("translateX", 2),
130+
folly::dynamic::object("scale", 3))));
131+
snapshot();
132+
auto transform =
133+
folly::dynamic::array(folly::dynamic::object("rotate", "1rad"));
134+
updateRaw(folly::dynamic::object("transform", transform));
135+
EXPECT_EQ((*snapshot().rawProps)["transform"], transform);
136+
}
137+
138+
TEST_F(AnimatedPropsRegistryTest, valuesReplaceRegardlessOfType) {
139+
updateRaw(folly::dynamic::object("value", 5));
140+
snapshot();
141+
folly::dynamic object = folly::dynamic::object("width", 2)("height", nullptr);
142+
updateRaw(folly::dynamic::object("value", object));
143+
expectRawProps(folly::dynamic::object("value", object));
144+
updateRaw(folly::dynamic::object("value", 7));
145+
expectRawProps(folly::dynamic::object("value", 7));
146+
}
147+
148+
TEST_F(AnimatedPropsRegistryTest, typedPropsCanFollowRawProps) {
149+
updateRaw(folly::dynamic::object("opacity", 0.25));
150+
snapshot();
151+
AnimatedPropsBuilder builder;
152+
builder.setOpacity(0.75);
153+
update(builder.get());
154+
auto& props = snapshot();
155+
ASSERT_NE(props.props, nullptr);
156+
EXPECT_FLOAT_EQ(props.props->opacity, 0.75);
157+
EXPECT_TRUE(props.propNames.contains(OPACITY));
158+
EXPECT_EQ((*props.rawProps)["opacity"], 0.25);
159+
BaseViewProps viewProps;
160+
updateProp(OPACITY, viewProps, props);
161+
EXPECT_FLOAT_EQ(viewProps.opacity, 0.75);
162+
}
163+
164+
TEST_F(AnimatedPropsRegistryTest, rawPropsCanFollowTypedProps) {
165+
AnimatedPropsBuilder builder;
166+
builder.setOpacity(0.75);
167+
update(builder.get());
168+
snapshot();
169+
updateRaw(folly::dynamic::object("opacity", 0.25));
170+
auto& props = snapshot();
171+
ASSERT_NE(props.props, nullptr);
172+
EXPECT_FLOAT_EQ(props.props->opacity, 0.75);
173+
EXPECT_TRUE(props.propNames.contains(OPACITY));
174+
EXPECT_EQ((*props.rawProps)["opacity"], 0.25);
175+
}
176+
177+
TEST_F(AnimatedPropsRegistryTest, mergesTypedUpdatesAcrossCommits) {
178+
AnimatedPropsBuilder builder;
179+
builder.setOpacity(0.75);
180+
builder.setShadowRadius(2);
181+
update(builder.get());
182+
snapshot();
183+
builder.setOpacity(0.5);
184+
update(builder.get());
185+
auto& props = snapshot();
186+
ASSERT_NE(props.props, nullptr);
187+
EXPECT_FLOAT_EQ(props.props->opacity, 0.5);
188+
EXPECT_FLOAT_EQ(props.props->shadowRadius, 2);
189+
EXPECT_EQ(props.propNames.size(), 2u);
190+
}
191+
192+
TEST_F(AnimatedPropsRegistryTest, jsiRawPropsAreConverted) {
193+
auto runtime = hermes::makeHermesRuntime();
194+
{
195+
auto object = jsi::Object(*runtime);
196+
object.setProperty(*runtime, "opacity", 0.25);
197+
update(
198+
{.props = {},
199+
.rawProps = std::make_unique<RawProps>(
200+
*runtime, jsi::Value(*runtime, object))});
201+
object.setProperty(*runtime, "opacity", 0.5);
202+
update(
203+
{.props = {},
204+
.rawProps = std::make_unique<RawProps>(
205+
*runtime, jsi::Value(*runtime, object))});
206+
}
207+
runtime.reset();
208+
expectRawProps(folly::dynamic::object("opacity", 0.5));
209+
EXPECT_EQ(snapshot().props, nullptr);
210+
}
211+
212+
TEST_F(AnimatedPropsRegistryTest, stoppedSurfacesIgnoreUpdates) {
213+
registry.clearOnSurfaceStop(1);
214+
updateRaw(folly::dynamic::object("opacity", 0.5));
215+
registry.initializeSurface(1);
216+
EXPECT_TRUE(registry.getMap(1).second.empty());
217+
}
218+
219+
} // namespace facebook::react

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7791,7 +7791,7 @@ struct facebook::react::PropsParserContext {
77917791
}
77927792

77937793
struct facebook::react::PropsSnapshot {
7794-
public facebook::react::BaseViewProps props;
7794+
public std::unique_ptr<facebook::react::BaseViewProps> props;
77957795
public std::unique_ptr<folly::dynamic> rawProps;
77967796
public std::unordered_set<facebook::react::PropName> propNames;
77977797
}

‎scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7551,7 +7551,7 @@ struct facebook::react::PropsParserContext {
75517551
}
75527552

75537553
struct facebook::react::PropsSnapshot {
7554-
public facebook::react::BaseViewProps props;
7554+
public std::unique_ptr<facebook::react::BaseViewProps> props;
75557555
public std::unique_ptr<folly::dynamic> rawProps;
75567556
public std::unordered_set<facebook::react::PropName> propNames;
75577557
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7782,7 +7782,7 @@ struct facebook::react::PropsParserContext {
77827782
}
77837783

77847784
struct facebook::react::PropsSnapshot {
7785-
public facebook::react::BaseViewProps props;
7785+
public std::unique_ptr<facebook::react::BaseViewProps> props;
77867786
public std::unique_ptr<folly::dynamic> rawProps;
77877787
public std::unordered_set<facebook::react::PropName> propNames;
77887788
}

‎scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9782,7 +9782,7 @@ struct facebook::react::PropsParserContext {
97829782
}
97839783

97849784
struct facebook::react::PropsSnapshot {
9785-
public facebook::react::BaseViewProps props;
9785+
public std::unique_ptr<facebook::react::BaseViewProps> props;
97869786
public std::unique_ptr<folly::dynamic> rawProps;
97879787
public std::unordered_set<facebook::react::PropName> propNames;
97889788
}

‎scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9598,7 +9598,7 @@ struct facebook::react::PropsParserContext {
95989598
}
95999599

96009600
struct facebook::react::PropsSnapshot {
9601-
public facebook::react::BaseViewProps props;
9601+
public std::unique_ptr<facebook::react::BaseViewProps> props;
96029602
public std::unique_ptr<folly::dynamic> rawProps;
96039603
public std::unordered_set<facebook::react::PropName> propNames;
96049604
}

‎scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9773,7 +9773,7 @@ struct facebook::react::PropsParserContext {
97739773
}
97749774

97759775
struct facebook::react::PropsSnapshot {
9776-
public facebook::react::BaseViewProps props;
9776+
public std::unique_ptr<facebook::react::BaseViewProps> props;
97779777
public std::unique_ptr<folly::dynamic> rawProps;
97789778
public std::unordered_set<facebook::react::PropName> propNames;
97799779
}

‎scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5931,7 +5931,7 @@ struct facebook::react::PropsParserContext {
59315931
}
59325932

59335933
struct facebook::react::PropsSnapshot {
5934-
public facebook::react::BaseViewProps props;
5934+
public std::unique_ptr<facebook::react::BaseViewProps> props;
59355935
public std::unique_ptr<folly::dynamic> rawProps;
59365936
public std::unordered_set<facebook::react::PropName> propNames;
59375937
}

0 commit comments

Comments
 (0)