Skip to content

Commit 155d032

Browse files
Bartlomiej Bloniarzfacebook-github-bot
authored andcommitted
Pass families to Native Animated (#54613)
Summary: This PR allows C++ Native Animated to use `ShadowNodeFamily` instances to use the `cloneMultiple` method when pushing updates through the `ShadowTree` in `AnimationBackend` # Changelog [General] [Added] - Add `connectAnimatedNodeToShadowNodeFamily` method to `NativeAnimatedModule` and `NativeAnimatedTurboModule` Reviewed By: sammy-SC, zeyap Differential Revision: D84055752
1 parent 95cc1e7 commit 155d032

13 files changed

Lines changed: 245 additions & 15 deletions

File tree

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ensureInstance from '../../../src/private/__tests__/utilities/ensureInsta
1717
import * as Fantom from '@react-native/fantom';
1818
import {createRef} from 'react';
1919
import {Animated, useAnimatedValue} from 'react-native';
20+
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
2021
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
2122

2223
test('animated opacity', () => {
@@ -83,3 +84,60 @@ test('animated opacity', () => {
8384
<rn-view opacity="0" />,
8485
);
8586
});
87+
88+
test('animate layout props', () => {
89+
const viewRef = createRef<HostInstance>();
90+
allowStyleProp('height');
91+
92+
let _animatedHeight;
93+
let _heightAnimation;
94+
95+
function MyApp() {
96+
const animatedHeight = useAnimatedValue(0);
97+
_animatedHeight = animatedHeight;
98+
return (
99+
<Animated.View
100+
ref={viewRef}
101+
style={[
102+
{
103+
width: 100,
104+
height: animatedHeight,
105+
},
106+
]}
107+
/>
108+
);
109+
}
110+
111+
const root = Fantom.createRoot();
112+
113+
Fantom.runTask(() => {
114+
root.render(<MyApp />);
115+
});
116+
117+
Fantom.runTask(() => {
118+
_heightAnimation = Animated.timing(_animatedHeight, {
119+
toValue: 100,
120+
duration: 200,
121+
useNativeDriver: true,
122+
}).start();
123+
});
124+
125+
Fantom.unstable_produceFramesForDuration(100);
126+
127+
// TODO: getFabricUpdateProps is not working with the cloneMutliple method
128+
// expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100);
129+
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
130+
<rn-view height="50.000000" />,
131+
);
132+
133+
Fantom.unstable_produceFramesForDuration(100);
134+
135+
// TODO: this shouldn't be neccessary since animation should be stopped after duration
136+
Fantom.runTask(() => {
137+
_heightAnimation?.stop();
138+
});
139+
140+
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
141+
<rn-view height="100.000000" />,
142+
);
143+
});

packages/react-native/Libraries/Animated/nodes/AnimatedProps.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import type {AnimatedNodeConfig} from './AnimatedNode';
1414
import type {AnimatedStyleAllowlist} from './AnimatedStyle';
1515

1616
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
17+
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
1718
import {findNodeHandle} from '../../ReactNative/RendererProxy';
19+
import {getNodeFromPublicInstance} from '../../ReactPrivate/ReactNativePrivateInterface';
1820
import flattenStyle from '../../StyleSheet/flattenStyle';
1921
import {AnimatedEvent} from '../AnimatedEvent';
2022
import AnimatedNode from './AnimatedNode';
@@ -251,7 +253,9 @@ export default class AnimatedProps extends AnimatedNode {
251253
super.__setPlatformConfig(platformConfig);
252254

253255
if (this._target != null) {
254-
this.#connectAnimatedView(this._target);
256+
const target = this._target;
257+
this.#connectAnimatedView(target);
258+
this.#connectShadowNode(target);
255259
}
256260
}
257261
}
@@ -260,9 +264,10 @@ export default class AnimatedProps extends AnimatedNode {
260264
if (this._target?.instance === instance) {
261265
return;
262266
}
263-
this._target = {instance, connectedViewTag: null};
267+
const target = (this._target = {instance, connectedViewTag: null});
264268
if (this.__isNative) {
265-
this.#connectAnimatedView(this._target);
269+
this.#connectAnimatedView(target);
270+
this.#connectShadowNode(target);
266271
}
267272
}
268273

