Skip to content

Commit e091759

Browse files
Animation Backend init (#53526)
Summary: This diff adds AnimatedProps class that allows prop diffs to be passed to the backend. Currently it is implemented through subclassing, but the implementation details are subject to change. To simplify integration with animation frameworks AnimatedPropsBuilder class was created - frameworks can use it to prepare their diffs. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [GENERAL] [ADDED] - AnimatedProps.h AnimatedPropsBuilder.h [GENERAL] [CHANGED] - AnimationBackend initialisation and style updates For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests Pull Request resolved: #53526 Test Plan: js1 fantom AnimatedBackend-itest.js js1 fantom Animated-itest.js Reviewed By: zeyap Differential Revision: D81309558 Pulled By: bartlomiejbloniarz fbshipit-source-id: 92476568b6498d7ee64203f70a17c3e578de7ac1
1 parent 7ec470d commit e091759

File tree

9 files changed

+287
-16
lines changed

9 files changed

+287
-16
lines changed

packages/react-native/ReactCommon/React-Fabric.podspec

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,23 @@ Pod::Spec.new do |s|
5454
depend_on_js_engine(s)
5555
add_rn_third_party_dependencies(s)
5656
add_rncore_dependency(s)
57+
58+
s.subspec "animated" do |ss|
59+
ss.source_files = podspec_sources("react/renderer/animated/**/*.{m,mm,cpp,h}", "react/renderer/animated/**/*.{h}")
60+
ss.exclude_files = "react/renderer/animated/tests"
61+
ss.header_dir = "react/renderer/animated"
62+
end
5763

5864
s.subspec "animations" do |ss|
5965
ss.source_files = podspec_sources("react/renderer/animations/**/*.{m,mm,cpp,h}", "react/renderer/animations/**/*.{h}")
6066
ss.exclude_files = "react/renderer/animations/tests"
6167
ss.header_dir = "react/renderer/animations"
6268
end
69+
70+
s.subspec "animationbackend" do |ss|
71+
ss.source_files = podspec_sources("react/renderer/animationbackend/**/*.{m,mm,cpp,h}", "react/renderer/animationbackend/**/*.{h}")
72+
ss.header_dir = "react/renderer/animationbackend"
73+
end
6374

6475
s.subspec "attributedstring" do |ss|
6576
ss.source_files = podspec_sources("react/renderer/attributedstring/**/*.{m,mm,cpp,h}", "react/renderer/attributedstring/**/*.{h}")

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
#pragma once
99

10+
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
11+
#include "FBReactNativeSpecJSI.h"
12+
#else
1013
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
14+
#endif
1115
#include <ReactCommon/TurboModuleWithJSIBindings.h>
1216
#include <folly/dynamic.h>
1317
#include <react/renderer/animated/NativeAnimatedNodesManager.h>

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,11 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
852852
bool layoutStyleUpdated,
853853
bool forceFabricCommit) noexcept {
854854
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
855-
mergeObjects(updateViewProps_[viewTag], props);
855+
if (layoutStyleUpdated) {
856+
mergeObjects(updateViewProps_[viewTag], props);
857+
} else {
858+
mergeObjects(updateViewPropsDirect_[viewTag], props);
859+
}
856860
return;
857861
}
858862

@@ -910,6 +914,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
910914

911915
auto timestamp = static_cast<double>(microseconds) / 1000.0;
912916
bool containsChange = false;
917+
AnimatedPropsBuilder propsBuilder;
913918
{
914919
// copied from onAnimationFrame
915920
// Run all active animations
@@ -949,9 +954,13 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
949954
}
950955
}
951956

952-
for (auto& [tag, props] : updateViewProps_) {
953-
mutations.emplace_back(
954-
AnimationMutation{tag, props["opacity"].asDouble()});
957+
for (auto& [tag, props] : updateViewPropsDirect_) {
958+
// TODO: also handle layout props (updateViewProps_). It is skipped for
959+
// now, because the backend requires shadowNodeFamilies to be able to
960+
// commit to the ShadowTree
961+
propsBuilder.storeDynamic(props);
962+
mutations.push_back(
963+
AnimationMutation{tag, nullptr, propsBuilder.get()});
955964
containsChange = true;
956965
}
957966
}
@@ -978,9 +987,11 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
978987

