From 8949c78b4584746cd838911871c8c5c5f306e56d Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Thu, 6 Nov 2025 02:51:20 -0800 Subject: [PATCH 1/2] 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); From 49d78d5fda102d5d592014fe3e846015bc2b82e4 Mon Sep 17 00:00:00 2001 From: Bartlomiej Bloniarz Date: Thu, 6 Nov 2025 02:51:20 -0800 Subject: [PATCH 2/2] Make android frame updates work with AnimationBackend (#54425) Summary: Animation frames on Android are tiggered from UIManagerNativeAnimatedDelegate::runAnimationFrame, so we implement that for the backend Reviewed By: zeyap Differential Revision: D84055754 --- .../animated/NativeAnimatedNodesManager.cpp | 1 + .../NativeAnimatedNodesManagerProvider.cpp | 14 ++++++--- .../animationbackend/AnimationBackend.cpp | 31 ++++++++++++++----- .../animationbackend/AnimationBackend.h | 12 +++++++ 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp index 7d2e25522360..355e69ac7010 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -992,6 +992,7 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() { AnimationMutation{tag, nullptr, propsBuilder.get()}); containsChange = true; } + updateViewPropsDirect_.clear(); } if (!containsChange) { diff --git a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp index b92bf481d471..ed571e15019b 100644 --- a/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animated/NativeAnimatedNodesManagerProvider.cpp @@ -80,12 +80,16 @@ NativeAnimatedNodesManagerProvider::getOrCreate( std::move(directManipulationCallback), std::move(fabricCommitCallback), uiManager); -#endif nativeAnimatedNodesManager_ = std::make_shared(animationBackend_); + nativeAnimatedDelegate_ = + std::make_shared( + animationBackend_); + uiManager->unstable_setAnimationBackend(animationBackend_); +#endif } else { nativeAnimatedNodesManager_ = std::make_shared( @@ -93,6 +97,10 @@ NativeAnimatedNodesManagerProvider::getOrCreate( std::move(fabricCommitCallback), std::move(startOnRenderCallback_), std::move(stopOnRenderCallback_)); + + nativeAnimatedDelegate_ = + std::make_shared( + nativeAnimatedNodesManager_); } addEventEmitterListener( @@ -117,10 +125,6 @@ NativeAnimatedNodesManagerProvider::getOrCreate( return false; })); - nativeAnimatedDelegate_ = - std::make_shared( - nativeAnimatedNodesManager_); - uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_); // TODO: remove force casting. diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp index c9a9b062ee32..2aaa449bb05a 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.cpp @@ -10,6 +10,18 @@ namespace facebook::react { +UIManagerNativeAnimatedDelegateBackendImpl:: + UIManagerNativeAnimatedDelegateBackendImpl( + std::weak_ptr animationBackend) + : animationBackend_(std::move(animationBackend)) {} + +void UIManagerNativeAnimatedDelegateBackendImpl::runAnimationFrame() { + if (auto animationBackendStrong = animationBackend_.lock()) { + animationBackendStrong->onAnimationFrame( + std::chrono::steady_clock::now().time_since_epoch().count() / 1000); + } +} + static inline Props::Shared cloneProps( AnimatedProps& animatedProps, const ShadowNode& shadowNode) { @@ -108,15 +120,20 @@ void AnimationBackend::onAnimationFrame(double timestamp) { void AnimationBackend::start(const Callback& callback, bool isAsync) { callbacks.push_back(callback); // TODO: startOnRenderCallback_ should provide the timestamp from the platform - startOnRenderCallback_( - [this]() { - onAnimationFrame( - std::chrono::steady_clock::now().time_since_epoch().count() / 1000); - }, - isAsync); + if (startOnRenderCallback_) { + startOnRenderCallback_( + [this]() { + onAnimationFrame( + std::chrono::steady_clock::now().time_since_epoch().count() / + 1000); + }, + isAsync); + } } void AnimationBackend::stop(bool isAsync) { - stopOnRenderCallback_(isAsync); + if (stopOnRenderCallback_) { + stopOnRenderCallback_(isAsync); + } callbacks.clear(); } diff --git a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h index 9236370c3829..544beae7b16a 100644 --- a/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h +++ b/packages/react-native/ReactCommon/react/renderer/animationbackend/AnimationBackend.h @@ -18,6 +18,18 @@ namespace facebook::react { +class AnimationBackend; + +class UIManagerNativeAnimatedDelegateBackendImpl : public UIManagerNativeAnimatedDelegate { + public: + explicit UIManagerNativeAnimatedDelegateBackendImpl(std::weak_ptr animationBackend); + + void runAnimationFrame() override; + + private: + std::weak_ptr animationBackend_; +}; + struct AnimationMutation { Tag tag; const ShadowNodeFamily *family;