@@ -283,6 +288,27 @@ export default class AnimatedProps extends AnimatedNode {
283288
target.connectedViewTag = viewTag;
284289
}
285290

291+
#connectShadowNode(target: TargetView): void {
292+
if (
293+
!ReactNativeFeatureFlags.cxxNativeAnimatedEnabled() ||
294+
//eslint-disable-next-line
295+
!ReactNativeFeatureFlags.useSharedAnimatedBackend()
296+
) {
297+
return;
298+
}
299+
300+
invariant(this.__isNative, 'Expected node to be marked as "native"');
301+
// $FlowExpectedError[incompatible-type] - target.instance may be an HTMLElement but we need ReactNativeElement for Fabric
302+
const shadowNode = getNodeFromPublicInstance(target.instance);
303+
if (shadowNode == null) {
304+
return;
305+
}
306+
NativeAnimatedHelper.API.connectAnimatedNodeToShadowNodeFamily(
307+
this.__getNativeTag(),
308+
shadowNode,
309+
);
310+
}
311+
286312
#disconnectAnimatedView(target: TargetView): void {
287313
invariant(this.__isNative, 'Expected node to be marked as "native"');
288314
const viewTag = target.connectedViewTag;

packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#import <React/RCTInitializing.h>
1111
#import <React/RCTNativeAnimatedNodesManager.h>
1212
#import <React/RCTNativeAnimatedTurboModule.h>
13+
#import <react/debug/react_native_assert.h>
1314
#import <react/featureflags/ReactNativeFeatureFlags.h>
1415

1516
#import "RCTAnimationPlugins.h"
@@ -165,6 +166,12 @@ - (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
165166
}];
166167
}
167168

