diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js
index ae7e2f61ca8423..ceb8d93bcd0de1 100644
--- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js
+++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js
@@ -15,9 +15,10 @@ import type {HostInstance} from 'react-native';
import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
import * as Fantom from '@react-native/fantom';
-import {createRef} from 'react';
+import {createRef, useState} from 'react';
import {Animated, useAnimatedValue} from 'react-native';
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
+import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
test('animated opacity', () => {
let _opacity;
@@ -73,3 +74,119 @@ test('animated opacity', () => {
,
);
});
+
+test('animate layout props', () => {
+ const viewRef = createRef();
+ allowStyleProp('height');
+
+ let _animatedHeight;
+ let _heightAnimation;
+
+ function MyApp() {
+ const animatedHeight = useAnimatedValue(0);
+ _animatedHeight = animatedHeight;
+ return (
+
+ );
+ }
+
+ const root = Fantom.createRoot();
+
+ Fantom.runTask(() => {
+ root.render();
+ });
+
+ const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
+
+ Fantom.runTask(() => {
+ _heightAnimation = Animated.timing(_animatedHeight, {
+ toValue: 100,
+ duration: 10,
+ useNativeDriver: true,
+ }).start();
+ });
+
+ Fantom.unstable_produceFramesForDuration(10);
+
+ // TODO: this shouldn't be neccessary since animation should be stopped after duration
+ Fantom.runTask(() => {
+ _heightAnimation?.stop();
+ });
+
+ // TODO: getFabricUpdateProps is not working with the cloneMutliple method
+ // expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100);
+ expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
+ ,
+ );
+});
+
+test('animate layout props and rerender', () => {
+ const viewRef = createRef();
+ allowStyleProp('height');
+
+ let _animatedHeight;
+ let _heightAnimation;
+ let _setWidth;
+
+ function MyApp() {
+ const animatedHeight = useAnimatedValue(0);
+ const [width, setWidth] = useState(100);
+ _animatedHeight = animatedHeight;
+ _setWidth = setWidth;
+ return (
+
+ );
+ }
+
+ const root = Fantom.createRoot();
+
+ Fantom.runTask(() => {
+ root.render();
+ });
+
+ const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
+
+ Fantom.runTask(() => {
+ _heightAnimation = Animated.timing(_animatedHeight, {
+ toValue: 100,
+ duration: 1000,
+ useNativeDriver: true,
+ }).start();
+ });
+
+ Fantom.unstable_produceFramesForDuration(500);
+ expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
+ ,
+ );
+
+ Fantom.runTask(() => {
+ _setWidth(200);
+ });
+
+ // TODO: this shouldn't be neccessary since animation should be stopped after duration
+ Fantom.runTask(() => {
+ _heightAnimation?.stop();
+ });
+
+ // TODO: getFabricUpdateProps is not working with the cloneMutliple method
+ // expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(50);
+ expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
+ ,
+ );
+});
diff --git a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js
index af89f0d9ba21a1..f3d4499fe235d1 100644
--- a/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js
+++ b/packages/react-native/Libraries/Animated/nodes/AnimatedProps.js
@@ -14,6 +14,7 @@ import type {AnimatedStyleAllowlist} from './AnimatedStyle';
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
import {findNodeHandle} from '../../ReactNative/RendererProxy';
+import {getInternalInstanceHandleFromPublicInstance} from '../../ReactPrivate/ReactNativePrivateInterface';
import flattenStyle from '../../StyleSheet/flattenStyle';
import {AnimatedEvent} from '../AnimatedEvent';
import AnimatedNode from './AnimatedNode';
@@ -248,6 +249,7 @@ export default class AnimatedProps extends AnimatedNode {
if (this._target != null) {
this.#connectAnimatedView(this._target);
+ this.#connectShadowNode(this._target);
}
}
}
@@ -259,6 +261,7 @@ export default class AnimatedProps extends AnimatedNode {
this._target = {instance, connectedViewTag: null};
if (this.__isNative) {
this.#connectAnimatedView(this._target);
+ this.#connectShadowNode(this._target);
}
}
@@ -279,6 +282,17 @@ export default class AnimatedProps extends AnimatedNode {
target.connectedViewTag = viewTag;
}
+ #connectShadowNode(target: TargetView): void {
+ invariant(this.__isNative, 'Expected node to be marked as "native"');
+ let shadowNode = getInternalInstanceHandleFromPublicInstance(
+ target.instance,
+ ).stateNode.node;
+ NativeAnimatedHelper.API.connectAnimatedNodeToShadowNode(
+ this.__getNativeTag(),
+ shadowNode,
+ );
+ }
+
#disconnectAnimatedView(target: TargetView): void {
invariant(this.__isNative, 'Expected node to be marked as "native"');
const viewTag = target.connectedViewTag;
diff --git a/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm b/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm
index 000aa701865375..4b22981b0d46a5 100644
--- a/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm
+++ b/packages/react-native/Libraries/NativeAnimation/RCTNativeAnimatedTurboModule.mm
@@ -167,6 +167,11 @@ - (void)setSurfacePresenter:(id)surfacePresenter
}];
}
+RCT_EXPORT_METHOD(connectAnimatedNodeToShadowNode:(double)nodeTag shadowNode:(NSDictionary *)shadowNode)
+{
+
+}
+
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.cpp b/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.cpp
index d03cd98595a1ac..05270dfa186aab 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.cpp
@@ -158,6 +158,17 @@ void AnimatedModule::connectAnimatedNodeToView(
ConnectAnimatedNodeToViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
}
+void AnimatedModule::connectAnimatedNodeToShadowNode(
+ jsi::Runtime& rt,
+ Tag nodeTag,
+ jsi::Object shadowNode) {
+ auto nativeState = shadowNode.getNativeState(rt);
+ auto shadowNodeWrapper =
+ std::dynamic_pointer_cast(nativeState);
+ operations_.emplace_back(ConnectAnimatedNodeToShadowNodeOp{
+ .nodeTag = nodeTag, .shadowNode = shadowNodeWrapper->shadowNode});
+}
+
void AnimatedModule::disconnectAnimatedNodeFromView(
jsi::Runtime& /*rt*/,
Tag nodeTag,
@@ -270,6 +281,11 @@ void AnimatedModule::executeOperation(const Operation& operation) {
nodesManager_->extractAnimatedNodeOffsetOp(op.nodeTag);
} else if constexpr (std::is_same_v) {
nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag);
+ } else if constexpr (std::is_same_v<
+ T,
+ ConnectAnimatedNodeToShadowNodeOp>) {
+ nodesManager_->connectAnimatedNodeToShadowNode(
+ op.nodeTag, op.shadowNode);
} else if constexpr (std::is_same_v<
T,
DisconnectAnimatedNodeFromViewOp>) {
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h b/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h
index a0f84ccbb2c4fd..cd4d2822796dda 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h
+++ b/packages/react-native/ReactCommon/react/renderer/animated/AnimatedModule.h
@@ -88,6 +88,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec,
Tag viewTag{};
};
+ struct ConnectAnimatedNodeToShadowNodeOp {
+ Tag nodeTag{};
+ ShadowNode::Shared shadowNode{};
+ };
+
struct DisconnectAnimatedNodeFromViewOp {
Tag nodeTag{};
Tag viewTag{};
@@ -125,6 +130,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec,
SetAnimatedNodeOffsetOp,
SetAnimatedNodeValueOp,
ConnectAnimatedNodeToViewOp,
+ ConnectAnimatedNodeToShadowNodeOp,
DisconnectAnimatedNodeFromViewOp,
RestoreDefaultValuesOp,
FlattenAnimatedNodeOffsetOp,
@@ -178,6 +184,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec,
void connectAnimatedNodeToView(jsi::Runtime& rt, Tag nodeTag, Tag viewTag);
+ void connectAnimatedNodeToShadowNode(
+ jsi::Runtime& rt,
+ Tag nodeTag,
+ jsi::Object shadowNode);
+
void
disconnectAnimatedNodeFromView(jsi::Runtime& rt, Tag nodeTag, Tag viewTag);
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
index 248018cb4ed218..da7bf72425d25a 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
@@ -234,6 +234,20 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToView(
}
}
+void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNode(
+ Tag propsNodeTag,
+ ShadowNode::Shared shadowNode) noexcept {
+ react_native_assert(propsNodeTag);
+ auto node = getAnimatedNode(propsNodeTag);
+ if (node != nullptr) {
+ node->connectToShadowNode(shadowNode);
+ updatedNodeTags_.insert(node->tag());
+ } else {
+ LOG(WARNING)
+ << "Cannot ConnectAnimatedNodeToShadowNode, animated node has to be props type";
+ }
+}
+
void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
Tag propsNodeTag,
Tag viewTag) noexcept {
@@ -861,10 +875,14 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
Tag viewTag,
const folly::dynamic& props,
bool layoutStyleUpdated,
- bool forceFabricCommit) noexcept {
+ bool forceFabricCommit,
+ ShadowNode::Shared shadowNode) noexcept {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
if (layoutStyleUpdated) {
mergeObjects(updateViewProps_[viewTag], props);
+ if (shadowNode) {
+ tagToShadowNode_[viewTag] = std::move(shadowNode);
+ }
} else {
mergeObjects(updateViewPropsDirect_[viewTag], props);
}
@@ -975,6 +993,28 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
AnimationMutation{tag, nullptr, propsBuilder.get()});
containsChange = true;
}
+ for (auto& [tag, props] : updateViewProps_) {
+ auto node = tagToShadowNode_.find(tag);
+ if (node != tagToShadowNode_.end()) {
+ if (props.find("width") != props.items().end()) {
+ propsBuilder.setWidth(
+ yoga::Style::SizeLength::points(props["width"].asDouble()));
+ }
+ if (props.find("height") != props.items().end()) {
+ propsBuilder.setHeight(
+ yoga::Style::SizeLength::points(props["height"].asDouble()));
+ }
+ // propsBuilder.storeDynamic(props);
+ mutations.push_back(AnimationMutation{
+ tag, &node->second->getFamily(), propsBuilder.get()});
+ }
+ containsChange = true;
+ }
+ if (containsChange) {
+ updateViewPropsDirect_.clear();
+ updateViewProps_.clear();
+ tagToShadowNode_.clear();
+ }
}
if (!containsChange) {
@@ -994,7 +1034,8 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
}
}
- // Step 2: update all nodes that are connected to the finished animations.
+ // Step 2: update all nodes that are connected to the finished
+ // animations.
updateNodes(finishedAnimationValueNodes);
isEventAnimationInProgress_ = false;
@@ -1005,6 +1046,14 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
mutations.push_back(
AnimationMutation{tag, nullptr, propsBuilder.get()});
}
+ for (auto& [tag, props] : updateViewProps_) {
+ auto node = tagToShadowNode_.find(tag);
+ if (node != tagToShadowNode_.end()) {
+ propsBuilder.storeDynamic(props);
+ mutations.push_back(AnimationMutation{
+ tag, &node->second->getFamily(), propsBuilder.get()});
+ }
+ }
}
} else {
// There is no active animation. Stop the render callback.
@@ -1073,7 +1122,8 @@ void NativeAnimatedNodesManager::onRender() {
}
}
- // Step 2: update all nodes that are connected to the finished animations.
+ // Step 2: update all nodes that are connected to the finished
+ // animations.
updateNodes(finishedAnimationValueNodes);
isEventAnimationInProgress_ = false;
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h
index 9ecf6de7443b6d..906e1a59d252cc 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h
+++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h
@@ -100,6 +100,10 @@ class NativeAnimatedNodesManager {
void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;
+ void connectAnimatedNodeToShadowNode(
+ Tag propsNodeTag,
+ ShadowNode::Shared node) noexcept;
+
void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
@@ -158,7 +162,8 @@ class NativeAnimatedNodesManager {
Tag viewTag,
const folly::dynamic& props,
bool layoutStyleUpdated,
- bool forceFabricCommit) noexcept;
+ bool forceFabricCommit,
+ ShadowNode::Shared shadowNode = nullptr) noexcept;
/**
* Commits all pending animated property updates to their respective views.
@@ -266,6 +271,7 @@ class NativeAnimatedNodesManager {
std::unordered_map updateViewProps_{};
std::unordered_map updateViewPropsDirect_{};
+ std::unordered_map tagToShadowNode_{};
/*
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp
index 8f45b416490735..c5dd2fc586f390 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp
@@ -82,6 +82,12 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
nativeAnimatedNodesManager_ =
std::make_shared(animationBackend_);
+ nativeAnimatedDelegate_ =
+ std::make_shared(
+ animationBackend_);
+
+ uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
+
uiManager->unstable_setAnimationBackend(animationBackend_);
} else {
nativeAnimatedNodesManager_ =
@@ -90,6 +96,12 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
std::move(fabricCommitCallback),
std::move(startOnRenderCallback_),
std::move(stopOnRenderCallback_));
+
+ nativeAnimatedDelegate_ =
+ std::make_shared(
+ nativeAnimatedNodesManager_);
+
+ uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
}
addEventEmitterListener(
@@ -112,36 +124,32 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
return false;
}));
- nativeAnimatedDelegate_ =
- std::make_shared(
- nativeAnimatedNodesManager_);
-
- uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
-
- // TODO: remove force casting.
- auto* scheduler = (Scheduler*)uiManager->getDelegate();
- animatedMountingOverrideDelegate_ =
- std::make_shared(
- *nativeAnimatedNodesManager_, *scheduler);
-
- // Register on existing surfaces
- uiManager->getShadowTreeRegistry().enumerate(
- [animatedMountingOverrideDelegate =
- std::weak_ptr(
- animatedMountingOverrideDelegate_)](
- const ShadowTree& shadowTree, bool& /*stop*/) {
- shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
- animatedMountingOverrideDelegate);
- });
- // Register on surfaces started in the future
- uiManager->setOnSurfaceStartCallback(
- [animatedMountingOverrideDelegate =
- std::weak_ptr(
- animatedMountingOverrideDelegate_)](
- const ShadowTree& shadowTree) {
- shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
- animatedMountingOverrideDelegate);
- });
+ if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
+ // TODO: remove force casting.
+ auto* scheduler = (Scheduler*)uiManager->getDelegate();
+ animatedMountingOverrideDelegate_ =
+ std::make_shared(
+ *nativeAnimatedNodesManager_, *scheduler);
+
+ // Register on existing surfaces
+ uiManager->getShadowTreeRegistry().enumerate(
+ [animatedMountingOverrideDelegate =
+ std::weak_ptr(
+ animatedMountingOverrideDelegate_)](
+ const ShadowTree& shadowTree, bool& /*stop*/) {
+ shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
+ animatedMountingOverrideDelegate);
+ });
+ // Register on surfaces started in the future
+ uiManager->setOnSurfaceStartCallback(
+ [animatedMountingOverrideDelegate =
+ std::weak_ptr(
+ animatedMountingOverrideDelegate_)](
+ const ShadowTree& shadowTree) {
+ shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
+ animatedMountingOverrideDelegate);
+ });
+ }
}
return nativeAnimatedNodesManager_;
}
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp
index 8303b74741b898..f5e13d43b8960f 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp
@@ -58,6 +58,11 @@ void PropsAnimatedNode::connectToView(Tag viewTag) {
connectedViewTag_ = viewTag;
}
+void PropsAnimatedNode::connectToShadowNode(
+ std::shared_ptr shadowNode) {
+ viewShadowNode_ = shadowNode;
+}
+
void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
react_native_assert(
connectedViewTag_ == viewTag &&
@@ -144,7 +149,11 @@ void PropsAnimatedNode::update(bool forceFabricCommit) {
layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);
manager_->schedulePropsCommit(
- connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
+ connectedViewTag_,
+ props_,
+ layoutStyleUpdated_,
+ forceFabricCommit,
+ viewShadowNode_.lock());
}
} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h
index 29e300b42f8479..142ccab90518d9 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h
+++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h
@@ -14,6 +14,7 @@
#include "AnimatedNode.h"
#include
+#include
#include
namespace facebook::react {
@@ -24,6 +25,7 @@ class PropsAnimatedNode final : public AnimatedNode {
const folly::dynamic& config,
NativeAnimatedNodesManager& manager);
void connectToView(Tag viewTag);
+ void connectToShadowNode(std::shared_ptr shadowNode);
void disconnectFromView(Tag viewTag);
void restoreDefaultValues();
@@ -46,5 +48,6 @@ class PropsAnimatedNode final : public AnimatedNode {
bool layoutStyleUpdated_{false};
Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier};
+ std::weak_ptr viewShadowNode_{};
};
} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp
new file mode 100644
index 00000000000000..e42a9cbb447a84
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include "AnimatedPropsRegistry.h"
+#include
+#include "AnimatedProps.h"
+
+namespace facebook::react {
+
+void AnimatedPropsRegistry::update(
+ std::unordered_map& animatedPropsMap,
+ std::unordered_set& families) {
+ for (const auto& family : families) {
+ if (family != nullptr) {
+ surfaceToFamilies_[family->getSurfaceId()].insert(family);
+ }
+ }
+ for (auto& [tag, animatedProps] : animatedPropsMap) {
+ auto& snapshot = map_[tag];
+ auto& viewProps = snapshot.props;
+
+ for (auto& animatedProp : animatedProps.props) {
+ snapshot.propNames.insert(animatedProp->propName);
+ switch (animatedProp->propName) {
+ case OPACITY:
+ viewProps.opacity = get(animatedProp);
+ break;
+
+ case WIDTH:
+ viewProps.yogaStyle.setDimension(
+ yoga::Dimension::Width,
+ get(animatedProp));
+ break;
+
+ case HEIGHT:
+ viewProps.yogaStyle.setDimension(
+ yoga::Dimension::Height,
+ get(animatedProp));
+ break;
+
+ case BORDER_RADII:
+ viewProps.borderRadii = get(animatedProp);
+ break;
+
+ case FLEX:
+ viewProps.yogaStyle.setFlex(get(animatedProp));
+ break;
+
+ case TRANSFORM:
+ viewProps.transform = get(animatedProp);
+ break;
+ }
+ }
+ }
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h
new file mode 100644
index 00000000000000..1770b8831d451b
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+#include "AnimatedProps.h"
+
+namespace facebook::react {
+
+struct PropsSnapshot {
+ BaseViewProps props;
+ std::unordered_set propNames;
+};
+
+class AnimatedPropsRegistry {
+ public:
+ std::unordered_map map_;
+ std::unordered_map>
+ surfaceToFamilies_;
+
+ void update(
+ std::unordered_map& animatedPropsMap,
+ std::unordered_set& families);
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp
index c5e438c6eea384..932052b5581f6f 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp
@@ -7,9 +7,22 @@
#include "AnimationBackend.h"
#include
+#include "AnimatedPropsRegistry.h"
namespace facebook::react {
+UIManagerNativeAnimatedDelegateBackendImpl::
+ UIManagerNativeAnimatedDelegateBackendImpl(
+ std::weak_ptr animationBackend)
+ : animationBackend_(std::move(animationBackend)) {}
+
+void UIManagerNativeAnimatedDelegateBackendImpl::runAnimationFrame() {
+ if (auto animationBackendStrong = animationBackend_.lock()) {
+ animationBackendStrong->onAnimationFrame(
+ std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
+ }
+}
+
static inline Props::Shared cloneProps(
AnimatedProps& animatedProps,
const ShadowNode& shadowNode) {
@@ -83,7 +96,11 @@ AnimationBackend::AnimationBackend(
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
directManipulationCallback_(std::move(directManipulationCallback)),
fabricCommitCallback_(std::move(fabricCommitCallback)),
- uiManager_(uiManager) {}
+ animatedPropsRegistry_(std::make_shared()),
+ uiManager_(uiManager),
+ commitHook_(std::make_unique(
+ uiManager,
+ animatedPropsRegistry_)) {}
void AnimationBackend::onAnimationFrame(double timestamp) {
std::unordered_map updates;
@@ -98,6 +115,8 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
}
}
+ animatedPropsRegistry_->update(updates, families);
+
if (hasAnyLayoutUpdates) {
commitUpdatesWithFamilies(families, updates);
} else {
@@ -108,39 +127,58 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
void AnimationBackend::start(const Callback& callback) {
callbacks.push_back(callback);
// TODO: startOnRenderCallback_ should provide the timestamp from the platform
- startOnRenderCallback_([this]() {
- onAnimationFrame(
- std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
- });
+ if (startOnRenderCallback_) {
+ startOnRenderCallback_([this]() {
+ onAnimationFrame(
+ std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
+ });
+ }
}
void AnimationBackend::stop() {
- stopOnRenderCallback_();
+ if (stopOnRenderCallback_) {
+ stopOnRenderCallback_();
+ }
callbacks.clear();
}
void AnimationBackend::commitUpdatesWithFamilies(
const std::unordered_set& families,
std::unordered_map& updates) {
- uiManager_->getShadowTreeRegistry().enumerate(
- [families, &updates](const ShadowTree& shadowTree, bool& /*stop*/) {
- shadowTree.commit(
- [families, &updates](const RootShadowNode& oldRootShadowNode) {
- return std::static_pointer_cast(
- oldRootShadowNode.cloneMultiple(
- families,
- [families, &updates](
- const ShadowNode& shadowNode,
- const ShadowNodeFragment& fragment) {
- auto& animatedProps = updates.at(shadowNode.getTag());
- auto newProps = cloneProps(animatedProps, shadowNode);
- return shadowNode.clone(
- {newProps,
- fragment.children,
- shadowNode.getState()});
- }));
- },
- {.mountSynchronously = true});
- });
+ std::unordered_map>
+ surfaceToFamilies;
+ for (auto& family : families) {
+ surfaceToFamilies[family->getSurfaceId()].insert(family);
+ }
+ for (const auto& [surfaceId, surfaceFamilies] : surfaceToFamilies) {
+ uiManager_->getShadowTreeRegistry().visit(
+ surfaceId, [&surfaceFamilies, &updates](const ShadowTree& shadowTree) {
+ shadowTree.commit(
+ [&surfaceFamilies,
+ &updates](const RootShadowNode& oldRootShadowNode) {
+ return std::static_pointer_cast(
+ oldRootShadowNode.cloneMultiple(
+ surfaceFamilies,
+ [&surfaceFamilies, &updates](
+ const ShadowNode& shadowNode,
+ const ShadowNodeFragment& fragment) {
+ auto newProps =
+ ShadowNodeFragment::propsPlaceholder();
+ if (surfaceFamilies.contains(
+ &shadowNode.getFamily())) {
+ auto& animatedProps =
+ updates.at(shadowNode.getTag());
+ newProps = cloneProps(animatedProps, shadowNode);
+ }
+ return shadowNode.clone(
+ {.props = newProps,
+ .children = fragment.children,
+ .state = shadowNode.getState(),
+ .runtimeShadowNodeReference = false});
+ }));
+ },
+ {.mountSynchronously = true});
+ });
+ }
}
void AnimationBackend::synchronouslyUpdateProps(
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h
index b4eea2d64c3368..fa2adc52909779 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h
@@ -15,9 +15,25 @@
#include
#include "AnimatedProps.h"
#include "AnimatedPropsBuilder.h"
+#include "AnimatedPropsRegistry.h"
+#include "AnimationBackendCommitHook.h"
namespace facebook::react {
+class AnimationBackend;
+
+class UIManagerNativeAnimatedDelegateBackendImpl
+ : public UIManagerNativeAnimatedDelegate {
+ public:
+ explicit UIManagerNativeAnimatedDelegateBackendImpl(
+ std::weak_ptr animationBackend);
+
+ void runAnimationFrame() override;
+
+ private:
+ std::weak_ptr animationBackend_;
+};
+
struct AnimationMutation {
Tag tag;
const ShadowNodeFamily* family;
@@ -41,7 +57,9 @@ class AnimationBackend : public UIManagerAnimationBackend {
const StopOnRenderCallback stopOnRenderCallback_;
const DirectManipulationCallback directManipulationCallback_;
const FabricCommitCallback fabricCommitCallback_;
+ std::shared_ptr animatedPropsRegistry_;
UIManager* uiManager_;
+ std::unique_ptr commitHook_;
AnimationBackend(
StartOnRenderCallback&& startOnRenderCallback,
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp
new file mode 100644
index 00000000000000..bda38022e170da
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#include
+
+namespace facebook::react {
+
+AnimationBackendCommitHook::AnimationBackendCommitHook(
+ UIManager* uiManager,
+ std::shared_ptr animatedPropsRegistry)
+ : uiManager_(uiManager),
+ animatedPropsRegistry_(std::move(animatedPropsRegistry)) {
+ uiManager_->registerCommitHook(*this);
+}
+
+RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit(
+ const ShadowTree& shadowTree,
+ const RootShadowNode::Shared& oldRootShadowNode,
+ const RootShadowNode::Unshared& newRootShadowNode,
+ const ShadowTreeCommitOptions& commitOptions) noexcept {
+ if (commitOptions.source != ShadowTreeCommitSource::React) {
+ return newRootShadowNode;
+ }
+ auto surfaceFamilies =
+ animatedPropsRegistry_->surfaceToFamilies_[shadowTree.getSurfaceId()];
+ auto& updates = animatedPropsRegistry_->map_;
+ if (surfaceFamilies.empty()) {
+ return newRootShadowNode;
+ }
+ return std::static_pointer_cast<
+ RootShadowNode>(newRootShadowNode->cloneMultiple(
+ surfaceFamilies,
+ [&surfaceFamilies, &updates](
+ const ShadowNode& shadowNode, const ShadowNodeFragment& fragment) {
+ auto newProps = ShadowNodeFragment::propsPlaceholder();
+ std::shared_ptr viewProps = nullptr;
+ if (surfaceFamilies.contains(&shadowNode.getFamily())) {
+ auto& snapshot = updates.at(shadowNode.getTag());
+ if (!snapshot.propNames.empty()) {
+ PropsParserContext propsParserContext{
+ shadowNode.getSurfaceId(), *shadowNode.getContextContainer()};
+
+ newProps = shadowNode.getComponentDescriptor().cloneProps(
+ propsParserContext, shadowNode.getProps(), {});
+ viewProps = std::const_pointer_cast(
+ std::static_pointer_cast(newProps));
+ }
+
+ for (const auto& propName : snapshot.propNames) {
+ switch (propName) {
+ case OPACITY:
+ viewProps->opacity = snapshot.props.opacity;
+ break;
+
+ case WIDTH:
+ viewProps->yogaStyle.setDimension(
+ yoga::Dimension::Width,
+ snapshot.props.yogaStyle.dimension(yoga::Dimension::Width));
+ break;
+
+ case HEIGHT:
+ viewProps->yogaStyle.setDimension(
+ yoga::Dimension::Height,
+ snapshot.props.yogaStyle.dimension(
+ yoga::Dimension::Height));
+ break;
+
+ case TRANSFORM:
+ viewProps->transform = snapshot.props.transform;
+ break;
+
+ case BORDER_RADII:
+ viewProps->borderRadii = snapshot.props.borderRadii;
+ break;
+
+ case FLEX:
+ viewProps->yogaStyle.setFlex(snapshot.props.yogaStyle.flex());
+ break;
+ }
+ }
+ }
+ return shadowNode.clone(
+ {.props = newProps,
+ .children = fragment.children,
+ .state = shadowNode.getState(),
+ .runtimeShadowNodeReference = false});
+ }));
+}
+
+AnimationBackendCommitHook::~AnimationBackendCommitHook() {
+ // TODO: fix
+ // uiManager get's deallocated first, so we can't call unregisterCommitHook
+ // here (or need to get a shared_ptr) uiManager_->unregisterCommitHook(*this);
+}
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h
new file mode 100644
index 00000000000000..ee4907f4426781
--- /dev/null
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "AnimatedPropsRegistry.h"
+
+namespace facebook::react {
+
+class AnimationBackendCommitHook : public UIManagerCommitHook {
+ UIManager* uiManager_;
+ std::shared_ptr animatedPropsRegistry_;
+
+ public:
+ AnimationBackendCommitHook(
+ UIManager* uiManager,
+ std::shared_ptr animatedPropsRegistry);
+ RootShadowNode::Unshared shadowTreeWillCommit(
+ const ShadowTree& shadowTree,
+ const RootShadowNode::Shared& oldRootShadowNode,
+ const RootShadowNode::Unshared& newRootShadowNode,
+ const ShadowTreeCommitOptions& commitOptions) noexcept override;
+ void commitHookWasRegistered(const UIManager& uiManager) noexcept override {}
+ void commitHookWasUnregistered(const UIManager& uiManager) noexcept override {
+ }
+ ~AnimationBackendCommitHook() override;
+};
+
+} // namespace facebook::react
diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp
index e50a06cbf35bd8..8679fa247f7e81 100644
--- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp
@@ -435,11 +435,7 @@ std::shared_ptr cloneMultipleRecursive(
}
}
- ShadowNodeFragment fragment{.children = newChildren};
- if (familiesToUpdate.contains(family)) {
- return callback(shadowNode, fragment);
- }
- return shadowNode.clone(fragment);
+ return callback(shadowNode, {.children = newChildren});
}
} // namespace
diff --git a/packages/react-native/src/private/animated/NativeAnimatedHelper.js b/packages/react-native/src/private/animated/NativeAnimatedHelper.js
index 5cde2ceef75da7..14d7090771ee5e 100644
--- a/packages/react-native/src/private/animated/NativeAnimatedHelper.js
+++ b/packages/react-native/src/private/animated/NativeAnimatedHelper.js
@@ -83,6 +83,7 @@ function createNativeOperations(): $NonMaybeType {
'removeAnimatedEventFromView', // 19
'addListener', // 20
'removeListener', // 21
+ 'connectAnimatedNodeToShadowNode', // 22
];
const nativeOperations: {
[$Values]: (...$ReadOnlyArray) => void,
@@ -312,6 +313,10 @@ const API = {
NativeOperations.connectAnimatedNodeToView(nodeTag, viewTag);
},
+ connectAnimatedNodeToShadowNode(nodeTag: number, shadowNode: {}): void {
+ NativeOperations.connectAnimatedNodeToShadowNode(nodeTag, shadowNode);
+ },
+
disconnectAnimatedNodeFromView(nodeTag: number, viewTag: number): void {
NativeOperations.disconnectAnimatedNodeFromView(nodeTag, viewTag);
},
diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedModule.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedModule.js
index 91f64cdcdc3929..8e04813a5c010f 100644
--- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedModule.js
+++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedModule.js
@@ -49,6 +49,10 @@ export interface Spec extends TurboModule {
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
+extractAnimatedNodeOffset: (nodeTag: number) => void;
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
+ +connectAnimatedNodeToShadowNode: (
+ nodeTag: number,
+ shadowNode: Object,
+ ) => void;
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
+restoreDefaultValues: (nodeTag: number) => void;
+dropAnimatedNode: (tag: number) => void;
diff --git a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedTurboModule.js b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedTurboModule.js
index 5a20ac1539de67..788c936856c43e 100644
--- a/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedTurboModule.js
+++ b/packages/react-native/src/private/specs_DEPRECATED/modules/NativeAnimatedTurboModule.js
@@ -49,6 +49,10 @@ export interface Spec extends TurboModule {
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
+extractAnimatedNodeOffset: (nodeTag: number) => void;
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
+ +connectAnimatedNodeToShadowNode: (
+ nodeTag: number,
+ shadowNode: Object,
+ ) => void;
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
+restoreDefaultValues: (nodeTag: number) => void;
+dropAnimatedNode: (tag: number) => void;