Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -551,61 +551,84 @@ 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 (useSharedAnimatedBackend_) {
if (auto animationBackend = animationBackend_.lock()) {
auto weak = weak_from_this();
animationBackendCallbackId_ = animationBackend->start(
[weak](AnimationTimestamp timestamp) -> AnimationMutations {
if (auto self = weak.lock()) {
return self->pullAnimationMutations(timestamp);
}
return {};
});
if (!useSharedAnimatedBackend_) {
// 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);
}

return;
}

if (startOnRenderCallback_) {
startOnRenderCallback_([this]() { onRender(); }, isAsync);
{
auto animationBackend = animationBackend_.lock();
if (!animationBackend) {
return;
}
// AnimationBackend::start registers the callback before returning the id.
// Keep registration and id publication in one critical section; otherwise a
// concurrent stop can observe no id and leave the registered callback
// orphaned in the backend.
std::lock_guard<std::mutex> lock(animationBackendCallbackMutex_);
if (animationBackendCallbackId_.has_value()) {
return;
}
auto weak = weak_from_this();
auto callbackId = animationBackend->start(
[weak](AnimationTimestamp timestamp) -> AnimationMutations {
if (auto self = weak.lock()) {
return self->pullAnimationMutations(timestamp);
}
return {};
});
animationBackendCallbackId_ = callbackId;
}
}

void NativeAnimatedNodesManager::stopRenderCallbackIfNeeded(
bool isAsync) noexcept {
// 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 (useSharedAnimatedBackend_) {
if (!useSharedAnimatedBackend_) {
// 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 (isRenderCallbackStarted) {
if (auto animationBackend = animationBackend_.lock()) {
animationBackend->stop(animationBackendCallbackId_);
if (stopOnRenderCallback_) {
stopOnRenderCallback_(isAsync);

if (frameRateListenerCallback_) {
frameRateListenerCallback_(false);
}
}
}
return;
}

if (isRenderCallbackStarted) {
if (stopOnRenderCallback_) {
stopOnRenderCallback_(isAsync);

if (frameRateListenerCallback_) {
frameRateListenerCallback_(false);
{
auto animationBackend = animationBackend_.lock();
CallbackId callbackId;
{
std::lock_guard<std::mutex> lock(animationBackendCallbackMutex_);
if (!animationBackendCallbackId_.has_value()) {
return;
}
// Clear the published id while holding the same mutex that protects
// start registration. This makes a concurrent start either see an active
// callback or wait until this stop owns the callback id.
callbackId = *animationBackendCallbackId_;
animationBackendCallbackId_.reset();
}
if (animationBackend) {
animationBackend->stop(callbackId);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,10 @@ class NativeAnimatedNodesManager : public std::enable_shared_from_this<NativeAni
bool warnedAboutGraphTraversal_ = false;
#endif

CallbackId animationBackendCallbackId_{0};
// Protects the register/publish and exchange/stop lifecycle for the shared
// AnimationBackend callback.
std::mutex animationBackendCallbackMutex_;
std::optional<CallbackId> animationBackendCallbackId_;

friend class ColorAnimatedNode;
friend class AnimationDriver;
Expand Down
Loading