169+
RCT_EXPORT_METHOD(connectAnimatedNodeToShadowNodeFamily : (double)nodeTag shadowNode : (NSDictionary *)shadowNode)
170+
{
171+
// This method should only be called when using CxxNativeAnimated
172+
react_native_assert(false);
173+
}
174+
168175
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
169176
{
170177
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {

packages/react-native/Libraries/NativeAnimation/React-RCTAnimation.podspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Pod::Spec.new do |s|
4848
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
4949
add_dependency(s, "React-NativeModulesApple")
5050
add_dependency(s, "React-featureflags")
51+
add_dependency(s, "React-debug")
5152

5253
add_rn_third_party_dependencies(s)
5354
add_rncore_dependency(s)

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

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99

1010
#include <glog/logging.h>
1111
#include <jsi/JSIDynamic.h>
12+
#include <react/renderer/bridging/bridging.h>
1213

1314
namespace facebook::react {
14-
1515
AnimatedModule::AnimatedModule(
1616
std::shared_ptr<CallInvoker> jsInvoker,
1717
std::shared_ptr<NativeAnimatedNodesManagerProvider> nodesManagerProvider)
@@ -160,6 +160,19 @@ void AnimatedModule::connectAnimatedNodeToView(
160160
ConnectAnimatedNodeToViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
161161
}
162162

163+
void AnimatedModule::connectAnimatedNodeToShadowNodeFamily(
164+
jsi::Runtime& rt,
165+
Tag nodeTag,
166+
jsi::Object shadowNodeObj) {
167+
const auto& shadowNode = Bridging<std::shared_ptr<const ShadowNode>>::fromJs(
168+
rt, jsi::Value(rt, shadowNodeObj));
169+
170+
operations_.emplace_back(
171+
ConnectAnimatedNodeToShadowNodeFamilyOp{
172+
.nodeTag = nodeTag,
173+
.shadowNodeFamily = shadowNode->getFamilyShared()});
174+
}
175+
163176
void AnimatedModule::disconnectAnimatedNodeFromView(
164177
jsi::Runtime& /*rt*/,
165178
Tag nodeTag,
@@ -282,6 +295,11 @@ void AnimatedModule::executeOperation(
282295
DisconnectAnimatedNodeFromViewOp>) {
283296
nodesManager->disconnectAnimatedNodeFromView(
284297
op.nodeTag, op.viewTag);
298+
} else if constexpr (std::is_same_v<
299+
T,
300+
ConnectAnimatedNodeToShadowNodeFamilyOp>) {
301+
nodesManager->connectAnimatedNodeToShadowNodeFamily(
302+
op.nodeTag, op.shadowNodeFamily);
285303
} else if constexpr (std::is_same_v<T, RestoreDefaultValuesOp>) {
286304
nodesManager->restoreDefaultValues(op.nodeTag);
287305
} else if constexpr (std::is_same_v<T, DropAnimatedNodeOp>) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>, publi
8787
Tag viewTag{};
8888
};
8989

90+
struct ConnectAnimatedNodeToShadowNodeFamilyOp {
91+
Tag nodeTag{};
92+
std::shared_ptr<const ShadowNodeFamily> shadowNodeFamily{};
93+
};
94+
9095
struct DisconnectAnimatedNodeFromViewOp {
9196
Tag nodeTag{};
9297
Tag viewTag{};
@@ -124,6 +129,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>, publi
124129
SetAnimatedNodeOffsetOp,
125130
SetAnimatedNodeValueOp,
126131
ConnectAnimatedNodeToViewOp,
132+
ConnectAnimatedNodeToShadowNodeFamilyOp,
127133
DisconnectAnimatedNodeFromViewOp,
128134
RestoreDefaultValuesOp,
129135
FlattenAnimatedNodeOffsetOp,
@@ -176,6 +182,8 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>, publi
176182

177183
void connectAnimatedNodeToView(jsi::Runtime &rt, Tag nodeTag, Tag viewTag);
178184

185+
void connectAnimatedNodeToShadowNodeFamily(jsi::Runtime &rt, Tag nodeTag, jsi::Object shadowNode);
186+
179187
void disconnectAnimatedNodeFromView(jsi::Runtime &rt, Tag nodeTag, Tag viewTag);
180188

181189
void restoreDefaultValues(jsi::Runtime &rt, Tag nodeTag);

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

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,20 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToView(
238238
}
239239
}
240240

241+
void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNodeFamily(
242+
Tag propsNodeTag,
243+
std::shared_ptr<const ShadowNodeFamily> family) noexcept {
244+
react_native_assert(propsNodeTag);
245+
auto node = getAnimatedNode<PropsAnimatedNode>(propsNodeTag);
246+
if (node != nullptr && family != nullptr) {
247+
std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
248+
tagToShadowNodeFamily_[family->getTag()] = family;
249+
} else {
250+
LOG(WARNING)
251+
<< "Cannot ConnectAnimatedNodeToShadowNodeFamily, animated node has to be props type";
252+
}
253+
}
254+
241255
void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
242256
Tag propsNodeTag,
243257
Tag viewTag) noexcept {
@@ -251,6 +265,10 @@ void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
251265
std::lock_guard<std::mutex> lock(connectedAnimatedNodesMutex_);
252266
connectedAnimatedNodes_.erase(viewTag);
253267
}
268+
{
269+
std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
270+
tagToShadowNodeFamily_.erase(viewTag);
271+
}
254272
updatedNodeTags_.insert(node->tag());
255273

256274
onManagedPropsRemoved(viewTag);
@@ -985,15 +1003,47 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
9851003
}
9861004

9871005
for (auto& [tag, props] : updateViewPropsDirect_) {
988-
// TODO: also handle layout props (updateViewProps_). It is skipped for
989-
// now, because the backend requires shadowNodeFamilies to be able to
990-
// commit to the ShadowTree
9911006
propsBuilder.storeDynamic(props);
9921007
mutations.push_back(
9931008
AnimationMutation{tag, nullptr, propsBuilder.get()});
9941009
containsChange = true;
9951010
}
996-
updateViewPropsDirect_.clear();
1011+
{
1012+
std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
1013+
for (auto& [tag, props] : updateViewProps_) {
1014+
auto familyIt = tagToShadowNodeFamily_.find(tag);
1015+
if (familyIt == tagToShadowNodeFamily_.end()) {
1016+
continue;
1017+
}
1018+
if (auto family = familyIt->second.lock()) {
1019+
// C++ Animated produces props in the form of a folly::dynamic, so
1020+
// it wouldn't make sense to unpack it here. However, for the
1021+
// purposes of testing, we want to be able to use the statically
1022+
// typed AnimationMutation. At a later stage we will instead just
1023+
// pass the dynamic directly to propsBuilder and the new API could
1024+
// be used by 3rd party libraries or in the fututre by Animated.
1025+
if (props.find("width") != props.items().end()) {
1026+
propsBuilder.setWidth(
1027+
yoga::Style::SizeLength::points(props["width"].asDouble()));
1028+
}
1029+
if (props.find("height") != props.items().end()) {
1030+
propsBuilder.setHeight(
1031+
yoga::Style::SizeLength::points(props["height"].asDouble()));
1032+
}
1033+
mutations.push_back(
1034+
AnimationMutation{
1035+
.tag = tag,
1036+
.family = family,
1037+
.props = propsBuilder.get(),
1038+
});
1039+
}
1040+
containsChange = true;
1041+
}
1042+
}
1043+
if (containsChange) {
1044+
updateViewPropsDirect_.clear();
1045+
updateViewProps_.clear();
1046+
}
9971047
}
9981048

