Compare commits

...
4 Commits
Author SHA1 Message Date
Bartlomiej Bloniarz 43991b7bab Commit hook
Differential Revision: D84250600
2025-10-13 05:01:46 -07:00
Bartlomiej Bloniarz d4e972843e Pass families to Native Animated
Differential Revision: D84055752
2025-10-08 01:35:46 -07:00
Bartlomiej Bloniarz 5c94283be3 Split families by surfaceId
Differential Revision: D84055753
2025-10-07 23:43:09 -07:00
Bartlomiej Bloniarz ca5a0cc765 Make android work
Differential Revision: D84055754
2025-10-07 23:43:09 -07:00
20 changed files with 603 additions and 66 deletions
@@ -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', () => {
<rn-view opacity="0" />,
);
});
test('animate layout props', () => {
const viewRef = createRef<HostInstance>();
allowStyleProp('height');
let _animatedHeight;
let _heightAnimation;
function MyApp() {
const animatedHeight = useAnimatedValue(0);
_animatedHeight = animatedHeight;
return (
<Animated.View
ref={viewRef}
style={[
{
width: 100,
height: animatedHeight,
},
]}
/>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp />);
});
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(
<rn-view height="100.000000" />,
);
});
test('animate layout props and rerender', () => {
const viewRef = createRef<HostInstance>();
allowStyleProp('height');
let _animatedHeight;
let _heightAnimation;
let _setWidth;
function MyApp() {
const animatedHeight = useAnimatedValue(0);
const [width, setWidth] = useState(100);
_animatedHeight = animatedHeight;
_setWidth = setWidth;
return (
<Animated.View
ref={viewRef}
style={[
{
width: width,
height: animatedHeight,
},
]}
/>
);
}
const root = Fantom.createRoot();
Fantom.runTask(() => {
root.render(<MyApp />);
});
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(
<rn-view height="50.000000" width="100.000000" />,
);
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(
<rn-view height="50.000000" width="200.000000" />,
);
});
@@ -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;
@@ -167,6 +167,11 @@ RCT_EXPORT_METHOD(connectAnimatedNodeToView : (double)nodeTag viewTag : (double)
}];
}
RCT_EXPORT_METHOD(connectAnimatedNodeToShadowNode:(double)nodeTag shadowNode:(NSDictionary *)shadowNode)
{
}
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
@@ -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<ShadowNodeWrapper>(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<T, ConnectAnimatedNodeToViewOp>) {
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>) {
@@ -88,6 +88,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
Tag viewTag{};
};
struct ConnectAnimatedNodeToShadowNodeOp {
Tag nodeTag{};
ShadowNode::Shared shadowNode{};
};
struct DisconnectAnimatedNodeFromViewOp {
Tag nodeTag{};
Tag viewTag{};
@@ -125,6 +130,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
SetAnimatedNodeOffsetOp,
SetAnimatedNodeValueOp,
ConnectAnimatedNodeToViewOp,
ConnectAnimatedNodeToShadowNodeOp,
DisconnectAnimatedNodeFromViewOp,
RestoreDefaultValuesOp,
FlattenAnimatedNodeOffsetOp,
@@ -178,6 +184,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
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);
@@ -234,6 +234,20 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToView(
}
}
void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNode(
Tag propsNodeTag,
ShadowNode::Shared shadowNode) noexcept {
react_native_assert(propsNodeTag);
auto node = getAnimatedNode<PropsAnimatedNode>(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;
@@ -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<Tag, folly::dynamic> updateViewProps_{};
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
std::unordered_map<Tag, ShadowNode::Shared> tagToShadowNode_{};
/*
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
@@ -82,6 +82,12 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
nativeAnimatedNodesManager_ =
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);
nativeAnimatedDelegate_ =
std::make_shared<UIManagerNativeAnimatedDelegateBackendImpl>(
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<UIManagerNativeAnimatedDelegateImpl>(
nativeAnimatedNodesManager_);
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
}
addEventEmitterListener(
@@ -112,36 +124,32 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
return false;
}));
nativeAnimatedDelegate_ =
std::make_shared<UIManagerNativeAnimatedDelegateImpl>(
nativeAnimatedNodesManager_);
if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
// TODO: remove force casting.
auto* scheduler = (Scheduler*)uiManager->getDelegate();
animatedMountingOverrideDelegate_ =
std::make_shared<AnimatedMountingOverrideDelegate>(
*nativeAnimatedNodesManager_, *scheduler);
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
// TODO: remove force casting.
auto* scheduler = (Scheduler*)uiManager->getDelegate();
animatedMountingOverrideDelegate_ =
std::make_shared<AnimatedMountingOverrideDelegate>(
*nativeAnimatedNodesManager_, *scheduler);
// Register on existing surfaces
uiManager->getShadowTreeRegistry().enumerate(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree, bool& /*stop*/) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
// Register on surfaces started in the future
uiManager->setOnSurfaceStartCallback(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
// Register on existing surfaces
uiManager->getShadowTreeRegistry().enumerate(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree, bool& /*stop*/) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
// Register on surfaces started in the future
uiManager->setOnSurfaceStartCallback(
[animatedMountingOverrideDelegate =
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
animatedMountingOverrideDelegate_)](
const ShadowTree& shadowTree) {
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
animatedMountingOverrideDelegate);
});
}
}
return nativeAnimatedNodesManager_;
}
@@ -58,6 +58,11 @@ void PropsAnimatedNode::connectToView(Tag viewTag) {
connectedViewTag_ = viewTag;
}
void PropsAnimatedNode::connectToShadowNode(
std::shared_ptr<const ShadowNode> 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
@@ -14,6 +14,7 @@
#include "AnimatedNode.h"
#include <react/renderer/animated/internal/primitives.h>
#include <react/renderer/core/ShadowNode.h>
#include <mutex>
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<const ShadowNode> 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<const ShadowNode> viewShadowNode_{};
};
} // namespace facebook::react
@@ -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 <react/renderer/core/PropsParserContext.h>
#include "AnimatedProps.h"
namespace facebook::react {
void AnimatedPropsRegistry::update(
std::unordered_map<Tag, AnimatedProps>& animatedPropsMap,
std::unordered_set<const ShadowNodeFamily*>& 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<Float>(animatedProp);
break;
case WIDTH:
viewProps.yogaStyle.setDimension(
yoga::Dimension::Width,
get<yoga::Style::SizeLength>(animatedProp));
break;
case HEIGHT:
viewProps.yogaStyle.setDimension(
yoga::Dimension::Height,
get<yoga::Style::SizeLength>(animatedProp));
break;
case BORDER_RADII:
viewProps.borderRadii = get<CascadedBorderRadii>(animatedProp);
break;
case FLEX:
viewProps.yogaStyle.setFlex(get<yoga::FloatOptional>(animatedProp));
break;
case TRANSFORM:
viewProps.transform = get<Transform>(animatedProp);
break;
}
}
}
}
} // namespace facebook::react
@@ -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 <folly/dynamic.h>
#include <react/renderer/components/view/BaseViewProps.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerCommitHook.h>
#include "AnimatedProps.h"
namespace facebook::react {
struct PropsSnapshot {
BaseViewProps props;
std::unordered_set<PropName> propNames;
};
class AnimatedPropsRegistry {
public:
std::unordered_map<Tag, PropsSnapshot> map_;
std::unordered_map<SurfaceId, std::unordered_set<const ShadowNodeFamily*>>
surfaceToFamilies_;
void update(
std::unordered_map<Tag, AnimatedProps>& animatedPropsMap,
std::unordered_set<const ShadowNodeFamily*>& families);
};
} // namespace facebook::react
@@ -7,9 +7,22 @@
#include "AnimationBackend.h"
#include <chrono>
#include "AnimatedPropsRegistry.h"
namespace facebook::react {
UIManagerNativeAnimatedDelegateBackendImpl::
UIManagerNativeAnimatedDelegateBackendImpl(
std::weak_ptr<AnimationBackend> 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<AnimatedPropsRegistry>()),
uiManager_(uiManager),
commitHook_(std::make_unique<AnimationBackendCommitHook>(
uiManager,
animatedPropsRegistry_)) {}
void AnimationBackend::onAnimationFrame(double timestamp) {
std::unordered_map<Tag, AnimatedProps> 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<const ShadowNodeFamily*>& families,
std::unordered_map<Tag, AnimatedProps>& updates) {
uiManager_->getShadowTreeRegistry().enumerate(
[families, &updates](const ShadowTree& shadowTree, bool& /*stop*/) {
shadowTree.commit(
[families, &updates](const RootShadowNode& oldRootShadowNode) {
return std::static_pointer_cast<RootShadowNode>(
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<SurfaceId, std::unordered_set<const ShadowNodeFamily*>>
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<RootShadowNode>(
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(
@@ -15,9 +15,25 @@
#include <vector>
#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> animationBackend);
void runAnimationFrame() override;
private:
std::weak_ptr<AnimationBackend> 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> animatedPropsRegistry_;
UIManager* uiManager_;
std::unique_ptr<AnimationBackendCommitHook> commitHook_;
AnimationBackend(
StartOnRenderCallback&& startOnRenderCallback,
@@ -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 <react/renderer/animationbackend/AnimationBackendCommitHook.h>
namespace facebook::react {
AnimationBackendCommitHook::AnimationBackendCommitHook(
UIManager* uiManager,
std::shared_ptr<AnimatedPropsRegistry> 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<BaseViewProps> 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<BaseViewProps>(
std::static_pointer_cast<const BaseViewProps>(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
@@ -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 <folly/dynamic.h>
#include <react/renderer/core/ReactPrimitives.h>
#include <react/renderer/uimanager/UIManager.h>
#include <react/renderer/uimanager/UIManagerCommitHook.h>
#include "AnimatedPropsRegistry.h"
namespace facebook::react {
class AnimationBackendCommitHook : public UIManagerCommitHook {
UIManager* uiManager_;
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
public:
AnimationBackendCommitHook(
UIManager* uiManager,
std::shared_ptr<AnimatedPropsRegistry> 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
@@ -435,11 +435,7 @@ std::shared_ptr<ShadowNode> cloneMultipleRecursive(
}
}
ShadowNodeFragment fragment{.children = newChildren};
if (familiesToUpdate.contains(family)) {
return callback(shadowNode, fragment);
}
return shadowNode.clone(fragment);
return callback(shadowNode, {.children = newChildren});
}
} // namespace
@@ -83,6 +83,7 @@ function createNativeOperations(): $NonMaybeType<typeof NativeAnimatedModule> {
'removeAnimatedEventFromView', // 19
'addListener', // 20
'removeListener', // 21
'connectAnimatedNodeToShadowNode', // 22
];
const nativeOperations: {
[$Values<typeof methodNames>]: (...$ReadOnlyArray<mixed>) => 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);
},
@@ -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;
@@ -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;