diff --git a/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js b/packages/react-native/Libraries/Animated/__tests__/AnimatedBackend-itest.js index e36beafdfe382f..3886f2ac320866 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( , ); diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index 7341352e0cc336..6a92aa05602328 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"; @@ -261,14 +260,13 @@ void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView( auto node = getAnimatedNode(propsNodeTag); if (node != nullptr) { node->disconnectFromView(viewTag); + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + node->disconnectFromShadowNodeFamily(); + } { std::lock_guard lock(connectedAnimatedNodesMutex_); connectedAnimatedNodes_.erase(viewTag); } - { - std::lock_guard lock(tagToShadowNodeFamilyMutex_); - tagToShadowNodeFamily_.erase(viewTag); - } updatedNodeTags_.insert(node->tag()); onManagedPropsRemoved(viewTag); @@ -907,13 +905,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_.insert(viewTag); } + auto& current = layoutStyleUpdated + ? updateViewPropsForBackend_[viewTag] + : updateViewPropsDirectForBackend_[viewTag]; + current.first = std::move(shadowNodeFamily); + mergeObjects(current.second, props); return; } @@ -940,6 +942,32 @@ void NativeAnimatedNodesManager::schedulePropsCommit( } #ifdef RN_USE_ANIMATION_BACKEND + +void NativeAnimatedNodesManager::insertMutations( + std::unordered_map>& + updates, + AnimationMutations& mutations, + AnimatedPropsBuilder& propsBuilder, + bool hasLayoutUpdates) { + for (auto& [tag, update] : updates) { + auto weakFamily = update.first; + + if (auto family = weakFamily.lock()) { + propsBuilder.storeDynamic(update.second); + if (shouldRequestAsyncFlush_.contains(tag)) { + mutations.asyncFlushSurfaces.insert(family->getSurfaceId()); + } + mutations.batch.push_back( + AnimationMutation{ + .tag = tag, + .family = family, + .props = propsBuilder.get(), + .hasLayoutUpdates = hasLayoutUpdates, + }); + } + } +} + AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) { return {}; @@ -962,7 +990,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { task(); } - AnimationMutations mutations; + AnimationMutations mutations{}; // Step through the animation loop if (isAnimationUpdateNeeded()) { @@ -1007,49 +1035,18 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { } } - { - std::lock_guard lock(tagToShadowNodeFamilyMutex_); - for (auto& [tag, props] : updateViewPropsDirect_) { - auto familyIt = tagToShadowNodeFamily_.find(tag); - if (familyIt == tagToShadowNodeFamily_.end()) { - continue; - } + insertMutations( + updateViewPropsDirectForBackend_, mutations, propsBuilder); - auto weakFamily = familyIt->second; - if (auto family = weakFamily.lock()) { - propsBuilder.storeDynamic(props); - mutations.batch.push_back( - AnimationMutation{ - .tag = tag, - .family = family, - .props = propsBuilder.get(), - }); - } - containsChange = true; - } - for (auto& [tag, props] : updateViewProps_) { - auto familyIt = tagToShadowNodeFamily_.find(tag); - if (familyIt == tagToShadowNodeFamily_.end()) { - continue; - } + insertMutations( + updateViewPropsForBackend_, mutations, propsBuilder, true); + + containsChange = !updateViewPropsForBackend_.empty() || + !updateViewPropsDirectForBackend_.empty(); - auto weakFamily = familyIt->second; - if (auto family = weakFamily.lock()) { - propsBuilder.storeDynamic(props); - mutations.batch.push_back( - AnimationMutation{ - .tag = tag, - .family = family, - .props = propsBuilder.get(), - .hasLayoutUpdates = true, - }); - } - containsChange = true; - } - } if (containsChange) { - updateViewPropsDirect_.clear(); - updateViewProps_.clear(); + updateViewPropsDirectForBackend_.clear(); + updateViewPropsForBackend_.clear(); } } @@ -1076,51 +1073,20 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { isEventAnimationInProgress_ = false; - { - std::lock_guard lock(tagToShadowNodeFamilyMutex_); - for (auto& [tag, props] : updateViewPropsDirect_) { - auto familyIt = tagToShadowNodeFamily_.find(tag); - if (familyIt == tagToShadowNodeFamily_.end()) { - continue; - } + insertMutations( + updateViewPropsDirectForBackend_, mutations, propsBuilder); - auto weakFamily = familyIt->second; - if (auto family = weakFamily.lock()) { - propsBuilder.storeDynamic(props); - mutations.batch.push_back( - AnimationMutation{ - .tag = tag, - .family = family, - .props = propsBuilder.get(), - }); - } - } - for (auto& [tag, props] : updateViewProps_) { - auto familyIt = tagToShadowNodeFamily_.find(tag); - if (familyIt == tagToShadowNodeFamily_.end()) { - continue; - } + insertMutations( + updateViewPropsForBackend_, mutations, propsBuilder, true); - auto weakFamily = familyIt->second; - if (auto family = weakFamily.lock()) { - propsBuilder.storeDynamic(props); - mutations.batch.push_back( - AnimationMutation{ - .tag = tag, - .family = family, - .props = propsBuilder.get(), - .hasLayoutUpdates = true, - }); - } - } - } - updateViewProps_.clear(); - updateViewPropsDirect_.clear(); + updateViewPropsForBackend_.clear(); + updateViewPropsDirectForBackend_.clear(); } } else { // There is no active animation. Stop the render callback. stopRenderCallbackIfNeeded(false); } + shouldRequestAsyncFlush_.clear(); 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 40632cbc1dcf71..9a40a66497f3ab 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.h @@ -121,6 +121,11 @@ class NativeAnimatedNodesManager { void setAnimatedNodeOffset(Tag tag, double offset); #ifdef RN_USE_ANIMATION_BACKEND + void insertMutations( + std::unordered_map> &updates, + AnimationMutations &mutations, + AnimatedPropsBuilder &propsBuilder, + bool hasLayoutUpdates = false); AnimationMutations pullAnimationMutations(); #endif @@ -153,7 +158,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,10 +266,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_{}; + std::unordered_set shouldRequestAsyncFlush_{}; /* * Sometimes a view is not longer connected to a PropsAnimatedNode, but * NativeAnimated has previously changed the view's props via direct diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index bf8bad41ba96c2..b0d08ce645fe6c 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_); 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 48660d81531d6d..5cee6b457e4a0c 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/nodes/PropsAnimatedNode.cpp @@ -12,6 +12,7 @@ #include "PropsAnimatedNode.h" #include +#include #include #include #include @@ -55,6 +56,15 @@ PropsAnimatedNode::PropsAnimatedNode( } } +void PropsAnimatedNode::connectToShadowNodeFamily( + ShadowNodeFamily::Weak shadowNodeFamily) { + shadowNodeFamily_ = std::move(shadowNodeFamily); +} + +void PropsAnimatedNode::disconnectFromShadowNodeFamily() { + shadowNodeFamily_.reset(); +} + void PropsAnimatedNode::connectToView(Tag viewTag) { react_native_assert( connectedViewTag_ == animated::undefinedAnimatedNodeIdentifier && @@ -74,8 +84,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 +166,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 3da4db587d46ac..51b1ac76660432 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,11 @@ namespace facebook::react { class PropsAnimatedNode final : public AnimatedNode { public: PropsAnimatedNode(Tag tag, const folly::dynamic &config, NativeAnimatedNodesManager &manager); + + // Only called when `useSharedAnimatedBackend`==true + void connectToShadowNodeFamily(ShadowNodeFamily::Weak shadowNodeFamily); + void disconnectFromShadowNodeFamily(); + void connectToView(Tag viewTag); void disconnectFromView(Tag viewTag); void restoreDefaultValues(); @@ -51,6 +56,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/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index 6cb8a707c4e0ac..ed353a38f999b0 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -68,21 +68,25 @@ AnimationBackend::AnimationBackend( 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 surfaceUpdates; + std::set asyncFlushSurfaces; for (auto& callback : callbacks) { - auto muatations = callback(static_cast(timestamp)); - for (auto& mutation : muatations.batch) { + auto mutations = callback(static_cast(timestamp)); + asyncFlushSurfaces.merge(mutations.asyncFlushSurfaces); + for (auto& mutation : mutations.batch) { const auto family = mutation.family; react_native_assert(family != nullptr); @@ -103,6 +107,8 @@ void AnimationBackend::onAnimationFrame(double timestamp) { synchronouslyUpdateProps(updates.propsMap); } } + + requestAsyncFlushForSurfaces(asyncFlushSurfaces); } void AnimationBackend::start(const Callback& callback, bool isAsync) { @@ -168,6 +174,25 @@ void AnimationBackend::synchronouslyUpdateProps( } } +void AnimationBackend::requestAsyncFlushForSurfaces( + const std::set& surfaces) { + for (const auto& surfaceId : surfaces) { + // perform an empty commit on the js thread, to force the commit hook to + // push updated shadow nodes to react through RSNRU + jsInvoker_->invokeAsync([this, surfaceId]() { + uiManager_->getShadowTreeRegistry().visit( + surfaceId, [](const ShadowTree& shadowTree) { + shadowTree.commit( + [](const RootShadowNode& oldRootShadowNode) { + return std::static_pointer_cast( + oldRootShadowNode.ShadowNode::clone({})); + }, + {.source = ShadowTreeCommitSource::AnimationEndSync}); + }); + }); + } +} + void AnimationBackend::clearRegistry(SurfaceId surfaceId) { animatedPropsRegistry_->clear(surfaceId); } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index 7e0bd9bbe81fb8..b8394f908b663a 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -7,11 +7,14 @@ #pragma once +#include #include #include #include #include #include +#include +#include #include #include "AnimatedProps.h" #include "AnimatedPropsBuilder.h" @@ -41,6 +44,7 @@ struct AnimationMutation { struct AnimationMutations { std::vector batch; + std::set asyncFlushSurfaces; }; class AnimationBackend : public UIManagerAnimationBackend { @@ -58,6 +62,7 @@ class AnimationBackend : public UIManagerAnimationBackend { const FabricCommitCallback fabricCommitCallback_; std::shared_ptr animatedPropsRegistry_; UIManager *uiManager_; + std::shared_ptr jsInvoker_; AnimationBackendCommitHook commitHook_; AnimationBackend( @@ -65,9 +70,11 @@ class AnimationBackend : public UIManagerAnimationBackend { StopOnRenderCallback &&stopOnRenderCallback, DirectManipulationCallback &&directManipulationCallback, FabricCommitCallback &&fabricCommitCallback, - UIManager *uiManager); + UIManager *uiManager, + std::shared_ptr jsInvoker); void commitUpdates(SurfaceId surfaceId, SurfaceUpdates &surfaceUpdates); void synchronouslyUpdateProps(const std::unordered_map &updates); + void requestAsyncFlushForSurfaces(const std::set &surfaces); void clearRegistry(SurfaceId surfaceId) override; void onAnimationFrame(double timestamp) override; diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp index 6d359ea8c1c38e..2058d3d5caa747 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackendCommitHook.cpp @@ -21,7 +21,8 @@ RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit( const RootShadowNode::Shared& oldRootShadowNode, const RootShadowNode::Unshared& newRootShadowNode, const ShadowTreeCommitOptions& commitOptions) noexcept { - if (commitOptions.source != ShadowTreeCommitSource::React) { + if (commitOptions.source != ShadowTreeCommitSource::React && + commitOptions.source != ShadowTreeCommitSource::AnimationEndSync) { return newRootShadowNode; } diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h index 3b44d6e49ca470..c1f51fc1a91e82 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h +++ b/packages/react-native/ReactCommon/react/renderer/mounting/ShadowTree.h @@ -50,6 +50,7 @@ enum class ShadowTreeCommitMode { enum class ShadowTreeCommitSource { Unknown, React, + AnimationEndSync, }; struct ShadowTreeCommitOptions {