9991049
if (!containsChange) {
@@ -1013,16 +1063,38 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10131063
}
10141064
}
10151065

1016-
// Step 2: update all nodes that are connected to the finished animations.
1066+
// Step 2: update all nodes that are connected to the finished
1067+
// animations.
10171068
updateNodes(finishedAnimationValueNodes);
10181069

10191070
isEventAnimationInProgress_ = false;
10201071

10211072
for (auto& [tag, props] : updateViewPropsDirect_) {
1022-
// TODO: handle layout props
10231073
propsBuilder.storeDynamic(props);
10241074
mutations.push_back(
1025-
AnimationMutation{tag, nullptr, propsBuilder.get()});
1075+
AnimationMutation{
1076+
.tag = tag,
1077+
.family = nullptr,
1078+
.props = propsBuilder.get(),
1079+
});
1080+
}
1081+
{
1082+
std::lock_guard<std::mutex> lock(tagToShadowNodeFamilyMutex_);
1083+
for (auto& [tag, props] : updateViewProps_) {
1084+
auto familyIt = tagToShadowNodeFamily_.find(tag);
1085+
if (familyIt == tagToShadowNodeFamily_.end()) {
1086+
continue;
1087+
}
1088+
if (auto family = familyIt->second.lock()) {
1089+
propsBuilder.storeDynamic(props);
1090+
mutations.push_back(
1091+
AnimationMutation{
1092+
.tag = tag,
1093+
.family = family,
1094+
.props = propsBuilder.get(),
1095+
});
1096+
}
1097+
}
10261098
}
10271099
}
10281100
} else {
@@ -1103,7 +1175,8 @@ void NativeAnimatedNodesManager::onRender() {
11031175
}
11041176
}
11051177

1106-
// Step 2: update all nodes that are connected to the finished animations.
1178+
// Step 2: update all nodes that are connected to the finished
1179+
// animations.
11071180
updateNodes(finishedAnimationValueNodes);
11081181

11091182
isEventAnimationInProgress_ = false;

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <react/renderer/animationbackend/AnimationBackend.h>
2222
#endif
2323
#include <react/renderer/core/ReactPrimitives.h>
24+
#include <react/renderer/core/ShadowNode.h>
2425
#include <react/renderer/uimanager/UIManagerAnimationBackend.h>
2526
#include <chrono>
2627
#include <memory>
@@ -101,6 +102,8 @@ class NativeAnimatedNodesManager {
101102

102103
void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;
103104

105+
void connectAnimatedNodeToShadowNodeFamily(Tag propsNodeTag, std::shared_ptr<const ShadowNodeFamily> family) noexcept;
106+
104107
void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
105108

106109
void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
@@ -258,6 +261,9 @@ class NativeAnimatedNodesManager {
258261
std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
259262
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
260263

264+
mutable std::mutex tagToShadowNodeFamilyMutex_;
265+
std::unordered_map<Tag, std::weak_ptr<const ShadowNodeFamily>> tagToShadowNodeFamily_{};
266+
261267
/*
262268
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
263269
* NativeAnimated has previously changed the view's props via direct

0 commit comments

Comments
 (0)