979988
isEventAnimationInProgress_ = false;
980989

981-
for (auto& [tag, props] : updateViewProps_) {
982-
mutations.emplace_back(
983-
AnimationMutation{tag, props["opacity"].asDouble()});
990+
for (auto& [tag, props] : updateViewPropsDirect_) {
991+
// TODO: handle layout props
992+
propsBuilder.storeDynamic(props);
993+
mutations.push_back(
994+
AnimationMutation{tag, nullptr, propsBuilder.get()});
984995
}
985996
}
986997
} else {

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77

88
#pragma once
99

10+
#if __has_include("FBReactNativeSpecJSI.h") // CocoaPod headers on Apple
11+
#include "FBReactNativeSpecJSI.h"
12+
#else
1013
#include <FBReactNativeSpec/FBReactNativeSpecJSI.h>
14+
#endif
1115
#include <folly/dynamic.h>
1216
#include <react/bridging/Function.h>
1317
#include <react/debug/flags.h>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,9 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
7171
animationBackend_ = std::make_shared<AnimationBackend>(
7272
std::move(startOnRenderCallback_),
7373
std::move(stopOnRenderCallback_),
74-
std::move(directManipulationCallback));
74+
std::move(directManipulationCallback),
75+
std::move(fabricCommitCallback),
76+
uiManager);
7577

7678
nativeAnimatedNodesManager_ =
7779
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
#pragma once
9+
#include <react/renderer/components/view/BaseViewProps.h>
10+
11+
#include <utility>
12+
13+
namespace facebook::react {
14+
15+
enum PropName { OPACITY, WIDTH, HEIGHT, BORDER_RADII, FLEX, TRANSFORM };
16+
17+
struct AnimatedPropBase {
18+
PropName propName;
19+
explicit AnimatedPropBase(PropName propName) : propName(propName) {}
20+
virtual ~AnimatedPropBase() = default;
21+
};
22+
23+
template <typename T>
24+
struct AnimatedProp : AnimatedPropBase {
25+
T value;
26+
AnimatedProp() = default;
27+
AnimatedProp(PropName propName, const T& value)
28+
: AnimatedPropBase{propName}, value(std::move(value)) {}
29+
};
30+
31+
template <typename T>
32+
T get(const std::unique_ptr<AnimatedPropBase>& animatedProp) {
33+
return static_cast<AnimatedProp<T>*>(animatedProp.get())->value;
34+
}
35+
36+
struct AnimatedProps {
37+
std::vector<std::unique_ptr<AnimatedPropBase>> props;
38+
std::unique_ptr<RawProps> rawProps;
39+
};
40+
} // namespace facebook::react
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
#pragma once
9+
#include <react/renderer/components/view/BaseViewProps.h>
10+
#include "AnimatedProps.h"
11+
12+
namespace facebook::react {
13+
14+
struct AnimatedPropsBuilder {
15+
std::vector<std::unique_ptr<AnimatedPropBase>> props;
16+
std::unique_ptr<RawProps> rawProps;
17+
18+
void setOpacity(Float value) {
19+
props.push_back(std::make_unique<AnimatedProp<Float>>(OPACITY, value));
20+
}
21+
void setWidth(yoga::Style::SizeLength value) {
22+
props.push_back(
23+
std::make_unique<AnimatedProp<yoga::Style::SizeLength>>(WIDTH, value));
24+
}
25+
void setHeight(yoga::Style::SizeLength value) {
26+
props.push_back(
27+
std::make_unique<AnimatedProp<yoga::Style::SizeLength>>(HEIGHT, value));
28+
}
29+
void setBorderRadii(CascadedBorderRadii& value) {
30+
props.push_back(std::make_unique<AnimatedProp<CascadedBorderRadii>>(
31+
BORDER_RADII, value));
32+
}
33+
void setTransform(Transform& t) {
34+
props.push_back(
35+
std::make_unique<AnimatedProp<Transform>>(TRANSFORM, std::move(t)));
36+
}
37+
void storeDynamic(folly::dynamic& d) {
38+
rawProps = std::make_unique<RawProps>(std::move(d));
39+
}
40+
void storeJSI(jsi::Runtime& runtime, jsi::Value& value) {
41+
rawProps = std::make_unique<RawProps>(runtime, value);
42+
}
43+
AnimatedProps get() {
44+
return AnimatedProps{std::move(props), std::move(rawProps)};
45+
}
46+
};
47+
48+
} // namespace facebook::react

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

