@@ -551,61 +551,84 @@ NativeAnimatedNodesManager::ensureEventEmitterListener() noexcept {
551551}
552552
553553void NativeAnimatedNodesManager::startRenderCallbackIfNeeded (bool isAsync) {
554- // This method can be called from either the UI thread or JavaScript thread.
555- // It ensures `startOnRenderCallback_` is called exactly once using atomic
556- // operations. We use std::atomic_bool rather than std::mutex to avoid
557- // potential deadlocks that could occur if we called external code while
558- // holding a mutex.
559- auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (true );
560- if (isRenderCallbackStarted) {
561- // onRender callback is already started.
562- return ;
563- }
564-
565- if (useSharedAnimatedBackend_) {
566- if (auto animationBackend = animationBackend_.lock ()) {
567- auto weak = weak_from_this ();
568- animationBackendCallbackId_ = animationBackend->start (
569- [weak](AnimationTimestamp timestamp) -> AnimationMutations {
570- if (auto self = weak.lock ()) {
571- return self->pullAnimationMutations (timestamp);
572- }
573- return {};
574- });
554+ if (!useSharedAnimatedBackend_) {
555+ // This method can be called from either the UI thread or JavaScript thread.
556+ // It ensures `startOnRenderCallback_` is called exactly once using atomic
557+ // operations. We use std::atomic_bool rather than std::mutex to avoid
558+ // potential deadlocks that could occur if we called external code while
559+ // holding a mutex.
560+ auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (true );
561+ if (isRenderCallbackStarted) {
562+ // onRender callback is already started.
563+ return ;
564+ }
565+ if (startOnRenderCallback_) {
566+ startOnRenderCallback_ ([this ]() { onRender (); }, isAsync);
575567 }
576-
577568 return ;
578569 }
579570
580- if (startOnRenderCallback_) {
581- startOnRenderCallback_ ([this ]() { onRender (); }, isAsync);
571+ {
572+ auto animationBackend = animationBackend_.lock ();
573+ if (!animationBackend) {
574+ return ;
575+ }
576+ // AnimationBackend::start registers the callback before returning the id.
577+ // Keep registration and id publication in one critical section; otherwise a
578+ // concurrent stop can observe no id and leave the registered callback
579+ // orphaned in the backend.
580+ std::lock_guard<std::mutex> lock (animationBackendCallbackMutex_);
581+ if (animationBackendCallbackId_.has_value ()) {
582+ return ;
583+ }
584+ auto weak = weak_from_this ();
585+ auto callbackId = animationBackend->start (
586+ [weak](AnimationTimestamp timestamp) -> AnimationMutations {
587+ if (auto self = weak.lock ()) {
588+ return self->pullAnimationMutations (timestamp);
589+ }
590+ return {};
591+ });
592+ animationBackendCallbackId_ = callbackId;
582593 }
583594}
584595
585596void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded (
586597 bool isAsync) noexcept {
587- // When multiple threads reach this point, only one thread should call
588- // stopOnRenderCallback_. This synchronization is primarily needed during
589- // destruction of NativeAnimatedNodesManager. In normal operation,
590- // stopRenderCallbackIfNeeded is always called from the UI thread.
591- auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (false );
592-
593- if (useSharedAnimatedBackend_) {
598+ if (!useSharedAnimatedBackend_) {
599+ // When multiple threads reach this point, only one thread should call
600+ // stopOnRenderCallback_. This synchronization is primarily needed during
601+ // destruction of NativeAnimatedNodesManager. In normal operation,
602+ // stopRenderCallbackIfNeeded is always called from the UI thread.
603+ auto isRenderCallbackStarted = isRenderCallbackStarted_.exchange (false );
594604 if (isRenderCallbackStarted) {
595- if (auto animationBackend = animationBackend_.lock ()) {
596- animationBackend->stop (animationBackendCallbackId_);
605+ if (stopOnRenderCallback_) {
606+ stopOnRenderCallback_ (isAsync);
607+
608+ if (frameRateListenerCallback_) {
609+ frameRateListenerCallback_ (false );
610+ }
597611 }
598612 }
599613 return ;
600614 }
601615
602- if (isRenderCallbackStarted) {
603- if (stopOnRenderCallback_) {
604- stopOnRenderCallback_ (isAsync);
605-
606- if (frameRateListenerCallback_) {
607- frameRateListenerCallback_ (false );
616+ {
617+ auto animationBackend = animationBackend_.lock ();
618+ CallbackId callbackId;
619+ {
620+ std::lock_guard<std::mutex> lock (animationBackendCallbackMutex_);
621+ if (!animationBackendCallbackId_.has_value ()) {
622+ return ;
608623 }
624+ // Clear the published id while holding the same mutex that protects
625+ // start registration. This makes a concurrent start either see an active
626+ // callback or wait until this stop owns the callback id.
627+ callbackId = *animationBackendCallbackId_;
628+ animationBackendCallbackId_.reset ();
629+ }
630+ if (animationBackend) {
631+ animationBackend->stop (callbackId);
609632 }
610633 }
611634}
0 commit comments