From e298acedb4b3e1e3ca7848a3bd45385f0e83031e Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:09:01 +0200 Subject: [PATCH 01/21] rename to processedMutations --- .../LayoutAnimationsProxy_Experimental.cpp | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index a43711a59083..6f46fc5d0f87 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -25,17 +25,17 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans ReanimatedSystraceSection d("pullTransaction"); auto lock = std::unique_lock(mutex); const PropsParserContext propsParserContext{surfaceId, *contextContainer_}; - ShadowViewMutationList filteredMutations; + ShadowViewMutationList processedMutations; auto rootChildCount = static_cast(lightNodes_[surfaceId]->children.size()); const std::vector> roots; const bool isInTransition = static_cast(transitionState_); if (isInTransition) { - updateLightTree(propsParserContext, mutations, filteredMutations); - handleProgressTransition(filteredMutations, mutations, propsParserContext, surfaceId); + updateLightTree(propsParserContext, mutations, processedMutations); + handleProgressTransition(processedMutations, mutations, propsParserContext, surfaceId); } else if (!synchronized_) { auto actualTop = topScreen[surfaceId]; - updateLightTree(propsParserContext, mutations, filteredMutations); + updateLightTree(propsParserContext, mutations, processedMutations); auto reactTop = findTopScreen(lightNodes_[surfaceId]); if (reactTop == actualTop) { synchronized_ = true; @@ -49,7 +49,7 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans findSharedElementsOnScreen(beforeTopScreen, BEFORE, propsParserContext); } - updateLightTree(propsParserContext, mutations, filteredMutations); + updateLightTree(propsParserContext, mutations, processedMutations); auto afterTopScreen = findTopScreen(root); topScreen[surfaceId] = afterTopScreen; @@ -68,13 +68,13 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans // and mutations to hide target views after all mutations. std::vector mergedMutations; hideTransitioningViews(BEFORE, mergedMutations, propsParserContext); - mergedMutations.insert(mergedMutations.end(), filteredMutations.begin(), filteredMutations.end()); + mergedMutations.insert(mergedMutations.end(), processedMutations.begin(), processedMutations.end()); hideTransitioningViews(AFTER, mergedMutations, propsParserContext); - std::swap(filteredMutations, mergedMutations); + std::swap(processedMutations, mergedMutations); } handleSharedTransitionsStart( - afterTopScreen, beforeTopScreen, filteredMutations, mutations, propsParserContext, surfaceId); + afterTopScreen, beforeTopScreen, processedMutations, mutations, propsParserContext, surfaceId); } for (auto &node : entering_) { @@ -86,19 +86,19 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans entering_.clear(); layout_.clear(); - handleRemovals(filteredMutations, exiting_); + handleRemovals(processedMutations, exiting_); exiting_.clear(); - addOngoingAnimations(surfaceId, filteredMutations); + addOngoingAnimations(surfaceId, processedMutations); - cleanupAnimations(filteredMutations, propsParserContext, surfaceId); + cleanupAnimations(processedMutations, propsParserContext, surfaceId); transitionMap_.clear(); transitions_.clear(); - insertContainers(filteredMutations, rootChildCount, surfaceId); + insertContainers(processedMutations, rootChildCount, surfaceId); - return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry}; + return MountingTransaction{surfaceId, transactionNumber, std::move(processedMutations), telemetry}; } bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const { @@ -111,7 +111,7 @@ bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const { void LayoutAnimationsProxy_Experimental::updateLightTree( const PropsParserContext &propsParserContext, const ShadowViewMutationList &mutations, - ShadowViewMutationList &filteredMutations) const { + ShadowViewMutationList &processedMutations) const { ReanimatedSystraceSection s("updateLightTree"); std::unordered_set inserted, moved, deleted; for (auto it = mutations.rbegin(); it != mutations.rend(); it++) { @@ -168,7 +168,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { layout_.push_back(node); } else { - filteredMutations.push_back(mutation); + processedMutations.push_back(mutation); } break; } @@ -178,7 +178,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( react_native_assert(!lightNodes_.contains(mutation.newChildShadowView.tag) && "LightNode already exists"); lightNodes_[mutation.newChildShadowView.tag] = node; - filteredMutations.push_back(mutation); + processedMutations.push_back(mutation); break; } case ShadowViewMutation::Delete: { @@ -193,13 +193,13 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( node->parent = parent; const auto tag = mutation.newChildShadowView.tag; if (moved.contains(tag) && layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { - filteredMutations.push_back( + processedMutations.push_back( ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, mutation.index)); } else if (layoutAnimationsManager_->hasLayoutAnimation(tag, ENTERING)) { entering_.push_back(node); - filteredMutations.push_back(mutation); + processedMutations.push_back(mutation); } else { - filteredMutations.push_back(mutation); + processedMutations.push_back(mutation); } break; } @@ -214,10 +214,10 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (deleted.contains(tag) && !deleted.contains(parentTag)) { exiting_.push_back(node); - filteredMutations.push_back(mutation); + processedMutations.push_back(mutation); parent->children.erase(parent->children.begin() + mutation.index); } else if (!deleted.contains(tag)) { - filteredMutations.push_back(mutation); + processedMutations.push_back(mutation); parent->children.erase(parent->children.begin() + mutation.index); } break; @@ -306,7 +306,7 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( } void LayoutAnimationsProxy_Experimental::handleRemovals( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, std::vector> &roots) const { ReanimatedSystraceSection s("handleRemovals"); // iterate from the end, so that children @@ -320,7 +320,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(node, filteredMutations, config)) { + if (startAnimationsRecursively(node, processedMutations, config)) { auto parent = node->parent.lock(); react_native_assert(parent && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this @@ -334,7 +334,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( if (layoutAnimations_.contains(node->current.tag)) { current = layoutAnimations_.at(node->current.tag).currentView; } - filteredMutations.push_back( + processedMutations.push_back( ShadowViewMutation::InsertMutation(parent->current.tag, current, static_cast(parent->children.size()))); parent->children.push_back(node); if (node->state == UNDEFINED) { @@ -342,7 +342,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( } } else { maybeCancelAnimation(node->current.tag); - filteredMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); + processedMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); } } @@ -353,8 +353,8 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( auto index = parent->removeChild(node); react_native_assert(index != -1 && "Dead node not found"); - endAnimationsRecursively(node, index, filteredMutations); - maybeDropAncestors(parent, filteredMutations); + endAnimationsRecursively(node, index, processedMutations); + maybeDropAncestors(parent, processedMutations); } } deadNodes.clear(); @@ -616,11 +616,11 @@ void LayoutAnimationsProxy_Experimental::maybeUpdateWindowDimensions( } void LayoutAnimationsProxy_Experimental::cleanupAnimations( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const { ReanimatedSystraceSection s("cleanupAnimations"); - cleanupSharedTransitions(filteredMutations, propsParserContext, surfaceId); + cleanupSharedTransitions(processedMutations, propsParserContext, surfaceId); #ifdef ANDROID restoreOpacityInCaseOfFlakyEnteringAnimation(surfaceId); From 76a13c11f32968a281368d499fc779889d59526c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:09:08 +0200 Subject: [PATCH 02/21] remove dead line --- .../LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 6f46fc5d0f87..63a935b440fc 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -27,7 +27,6 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans const PropsParserContext propsParserContext{surfaceId, *contextContainer_}; ShadowViewMutationList processedMutations; auto rootChildCount = static_cast(lightNodes_[surfaceId]->children.size()); - const std::vector> roots; const bool isInTransition = static_cast(transitionState_); if (isInTransition) { From 5bc1d6b13a8dcc8e52abe4fc149369e75d481a03 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:10:18 +0200 Subject: [PATCH 03/21] rename to processedMutations --- .../LayoutAnimationsProxy_Experimental.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index eca44e4cf651..7a0bf4c576ca 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -98,7 +98,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void startProgressTransition(const int tag, const ShadowView &before, const ShadowView &after, SurfaceId surfaceId) const; void handleProgressTransition( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, const ShadowViewMutationList &mutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; @@ -106,22 +106,22 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void updateLightTree( const PropsParserContext &propsParserContext, const ShadowViewMutationList &mutations, - ShadowViewMutationList &filteredMutations) const; + ShadowViewMutationList &processedMutations) const; void handleSharedTransitionsStart( const std::shared_ptr &afterTopScreen, const std::shared_ptr &beforeTopScreen, - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, const ShadowViewMutationList &mutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; void cleanupAnimations( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; void cleanupSharedTransitions( - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; @@ -133,7 +133,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void hideTransitioningViews( BeforeOrAfter index, - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, const PropsParserContext &propsParserContext) const; void transferConfigFromNativeID(const std::string &nativeId, const int tag) const; @@ -152,7 +152,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, BeforeOrAfter index, const PropsParserContext &propsParserContext) const; - void insertContainers(ShadowViewMutationList &filteredMutations, int &rootChildCount, SurfaceId surfaceId) const; + void insertContainers(ShadowViewMutationList &processedMutations, int &rootChildCount, SurfaceId surfaceId) const; std::vector getAbsolutePositionsForRootPathView(const std::shared_ptr &node) const; @@ -161,7 +161,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, Tag getOrCreateContainer( const ShadowView &before, const SharedTag &sharedTag, - ShadowViewMutationList &filteredMutations, + ShadowViewMutationList &processedMutations, SurfaceId surfaceId) const; void overrideTransform( @@ -179,7 +179,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, std::array getTranslateForTransformOrigin(float viewWidth, float viewHeight, const TransformOrigin &transformOrigin) const; - void handleRemovals(ShadowViewMutationList &filteredMutations, std::vector> &roots) const; + void handleRemovals(ShadowViewMutationList &processedMutations, std::vector> &roots) const; void addOngoingAnimations(SurfaceId surfaceId, ShadowViewMutationList &mutations) const; void updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation) const; From 319fa1c7592d97bb8d391d61b25da75a3fdf890f Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:24:13 +0200 Subject: [PATCH 04/21] remove unnecessary cast and indirection --- .../LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 63a935b440fc..9b717474352a 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -27,9 +27,8 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans const PropsParserContext propsParserContext{surfaceId, *contextContainer_}; ShadowViewMutationList processedMutations; auto rootChildCount = static_cast(lightNodes_[surfaceId]->children.size()); - const bool isInTransition = static_cast(transitionState_); - if (isInTransition) { + if (transitionState_ != TransitionState::NONE) { updateLightTree(propsParserContext, mutations, processedMutations); handleProgressTransition(processedMutations, mutations, propsParserContext, surfaceId); } else if (!synchronized_) { From a859adffebecfc5b1b8da738da56a3daff047c86 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:41:14 +0200 Subject: [PATCH 05/21] make names more explicit --- .../LayoutAnimationsProxy_Experimental.cpp | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 9b717474352a..6bfc75b0b6b9 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -305,12 +305,11 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( void LayoutAnimationsProxy_Experimental::handleRemovals( ShadowViewMutationList &processedMutations, - std::vector> &roots) const { + std::vector> &exitingRoots) const { ReanimatedSystraceSection s("handleRemovals"); - // iterate from the end, so that children - // with higher indices appear first in the mutations list - for (auto it = roots.rbegin(); it != roots.rend(); it++) { - auto &node = *it; + // iterate from the end, so that children with higher indices appear first in the mutations list + for (auto it = exitingRoots.rbegin(); it != exitingRoots.rend(); it++) { + auto &exitingRoot = *it; const StartAnimationsRecursivelyConfig config = { .shouldRemoveSubviewsWithoutAnimations = true, @@ -318,9 +317,9 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(node, processedMutations, config)) { - auto parent = node->parent.lock(); - react_native_assert(parent && "Parent node is nullptr"); + if (startAnimationsRecursively(exitingRoot, processedMutations, config)) { + auto parentOfExitingRoot = exitingRoot->parent.lock(); + react_native_assert(parentOfExitingRoot && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this // Currently we remove each view, and then if we want to animate it, reinsert it at the end. // This is nice, but introduces extra mutations (which could have some side effects, like making a snapshot in @@ -328,31 +327,31 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( // convenience of this approach is that it is much easier to maintain indices of animated views, and handle // reparentings. - auto current = node->current; - if (layoutAnimations_.contains(node->current.tag)) { - current = layoutAnimations_.at(node->current.tag).currentView; + auto exitingShadowView = exitingRoot->current; + if (layoutAnimations_.contains(exitingRoot->current.tag)) { + exitingShadowView = layoutAnimations_.at(exitingRoot->current.tag).currentView; } - processedMutations.push_back( - ShadowViewMutation::InsertMutation(parent->current.tag, current, static_cast(parent->children.size()))); - parent->children.push_back(node); - if (node->state == UNDEFINED) { - node->state = WAITING; + processedMutations.push_back(ShadowViewMutation::InsertMutation( + parentOfExitingRoot->current.tag, exitingShadowView, static_cast(parentOfExitingRoot->children.size()))); + parentOfExitingRoot->children.push_back(exitingRoot); + if (exitingRoot->state == UNDEFINED) { + exitingRoot->state = WAITING; } } else { - maybeCancelAnimation(node->current.tag); - processedMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); + maybeCancelAnimation(exitingRoot->current.tag); + processedMutations.push_back(ShadowViewMutation::DeleteMutation(exitingRoot->current)); } } - for (const auto &node : deadNodes) { - if (node->state != DELETED) { - auto parent = node->parent.lock(); - react_native_assert(parent && "Parent node is nullptr"); - auto index = parent->removeChild(node); - react_native_assert(index != -1 && "Dead node not found"); + for (const auto &deadNode : deadNodes) { + if (deadNode->state != DELETED) { + auto parentOfDeadNode = deadNode->parent.lock(); + react_native_assert(parentOfDeadNode && "Parent node is nullptr"); + auto deadNodeIndex = parentOfDeadNode->removeChild(deadNode); + react_native_assert(deadNodeIndex != -1 && "Dead node not found"); - endAnimationsRecursively(node, index, processedMutations); - maybeDropAncestors(parent, processedMutations); + endAnimationsRecursively(deadNode, deadNodeIndex, processedMutations); + maybeDropAncestors(parentOfDeadNode, processedMutations); } } deadNodes.clear(); From 3cc4073dc78616b970117740ef4d6212139244f8 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:48:40 +0200 Subject: [PATCH 06/21] refine naming --- .../LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp | 6 +++--- .../LayoutAnimations/LayoutAnimationsProxy_Experimental.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 6bfc75b0b6b9..a61cab388b0a 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -84,8 +84,8 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans entering_.clear(); layout_.clear(); - handleRemovals(processedMutations, exiting_); - exiting_.clear(); + handleRemovals(processedMutations, exitingRoots_); + exitingRoots_.clear(); addOngoingAnimations(surfaceId, processedMutations); @@ -211,7 +211,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( "Indicies are wrong in Remove mutation"); if (deleted.contains(tag) && !deleted.contains(parentTag)) { - exiting_.push_back(node); + exitingRoots_.push_back(node); processedMutations.push_back(mutation); parent->children.erase(parent->children.begin() + mutation.index); } else if (!deleted.contains(tag)) { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index 7a0bf4c576ca..3d9fbb99d843 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -53,7 +53,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, mutable TransitionMap transitionMap_; mutable Transitions transitions_; mutable bool synchronized_ = true; - mutable std::vector> entering_, layout_, exiting_; + mutable std::vector> entering_, layout_, exitingRoots_; std::shared_ptr sharedTransitionManager_; mutable std::unordered_map> lightNodes_; mutable std::vector> containersToInsert_; From 588225326c4f2bc7b363d639ad665f2a898eef4c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 17:56:11 +0200 Subject: [PATCH 07/21] rename --- .../LayoutAnimationsProxy_Experimental.cpp | 90 ++++++++++--------- .../LayoutAnimationsProxy_Experimental.h | 20 ++--- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index a61cab388b0a..36b7da1ccf15 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -25,15 +25,15 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans ReanimatedSystraceSection d("pullTransaction"); auto lock = std::unique_lock(mutex); const PropsParserContext propsParserContext{surfaceId, *contextContainer_}; - ShadowViewMutationList processedMutations; + ShadowViewMutationList outputMutations; auto rootChildCount = static_cast(lightNodes_[surfaceId]->children.size()); if (transitionState_ != TransitionState::NONE) { - updateLightTree(propsParserContext, mutations, processedMutations); - handleProgressTransition(processedMutations, mutations, propsParserContext, surfaceId); + updateLightTree(propsParserContext, mutations, outputMutations); + handleProgressTransition(outputMutations, mutations, propsParserContext, surfaceId); } else if (!synchronized_) { auto actualTop = topScreen[surfaceId]; - updateLightTree(propsParserContext, mutations, processedMutations); + updateLightTree(propsParserContext, mutations, outputMutations); auto reactTop = findTopScreen(lightNodes_[surfaceId]); if (reactTop == actualTop) { synchronized_ = true; @@ -47,7 +47,7 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans findSharedElementsOnScreen(beforeTopScreen, BEFORE, propsParserContext); } - updateLightTree(propsParserContext, mutations, processedMutations); + updateLightTree(propsParserContext, mutations, outputMutations); auto afterTopScreen = findTopScreen(root); topScreen[surfaceId] = afterTopScreen; @@ -66,13 +66,13 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans // and mutations to hide target views after all mutations. std::vector mergedMutations; hideTransitioningViews(BEFORE, mergedMutations, propsParserContext); - mergedMutations.insert(mergedMutations.end(), processedMutations.begin(), processedMutations.end()); + mergedMutations.insert(mergedMutations.end(), outputMutations.begin(), outputMutations.end()); hideTransitioningViews(AFTER, mergedMutations, propsParserContext); - std::swap(processedMutations, mergedMutations); + std::swap(outputMutations, mergedMutations); } handleSharedTransitionsStart( - afterTopScreen, beforeTopScreen, processedMutations, mutations, propsParserContext, surfaceId); + afterTopScreen, beforeTopScreen, outputMutations, mutations, propsParserContext, surfaceId); } for (auto &node : entering_) { @@ -84,19 +84,19 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans entering_.clear(); layout_.clear(); - handleRemovals(processedMutations, exitingRoots_); - exitingRoots_.clear(); + handleRemovals(outputMutations, exitingRootsToProcess_); + exitingRootsToProcess_.clear(); - addOngoingAnimations(surfaceId, processedMutations); + addOngoingAnimations(surfaceId, outputMutations); - cleanupAnimations(processedMutations, propsParserContext, surfaceId); + cleanupAnimations(outputMutations, propsParserContext, surfaceId); transitionMap_.clear(); transitions_.clear(); - insertContainers(processedMutations, rootChildCount, surfaceId); + insertContainers(outputMutations, rootChildCount, surfaceId); - return MountingTransaction{surfaceId, transactionNumber, std::move(processedMutations), telemetry}; + return MountingTransaction{surfaceId, transactionNumber, std::move(outputMutations), telemetry}; } bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const { @@ -109,7 +109,7 @@ bool LayoutAnimationsProxy_Experimental::shouldOverridePullTransaction() const { void LayoutAnimationsProxy_Experimental::updateLightTree( const PropsParserContext &propsParserContext, const ShadowViewMutationList &mutations, - ShadowViewMutationList &processedMutations) const { + ShadowViewMutationList &outputMutations) const { ReanimatedSystraceSection s("updateLightTree"); std::unordered_set inserted, moved, deleted; for (auto it = mutations.rbegin(); it != mutations.rend(); it++) { @@ -166,7 +166,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { layout_.push_back(node); } else { - processedMutations.push_back(mutation); + outputMutations.push_back(mutation); } break; } @@ -176,7 +176,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( react_native_assert(!lightNodes_.contains(mutation.newChildShadowView.tag) && "LightNode already exists"); lightNodes_[mutation.newChildShadowView.tag] = node; - processedMutations.push_back(mutation); + outputMutations.push_back(mutation); break; } case ShadowViewMutation::Delete: { @@ -191,13 +191,13 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( node->parent = parent; const auto tag = mutation.newChildShadowView.tag; if (moved.contains(tag) && layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { - processedMutations.push_back( + outputMutations.push_back( ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, mutation.index)); } else if (layoutAnimationsManager_->hasLayoutAnimation(tag, ENTERING)) { entering_.push_back(node); - processedMutations.push_back(mutation); + outputMutations.push_back(mutation); } else { - processedMutations.push_back(mutation); + outputMutations.push_back(mutation); } break; } @@ -211,11 +211,11 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( "Indicies are wrong in Remove mutation"); if (deleted.contains(tag) && !deleted.contains(parentTag)) { - exitingRoots_.push_back(node); - processedMutations.push_back(mutation); + exitingRootsToProcess_.push_back(node); + outputMutations.push_back(mutation); parent->children.erase(parent->children.begin() + mutation.index); } else if (!deleted.contains(tag)) { - processedMutations.push_back(mutation); + outputMutations.push_back(mutation); parent->children.erase(parent->children.begin() + mutation.index); } break; @@ -304,12 +304,12 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( } void LayoutAnimationsProxy_Experimental::handleRemovals( - ShadowViewMutationList &processedMutations, - std::vector> &exitingRoots) const { + ShadowViewMutationList &outputMutations, + std::vector> &newExitingRoots) const { ReanimatedSystraceSection s("handleRemovals"); // iterate from the end, so that children with higher indices appear first in the mutations list - for (auto it = exitingRoots.rbegin(); it != exitingRoots.rend(); it++) { - auto &exitingRoot = *it; + for (auto it = newExitingRoots.rbegin(); it != newExitingRoots.rend(); it++) { + auto &newExitingRoot = *it; const StartAnimationsRecursivelyConfig config = { .shouldRemoveSubviewsWithoutAnimations = true, @@ -317,9 +317,9 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(exitingRoot, processedMutations, config)) { - auto parentOfExitingRoot = exitingRoot->parent.lock(); - react_native_assert(parentOfExitingRoot && "Parent node is nullptr"); + if (startAnimationsRecursively(newExitingRoot, outputMutations, config)) { + auto parentOfNewExitingRoot = newExitingRoot->parent.lock(); + react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this // Currently we remove each view, and then if we want to animate it, reinsert it at the end. // This is nice, but introduces extra mutations (which could have some side effects, like making a snapshot in @@ -327,19 +327,21 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( // convenience of this approach is that it is much easier to maintain indices of animated views, and handle // reparentings. - auto exitingShadowView = exitingRoot->current; - if (layoutAnimations_.contains(exitingRoot->current.tag)) { - exitingShadowView = layoutAnimations_.at(exitingRoot->current.tag).currentView; + auto exitingShadowView = newExitingRoot->current; + if (layoutAnimations_.contains(newExitingRoot->current.tag)) { + exitingShadowView = layoutAnimations_.at(newExitingRoot->current.tag).currentView; } - processedMutations.push_back(ShadowViewMutation::InsertMutation( - parentOfExitingRoot->current.tag, exitingShadowView, static_cast(parentOfExitingRoot->children.size()))); - parentOfExitingRoot->children.push_back(exitingRoot); - if (exitingRoot->state == UNDEFINED) { - exitingRoot->state = WAITING; + outputMutations.push_back(ShadowViewMutation::InsertMutation( + parentOfNewExitingRoot->current.tag, + exitingShadowView, + static_cast(parentOfNewExitingRoot->children.size()))); + parentOfNewExitingRoot->children.push_back(newExitingRoot); + if (newExitingRoot->state == UNDEFINED) { + newExitingRoot->state = WAITING; } } else { - maybeCancelAnimation(exitingRoot->current.tag); - processedMutations.push_back(ShadowViewMutation::DeleteMutation(exitingRoot->current)); + maybeCancelAnimation(newExitingRoot->current.tag); + outputMutations.push_back(ShadowViewMutation::DeleteMutation(newExitingRoot->current)); } } @@ -350,8 +352,8 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( auto deadNodeIndex = parentOfDeadNode->removeChild(deadNode); react_native_assert(deadNodeIndex != -1 && "Dead node not found"); - endAnimationsRecursively(deadNode, deadNodeIndex, processedMutations); - maybeDropAncestors(parentOfDeadNode, processedMutations); + endAnimationsRecursively(deadNode, deadNodeIndex, outputMutations); + maybeDropAncestors(parentOfDeadNode, outputMutations); } } deadNodes.clear(); @@ -613,11 +615,11 @@ void LayoutAnimationsProxy_Experimental::maybeUpdateWindowDimensions( } void LayoutAnimationsProxy_Experimental::cleanupAnimations( - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const { ReanimatedSystraceSection s("cleanupAnimations"); - cleanupSharedTransitions(processedMutations, propsParserContext, surfaceId); + cleanupSharedTransitions(outputMutations, propsParserContext, surfaceId); #ifdef ANDROID restoreOpacityInCaseOfFlakyEnteringAnimation(surfaceId); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index 3d9fbb99d843..c568a8281547 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -53,7 +53,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, mutable TransitionMap transitionMap_; mutable Transitions transitions_; mutable bool synchronized_ = true; - mutable std::vector> entering_, layout_, exitingRoots_; + mutable std::vector> entering_, layout_, exitingRootsToProcess_; std::shared_ptr sharedTransitionManager_; mutable std::unordered_map> lightNodes_; mutable std::vector> containersToInsert_; @@ -98,7 +98,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void startProgressTransition(const int tag, const ShadowView &before, const ShadowView &after, SurfaceId surfaceId) const; void handleProgressTransition( - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, const ShadowViewMutationList &mutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; @@ -106,22 +106,22 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void updateLightTree( const PropsParserContext &propsParserContext, const ShadowViewMutationList &mutations, - ShadowViewMutationList &processedMutations) const; + ShadowViewMutationList &outputMutations) const; void handleSharedTransitionsStart( const std::shared_ptr &afterTopScreen, const std::shared_ptr &beforeTopScreen, - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, const ShadowViewMutationList &mutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; void cleanupAnimations( - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; void cleanupSharedTransitions( - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext, SurfaceId surfaceId) const; @@ -133,7 +133,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void hideTransitioningViews( BeforeOrAfter index, - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, const PropsParserContext &propsParserContext) const; void transferConfigFromNativeID(const std::string &nativeId, const int tag) const; @@ -152,7 +152,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, BeforeOrAfter index, const PropsParserContext &propsParserContext) const; - void insertContainers(ShadowViewMutationList &processedMutations, int &rootChildCount, SurfaceId surfaceId) const; + void insertContainers(ShadowViewMutationList &outputMutations, int &rootChildCount, SurfaceId surfaceId) const; std::vector getAbsolutePositionsForRootPathView(const std::shared_ptr &node) const; @@ -161,7 +161,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, Tag getOrCreateContainer( const ShadowView &before, const SharedTag &sharedTag, - ShadowViewMutationList &processedMutations, + ShadowViewMutationList &outputMutations, SurfaceId surfaceId) const; void overrideTransform( @@ -179,7 +179,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, std::array getTranslateForTransformOrigin(float viewWidth, float viewHeight, const TransformOrigin &transformOrigin) const; - void handleRemovals(ShadowViewMutationList &processedMutations, std::vector> &roots) const; + void handleRemovals(ShadowViewMutationList &outputMutations, std::vector> &roots) const; void addOngoingAnimations(SurfaceId surfaceId, ShadowViewMutationList &mutations) const; void updateOngoingAnimationTarget(const int tag, const ShadowViewMutation &mutation) const; From 51d27bc4e8f14e0ac918fff6c4261ff8018ee587 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 18:00:17 +0200 Subject: [PATCH 08/21] refine name --- .../LayoutAnimationsProxy_Experimental.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 36b7da1ccf15..8c5aa5f986f9 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -309,7 +309,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( ReanimatedSystraceSection s("handleRemovals"); // iterate from the end, so that children with higher indices appear first in the mutations list for (auto it = newExitingRoots.rbegin(); it != newExitingRoots.rend(); it++) { - auto &newExitingRoot = *it; + auto &rootToStartExiting = *it; const StartAnimationsRecursivelyConfig config = { .shouldRemoveSubviewsWithoutAnimations = true, @@ -317,8 +317,8 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(newExitingRoot, outputMutations, config)) { - auto parentOfNewExitingRoot = newExitingRoot->parent.lock(); + if (startAnimationsRecursively(rootToStartExiting, outputMutations, config)) { + auto parentOfNewExitingRoot = rootToStartExiting->parent.lock(); react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this // Currently we remove each view, and then if we want to animate it, reinsert it at the end. @@ -327,21 +327,21 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( // convenience of this approach is that it is much easier to maintain indices of animated views, and handle // reparentings. - auto exitingShadowView = newExitingRoot->current; - if (layoutAnimations_.contains(newExitingRoot->current.tag)) { - exitingShadowView = layoutAnimations_.at(newExitingRoot->current.tag).currentView; + auto exitingShadowView = rootToStartExiting->current; + if (layoutAnimations_.contains(rootToStartExiting->current.tag)) { + exitingShadowView = layoutAnimations_.at(rootToStartExiting->current.tag).currentView; } outputMutations.push_back(ShadowViewMutation::InsertMutation( parentOfNewExitingRoot->current.tag, exitingShadowView, static_cast(parentOfNewExitingRoot->children.size()))); - parentOfNewExitingRoot->children.push_back(newExitingRoot); - if (newExitingRoot->state == UNDEFINED) { - newExitingRoot->state = WAITING; + parentOfNewExitingRoot->children.push_back(rootToStartExiting); + if (rootToStartExiting->state == UNDEFINED) { + rootToStartExiting->state = WAITING; } } else { - maybeCancelAnimation(newExitingRoot->current.tag); - outputMutations.push_back(ShadowViewMutation::DeleteMutation(newExitingRoot->current)); + maybeCancelAnimation(rootToStartExiting->current.tag); + outputMutations.push_back(ShadowViewMutation::DeleteMutation(rootToStartExiting->current)); } } From fe18c205a2df398ccc6cc9ce40d28fa47c0b2483 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 22:03:05 +0200 Subject: [PATCH 09/21] names more explicit --- .../LayoutAnimationsProxy_Experimental.cpp | 44 +++++++++---------- .../LayoutAnimations/LayoutAnimationsUtils.h | 2 +- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 8c5aa5f986f9..b8c917624fac 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -296,7 +296,7 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( auto node = lightNodes_[tag]; react_native_assert(node && "LightNode not found"); - node->state = DEAD; + node->exitingState = DEAD; lightNodes_.erase(tag); deadNodes.insert(node); @@ -309,7 +309,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( ReanimatedSystraceSection s("handleRemovals"); // iterate from the end, so that children with higher indices appear first in the mutations list for (auto it = newExitingRoots.rbegin(); it != newExitingRoots.rend(); it++) { - auto &rootToStartExiting = *it; + auto &newExitingRoot = *it; const StartAnimationsRecursivelyConfig config = { .shouldRemoveSubviewsWithoutAnimations = true, @@ -317,8 +317,8 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(rootToStartExiting, outputMutations, config)) { - auto parentOfNewExitingRoot = rootToStartExiting->parent.lock(); + if (startAnimationsRecursively(newExitingRoot, outputMutations, config)) { + auto parentOfNewExitingRoot = newExitingRoot->parent.lock(); react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this // Currently we remove each view, and then if we want to animate it, reinsert it at the end. @@ -327,26 +327,26 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( // convenience of this approach is that it is much easier to maintain indices of animated views, and handle // reparentings. - auto exitingShadowView = rootToStartExiting->current; - if (layoutAnimations_.contains(rootToStartExiting->current.tag)) { - exitingShadowView = layoutAnimations_.at(rootToStartExiting->current.tag).currentView; + auto exitingShadowView = newExitingRoot->current; + if (layoutAnimations_.contains(newExitingRoot->current.tag)) { + exitingShadowView = layoutAnimations_.at(newExitingRoot->current.tag).currentView; } outputMutations.push_back(ShadowViewMutation::InsertMutation( parentOfNewExitingRoot->current.tag, exitingShadowView, static_cast(parentOfNewExitingRoot->children.size()))); - parentOfNewExitingRoot->children.push_back(rootToStartExiting); - if (rootToStartExiting->state == UNDEFINED) { - rootToStartExiting->state = WAITING; + parentOfNewExitingRoot->children.push_back(newExitingRoot); + if (newExitingRoot->exitingState == UNDEFINED) { + newExitingRoot->exitingState = WAITING; } } else { - maybeCancelAnimation(rootToStartExiting->current.tag); - outputMutations.push_back(ShadowViewMutation::DeleteMutation(rootToStartExiting->current)); + maybeCancelAnimation(newExitingRoot->current.tag); + outputMutations.push_back(ShadowViewMutation::DeleteMutation(newExitingRoot->current)); } } for (const auto &deadNode : deadNodes) { - if (deadNode->state != DELETED) { + if (deadNode->exitingState != DELETED) { auto parentOfDeadNode = deadNode->parent.lock(); react_native_assert(parentOfDeadNode && "Parent node is nullptr"); auto deadNodeIndex = parentOfDeadNode->removeChild(deadNode); @@ -420,14 +420,14 @@ void LayoutAnimationsProxy_Experimental::endAnimationsRecursively( int index, ShadowViewMutationList &mutations) const { maybeCancelAnimation(node->current.tag); - node->state = DELETED; + node->exitingState = DELETED; // iterate from the end, so that children // with higher indices appear first in the mutations list const int childrenSize = static_cast(node->children.size()); for (int i = childrenSize - 1; i >= 0; i--) { auto &subNode = node->children[i]; - if (subNode->state != DELETED) { + if (subNode->exitingState != DELETED) { endAnimationsRecursively(subNode, i, mutations); } } @@ -442,7 +442,7 @@ void LayoutAnimationsProxy_Experimental::endAnimationsRecursively( void LayoutAnimationsProxy_Experimental::maybeDropAncestors( const std::shared_ptr &node, ShadowViewMutationList &cleanupMutations) const { - if (node->children.size() != 0 || node->state == ANIMATING || node->state == UNDEFINED) { + if (node->children.size() != 0 || node->exitingState == ANIMATING || node->exitingState == UNDEFINED) { return; } @@ -451,7 +451,7 @@ void LayoutAnimationsProxy_Experimental::maybeDropAncestors( auto index = parent->removeChild(node); react_native_assert(index != -1 && "Child node not found"); - node->state = DELETED; + node->exitingState = DELETED; maybeCancelAnimation(node->current.tag); cleanupMutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); cleanupMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); @@ -487,8 +487,8 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( for (auto it = node->children.rbegin(); it != node->children.rend(); it++) { index--; auto &subNode = *it; - if (subNode->state != UNDEFINED) { - if (shouldAnimate && subNode->state != DEAD) { + if (subNode->exitingState != UNDEFINED) { + if (shouldAnimate && subNode->exitingState != DEAD) { hasAnimatedChildren = true; } else { endAnimationsRecursively(subNode, index, mutations); @@ -500,10 +500,10 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( maybeCancelAnimation(subNode->current.tag); mutations.push_back(ShadowViewMutation::RemoveMutation(node->current.tag, subNode->current, index)); toBeRemoved.push_back(subNode); - subNode->state = DELETED; + subNode->exitingState = DELETED; mutations.push_back(ShadowViewMutation::DeleteMutation(subNode->current)); } else { - subNode->state = WAITING; + subNode->exitingState = WAITING; } } @@ -514,7 +514,7 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( const bool wantAnimateExit = hasExitAnimation || hasAnimatedChildren; if (hasExitAnimation) { - node->state = ANIMATING; + node->exitingState = ANIMATING; startExitingAnimation(node); lightNodes_[node->current.tag] = node; } else { diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index a5c860f72689..79879fe7ef5d 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -83,7 +83,7 @@ enum class Intent : std::uint8_t { struct LightNode { ShadowView previous; ShadowView current; - ExitingState state = ExitingState::UNDEFINED; + ExitingState exitingState = ExitingState::UNDEFINED; std::weak_ptr parent; std::vector> children; int removeChild(const std::shared_ptr &child) { From 5f95761ff7c149fedf535b5a171df706f9fce8cb Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 22:06:42 +0200 Subject: [PATCH 10/21] explicit --- .../LayoutAnimationsProxy_Experimental.cpp | 14 +++++++------- .../LayoutAnimationsProxy_Experimental.h | 8 +++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index b8c917624fac..d8413e60e7a2 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -317,7 +317,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startAnimationsRecursively(newExitingRoot, outputMutations, config)) { + if (startExitingAnimationsRecursively(newExitingRoot, outputMutations, config)) { auto parentOfNewExitingRoot = newExitingRoot->parent.lock(); react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this @@ -352,7 +352,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( auto deadNodeIndex = parentOfDeadNode->removeChild(deadNode); react_native_assert(deadNodeIndex != -1 && "Dead node not found"); - endAnimationsRecursively(deadNode, deadNodeIndex, outputMutations); + endExitingAnimationsRecursively(deadNode, deadNodeIndex, outputMutations); maybeDropAncestors(parentOfDeadNode, outputMutations); } } @@ -415,7 +415,7 @@ void LayoutAnimationsProxy_Experimental::addOngoingAnimations(SurfaceId surfaceI updateMap.clear(); } -void LayoutAnimationsProxy_Experimental::endAnimationsRecursively( +void LayoutAnimationsProxy_Experimental::endExitingAnimationsRecursively( const std::shared_ptr &node, int index, ShadowViewMutationList &mutations) const { @@ -428,7 +428,7 @@ void LayoutAnimationsProxy_Experimental::endAnimationsRecursively( for (int i = childrenSize - 1; i >= 0; i--) { auto &subNode = node->children[i]; if (subNode->exitingState != DELETED) { - endAnimationsRecursively(subNode, i, mutations); + endExitingAnimationsRecursively(subNode, i, mutations); } } node->children.clear(); @@ -463,7 +463,7 @@ const ComponentDescriptor &LayoutAnimationsProxy_Experimental::getComponentDescr return componentDescriptorRegistry_->at(shadowView.componentHandle); } -bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( +bool LayoutAnimationsProxy_Experimental::startExitingAnimationsRecursively( const std::shared_ptr &node, ShadowViewMutationList &mutations, StartAnimationsRecursivelyConfig config) const { @@ -491,10 +491,10 @@ bool LayoutAnimationsProxy_Experimental::startAnimationsRecursively( if (shouldAnimate && subNode->exitingState != DEAD) { hasAnimatedChildren = true; } else { - endAnimationsRecursively(subNode, index, mutations); + endExitingAnimationsRecursively(subNode, index, mutations); toBeRemoved.push_back(subNode); } - } else if (startAnimationsRecursively(subNode, mutations, config)) { + } else if (startExitingAnimationsRecursively(subNode, mutations, config)) { hasAnimatedChildren = true; } else if (shouldRemoveSubviewsWithoutAnimations) { maybeCancelAnimation(subNode->current.tag); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index c568a8281547..91e3d9e81c13 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -190,12 +190,14 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, void maybeUpdateWindowDimensions(const facebook::react::ShadowViewMutation &mutation) const; ShadowView maybeCreateLayoutAnimation(ShadowView &before, const ShadowView &after, const Tag parentTag) const; - bool startAnimationsRecursively( + bool startExitingAnimationsRecursively( const std::shared_ptr &node, ShadowViewMutationList &mutations, StartAnimationsRecursivelyConfig config) const; - void endAnimationsRecursively(const std::shared_ptr &node, int index, ShadowViewMutationList &mutations) - const; + void endExitingAnimationsRecursively( + const std::shared_ptr &node, + int index, + ShadowViewMutationList &mutations) const; void maybeDropAncestors(const std::shared_ptr &node, ShadowViewMutationList &cleanupMutations) const; const ComponentDescriptor &getComponentDescriptorForShadowView(const ShadowView &shadowView) const; From 727e0aba4c8ddba6007743b286ebd89c12d3444a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 22:27:41 +0200 Subject: [PATCH 11/21] remove warning --- .../cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index 79879fe7ef5d..4d244ca33404 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -86,11 +86,12 @@ struct LightNode { ExitingState exitingState = ExitingState::UNDEFINED; std::weak_ptr parent; std::vector> children; + int removeChild(const std::shared_ptr &child) { - for (int i = children.size() - 1; i >= 0; i--) { + for (auto i = children.size() - 1; i >= 0; i--) { if (children[i]->current.tag == child->current.tag) { children.erase(children.begin() + i); - return i; + return static_cast(i); } } return -1; From f10321099ff4df4e18ce3a43dcacd21a7c375375 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Tue, 26 May 2026 23:02:45 +0200 Subject: [PATCH 12/21] count exiting nodes before rn index --- .../LayoutAnimations/LayoutAnimationsUtils.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index 4d244ca33404..b2f05356efe4 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -96,6 +96,17 @@ struct LightNode { } return -1; } + + int countExitingChildrenBeforeIndex(int index) const { + react_native_assert(index >= 0 && static_cast(index) <= children.size() && "index out of range"); + auto result = 0; + for (auto i = 0; i < index; i++) { + if (children[i]->exitingState != ExitingState::UNDEFINED) { + result++; + } + } + return result; + } }; struct SurfaceManager { From 5027ef02b1c7ca550807b29971511b84564ac2fe Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Wed, 27 May 2026 21:27:12 +0200 Subject: [PATCH 13/21] fix zindex for exit animations --- .../LayoutAnimationsProxy_Experimental.cpp | 183 ++++++++++++++---- .../LayoutAnimationsProxy_Experimental.h | 2 +- .../LayoutAnimations/LayoutAnimationsUtils.h | 10 + 3 files changed, 159 insertions(+), 36 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index d8413e60e7a2..1a1ffba0d85d 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -84,8 +84,8 @@ std::optional LayoutAnimationsProxy_Experimental::pullTrans entering_.clear(); layout_.clear(); - handleRemovals(outputMutations, exitingRootsToProcess_); - exitingRootsToProcess_.clear(); + handleRemovals(outputMutations, potentialExitingRoots_); + potentialExitingRoots_.clear(); addOngoingAnimations(surfaceId, outputMutations); @@ -139,6 +139,32 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( for (const auto &mutation : mutations) { maybeUpdateWindowDimensions(mutation); + const char *typeName = "Unknown"; + Tag logTag = 0; + switch (mutation.type) { + case ShadowViewMutation::Create: + typeName = "Create"; + logTag = mutation.newChildShadowView.tag; + break; + case ShadowViewMutation::Delete: + typeName = "Delete"; + logTag = mutation.oldChildShadowView.tag; + break; + case ShadowViewMutation::Insert: + typeName = "Insert"; + logTag = mutation.newChildShadowView.tag; + break; + case ShadowViewMutation::Remove: + typeName = "Remove"; + logTag = mutation.oldChildShadowView.tag; + break; + case ShadowViewMutation::Update: + typeName = "Update"; + logTag = mutation.newChildShadowView.tag; + break; + } + LOG(INFO) << "@@@mutation " << typeName << " tag=" << logTag << " parentTag=" << mutation.parentTag + << " index=" << mutation.index; switch (mutation.type) { case ShadowViewMutation::Update: { auto &node = lightNodes_[mutation.newChildShadowView.tag]; @@ -166,6 +192,8 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { layout_.push_back(node); } else { + LOG(INFO) << "@@@emit Update tag=" << tag << " parentTag=" << mutation.parentTag + << " index=" << mutation.index; outputMutations.push_back(mutation); } break; @@ -176,28 +204,63 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( react_native_assert(!lightNodes_.contains(mutation.newChildShadowView.tag) && "LightNode already exists"); lightNodes_[mutation.newChildShadowView.tag] = node; + LOG(INFO) << "@@@emit Create tag=" << mutation.newChildShadowView.tag << " parentTag=" << mutation.parentTag + << " index=" << mutation.index; outputMutations.push_back(mutation); break; } case ShadowViewMutation::Delete: { - lightNodes_.erase(mutation.oldChildShadowView.tag); + const auto deletedTag = mutation.oldChildShadowView.tag; + const bool isPotentialExitingRoot = std::ranges::any_of( + potentialExitingRoots_, [deletedTag](const auto &node) { return node->current.tag == deletedTag; }); + if (!isPotentialExitingRoot) { + lightNodes_.erase(deletedTag); + } break; } case ShadowViewMutation::Insert: { transferConfigFromNativeID(mutation.newChildShadowView.props->nativeId, mutation.newChildShadowView.tag); auto &node = lightNodes_[mutation.newChildShadowView.tag]; auto &parent = lightNodes_[mutation.parentTag]; - parent->children.insert(parent->children.begin() + mutation.index, node); + const auto actualIndex = mutation.index + parent->countExitingChildrenBeforeIndex(mutation.index); + + if (parent->countExitingChildrenBeforeIndex(mutation.index) > 0) { + std::string childTags; + for (std::size_t i = 0; i < parent->children.size(); i++) { + if (i > 0) { + childTags += ", "; + } + childTags += std::to_string(parent->children[i]->current.tag); + if (parent->children[i]->exitingState != ExitingState::UNDEFINED) { + childTags += "(exiting)"; + } + } + LOG(INFO) << "@@@Insert index shifted by exiting children: tag " << mutation.newChildShadowView.tag + << " under parent " << mutation.parentTag << " rnIndex=" << mutation.index + << " actualIndex=" << actualIndex + << " exitingBefore=" << parent->countExitingChildrenBeforeIndex(mutation.index) << " children=[" + << childTags << "]"; + } + + parent->children.insert(parent->children.begin() + actualIndex, node); node->parent = parent; const auto tag = mutation.newChildShadowView.tag; if (moved.contains(tag) && layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { + LOG(INFO) << "@@@emit Insert(moved/layout) tag=" << tag << " parentTag=" << mutation.parentTag + << " actualIndex=" << actualIndex; outputMutations.push_back( - ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, mutation.index)); + ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, actualIndex)); } else if (layoutAnimationsManager_->hasLayoutAnimation(tag, ENTERING)) { entering_.push_back(node); - outputMutations.push_back(mutation); + LOG(INFO) << "@@@emit Insert(entering) tag=" << tag << " parentTag=" << mutation.parentTag + << " actualIndex=" << actualIndex; + outputMutations.push_back( + ShadowViewMutation::InsertMutation(mutation.parentTag, mutation.newChildShadowView, actualIndex)); } else { - outputMutations.push_back(mutation); + LOG(INFO) << "@@@emit Insert tag=" << tag << " parentTag=" << mutation.parentTag + << " actualIndex=" << actualIndex; + outputMutations.push_back( + ShadowViewMutation::InsertMutation(mutation.parentTag, mutation.newChildShadowView, actualIndex)); } break; } @@ -206,17 +269,40 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( const auto tag = node->current.tag; const auto parentTag = mutation.parentTag; const auto &parent = lightNodes_[parentTag]; + const auto actualIndex = mutation.index + parent->countExitingChildrenBeforeIndex(mutation.index); + if (parent->children[actualIndex]->current.tag != mutation.oldChildShadowView.tag) { + std::string childTags; + for (std::size_t i = 0; i < parent->children.size(); i++) { + if (i > 0) { + childTags += ", "; + } + childTags += std::to_string(parent->children[i]->current.tag); + if (parent->children[i]->exitingState != ExitingState::UNDEFINED) { + childTags += "(exiting)"; + } + } + LOG(WARNING) << "@@@Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag + << " at actualIndex " << actualIndex << " under parent tag " << parentTag << ", but found tag " + << parent->children[actualIndex]->current.tag << " (rnIndex=" << mutation.index + << "); children=[" << childTags << "]"; + } react_native_assert( - parent->children[mutation.index]->current.tag == mutation.oldChildShadowView.tag && + parent->children[actualIndex]->current.tag == mutation.oldChildShadowView.tag && "Indicies are wrong in Remove mutation"); if (deleted.contains(tag) && !deleted.contains(parentTag)) { - exitingRootsToProcess_.push_back(node); - outputMutations.push_back(mutation); - parent->children.erase(parent->children.begin() + mutation.index); + potentialExitingRoots_.push_back(node); + node->exitingState = ExitingState::CANDIDATE; + // don't remove the view yet because reanimated needs to animate the exit + LOG(INFO) << "@@@PotentialExitingRoot. Not sending remove mutation yet. tag=" << tag; + // outputMutations.push_back(ShadowViewMutation::RemoveMutation(parentTag, node->current, actualIndex)); + // parent->children.erase(parent->children.begin() + actualIndex); } else if (!deleted.contains(tag)) { - outputMutations.push_back(mutation); - parent->children.erase(parent->children.begin() + mutation.index); + // removing to change parent + LOG(INFO) << "@@@emit Remove(reparent) tag=" << tag << " parentTag=" << parentTag + << " actualIndex=" << actualIndex; + outputMutations.push_back(ShadowViewMutation::RemoveMutation(parentTag, node->current, actualIndex)); + parent->children.erase(parent->children.begin() + actualIndex); } break; } @@ -305,11 +391,11 @@ std::optional LayoutAnimationsProxy_Experimental::endLayoutAnimation( void LayoutAnimationsProxy_Experimental::handleRemovals( ShadowViewMutationList &outputMutations, - std::vector> &newExitingRoots) const { + std::vector> &potentialExitingRoots) const { ReanimatedSystraceSection s("handleRemovals"); // iterate from the end, so that children with higher indices appear first in the mutations list - for (auto it = newExitingRoots.rbegin(); it != newExitingRoots.rend(); it++) { - auto &newExitingRoot = *it; + for (auto it = potentialExitingRoots.rbegin(); it != potentialExitingRoots.rend(); it++) { + auto &potentialExitingRoot = *it; const StartAnimationsRecursivelyConfig config = { .shouldRemoveSubviewsWithoutAnimations = true, @@ -317,8 +403,8 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( .isScreenPop = false, }; - if (startExitingAnimationsRecursively(newExitingRoot, outputMutations, config)) { - auto parentOfNewExitingRoot = newExitingRoot->parent.lock(); + if (startExitingAnimationsRecursively(potentialExitingRoot, outputMutations, config)) { + auto parentOfNewExitingRoot = potentialExitingRoot->parent.lock(); react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); // TODO (future): figure out a better way to handle this // Currently we remove each view, and then if we want to animate it, reinsert it at the end. @@ -327,21 +413,45 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( // convenience of this approach is that it is much easier to maintain indices of animated views, and handle // reparentings. - auto exitingShadowView = newExitingRoot->current; - if (layoutAnimations_.contains(newExitingRoot->current.tag)) { - exitingShadowView = layoutAnimations_.at(newExitingRoot->current.tag).currentView; - } - outputMutations.push_back(ShadowViewMutation::InsertMutation( - parentOfNewExitingRoot->current.tag, - exitingShadowView, - static_cast(parentOfNewExitingRoot->children.size()))); - parentOfNewExitingRoot->children.push_back(newExitingRoot); - if (newExitingRoot->exitingState == UNDEFINED) { - newExitingRoot->exitingState = WAITING; + // auto exitingShadowView = newExitingRoot->current; + // if (layoutAnimations_.contains(newExitingRoot->current.tag)) { + // exitingShadowView = layoutAnimations_.at(newExitingRoot->current.tag).currentView; + // } + // outputMutations.push_back(ShadowViewMutation::InsertMutation( + // parentOfNewExitingRoot->current.tag, + // exitingShadowView, + // static_cast(parentOfNewExitingRoot->children.size()))); + // parentOfNewExitingRoot->children.push_back(newExitingRoot); + if (potentialExitingRoot->exitingState == UNDEFINED) { + potentialExitingRoot->exitingState = WAITING; } } else { - maybeCancelAnimation(newExitingRoot->current.tag); - outputMutations.push_back(ShadowViewMutation::DeleteMutation(newExitingRoot->current)); + maybeCancelAnimation(potentialExitingRoot->current.tag); + auto parent = potentialExitingRoot->parent.lock(); + react_native_assert(parent && "Parent node is nullptr"); + auto actualIndex = parent->findChildIndexByTag(potentialExitingRoot->current.tag); + react_native_assert(actualIndex != -1 && "actualIndex == -1"); + std::string childTags; + for (std::size_t i = 0; i < parent->children.size(); i++) { + if (i > 0) { + childTags += ", "; + } + childTags += std::to_string(parent->children[i]->current.tag); + if (parent->children[i]->exitingState != ExitingState::UNDEFINED) { + childTags += "(exiting)"; + } + } + // parent->children.erase(parent->children.begin() + actualIndex); + parent->removeChild(potentialExitingRoot); + LOG(INFO) << "@@@ Postponed remove and delete: tag=" << potentialExitingRoot->current.tag + << " parentTag=" << parent->current.tag << " index=" << actualIndex << " parentChildren=[" << childTags + << "]"; + LOG(INFO) << "@@@emit Remove(handleRemovals) tag=" << potentialExitingRoot->current.tag + << " parentTag=" << parent->current.tag << " actualIndex=" << actualIndex; + outputMutations.push_back( + ShadowViewMutation::RemoveMutation(parent->current.tag, potentialExitingRoot->current, actualIndex)); + LOG(INFO) << "@@@emit Delete(handleRemovals) tag=" << potentialExitingRoot->current.tag; + outputMutations.push_back(ShadowViewMutation::DeleteMutation(potentialExitingRoot->current)); } } @@ -418,7 +528,7 @@ void LayoutAnimationsProxy_Experimental::addOngoingAnimations(SurfaceId surfaceI void LayoutAnimationsProxy_Experimental::endExitingAnimationsRecursively( const std::shared_ptr &node, int index, - ShadowViewMutationList &mutations) const { + ShadowViewMutationList &outputMutations) const { maybeCancelAnimation(node->current.tag); node->exitingState = DELETED; // iterate from the end, so that children @@ -428,15 +538,18 @@ void LayoutAnimationsProxy_Experimental::endExitingAnimationsRecursively( for (int i = childrenSize - 1; i >= 0; i--) { auto &subNode = node->children[i]; if (subNode->exitingState != DELETED) { - endExitingAnimationsRecursively(subNode, i, mutations); + endExitingAnimationsRecursively(subNode, i, outputMutations); } } node->children.clear(); const auto &parent = node->parent.lock(); react_native_assert(parent && "Parent node is nullptr"); - mutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); - mutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); + LOG(INFO) << "@@@emit Remove(endExitingAnimations) tag=" << node->current.tag << " parentTag=" << parent->current.tag + << " index=" << index; + outputMutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); + LOG(INFO) << "@@@emit Delete(endExitingAnimations) tag=" << node->current.tag; + outputMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); } void LayoutAnimationsProxy_Experimental::maybeDropAncestors( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index 91e3d9e81c13..ec5eca7a5687 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -53,7 +53,7 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, mutable TransitionMap transitionMap_; mutable Transitions transitions_; mutable bool synchronized_ = true; - mutable std::vector> entering_, layout_, exitingRootsToProcess_; + mutable std::vector> entering_, layout_, potentialExitingRoots_; std::shared_ptr sharedTransitionManager_; mutable std::unordered_map> lightNodes_; mutable std::vector> containersToInsert_; diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index b2f05356efe4..f91d349b7c8e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -62,6 +62,7 @@ typedef enum class ExitingState : std::uint8_t { ANIMATING = 3, DEAD = 4, DELETED = 5, + CANDIDATE = 6, } ExitingState; struct MutationNode; @@ -97,6 +98,15 @@ struct LightNode { return -1; } + int findChildIndexByTag(Tag tag) const { + for (std::size_t i = 0; i < children.size(); i++) { + if (children[i]->current.tag == tag) { + return static_cast(i); + } + } + return -1; + } + int countExitingChildrenBeforeIndex(int index) const { react_native_assert(index >= 0 && static_cast(index) <= children.size() && "index out of range"); auto result = 0; From 7353bc343d9cbfdcf0190d3efcc4f352a2d885b7 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Wed, 27 May 2026 21:54:58 +0200 Subject: [PATCH 14/21] fix --- .../LayoutAnimationsProxy_Experimental.cpp | 3 ++- .../LayoutAnimations/LayoutAnimationsUtils.h | 16 +++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 1a1ffba0d85d..836eb56d4a74 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -284,7 +284,8 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( LOG(WARNING) << "@@@Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag << " at actualIndex " << actualIndex << " under parent tag " << parentTag << ", but found tag " << parent->children[actualIndex]->current.tag << " (rnIndex=" << mutation.index - << "); children=[" << childTags << "]"; + << "); children=[" << childTags << "]" + << " count=" << parent->countExitingChildrenBeforeIndex(mutation.index); } react_native_assert( parent->children[actualIndex]->current.tag == mutation.oldChildShadowView.tag && diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index f91d349b7c8e..bf4c8bfc0498 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -108,14 +108,20 @@ struct LightNode { } int countExitingChildrenBeforeIndex(int index) const { - react_native_assert(index >= 0 && static_cast(index) <= children.size() && "index out of range"); - auto result = 0; - for (auto i = 0; i < index; i++) { + react_native_assert(index >= 0 && "index must be non-negative"); + int remainingNonExitingChildrenToCheck = index; + int exitingCount = 0; + for (std::size_t i = 0; i < children.size(); i++) { if (children[i]->exitingState != ExitingState::UNDEFINED) { - result++; + exitingCount++; + continue; + } + if (remainingNonExitingChildrenToCheck == 0) { + return exitingCount; } + remainingNonExitingChildrenToCheck--; } - return result; + return exitingCount; } }; From 29ad60c7a842a5dd0de74bf4f4b73aab03d52b0c Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 10:44:01 +0200 Subject: [PATCH 15/21] fix --- .../LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 836eb56d4a74..bbc92a11ff3e 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -444,6 +444,7 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( } // parent->children.erase(parent->children.begin() + actualIndex); parent->removeChild(potentialExitingRoot); + lightNodes_.erase(potentialExitingRoot->current.tag); LOG(INFO) << "@@@ Postponed remove and delete: tag=" << potentialExitingRoot->current.tag << " parentTag=" << parent->current.tag << " index=" << actualIndex << " parentChildren=[" << childTags << "]"; From 721cff3e8aba4e9770f6f9f5ab62b678483b4e8a Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 11:09:06 +0200 Subject: [PATCH 16/21] clean up --- .../LayoutAnimationsProxy_Experimental.cpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index bbc92a11ff3e..975d91f602cc 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -294,10 +294,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (deleted.contains(tag) && !deleted.contains(parentTag)) { potentialExitingRoots_.push_back(node); node->exitingState = ExitingState::CANDIDATE; - // don't remove the view yet because reanimated needs to animate the exit LOG(INFO) << "@@@PotentialExitingRoot. Not sending remove mutation yet. tag=" << tag; - // outputMutations.push_back(ShadowViewMutation::RemoveMutation(parentTag, node->current, actualIndex)); - // parent->children.erase(parent->children.begin() + actualIndex); } else if (!deleted.contains(tag)) { // removing to change parent LOG(INFO) << "@@@emit Remove(reparent) tag=" << tag << " parentTag=" << parentTag @@ -407,22 +404,6 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( if (startExitingAnimationsRecursively(potentialExitingRoot, outputMutations, config)) { auto parentOfNewExitingRoot = potentialExitingRoot->parent.lock(); react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); - // TODO (future): figure out a better way to handle this - // Currently we remove each view, and then if we want to animate it, reinsert it at the end. - // This is nice, but introduces extra mutations (which could have some side effects, like making a snapshot in - // RNScreens), and it changes the zIndex of animated views, which is different from what've had. The biggest - // convenience of this approach is that it is much easier to maintain indices of animated views, and handle - // reparentings. - - // auto exitingShadowView = newExitingRoot->current; - // if (layoutAnimations_.contains(newExitingRoot->current.tag)) { - // exitingShadowView = layoutAnimations_.at(newExitingRoot->current.tag).currentView; - // } - // outputMutations.push_back(ShadowViewMutation::InsertMutation( - // parentOfNewExitingRoot->current.tag, - // exitingShadowView, - // static_cast(parentOfNewExitingRoot->children.size()))); - // parentOfNewExitingRoot->children.push_back(newExitingRoot); if (potentialExitingRoot->exitingState == UNDEFINED) { potentialExitingRoot->exitingState = WAITING; } From 5f32e38bb104fbf81e23e4ea97df225fcfc5a619 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 11:27:54 +0200 Subject: [PATCH 17/21] clarify BBExample --- .../examples/LayoutAnimations/BBExample.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx index c43af88d0db1..24369947bdbe 100644 --- a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx @@ -2,6 +2,7 @@ import React, { useState } from 'react'; import { Button, StyleSheet, + Text, TouchableWithoutFeedback, View, } from 'react-native'; @@ -42,8 +43,11 @@ export default function BBExample() { entering={FadeInUp} layout={LinearTransition.duration(2000)} exiting={RotateOutDownLeft} - style={[styles.refresher, { width: show ? 200 : 100 }]} - /> + style={[styles.refresher, { width: show ? 200 : 100 }]}> + + This item should be on top of the red one + + )} ); @@ -74,4 +78,9 @@ const styles = StyleSheet.create({ height: 100, backgroundColor: 'blue', }, + label: { + color: 'white', + padding: 4, + width: 100 + }, }); From 4afa13dd6b528a969762504953d177c3c7e60dc0 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 11:54:09 +0200 Subject: [PATCH 18/21] fix styling on android --- .../apps/reanimated/examples/LayoutAnimations/BBExample.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx index 24369947bdbe..756948e5cc24 100644 --- a/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx +++ b/apps/common-app/src/apps/reanimated/examples/LayoutAnimations/BBExample.tsx @@ -81,6 +81,7 @@ const styles = StyleSheet.create({ label: { color: 'white', padding: 4, - width: 100 + width: 100, + height: 100 }, }); From c8d85607aac53851c037ce38c7c64a327958fff3 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 12:12:31 +0200 Subject: [PATCH 19/21] clean up --- .../LayoutAnimationsProxy_Experimental.cpp | 61 ++----------------- .../LayoutAnimations/LayoutAnimationsUtils.h | 14 +++-- 2 files changed, 13 insertions(+), 62 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 975d91f602cc..9e8cd54aacc9 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -139,32 +139,6 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( for (const auto &mutation : mutations) { maybeUpdateWindowDimensions(mutation); - const char *typeName = "Unknown"; - Tag logTag = 0; - switch (mutation.type) { - case ShadowViewMutation::Create: - typeName = "Create"; - logTag = mutation.newChildShadowView.tag; - break; - case ShadowViewMutation::Delete: - typeName = "Delete"; - logTag = mutation.oldChildShadowView.tag; - break; - case ShadowViewMutation::Insert: - typeName = "Insert"; - logTag = mutation.newChildShadowView.tag; - break; - case ShadowViewMutation::Remove: - typeName = "Remove"; - logTag = mutation.oldChildShadowView.tag; - break; - case ShadowViewMutation::Update: - typeName = "Update"; - logTag = mutation.newChildShadowView.tag; - break; - } - LOG(INFO) << "@@@mutation " << typeName << " tag=" << logTag << " parentTag=" << mutation.parentTag - << " index=" << mutation.index; switch (mutation.type) { case ShadowViewMutation::Update: { auto &node = lightNodes_[mutation.newChildShadowView.tag]; @@ -192,8 +166,6 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { layout_.push_back(node); } else { - LOG(INFO) << "@@@emit Update tag=" << tag << " parentTag=" << mutation.parentTag - << " index=" << mutation.index; outputMutations.push_back(mutation); } break; @@ -204,8 +176,6 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( react_native_assert(!lightNodes_.contains(mutation.newChildShadowView.tag) && "LightNode already exists"); lightNodes_[mutation.newChildShadowView.tag] = node; - LOG(INFO) << "@@@emit Create tag=" << mutation.newChildShadowView.tag << " parentTag=" << mutation.parentTag - << " index=" << mutation.index; outputMutations.push_back(mutation); break; } @@ -235,30 +205,19 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( childTags += "(exiting)"; } } - LOG(INFO) << "@@@Insert index shifted by exiting children: tag " << mutation.newChildShadowView.tag - << " under parent " << mutation.parentTag << " rnIndex=" << mutation.index - << " actualIndex=" << actualIndex - << " exitingBefore=" << parent->countExitingChildrenBeforeIndex(mutation.index) << " children=[" - << childTags << "]"; } parent->children.insert(parent->children.begin() + actualIndex, node); node->parent = parent; const auto tag = mutation.newChildShadowView.tag; if (moved.contains(tag) && layoutAnimationsManager_->hasLayoutAnimation(tag, LAYOUT)) { - LOG(INFO) << "@@@emit Insert(moved/layout) tag=" << tag << " parentTag=" << mutation.parentTag - << " actualIndex=" << actualIndex; outputMutations.push_back( ShadowViewMutation::InsertMutation(mutation.parentTag, node->previous, actualIndex)); } else if (layoutAnimationsManager_->hasLayoutAnimation(tag, ENTERING)) { entering_.push_back(node); - LOG(INFO) << "@@@emit Insert(entering) tag=" << tag << " parentTag=" << mutation.parentTag - << " actualIndex=" << actualIndex; outputMutations.push_back( ShadowViewMutation::InsertMutation(mutation.parentTag, mutation.newChildShadowView, actualIndex)); } else { - LOG(INFO) << "@@@emit Insert tag=" << tag << " parentTag=" << mutation.parentTag - << " actualIndex=" << actualIndex; outputMutations.push_back( ShadowViewMutation::InsertMutation(mutation.parentTag, mutation.newChildShadowView, actualIndex)); } @@ -270,6 +229,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( const auto parentTag = mutation.parentTag; const auto &parent = lightNodes_[parentTag]; const auto actualIndex = mutation.index + parent->countExitingChildrenBeforeIndex(mutation.index); + if (parent->children[actualIndex]->current.tag != mutation.oldChildShadowView.tag) { std::string childTags; for (std::size_t i = 0; i < parent->children.size(); i++) { @@ -281,7 +241,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( childTags += "(exiting)"; } } - LOG(WARNING) << "@@@Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag + LOG(WARNING) << "Remove mutation index mismatch: expected tag " << mutation.oldChildShadowView.tag << " at actualIndex " << actualIndex << " under parent tag " << parentTag << ", but found tag " << parent->children[actualIndex]->current.tag << " (rnIndex=" << mutation.index << "); children=[" << childTags << "]" @@ -293,12 +253,9 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( if (deleted.contains(tag) && !deleted.contains(parentTag)) { potentialExitingRoots_.push_back(node); - node->exitingState = ExitingState::CANDIDATE; - LOG(INFO) << "@@@PotentialExitingRoot. Not sending remove mutation yet. tag=" << tag; + node->exitingState = ExitingState::TRIAGE; } else if (!deleted.contains(tag)) { - // removing to change parent - LOG(INFO) << "@@@emit Remove(reparent) tag=" << tag << " parentTag=" << parentTag - << " actualIndex=" << actualIndex; + // reparenting outputMutations.push_back(ShadowViewMutation::RemoveMutation(parentTag, node->current, actualIndex)); parent->children.erase(parent->children.begin() + actualIndex); } @@ -423,17 +380,10 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( childTags += "(exiting)"; } } - // parent->children.erase(parent->children.begin() + actualIndex); parent->removeChild(potentialExitingRoot); lightNodes_.erase(potentialExitingRoot->current.tag); - LOG(INFO) << "@@@ Postponed remove and delete: tag=" << potentialExitingRoot->current.tag - << " parentTag=" << parent->current.tag << " index=" << actualIndex << " parentChildren=[" << childTags - << "]"; - LOG(INFO) << "@@@emit Remove(handleRemovals) tag=" << potentialExitingRoot->current.tag - << " parentTag=" << parent->current.tag << " actualIndex=" << actualIndex; outputMutations.push_back( ShadowViewMutation::RemoveMutation(parent->current.tag, potentialExitingRoot->current, actualIndex)); - LOG(INFO) << "@@@emit Delete(handleRemovals) tag=" << potentialExitingRoot->current.tag; outputMutations.push_back(ShadowViewMutation::DeleteMutation(potentialExitingRoot->current)); } } @@ -528,10 +478,7 @@ void LayoutAnimationsProxy_Experimental::endExitingAnimationsRecursively( const auto &parent = node->parent.lock(); react_native_assert(parent && "Parent node is nullptr"); - LOG(INFO) << "@@@emit Remove(endExitingAnimations) tag=" << node->current.tag << " parentTag=" << parent->current.tag - << " index=" << index; outputMutations.push_back(ShadowViewMutation::RemoveMutation(parent->current.tag, node->current, index)); - LOG(INFO) << "@@@emit Delete(endExitingAnimations) tag=" << node->current.tag; outputMutations.push_back(ShadowViewMutation::DeleteMutation(node->current)); } diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index bf4c8bfc0498..826aa67112a6 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -58,11 +58,15 @@ struct Snapshot { typedef enum class ExitingState : std::uint8_t { UNDEFINED = 1, - WAITING = 2, - ANIMATING = 3, - DEAD = 4, - DELETED = 5, - CANDIDATE = 6, + /** + * RN requested removal but Reanimated suppressed the Remove mutation. Reanimated needs to decide whether to + * play an exit animation or remove and delete the node. + */ + TRIAGE = 2, + WAITING = 3, + ANIMATING = 4, + DEAD = 5, + DELETED = 6, } ExitingState; struct MutationNode; From 2af87ba700dab647cd95d2f877156b920026c7f4 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 12:30:40 +0200 Subject: [PATCH 20/21] self review --- .../LayoutAnimationsProxy_Experimental.cpp | 41 ++++--------------- .../LayoutAnimations/LayoutAnimationsUtils.h | 2 +- 2 files changed, 9 insertions(+), 34 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index 9e8cd54aacc9..d47044ad418f 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -181,9 +181,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( } case ShadowViewMutation::Delete: { const auto deletedTag = mutation.oldChildShadowView.tag; - const bool isPotentialExitingRoot = std::ranges::any_of( - potentialExitingRoots_, [deletedTag](const auto &node) { return node->current.tag == deletedTag; }); - if (!isPotentialExitingRoot) { + if (lightNodes_[deletedTag]->exitingState != ExitingState::TRIAGE) { lightNodes_.erase(deletedTag); } break; @@ -192,21 +190,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( transferConfigFromNativeID(mutation.newChildShadowView.props->nativeId, mutation.newChildShadowView.tag); auto &node = lightNodes_[mutation.newChildShadowView.tag]; auto &parent = lightNodes_[mutation.parentTag]; - const auto actualIndex = mutation.index + parent->countExitingChildrenBeforeIndex(mutation.index); - - if (parent->countExitingChildrenBeforeIndex(mutation.index) > 0) { - std::string childTags; - for (std::size_t i = 0; i < parent->children.size(); i++) { - if (i > 0) { - childTags += ", "; - } - childTags += std::to_string(parent->children[i]->current.tag); - if (parent->children[i]->exitingState != ExitingState::UNDEFINED) { - childTags += "(exiting)"; - } - } - } - + const auto actualIndex = mutation.index + parent->countExitingChildrenAffectingIndex(mutation.index); parent->children.insert(parent->children.begin() + actualIndex, node); node->parent = parent; const auto tag = mutation.newChildShadowView.tag; @@ -228,7 +212,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( const auto tag = node->current.tag; const auto parentTag = mutation.parentTag; const auto &parent = lightNodes_[parentTag]; - const auto actualIndex = mutation.index + parent->countExitingChildrenBeforeIndex(mutation.index); + const auto actualIndex = mutation.index + parent->countExitingChildrenAffectingIndex(mutation.index); if (parent->children[actualIndex]->current.tag != mutation.oldChildShadowView.tag) { std::string childTags; @@ -245,7 +229,7 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( << " at actualIndex " << actualIndex << " under parent tag " << parentTag << ", but found tag " << parent->children[actualIndex]->current.tag << " (rnIndex=" << mutation.index << "); children=[" << childTags << "]" - << " count=" << parent->countExitingChildrenBeforeIndex(mutation.index); + << " count=" << parent->countExitingChildrenAffectingIndex(mutation.index); } react_native_assert( parent->children[actualIndex]->current.tag == mutation.oldChildShadowView.tag && @@ -359,9 +343,10 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( }; if (startExitingAnimationsRecursively(potentialExitingRoot, outputMutations, config)) { - auto parentOfNewExitingRoot = potentialExitingRoot->parent.lock(); - react_native_assert(parentOfNewExitingRoot && "Parent node is nullptr"); - if (potentialExitingRoot->exitingState == UNDEFINED) { + auto parentOfPotentialExitingRoot = potentialExitingRoot->parent.lock(); + react_native_assert(parentOfPotentialExitingRoot && "Parent node is nullptr"); + if (potentialExitingRoot->exitingState == UNDEFINED || + potentialExitingRoot->exitingState == ExitingState::TRIAGE) { potentialExitingRoot->exitingState = WAITING; } } else { @@ -370,16 +355,6 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( react_native_assert(parent && "Parent node is nullptr"); auto actualIndex = parent->findChildIndexByTag(potentialExitingRoot->current.tag); react_native_assert(actualIndex != -1 && "actualIndex == -1"); - std::string childTags; - for (std::size_t i = 0; i < parent->children.size(); i++) { - if (i > 0) { - childTags += ", "; - } - childTags += std::to_string(parent->children[i]->current.tag); - if (parent->children[i]->exitingState != ExitingState::UNDEFINED) { - childTags += "(exiting)"; - } - } parent->removeChild(potentialExitingRoot); lightNodes_.erase(potentialExitingRoot->current.tag); outputMutations.push_back( diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index 826aa67112a6..2da053ef9131 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -111,7 +111,7 @@ struct LightNode { return -1; } - int countExitingChildrenBeforeIndex(int index) const { + int countExitingChildrenAffectingIndex(int index) const { react_native_assert(index >= 0 && "index must be non-negative"); int remainingNonExitingChildrenToCheck = index; int exitingCount = 0; From c996e5bea477b802d901b4280f3f1795860e4ad2 Mon Sep 17 00:00:00 2001 From: Arkadiusz Kasprzyk Date: Thu, 28 May 2026 12:46:51 +0200 Subject: [PATCH 21/21] ai review --- .../LayoutAnimationsProxy_Experimental.cpp | 12 ++++++++---- .../LayoutAnimations/LayoutAnimationsUtils.h | 4 ++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp index d47044ad418f..a8dbb3785984 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.cpp @@ -181,8 +181,13 @@ void LayoutAnimationsProxy_Experimental::updateLightTree( } case ShadowViewMutation::Delete: { const auto deletedTag = mutation.oldChildShadowView.tag; - if (lightNodes_[deletedTag]->exitingState != ExitingState::TRIAGE) { - lightNodes_.erase(deletedTag); + const auto it = lightNodes_.find(deletedTag); + react_native_assert(it != lightNodes_.end() && "Delete mutation for unknown tag"); + if (it == lightNodes_.end()) { + break; + } + if (it->second->exitingState != ExitingState::TRIAGE) { + lightNodes_.erase(it); } break; } @@ -353,9 +358,8 @@ void LayoutAnimationsProxy_Experimental::handleRemovals( maybeCancelAnimation(potentialExitingRoot->current.tag); auto parent = potentialExitingRoot->parent.lock(); react_native_assert(parent && "Parent node is nullptr"); - auto actualIndex = parent->findChildIndexByTag(potentialExitingRoot->current.tag); + auto actualIndex = parent->removeChild(potentialExitingRoot); react_native_assert(actualIndex != -1 && "actualIndex == -1"); - parent->removeChild(potentialExitingRoot); lightNodes_.erase(potentialExitingRoot->current.tag); outputMutations.push_back( ShadowViewMutation::RemoveMutation(parent->current.tag, potentialExitingRoot->current, actualIndex)); diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h index 2da053ef9131..39453fa66d64 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h +++ b/packages/react-native-reanimated/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsUtils.h @@ -93,10 +93,10 @@ struct LightNode { std::vector> children; int removeChild(const std::shared_ptr &child) { - for (auto i = children.size() - 1; i >= 0; i--) { + for (int i = static_cast(children.size()) - 1; i >= 0; i--) { if (children[i]->current.tag == child->current.tag) { children.erase(children.begin() + i); - return static_cast(i); + return i; } } return -1;