Lines changed: 141 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,169 @@
66
*/
77

88
#include "AnimationBackend.h"
9+
#include <chrono>
910

1011
namespace facebook::react {
1112

13+
static inline Props::Shared cloneProps(
14+
AnimatedProps& animatedProps,
15+
const ShadowNode& shadowNode) {
16+
PropsParserContext propsParserContext{
17+
shadowNode.getSurfaceId(), *shadowNode.getContextContainer()};
18+
Props::Shared newProps;
19+
if (animatedProps.rawProps) {
20+
newProps = shadowNode.getComponentDescriptor().cloneProps(
21+
propsParserContext,
22+
shadowNode.getProps(),
23+
std::move(*animatedProps.rawProps));
24+
} else {
25+
newProps = shadowNode.getComponentDescriptor().cloneProps(
26+
propsParserContext, shadowNode.getProps(), {});
27+
}
28+
29+
auto viewProps = std::const_pointer_cast<BaseViewProps>(
30+
std::static_pointer_cast<const BaseViewProps>(newProps));
31+
for (auto& animatedProp : animatedProps.props) {
32+
switch (animatedProp->propName) {
33+
case OPACITY:
34+
viewProps->opacity = get<Float>(animatedProp);
35+
break;
36+
37+
case WIDTH:
38+
viewProps->yogaStyle.setDimension(
39+
yoga::Dimension::Width, get<yoga::Style::SizeLength>(animatedProp));
40+
break;
41+
42+
case HEIGHT:
43+
viewProps->yogaStyle.setDimension(
44+
yoga::Dimension::Height,
45+
get<yoga::Style::SizeLength>(animatedProp));
46+
break;
47+
48+
case BORDER_RADII:
49+
viewProps->borderRadii = get<CascadedBorderRadii>(animatedProp);
50+
break;
51+
52+
case FLEX:
53+
viewProps->yogaStyle.setFlex(get<yoga::FloatOptional>(animatedProp));
54+
break;
55+
56+
case TRANSFORM:
57+
viewProps->transform = get<Transform>(animatedProp);
58+
break;
59+
}
60+
}
61+
return newProps;
62+
}
63+
64+
static inline bool mutationHasLayoutUpdates(
65+
facebook::react::AnimationMutation& mutation) {
66+
for (auto& animatedProp : mutation.props.props) {
67+
// TODO: there should also be a check for the dynamic part
68+
if (animatedProp->propName == WIDTH || animatedProp->propName == HEIGHT ||
69+
animatedProp->propName == FLEX) {
70+
return true;
71+
}
72+
}
73+
return false;
74+
}
75+
1276
AnimationBackend::AnimationBackend(
1377
StartOnRenderCallback&& startOnRenderCallback,
1478
StopOnRenderCallback&& stopOnRenderCallback,
15-
DirectManipulationCallback&& directManipulationCallback)
79+
DirectManipulationCallback&& directManipulationCallback,
80+
FabricCommitCallback&& fabricCommitCallback,
81+
UIManager* uiManager)
1682
: startOnRenderCallback_(std::move(startOnRenderCallback)),
1783
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
18-
directManipulationCallback_(std::move(directManipulationCallback)) {}
84+
directManipulationCallback_(std::move(directManipulationCallback)),
85+
fabricCommitCallback_(std::move(fabricCommitCallback)),
86+
uiManager_(uiManager) {}
1987

