@@ -519,6 +519,17 @@ NativeAnimatedNodesManager::ensureEventEmitterListener() noexcept {
519519}
520520
521521void NativeAnimatedNodesManager::startRenderCallbackIfNeeded (bool isAsync) {
522+ // This method can be called from either the UI thread or JavaScript thread.
523+ // It ensures `startOnRenderCallback_` is called exactly once using atomic
524+ // operations. We use std::atomic_bool rather than std::mutex to avoid
525+ // potential deadlocks that could occur if we called external code while
526+ // holding a mutex.
527+ auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (true );
528+ if (isRenderCallbackStarted) {
529+ // onRender callback is already started.
530+ return ;
531+ }
532+
522533 if (ReactNativeFeatureFlags::useSharedAnimatedBackend ()) {
523534#ifdef RN_USE_ANIMATION_BACKEND
524535 if (auto animationBackend = animationBackend_.lock ()) {
@@ -531,16 +542,6 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) {
531542
532543 return ;
533544 }
534- // This method can be called from either the UI thread or JavaScript thread.
535- // It ensures `startOnRenderCallback_` is called exactly once using atomic
536- // operations. We use std::atomic_bool rather than std::mutex to avoid
537- // potential deadlocks that could occur if we called external code while
538- // holding a mutex.
539- auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (true );
540- if (isRenderCallbackStarted) {
541- // onRender callback is already started.
542- return ;
543- }
544545
545546 if (startOnRenderCallback_) {
546547 startOnRenderCallback_ ([this ]() { onRender (); }, isAsync);
@@ -549,18 +550,21 @@ void NativeAnimatedNodesManager::startRenderCallbackIfNeeded(bool isAsync) {
549550
550551void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded (
551552 bool isAsync) noexcept {
552- if (ReactNativeFeatureFlags::useSharedAnimatedBackend ()) {
553- if (auto animationBackend = animationBackend_.lock ()) {
554- animationBackend->stop (isAsync);
555- }
556- return ;
557- }
558553 // When multiple threads reach this point, only one thread should call
559554 // stopOnRenderCallback_. This synchronization is primarily needed during
560555 // destruction of NativeAnimatedNodesManager. In normal operation,
561556 // stopRenderCallbackIfNeeded is always called from the UI thread.
562557 auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (false );
563558
559+ if (ReactNativeFeatureFlags::useSharedAnimatedBackend ()) {
560+ if (isRenderCallbackStarted) {
561+ if (auto animationBackend = animationBackend_.lock ()) {
562+ animationBackend->stop (isAsync);
563+ }
564+ }
565+ return ;
566+ }
567+
564568 if (isRenderCallbackStarted) {
565569 if (stopOnRenderCallback_) {
566570 stopOnRenderCallback_ (isAsync);
0 commit comments