From 8949c78b4584746cd838911871c8c5c5f306e56d Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Thu, 6 Nov 2025 02:51:20 -0800 Subject: [PATCH] Start AnimationBackend callbacks once per frame in Animated (#54426) Summary: Withouth the backend, Animated triggers the start function once per frame (by updating an atomic bool). We now do the same with animation backend. Reviewed By: zeyap Differential Revision: D85234259 --- .../animated/NativeAnimatedNodesManager.cpp | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index bf56e2fee4dd..7d2e25522360 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -519,6 +519,17 @@ NativeAnimatedNodesManager::ensureEventEmitterListener() noexcept { } void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) { + // This method can be called from either the UI thread or JavaScript thread. + // It ensures `startOnRenderCallback_` is called exactly once using atomic + // operations. We use std::atomic_bool rather than std::mutex to avoid + // potential deadlocks that could occur if we called external code while + // holding a mutex. + auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange(true); + if (isRenderCallbackStarted) { + // onRender callback is already started. + return; + } + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { #ifdef RN_USE_ANIMATION_BACKEND if (auto animationBackend = animationBackend_.lock()) { @@ -531,16 +542,6 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) { return; } - // This method can be called from either the UI thread or JavaScript thread. - // It ensures `startOnRenderCallback_` is called exactly once using atomic - // operations. We use std::atomic_bool rather than std::mutex to avoid - // potential deadlocks that could occur if we called external code while - // holding a mutex. - auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange(true); - if (isRenderCallbackStarted) { - // onRender callback is already started. - return; - } if (startOnRenderCallback_) { startOnRenderCallback_([this]() { onRender(); }, isAsync); @@ -549,18 +550,21 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) { void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded( bool isAsync) noexcept { - if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { - if (auto animationBackend = animationBackend_.lock()) { - animationBackend->stop(isAsync); - } - return; - } // When multiple threads reach this point, only one thread should call // stopOnRenderCallback_. This synchronization is primarily needed during // destruction of NativeAnimatedNodesManager. In normal operation, // stopRenderCallbackIfNeeded is always called from the UI thread. auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange(false); + if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) { + if (isRenderCallbackStarted) { + if (auto animationBackend = animationBackend_.lock()) { + animationBackend->stop(isAsync); + } + } + return; + } + if (isRenderCallbackStarted) { if (stopOnRenderCallback_) { stopOnRenderCallback_(isAsync);