diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js
index 0fd23272409d..3886f2ac3208 100644
--- a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js
+++ b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js
@@ -70,16 +70,6 @@ test('animated opacity', () => {
_opacityAnimation?.stop();
});
- // TODO: T246961305 rendered output should be at this point
- expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
- ,
- );
-
- // Re-render
- Fantom.runTask(() => {
- root.render();
- });
-
expect(root.getRenderedOutput({props: ['opacity']}).toJSX()).toEqual(
,
);
@@ -191,15 +181,126 @@ test('animate layout props and rerender', () => {
_setWidth(200);
});
+ // 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(
+ ,
+ );
+
+ Fantom.unstable_produceFramesForDuration(500);
+
// TODO: this shouldn't be neccessary since animation should be stopped after duration
Fantom.runTask(() => {
_heightAnimation?.stop();
});
+ expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
+ ,
+ );
+
+ Fantom.runTask(() => {
+ _setWidth(300);
+ });
+
+ expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
+ ,
+ );
+});
+
+test('animate non-layout props and rerender', () => {
+ const viewRef = createRef();
+
+ let _animatedOpacity;
+ let _opacityAnimation;
+ let _setWidth;
+
+ function MyApp() {
+ const animatedOpacity = useAnimatedValue(0);
+ const [width, setWidth] = useState(100);
+ _animatedOpacity = animatedOpacity;
+ _setWidth = setWidth;
+ return (
+
+ );
+ }
+
+ const root = Fantom.createRoot();
+
+ Fantom.runTask(() => {
+ root.render();
+ });
+
+ const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
+
+ Fantom.runTask(() => {
+ _opacityAnimation = Animated.timing(_animatedOpacity, {
+ toValue: 0.5,
+ duration: 1000,
+ useNativeDriver: true,
+ }).start();
+ });
+
+ Fantom.unstable_produceFramesForDuration(500);
+
+ // TODO: rendered output should be at this point, but synchronous updates are not captured by fantom
+ expect(root.getRenderedOutput({props: ['width']}).toJSX()).toEqual(
+ ,
+ );
+
+ expect(
+ Fantom.unstable_getDirectManipulationProps(viewElement).opacity,
+ ).toBeCloseTo(0.25, 0.001);
+
+ // Re-render
+ Fantom.runTask(() => {
+ _setWidth(150);
+ });
+
+ expect(root.getRenderedOutput({props: ['opacity', 'width']}).toJSX()).toEqual(
+ ,
+ );
+
+ Fantom.runTask(() => {
+ _setWidth(200);
+ });
+
// 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(
- ,
+ expect(root.getRenderedOutput({props: ['opacity', 'width']}).toJSX()).toEqual(
+ ,
+ );
+
+ Fantom.unstable_produceFramesForDuration(500);
+
+ // TODO: this shouldn't be neccessary since animation should be stopped after duration
+ Fantom.runTask(() => {
+ _opacityAnimation?.stop();
+ });
+
+ // TODO: T246961305 rendered output should be at this point
+ expect(root.getRenderedOutput({props: ['width']}).toJSX()).toEqual(
+ ,
+ );
+
+ expect(Fantom.unstable_getDirectManipulationProps(viewElement).opacity).toBe(
+ 0.5,
+ );
+
+ // Re-render
+ Fantom.runTask(() => {
+ _setWidth(300);
+ });
+
+ expect(root.getRenderedOutput({props: ['opacity', 'width']}).toJSX()).toEqual(
+ ,
);
});
@@ -298,3 +399,72 @@ test('animate layout props and rerender in many components', () => {
,
);
});
+
+test('animate width, height and opacity at once', () => {
+ const viewRef = createRef();
+ allowStyleProp('width');
+ allowStyleProp('height');
+
+ let _animatedWidth;
+ let _animatedHeight;
+ let _animatedOpacity;
+ let _parallelAnimation;
+
+ function MyApp() {
+ const animatedWidth = useAnimatedValue(100);
+ const animatedHeight = useAnimatedValue(100);
+ const animatedOpacity = useAnimatedValue(1);
+ _animatedWidth = animatedWidth;
+ _animatedHeight = animatedHeight;
+ _animatedOpacity = animatedOpacity;
+ return (
+
+ );
+ }
+
+ const root = Fantom.createRoot();
+
+ Fantom.runTask(() => {
+ root.render();
+ });
+
+ Fantom.runTask(() => {
+ _parallelAnimation = Animated.parallel([
+ Animated.timing(_animatedWidth, {
+ toValue: 200,
+ duration: 100,
+ useNativeDriver: true,
+ }),
+ Animated.timing(_animatedHeight, {
+ toValue: 200,
+ duration: 100,
+ useNativeDriver: true,
+ }),
+ Animated.timing(_animatedOpacity, {
+ toValue: 0.5,
+ duration: 100,
+ useNativeDriver: true,
+ }),
+ ]).start();
+ });
+
+ Fantom.unstable_produceFramesForDuration(100);
+
+ // TODO: this shouldn't be neccessary since animation should be stopped after duration
+ Fantom.runTask(() => {
+ _parallelAnimation?.stop();
+ });
+
+ expect(
+ root.getRenderedOutput({props: ['width', 'height', 'opacity']}).toJSX(),
+ ).toEqual();
+});
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
index a17966d285d4..386d6b5bd99d 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp
@@ -244,8 +244,7 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNodeFamily(
react_native_assert(propsNodeTag);
auto node = getAnimatedNode(propsNodeTag);
if (node != nullptr && family != nullptr) {
- std::lock_guard lock(tagToShadowNodeFamilyMutex_);
- tagToShadowNodeFamily_[family->getTag()] = family;
+ node->connectToShadowNodeFamily(family);
} else {
LOG(WARNING)
<< "Cannot ConnectAnimatedNodeToShadowNodeFamily, animated node has to be props type";
@@ -265,10 +264,6 @@ void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
std::lock_guard lock(connectedAnimatedNodesMutex_);
connectedAnimatedNodes_.erase(viewTag);
}
- {
- std::lock_guard lock(tagToShadowNodeFamilyMutex_);
- tagToShadowNodeFamily_.erase(viewTag);
- }
updatedNodeTags_.insert(node->tag());
onManagedPropsRemoved(viewTag);
@@ -907,13 +902,17 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
Tag viewTag,
const folly::dynamic& props,
bool layoutStyleUpdated,
- bool forceFabricCommit) noexcept {
+ bool forceFabricCommit,
+ ShadowNodeFamily::Weak shadowNodeFamily) noexcept {
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
- if (layoutStyleUpdated) {
- mergeObjects(updateViewProps_[viewTag], props);
- } else {
- mergeObjects(updateViewPropsDirect_[viewTag], props);
+ if (forceFabricCommit) {
+ shouldRequestAsyncFlush_ = true;
}
+ auto& current = layoutStyleUpdated
+ ? updateViewPropsForBackend_[viewTag]
+ : updateViewPropsDirectForBackend_[viewTag];
+ current.first = std::move(shadowNodeFamily);
+ mergeObjects(current.second, props);
return;
}
@@ -962,7 +961,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
task();
}
- AnimationMutations mutations;
+ AnimationMutations mutations{};
// Step through the animation loop
if (isAnimationUpdateNeeded()) {
@@ -1007,44 +1006,36 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
}
}
- for (auto& [tag, props] : updateViewPropsDirect_) {
- propsBuilder.storeDynamic(props);
- mutations.push_back(
- AnimationMutation{tag, nullptr, propsBuilder.get()});
+ for (auto& [tag, update] : updateViewPropsDirectForBackend_) {
+ auto weakFamily = update.first;
+
+ if (auto family = weakFamily.lock()) {
+ propsBuilder.storeDynamic(update.second);
+ mutations.batch.push_back(
+ AnimationMutation{
+ .tag = tag,
+ .family = family,
+ .props = propsBuilder.get(),
+ });
+ }
containsChange = true;
}
- {
- std::lock_guard lock(tagToShadowNodeFamilyMutex_);
- for (auto& [tag, props] : updateViewProps_) {
- auto familyIt = tagToShadowNodeFamily_.find(tag);
- if (familyIt == tagToShadowNodeFamily_.end()) {
- continue;
- }
- if (auto family = familyIt->second.lock()) {
- // C++ Animated produces props in the form of a folly::dynamic, so
- // it wouldn't make sense to unpack it here. However, for the
- // purposes of testing, we want to be able to use the statically
- // typed AnimationMutation. At a later stage we will instead just
- // pass the dynamic directly to propsBuilder and the new API could
- // be used by 3rd party libraries or in the fututre by Animated.
- 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()));
- }
- mutations.push_back(
- AnimationMutation{
- .tag = tag,
- .family = family,
- .props = propsBuilder.get(),
- });
- }
- containsChange = true;
+ for (auto& [tag, update] : updateViewPropsForBackend_) {
+ auto weakFamily = update.first;
+
+ if (auto family = weakFamily.lock()) {
+ propsBuilder.storeDynamic(update.second);
+ mutations.batch.push_back(
+ AnimationMutation{
+ .tag = tag,
+ .family = family,
+ .props = propsBuilder.get(),
+ .hasLayoutUpdates = true,
+ });
}
+ containsChange = true;
}
+
if (containsChange) {
updateViewPropsDirect_.clear();
updateViewProps_.clear();
@@ -1074,38 +1065,43 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
isEventAnimationInProgress_ = false;
- for (auto& [tag, props] : updateViewPropsDirect_) {
- propsBuilder.storeDynamic(props);
- mutations.push_back(
- AnimationMutation{
- .tag = tag,
- .family = nullptr,
- .props = propsBuilder.get(),
- });
+ for (auto& [tag, update] : updateViewPropsDirectForBackend_) {
+ auto weakFamily = update.first;
+
+ if (auto family = weakFamily.lock()) {
+ propsBuilder.storeDynamic(update.second);
+ mutations.batch.push_back(
+ AnimationMutation{
+ .tag = tag,
+ .family = family,
+ .props = propsBuilder.get(),
+ });
+ }
}
- {
- std::lock_guard lock(tagToShadowNodeFamilyMutex_);
- for (auto& [tag, props] : updateViewProps_) {
- auto familyIt = tagToShadowNodeFamily_.find(tag);
- if (familyIt == tagToShadowNodeFamily_.end()) {
- continue;
- }
- if (auto family = familyIt->second.lock()) {
- propsBuilder.storeDynamic(props);
- mutations.push_back(
- AnimationMutation{
- .tag = tag,
- .family = family,
- .props = propsBuilder.get(),
- });
- }
+ for (auto& [tag, update] : updateViewPropsForBackend_) {
+ auto weakFamily = update.first;
+
+ if (auto family = weakFamily.lock()) {
+ propsBuilder.storeDynamic(update.second);
+ mutations.batch.push_back(
+ AnimationMutation{
+ .tag = tag,
+ .family = family,
+ .props = propsBuilder.get(),
+ .hasLayoutUpdates = true,
+ });
}
}
+
+ updateViewProps_.clear();
+ updateViewPropsDirect_.clear();
}
} else {
// There is no active animation. Stop the render callback.
stopRenderCallbackIfNeeded(false);
}
+ mutations.shouldRequestAsyncFlush = shouldRequestAsyncFlush_;
+ shouldRequestAsyncFlush_ = false;
return mutations;
}
#endif
diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h
index 40632cbc1dcf..50b0a7873e91 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h
+++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h
@@ -153,7 +153,8 @@ class NativeAnimatedNodesManager {
Tag viewTag,
const folly::dynamic &props,
bool layoutStyleUpdated,
- bool forceFabricCommit) noexcept;
+ bool forceFabricCommit,
+ ShadowNodeFamily::Weak shadowNodeFamily = {}) noexcept;
/**
* Commits all pending animated property updates to their respective views.
@@ -260,9 +261,9 @@ class NativeAnimatedNodesManager {
std::unordered_map updateViewProps_{};
std::unordered_map updateViewPropsDirect_{};
-
- mutable std::mutex tagToShadowNodeFamilyMutex_;
- std::unordered_map> tagToShadowNodeFamily_{};
+ std::unordered_map> updateViewPropsForBackend_{};
+ std::unordered_map> updateViewPropsDirectForBackend_{};
+ bool shouldRequestAsyncFlush_{false};
/*
* 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 569f662d9753..b0d08ce645fe 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp
@@ -95,7 +95,8 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
std::move(stopOnRenderCallback_),
std::move(directManipulationCallback),
std::move(fabricCommitCallback),
- uiManager);
+ uiManager,
+ jsInvoker);
nativeAnimatedNodesManager_ =
std::make_shared(animationBackend_);
@@ -145,28 +146,30 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
- 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()) {
+ 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 48660d81531d..5313a776151c 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp
@@ -55,6 +55,11 @@ PropsAnimatedNode::PropsAnimatedNode(
}
}
+void PropsAnimatedNode::connectToShadowNodeFamily(
+ ShadowNodeFamily::Weak shadowNodeFamily) {
+ shadowNodeFamily_ = std::move(shadowNodeFamily);
+}
+
void PropsAnimatedNode::connectToView(Tag viewTag) {
react_native_assert(
connectedViewTag_ == animated::undefinedAnimatedNodeIdentifier &&
@@ -67,6 +72,7 @@ void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
connectedViewTag_ == viewTag &&
"Attempting to disconnect view that has not been connected with the given animated node.");
connectedViewTag_ = animated::undefinedAnimatedNodeIdentifier;
+ shadowNodeFamily_.reset();
}
// restore the value to whatever the value was on the ShadowNode instead of in
@@ -74,8 +80,17 @@ void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
void PropsAnimatedNode::restoreDefaultValues() {
// If node is already disconnected from View, we cannot restore default values
if (connectedViewTag_ != animated::undefinedAnimatedNodeIdentifier) {
- manager_->schedulePropsCommit(
- connectedViewTag_, folly::dynamic::object(), false, false);
+ if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
+ manager_->schedulePropsCommit(
+ connectedViewTag_,
+ folly::dynamic::object(),
+ false,
+ false,
+ shadowNodeFamily_);
+ } else {
+ manager_->schedulePropsCommit(
+ connectedViewTag_, folly::dynamic::object(), false, false);
+ }
}
}
@@ -147,8 +162,17 @@ void PropsAnimatedNode::update(bool forceFabricCommit) {
layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);
- manager_->schedulePropsCommit(
- connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
+ if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
+ manager_->schedulePropsCommit(
+ connectedViewTag_,
+ props_,
+ layoutStyleUpdated_,
+ forceFabricCommit,
+ shadowNodeFamily_);
+ } else {
+ manager_->schedulePropsCommit(
+ connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
+ }
}
} // 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 3da4db587d46..ee17db51246d 100644
--- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h
+++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.h
@@ -21,6 +21,7 @@ namespace facebook::react {
class PropsAnimatedNode final : public AnimatedNode {
public:
PropsAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager);
+ void connectToShadowNodeFamily(ShadowNodeFamily::Weak shadowNodeFamily);
void connectToView(Tag viewTag);
void disconnectFromView(Tag viewTag);
void restoreDefaultValues();
@@ -51,6 +52,7 @@ class PropsAnimatedNode final : public AnimatedNode {
bool layoutStyleUpdated_{false};
Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier};
+ ShadowNodeFamily::Weak shadowNodeFamily_;
// Needed for PlatformColor resolver
SurfaceId connectedRootTag_{animated::undefinedAnimatedNodeIdentifier};
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp
index bfddaeaf427c..09ee3620c6fd 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.cpp
@@ -35,6 +35,18 @@ void AnimatedPropsRegistry::update(
auto& snapshot = it->second;
auto& viewProps = snapshot->props;
+ if (animatedProps.rawProps) {
+ const auto& newRawProps = *animatedProps.rawProps;
+ auto& currentRawProps = snapshot->rawProps;
+
+ if (currentRawProps) {
+ auto newRawPropsDynamic = newRawProps.toDynamic();
+ currentRawProps->merge_patch(newRawPropsDynamic);
+ } else {
+ currentRawProps =
+ std::make_unique(newRawProps.toDynamic());
+ }
+ }
for (const auto& animatedProp : animatedProps.props) {
snapshot->propNames.insert(animatedProp->propName);
cloneProp(viewProps, *animatedProp);
@@ -58,6 +70,13 @@ AnimatedPropsRegistry::getMap(SurfaceId surfaceId) {
map.insert_or_assign(tag, std::move(propsSnapshot));
} else {
auto& currentSnapshot = currentIt->second;
+ if (propsSnapshot->rawProps) {
+ if (currentSnapshot->rawProps) {
+ currentSnapshot->rawProps->merge_patch(*propsSnapshot->rawProps);
+ } else {
+ currentSnapshot->rawProps = std::move(propsSnapshot->rawProps);
+ }
+ }
for (auto& propName : propsSnapshot->propNames) {
currentSnapshot->propNames.insert(propName);
updateProp(propName, currentSnapshot->props, *propsSnapshot);
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h
index 8df9fc030465..da3cbbb84845 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimatedPropsRegistry.h
@@ -19,6 +19,7 @@ namespace facebook::react {
struct PropsSnapshot {
BaseViewProps props;
std::unordered_set propNames;
+ std::unique_ptr rawProps;
};
struct SurfaceContext {
@@ -29,6 +30,7 @@ struct SurfaceContext {
struct SurfaceUpdates {
std::unordered_set families;
std::unordered_map propsMap;
+ bool hasLayoutUpdates{false};
};
using SnapshotMap = std::unordered_map>;
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp
index 6ee64331da2b..30867e68be47 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp
@@ -6,6 +6,7 @@
*/
#include "AnimationBackend.h"
+#include
#include
#include
#include
@@ -49,58 +50,65 @@ static inline Props::Shared cloneProps(
return newProps;
}
-static inline bool mutationHasLayoutUpdates(
- facebook::react::AnimationMutation& mutation) {
- for (auto& animatedProp : mutation.props.props) {
- // TODO: there should also be a check for the dynamic part
- if (animatedProp->propName == WIDTH || animatedProp->propName == HEIGHT ||
- animatedProp->propName == FLEX) {
- return true;
- }
- }
- return false;
-}
-
AnimationBackend::AnimationBackend(
StartOnRenderCallback&& startOnRenderCallback,
StopOnRenderCallback&& stopOnRenderCallback,
DirectManipulationCallback&& directManipulationCallback,
FabricCommitCallback&& fabricCommitCallback,
- UIManager* uiManager)
+ UIManager* uiManager,
+ std::shared_ptr jsInvoker)
: startOnRenderCallback_(std::move(startOnRenderCallback)),
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
directManipulationCallback_(std::move(directManipulationCallback)),
fabricCommitCallback_(std::move(fabricCommitCallback)),
animatedPropsRegistry_(std::make_shared()),
uiManager_(uiManager),
+ jsInvoker_(std::move(jsInvoker)),
commitHook_(uiManager, animatedPropsRegistry_) {}
void AnimationBackend::onAnimationFrame(double timestamp) {
- std::unordered_map synchronousUpdates;
std::unordered_map surfaceUpdates;
+ bool shouldRequestAsyncFlush = false;
- bool hasAnyLayoutUpdates = false;
for (auto& callback : callbacks) {
- auto muatations = callback(static_cast(timestamp));
- for (auto& mutation : muatations) {
- hasAnyLayoutUpdates |= mutationHasLayoutUpdates(mutation);
+ auto mutations = callback(static_cast(timestamp));
+ shouldRequestAsyncFlush |= mutations.shouldRequestAsyncFlush;
+ for (auto& mutation : mutations.batch) {
const auto family = mutation.family;
- if (family != nullptr) {
- auto& [families, updates] = surfaceUpdates[family->getSurfaceId()];
- families.insert(family.get());
- updates[mutation.tag] = std::move(mutation.props);
- } else {
- synchronousUpdates[mutation.tag] = std::move(mutation.props);
- }
+ react_native_assert(family != nullptr);
+
+ auto& [families, updates, hasLayoutUpdates] =
+ surfaceUpdates[family->getSurfaceId()];
+ hasLayoutUpdates |= mutation.hasLayoutUpdates;
+ families.insert(family.get());
+ updates[mutation.tag] = std::move(mutation.props);
}
}
animatedPropsRegistry_->update(surfaceUpdates);
- if (hasAnyLayoutUpdates) {
- commitUpdates(surfaceUpdates);
- } else {
- synchronouslyUpdateProps(synchronousUpdates);
+ for (auto& [surfaceId, surfaceUpdates] : surfaceUpdates) {
+ if (surfaceUpdates.hasLayoutUpdates) {
+ commitUpdates(surfaceId, surfaceUpdates);
+ } else {
+ synchronouslyUpdateProps(surfaceUpdates.propsMap);
+ }
+ }
+
+ if (shouldRequestAsyncFlush) {
+ // for (const auto& [surfaceId, _] : surfaceUpdates) {
+ jsInvoker_->invokeAsync([this]() {
+ uiManager_->getShadowTreeRegistry().enumerate(
+ [](const ShadowTree& shadowTree, bool& stop) {
+ shadowTree.commit(
+ [](const RootShadowNode& oldRootShadowNode) {
+ return std::static_pointer_cast(
+ oldRootShadowNode.ShadowNode::clone({}));
+ },
+ {.source = ShadowTreeCommitSource::React});
+ });
+ });
+ // }
}
}
@@ -126,47 +134,41 @@ void AnimationBackend::stop(bool isAsync) {
}
void AnimationBackend::commitUpdates(
- std::unordered_map& surfaceUpdates) {
- for (auto& surfaceEntry : surfaceUpdates) {
- const auto& surfaceId = surfaceEntry.first;
- const auto& surfaceFamilies = surfaceEntry.second.families;
- auto& updates = surfaceEntry.second.propsMap;
- 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});
- });
- }
+ SurfaceId surfaceId,
+ SurfaceUpdates& surfaceUpdates) {
+ auto& [surfaceFamilies, updates, hasLayoutUpdates] = surfaceUpdates;
+ 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(
const std::unordered_map& updates) {
for (auto& [tag, animatedProps] : updates) {
- // TODO: We shouldn't repack it into dynamic, but for that a rewrite of
- // directManipulationCallback_ is needed
+ // TODO: We shouldn't repack it into dynamic, but for that a rewrite
+ // of directManipulationCallback_ is needed
auto dyn = animationbackend::packAnimatedProps(animatedProps);
directManipulationCallback_(tag, std::move(dyn));
}
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h
index d8f7e3a326d9..8b796d53c562 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h
@@ -7,11 +7,13 @@
#pragma once
+#include
#include
#include
#include
#include
#include
+#include
#include
#include "AnimatedProps.h"
#include "AnimatedPropsBuilder.h"
@@ -36,9 +38,13 @@ struct AnimationMutation {
Tag tag;
std::shared_ptr family;
AnimatedProps props;
+ bool hasLayoutUpdates{false};
};
-using AnimationMutations = std::vector;
+struct AnimationMutations {
+ std::vector batch;
+ bool shouldRequestAsyncFlush{false};
+};
class AnimationBackend : public UIManagerAnimationBackend {
public:
@@ -55,6 +61,7 @@ class AnimationBackend : public UIManagerAnimationBackend {
const FabricCommitCallback fabricCommitCallback_;
std::shared_ptr animatedPropsRegistry_;
UIManager *uiManager_;
+ std::shared_ptr jsInvoker_;
AnimationBackendCommitHook commitHook_;
AnimationBackend(
@@ -62,8 +69,9 @@ class AnimationBackend : public UIManagerAnimationBackend {
StopOnRenderCallback &&stopOnRenderCallback,
DirectManipulationCallback &&directManipulationCallback,
FabricCommitCallback &&fabricCommitCallback,
- UIManager *uiManager);
- void commitUpdates(std::unordered_map &surfaceUpdates);
+ UIManager *uiManager,
+ std::shared_ptr jsInvoker);
+ void commitUpdates(SurfaceId surfaceId, SurfaceUpdates &surfaceUpdates);
void synchronouslyUpdateProps(const std::unordered_map &updates);
void clearRegistry(SurfaceId surfaceId) override;
diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp
index 5cb990fb68ad..6d359ea8c1c3 100644
--- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp
@@ -43,13 +43,19 @@ RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit(
if (surfaceFamilies.contains(&shadowNode.getFamily()) &&
updates.contains(shadowNode.getTag())) {
auto& snapshot = updates.at(shadowNode.getTag());
- if (!snapshot->propNames.empty()) {
+ if (!snapshot->propNames.empty() || snapshot->rawProps) {
PropsParserContext propsParserContext{
shadowNode.getSurfaceId(),
*shadowNode.getContextContainer()};
-
- newProps = shadowNode.getComponentDescriptor().cloneProps(
- propsParserContext, shadowNode.getProps(), {});
+ if (snapshot->rawProps) {
+ newProps = shadowNode.getComponentDescriptor().cloneProps(
+ propsParserContext,
+ shadowNode.getProps(),
+ RawProps(*snapshot->rawProps));
+ } else {
+ newProps = shadowNode.getComponentDescriptor().cloneProps(
+ propsParserContext, shadowNode.getProps(), {});
+ }
viewProps = std::const_pointer_cast(
std::static_pointer_cast(newProps));
}