2088
void AnimationBackend::onAnimationFrame(double timestamp) {
89+
std::unordered_map<Tag, AnimatedProps> updates;
90+
std::unordered_set<const ShadowNodeFamily*> families;
91+
bool hasAnyLayoutUpdates = false;
2192
for (auto& callback : callbacks) {
2293
auto muatations = callback(static_cast<float>(timestamp));
2394
for (auto& mutation : muatations) {
24-
directManipulationCallback_(
25-
mutation.tag, folly::dynamic::object("opacity", mutation.opacity));
95+
hasAnyLayoutUpdates |= mutationHasLayoutUpdates(mutation);
96+
families.insert(mutation.family);
97+
updates[mutation.tag] = std::move(mutation.props);
2698
}
2799
}
100+
101+
if (hasAnyLayoutUpdates) {
102+
commitUpdatesWithFamilies(families, updates);
103+
} else {
104+
synchronouslyUpdateProps(updates);
105+
}
28106
}
29107

30108
void AnimationBackend::start(const Callback& callback) {
31109
callbacks.push_back(callback);
32-
// startOnRenderCallback_ should provide the timestamp from the platform
33-
startOnRenderCallback_([this]() { onAnimationFrame(0); });
110+
// TODO: startOnRenderCallback_ should provide the timestamp from the platform
111+
startOnRenderCallback_([this]() {
112+
onAnimationFrame(
113+
std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
114+
});
34115
}
35116
void AnimationBackend::stop() {
36117
stopOnRenderCallback_();
37118
callbacks.clear();
38119
}
120+
121+
void AnimationBackend::commitUpdatesWithFamilies(
122+
const std::unordered_set<const ShadowNodeFamily*>& families,
123+
std::unordered_map<Tag, AnimatedProps>& updates) {
124+
uiManager_->getShadowTreeRegistry().enumerate(
125+
[families, &updates](const ShadowTree& shadowTree, bool& /*stop*/) {
126+
shadowTree.commit(
127+
[families, &updates](const RootShadowNode& oldRootShadowNode) {
128+
return std::static_pointer_cast<RootShadowNode>(
129+
oldRootShadowNode.cloneMultiple(
130+
families,
131+
[families, &updates](
132+
const ShadowNode& shadowNode,
133+
const ShadowNodeFragment& fragment) {
134+
auto& animatedProps = updates.at(shadowNode.getTag());
135+
auto newProps = cloneProps(animatedProps, shadowNode);
136+
return shadowNode.clone(
137+
{newProps,
138+
fragment.children,
139+
shadowNode.getState()});
140+
}));
141+
},
142+
{.mountSynchronously = true});
143+
});
144+
}
145+
146+
void AnimationBackend::synchronouslyUpdateProps(
147+
const std::unordered_map<Tag, AnimatedProps>& updates) {
148+
for (auto& [tag, animatedProps] : updates) {
149+
auto dyn = animatedProps.rawProps ? animatedProps.rawProps->toDynamic()
150+
: folly::dynamic::object();
151+
for (auto& animatedProp : animatedProps.props) {
152+
// TODO: We shouldn't repack it into dynamic, but for that a rewrite of
153+
// directManipulationCallback_ is needed
154+
switch (animatedProp->propName) {
155+
case OPACITY:
156+
dyn.insert("opacity", get<Float>(animatedProp));
157+
break;
158+
159+
case BORDER_RADII:
160+
case TRANSFORM:
161+
// TODO: handle other things than opacity
162+
break;
163+
164+
case WIDTH:
165+
case HEIGHT:
166+
case FLEX:
167+
throw "Tried to synchronously update layout props";
168+
}
169+
}
170+
directManipulationCallback_(tag, dyn);
171+
}
172+
}
173+
39174
} // namespace facebook::react

0 commit comments

Comments
 (0)