Skip to content

Commit 9b6bb18

Browse files
Bartlomiej Bloniarzfacebook-github-bot
authored andcommitted
Pass families to Native Animated
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` Differential Revision: D84055752
1 parent e61bbd0 commit 9b6bb18

File tree

14 files changed

+204
-6
lines changed

14 files changed

+204
-6
lines changed

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

Lines changed: 54 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', () => {
@@ -73,3 +74,56 @@ test('animated opacity', () => {
7374
<rn-view opacity="0" />,
7475
);
7576
});
77+
78+
test('animate layout props', () => {
79+
const viewRef = createRef<HostInstance>();
80+
allowStyleProp('height');
81+
82+
let _animatedHeight;
83+
let _heightAnimation;
84+
85+
function MyApp() {
86+
const animatedHeight = useAnimatedValue(0);
87+
_animatedHeight = animatedHeight;
88+
return (
89+
<Animated.View
90+
ref={viewRef}
91+
style={[
92+
{
93+
width: 100,
94+
height: animatedHeight,
95+
},
96+
]}
97+
/>
98+
);
99+
}
100+
101+
const root = Fantom.createRoot();
102+
103+
Fantom.runTask(() => {
104+
root.render(<MyApp />);
105+
});
106+
107+
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
108+
109+
Fantom.runTask(() => {
110+
_heightAnimation = Animated.timing(_animatedHeight, {
111+
toValue: 100,
112+
duration: 10,
113+
useNativeDriver: true,
114+
}).start();
115+
});
116+
117+
Fantom.unstable_produceFramesForDuration(10);
118+
119+
// TODO: this shouldn't be neccessary since animation should be stopped after duration
120+
Fantom.runTask(() => {
121+
_heightAnimation?.stop();
122+
});
123+
124+
// TODO: getFabricUpdateProps is not working with the cloneMutliple method
125+
// expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100);
126+
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
127+
<rn-view height="100.000000" />,
128+
);
129+
});

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type {AnimatedStyleAllowlist} from './AnimatedStyle';
1414

1515
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
1616
import {findNodeHandle} from '../../ReactNative/RendererProxy';
17+
import {getNodeFromPublicInstance} from '../../ReactPrivate/ReactNativePrivateInterface';
1718
import flattenStyle from '../../StyleSheet/flattenStyle';
1819
import {AnimatedEvent} from '../AnimatedEvent';
1920
import AnimatedNode from './AnimatedNode';
@@ -249,6 +250,9 @@ export default class AnimatedProps extends AnimatedNode {
249250
if (this._target != null) {
250251
this.#connectAnimatedView(this._target);
251252
}
253+
if (this._target != null) {
254+
this.#connectShadowNode(this._target);
255+
}
252256
}
253257
}
254258

@@ -259,6 +263,9 @@ export default class AnimatedProps extends AnimatedNode {
259263
this._target = {instance, connectedViewTag: null};
260264
if (this.__isNative) {
261265
this.#connectAnimatedView(this._target);
266+
if (this._target) {
267+
this.#connectShadowNode(this._target);
268+
}
262269
}
263270
}
264271

@@ -279,6 +286,19 @@ export default class AnimatedProps extends AnimatedNode {
279286
target.connectedViewTag = viewTag;
280287
}
281288

289+
#connectShadowNode(target: TargetView): void {
290+
invariant(this.__isNative, 'Expected node to be marked as "native"');
291+
// $FlowExpectedError[incompatible-type] - target.instance may be an HTMLElement but we need ReactNativeElement for Fabric
292+
const shadowNode = getNodeFromPublicInstance(target.instance);
293+
if (shadowNode == null) {
294+
return;
295+
}
296+
NativeAnimatedHelper.API.connectAnimatedNodeToShadowNodeFamily(
297+
this.__getNativeTag(),
298+
shadowNode,
299+
);
300+
}
301+
282302
#disconnectAnimatedView(target: TargetView): void {
283303
invariant(this.__isNative, 'Expected node to be marked as "native"');
284304
const viewTag = target.connectedViewTag;

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ - (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
165165
}];
166166
}
167167

168+
RCT_EXPORT_METHOD(connectAnimatedNodeToShadowNodeFamily : (double)nodeTag shadowNode : (NSDictionary *)shadowNode) {}
169+
168170
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
169171
{
170172
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ public final class com/facebook/react/animated/NativeAnimatedModule : com/facebo
457457
public fun <init> (Lcom/facebook/react/bridge/ReactApplicationContext;)V
458458
public fun addAnimatedEventToView (DLjava/lang/String;Lcom/facebook/react/bridge/ReadableMap;)V
459459
public fun addListener (Ljava/lang/String;)V
460+
public fun connectAnimatedNodeToShadowNodeFamily (DLcom/facebook/react/bridge/ReadableMap;)V
460461
public fun connectAnimatedNodeToView (DD)V
461462
public fun connectAnimatedNodes (DD)V
462463
public fun createAnimatedNode (DLcom/facebook/react/bridge/ReadableMap;)V

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,4 +1117,6 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) :
11171117

11181118
public const val ANIMATED_MODULE_DEBUG: Boolean = false
11191119
}
1120+
1121+
override fun connectAnimatedNodeToShadowNodeFamily(p0: Double, p1: ReadableMap) {}
11201122
}

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ 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& nativeState = shadowNodeObj.getNativeState(rt);
168+
const auto& shadowNode =
169+
std::dynamic_pointer_cast<ShadowNodeWrapper>(nativeState)->shadowNode;
170+
171+
operations_.emplace_back(
172+
ConnectAnimatedNodeToShadowNodeFamilyOp{
173+
.nodeTag = nodeTag,
174+
.shadowNodeFamily = shadowNode->getFamilyShared()});
175+
}
176+
163177
void AnimatedModule::disconnectAnimatedNodeFromView(
164178
jsi::Runtime& /*rt*/,
165179
Tag nodeTag,
@@ -282,6 +296,11 @@ void AnimatedModule::executeOperation(
282296
DisconnectAnimatedNodeFromViewOp>) {
283297
nodesManager->disconnectAnimatedNodeFromView(
284298
op.nodeTag, op.viewTag);
299+
} else if constexpr (std::is_same_v<
300+
T,
301+
ConnectAnimatedNodeToShadowNodeFamilyOp>) {
302+
nodesManager->connectAnimatedNodeToShadowNodeFamily(
303+
op.nodeTag, op.shadowNodeFamily);
285304
} else if constexpr (std::is_same_v<T, RestoreDefaultValuesOp>) {
286305
nodesManager->restoreDefaultValues(op.nodeTag);
287306
} 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: 59 additions & 4 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) {
247+
node->connectToFamily(family);
248+
updatedNodeTags_.insert(node->tag());
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 {
@@ -889,10 +903,14 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
889903
Tag viewTag,
890904
const folly::dynamic& props,
891905
bool layoutStyleUpdated,
892-
bool forceFabricCommit) noexcept {
906+
bool forceFabricCommit,
907+
std::shared_ptr<const ShadowNodeFamily> family) noexcept {
893908
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
894909
if (layoutStyleUpdated) {
895910
mergeObjects(updateViewProps_[viewTag], props);
911+
if (family) {
912+
tagToShadowNodeFamily_[viewTag] = std::move(family);
913+
}
896914
} else {
897915
mergeObjects(updateViewPropsDirect_[viewTag], props);
898916
}
@@ -1003,7 +1021,31 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10031021
AnimationMutation{tag, nullptr, propsBuilder.get()});
10041022
containsChange = true;
10051023
}
1006-
updateViewPropsDirect_.clear();
1024+
for (auto& [tag, props] : updateViewProps_) {
1025+
auto familyIt = tagToShadowNodeFamily_.find(tag);
1026+
if (familyIt != tagToShadowNodeFamily_.end()) {
1027+
if (props.find("width") != props.items().end()) {
1028+
propsBuilder.setWidth(
1029+
yoga::Style::SizeLength::points(props["width"].asDouble()));
1030+
}
1031+
if (props.find("height") != props.items().end()) {
1032+
propsBuilder.setHeight(
1033+
yoga::Style::SizeLength::points(props["height"].asDouble()));
1034+
}
1035+
// propsBuilder.storeDynamic(props);
1036+
mutations.push_back(
1037+
AnimationMutation{
1038+
.tag = tag,
1039+
.family = familyIt->second.get(),
1040+
.props = propsBuilder.get()});
1041+
}
1042+
containsChange = true;
1043+
}
1044+
if (containsChange) {
1045+
updateViewPropsDirect_.clear();
1046+
updateViewProps_.clear();
1047+
tagToShadowNodeFamily_.clear();
1048+
}
10071049
}
10081050

10091051
if (!containsChange) {
@@ -1023,7 +1065,8 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10231065
}
10241066
}
10251067

1026-
// Step 2: update all nodes that are connected to the finished animations.
1068+
// Step 2: update all nodes that are connected to the finished
1069+
// animations.
10271070
updateNodes(finishedAnimationValueNodes);
10281071

10291072
isEventAnimationInProgress_ = false;
@@ -1034,6 +1077,17 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
10341077
mutations.push_back(
10351078
AnimationMutation{tag, nullptr, propsBuilder.get()});
10361079
}
1080+
for (auto& [tag, props] : updateViewProps_) {
1081+
auto familyIt = tagToShadowNodeFamily_.find(tag);
1082+
if (familyIt != tagToShadowNodeFamily_.end()) {
1083+
propsBuilder.storeDynamic(props);
1084+
mutations.push_back(
1085+
AnimationMutation{
1086+
.tag = tag,
1087+
.family = familyIt->second.get(),
1088+
.props = propsBuilder.get()});
1089+
}
1090+
}
10371091
}
10381092
} else {
10391093
// There is no active animation. Stop the render callback.
@@ -1113,7 +1167,8 @@ void NativeAnimatedNodesManager::onRender() {
11131167
}
11141168
}
11151169

1116-
// Step 2: update all nodes that are connected to the finished animations.
1170+
// Step 2: update all nodes that are connected to the finished
1171+
// animations.
11171172
updateNodes(finishedAnimationValueNodes);
11181173

11191174
isEventAnimationInProgress_ = false;

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

Lines changed: 6 additions & 1 deletion
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>
@@ -95,6 +96,8 @@ class NativeAnimatedNodesManager {
9596

9697
void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;
9798

99+
void connectAnimatedNodeToShadowNodeFamily(Tag propsNodeTag, std::shared_ptr<const ShadowNodeFamily> family) noexcept;
100+
98101
void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
99102

100103
void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
@@ -144,7 +147,8 @@ class NativeAnimatedNodesManager {
144147
Tag viewTag,
145148
const folly::dynamic &props,
146149
bool layoutStyleUpdated,
147-
bool forceFabricCommit) noexcept;
150+
bool forceFabricCommit,
151+
std::shared_ptr<const ShadowNodeFamily> family = nullptr) noexcept;
148152

149153
/**
150154
* Commits all pending animated property updates to their respective views.
@@ -251,6 +255,7 @@ class NativeAnimatedNodesManager {
251255

252256
std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
253257
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
258+
std::unordered_map<Tag, std::shared_ptr<const ShadowNodeFamily>> tagToShadowNodeFamily_{};
254259

255260
/*
256261
* Sometimes a view is not longer connected to a PropsAnimatedNode, but

packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ void PropsAnimatedNode::connectToView(Tag viewTag) {
5858
connectedViewTag_ = viewTag;
5959
}
6060

61+
void PropsAnimatedNode::connectToFamily(
62+
std::shared_ptr<const ShadowNodeFamily>& family) {
63+
viewShadowNodeFamily_ = family;
64+
}
65+
6166
void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
6267
react_native_assert(
6368
connectedViewTag_ == viewTag &&
@@ -144,7 +149,11 @@ void PropsAnimatedNode::update(bool forceFabricCommit) {
144149
layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);
145150

146151
manager_->schedulePropsCommit(
147-
connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
152+
connectedViewTag_,
153+
props_,
154+
layoutStyleUpdated_,
155+
forceFabricCommit,
156+
viewShadowNodeFamily_.lock());
148157
}
149158

150159
} // namespace facebook::react

0 commit comments

Comments
 (0)