diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h index 8603591..20d042b 100644 --- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h +++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxyCommon.h @@ -62,11 +62,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele const SharedComponentDescriptorRegistry &componentDescriptorRegistry, const std::shared_ptr &contextContainer, jsi::Runtime &uiRuntime, - const std::shared_ptr &uiScheduler + const std::shared_ptr &uiScheduler, + const std::shared_ptr &uiManager #ifdef ANDROID , const PreserveMountedTagsFunction &filterUnmountedTagsFunction, - const std::shared_ptr &uiManager, const std::shared_ptr &jsInvoker #endif ) @@ -74,11 +74,11 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele contextContainer_(contextContainer), componentDescriptorRegistry_(componentDescriptorRegistry), uiRuntime_(uiRuntime), - uiScheduler_(uiScheduler) + uiScheduler_(uiScheduler), + uiManager_(uiManager) #ifdef ANDROID , preserveMountedTags_(filterUnmountedTagsFunction), - uiManager_(uiManager), jsInvoker_(jsInvoker) #endif { @@ -98,10 +98,10 @@ class LayoutAnimationsProxyCommon : public facebook::react::MountingOverrideDele SharedComponentDescriptorRegistry componentDescriptorRegistry_; jsi::Runtime &uiRuntime_; const std::shared_ptr uiScheduler_; + std::shared_ptr uiManager_; PreserveMountedTagsFunction preserveMountedTags_; #ifdef ANDROID - std::shared_ptr uiManager_; std::shared_ptr jsInvoker_; void restoreOpacityInCaseOfFlakyEnteringAnimation(SurfaceId surfaceId) const; diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h index fcc677f..115971a 100644 --- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h +++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Experimental.h @@ -67,11 +67,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, const SharedComponentDescriptorRegistry &componentDescriptorRegistry, const std::shared_ptr &contextContainer, jsi::Runtime &uiRuntime, - const std::shared_ptr &uiScheduler + const std::shared_ptr &uiScheduler, + const std::shared_ptr &uiManager #ifdef ANDROID , const PreserveMountedTagsFunction &filterUnmountedTagsFunction, - const std::shared_ptr &uiManager, const std::shared_ptr &jsInvoker #endif ) @@ -80,11 +80,11 @@ struct LayoutAnimationsProxy_Experimental : public LayoutAnimationsProxyCommon, componentDescriptorRegistry, contextContainer, uiRuntime, - uiScheduler + uiScheduler, + uiManager #ifdef ANDROID , filterUnmountedTagsFunction, - uiManager, jsInvoker #endif ), diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp index df53d8d..735f138 100644 --- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp +++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.cpp @@ -1,6 +1,7 @@ #include #include +#include #include #include @@ -60,14 +61,37 @@ std::optional LayoutAnimationsProxy_Legacy::pullTransaction parseRemoveMutations(movedViews, mutations, roots); - auto shouldAnimate = !surfacesToRemove_.contains(surfaceId); - surfacesToRemove_.erase(surfaceId); + // Consume the teardown mark only on the transaction that actually clears + // the root — pulls emitted for animation frames must not eat it early. + auto shouldAnimate = true; + const auto removesRootChildren = std::ranges::any_of(mutations, [surfaceId](const auto &mutation) { + return mutation.type == ShadowViewMutation::Remove && mutation.parentTag == surfaceId; + }); + if (removesRootChildren) { + shouldAnimate = surfacesToRemove_.erase(surfaceId) == 0; + } handleRemovals(filteredMutations, roots, deadNodes, shouldAnimate); handleUpdatesAndEnterings(filteredMutations, movedViews, mutations, propsParserContext, surfaceId); addOngoingAnimations(surfaceId, filteredMutations); + // The LayoutAnimationDriver can emit a final keyframe update in the same + // transaction as the deferred Remove/Delete it withheld for a delete + // animation. We emit removals before updates, so such an update would + // otherwise reach the mounting layer after its view was deleted. + std::unordered_set deletedTags; + for (const auto &mutation : filteredMutations) { + if (mutation.type == ShadowViewMutation::Delete) { + deletedTags.insert(mutation.oldChildShadowView.tag); + } + } + if (!deletedTags.empty()) { + std::erase_if(filteredMutations, [&deletedTags](const auto &mutation) { + return mutation.type == ShadowViewMutation::Update && deletedTags.contains(mutation.newChildShadowView.tag); + }); + } + return MountingTransaction{surfaceId, transactionNumber, std::move(filteredMutations), telemetry}; } @@ -998,23 +1022,22 @@ inline bool MutationNode::isMutationNode() { return true; } -// UIManagerAnimationDelegate - -void LayoutAnimationsProxy_Legacy::uiManagerDidConfigureNextLayoutAnimation( - jsi::Runtime &runtime, - const RawValue &config, - const jsi::Value &successCallbackValue, - const jsi::Value &failureCallbackValue) const {} +// UIManagerCommitHook -void LayoutAnimationsProxy_Legacy::setComponentDescriptorRegistry( - const SharedComponentDescriptorRegistry &componentDescriptorRegistry) {} - -bool LayoutAnimationsProxy_Legacy::shouldAnimateFrame() const { - return false; -} - -void LayoutAnimationsProxy_Legacy::stopSurface(SurfaceId surfaceId) { - surfacesToRemove_.insert(surfaceId); +// Surface teardown commits an empty root (SurfaceHandler::stop) before the +// teardown transaction is pulled — mark it so pullTransaction skips exit +// animations. Reading the ShadowTreeRegistry here instead would deadlock (#8579). +RootShadowNode::Unshared LayoutAnimationsProxy_Legacy::shadowTreeWillCommit( + const ShadowTree &shadowTree, + const RootShadowNode::Shared & /*oldRootShadowNode*/, + const RootShadowNode::Unshared &newRootShadowNode) noexcept { + auto lock = std::unique_lock(mutex); + if (newRootShadowNode->getChildren().empty()) { + surfacesToRemove_.insert(shadowTree.getSurfaceId()); + } else { + surfacesToRemove_.erase(shadowTree.getSurfaceId()); + } + return newRootShadowNode; } } // namespace reanimated diff --git a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h index 57cc134..1a2966c 100644 --- a/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h +++ b/Common/cpp/reanimated/LayoutAnimations/LayoutAnimationsProxy_Legacy.h @@ -3,8 +3,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -102,7 +102,7 @@ struct SurfaceContext { }; struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon, - public UIManagerAnimationDelegate, + public UIManagerCommitHook, public std::enable_shared_from_this { mutable std::unordered_map> nodeForTag_; mutable std::recursive_mutex mutex; @@ -116,11 +116,11 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon, const SharedComponentDescriptorRegistry &componentDescriptorRegistry, const std::shared_ptr &contextContainer, jsi::Runtime &uiRuntime, - const std::shared_ptr &uiScheduler + const std::shared_ptr &uiScheduler, + const std::shared_ptr &uiManager #ifdef ANDROID , const PreserveMountedTagsFunction &filterUnmountedTagsFunction, - const std::shared_ptr &uiManager, const std::shared_ptr &jsInvoker #endif ) @@ -129,14 +129,19 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon, componentDescriptorRegistry, contextContainer, uiRuntime, - uiScheduler + uiScheduler, + uiManager #ifdef ANDROID , filterUnmountedTagsFunction, - uiManager, jsInvoker #endif ) { + uiManager->registerCommitHook(*this); + } + + ~LayoutAnimationsProxy_Legacy() override { + uiManager_->unregisterCommitHook(*this); } void startEnteringAnimation(const int tag, ShadowViewMutation &mutation) const; @@ -206,19 +211,15 @@ struct LayoutAnimationsProxy_Legacy : public LayoutAnimationsProxyCommon, const TransactionTelemetry &telemetry, ShadowViewMutationList mutations) const override; - // UIManagerAnimationDelegate - - void uiManagerDidConfigureNextLayoutAnimation( - jsi::Runtime &runtime, - const RawValue &config, - const jsi::Value &successCallbackValue, - const jsi::Value &failureCallbackValue) const override; - - void setComponentDescriptorRegistry(const SharedComponentDescriptorRegistry &componentDescriptorRegistry) override; + // UIManagerCommitHook - bool shouldAnimateFrame() const override; + void commitHookWasRegistered(const UIManager &uiManager) noexcept override {} + void commitHookWasUnregistered(const UIManager &uiManager) noexcept override {} - void stopSurface(SurfaceId surfaceId) override; + RootShadowNode::Unshared shadowTreeWillCommit( + const ShadowTree &shadowTree, + const RootShadowNode::Shared &oldRootShadowNode, + const RootShadowNode::Unshared &newRootShadowNode) noexcept override; }; } // namespace reanimated diff --git a/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 2b68ff7..d08b1ae 100644 --- a/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -1235,22 +1235,19 @@ void ReanimatedModuleProxy::initializeLayoutAnimationsProxy() { #endif layoutAnimationsProxy_ = std::move(layoutAnimationsProxyExperimental); } else { - auto layoutAnimationsProxyLegacy = std::make_shared( + layoutAnimationsProxy_ = std::make_shared( layoutAnimationsManager_, componentDescriptorRegistry, scheduler->getContextContainer(), getJSIRuntimeFromWorkletRuntime(uiRuntime_), - uiScheduler_ + uiScheduler_, + uiManager_ #ifdef ANDROID , filterUnmountedTagsFunction_, - uiManager_, jsInvoker_ #endif ); - // TODO (future): support in experimental - uiManager_->setAnimationDelegate(layoutAnimationsProxyLegacy.get()); - layoutAnimationsProxy_ = std::move(layoutAnimationsProxyLegacy); } } }