// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/ambient/ui/ambient_animation_progress_tracker.h"
#include <optional>
#include <utility>
#include "base/check.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
namespace ash {
namespace {
void MoveAnimation(
base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>>& from,
base::flat_set<raw_ptr<const lottie::Animation, CtnExperimental>>& to,
const lottie::Animation* animation) {
if (to.contains(animation)) {
CHECK(!from.contains(animation));
return;
}
CHECK_EQ(from.erase(animation), 1u);
to.insert(animation);
}
} // namespace
AmbientAnimationProgressTracker::ImmutableParams::ImmutableParams() = default;
AmbientAnimationProgressTracker::ImmutableParams::ImmutableParams(
const ImmutableParams& other) = default;
AmbientAnimationProgressTracker::ImmutableParams&
AmbientAnimationProgressTracker::ImmutableParams::operator=(
const ImmutableParams& other) = default;
AmbientAnimationProgressTracker::ImmutableParams::~ImmutableParams() = default;
AmbientAnimationProgressTracker::AmbientAnimationProgressTracker() = default;
AmbientAnimationProgressTracker::~AmbientAnimationProgressTracker() = default;
void AmbientAnimationProgressTracker::RegisterAnimation(
lottie::Animation* animation) {
DCHECK(animation);
if (animation_observations_.IsObservingSource(animation)) {
return;
}
animation_observations_.AddObservation(animation);
if (animation->GetPlaybackConfig()) {
// The parameters verified here all concern "time" in the animation in some
// form. They must match so that the "progress" returned by
// GetGlobalProgress() has the same frame of reference across all
// animations. Each animation's parameters are verified one time.
VerifyAnimationImmutableParams(*animation);
started_animations_.insert(animation);
} else {
DVLOG(4) << "Animation has not been Start()ed yet. Will be verified in "
"AnimationWillStartPlaying() later.";
inactive_animations_.insert(animation);
}
}
bool AmbientAnimationProgressTracker::HasActiveAnimations() const {
// Some of the started animations may not have painted a single frame yet. If
// this is the case, GetCurrentProgress() will be null, so at least one of
// them must return a non-null value for GetGlobalProgress() to return a valid
// value.
for (const lottie::Animation* animation : started_animations_) {
if (animation->GetCurrentProgress())
return true;
}
return false;
}
AmbientAnimationProgressTracker::Progress
AmbientAnimationProgressTracker::GetGlobalProgress() const {
// Currently, the method for picking one "global" progress is trivial. It just
// picks an arbitrary animation in the group because in practice, their
// timestamps should not have diverged by an amount that is user-perceptible
// (ex: less than 100 ms). If it's needed in the future, we can do something
// more complex here like taking the median or mean progress of all
// animations. But the complexity is currently not justified.
for (const lottie::Animation* animation : started_animations_) {
if (animation->GetCurrentProgress()) {
DCHECK(animation->GetNumCompletedCycles());
return {*animation->GetNumCompletedCycles(),
*animation->GetCurrentProgress()};
}
}
NOTREACHED() << "HasActiveAnimations() must be true before calling "
"GetGlobalProgress()";
}
AmbientAnimationProgressTracker::ImmutableParams
AmbientAnimationProgressTracker::GetImmutableParams() const {
DCHECK(HasActiveAnimations());
// The animation picked here is arbitrary since they all should have the same
// immutable params.
const lottie::Animation* animation = *started_animations_.begin();
auto playback_config = animation->GetPlaybackConfig();
ImmutableParams params;
params.total_duration = animation->GetAnimationDuration();
params.scheduled_cycles = playback_config->scheduled_cycles;
params.style = playback_config->style;
return params;
}
void AmbientAnimationProgressTracker::AnimationWillStartPlaying(
const lottie::Animation* animation) {
DCHECK(animation_observations_.IsObservingSource(
const_cast<lottie::Animation*>(animation)));
VerifyAnimationImmutableParams(*animation);
MoveAnimation(/*from=*/inactive_animations_, /*to=*/started_animations_,
animation);
}
void AmbientAnimationProgressTracker::AnimationStopped(
const lottie::Animation* animation) {
CHECK(animation_observations_.IsObservingSource(
const_cast<lottie::Animation*>(animation)));
MoveAnimation(/*from=*/started_animations_, /*to=*/inactive_animations_,
animation);
}
void AmbientAnimationProgressTracker::AnimationIsDeleting(
const lottie::Animation* animation) {
DCHECK(animation_observations_.IsObservingSource(
const_cast<lottie::Animation*>(animation)));
animation_observations_.RemoveObservation(
const_cast<lottie::Animation*>(animation));
started_animations_.erase(animation);
inactive_animations_.erase(animation);
}
void AmbientAnimationProgressTracker::VerifyAnimationImmutableParams(
const lottie::Animation& animation) const {
DCHECK(!started_animations_.contains(&animation));
if (started_animations_.empty()) {
DVLOG(4) << "Incoming animation is the first started in the session. No "
"need to verify against other animations";
return;
}
// The animation picked here is arbitrary since all existing animations should
// have gone through this method, verifying that all of their immutable params
// match.
const lottie::Animation* existing_animation = *started_animations_.begin();
DCHECK_EQ(animation.GetAnimationDuration(),
existing_animation->GetAnimationDuration());
auto incoming_playback_config = animation.GetPlaybackConfig();
auto existing_playback_config = animation.GetPlaybackConfig();
DCHECK(incoming_playback_config);
DCHECK(existing_playback_config);
DCHECK_EQ(incoming_playback_config->scheduled_cycles.size(),
existing_playback_config->scheduled_cycles.size());
for (size_t i = 0; i < incoming_playback_config->scheduled_cycles.size();
++i) {
DCHECK_EQ(incoming_playback_config->scheduled_cycles[i].start_offset,
existing_playback_config->scheduled_cycles[i].start_offset);
DCHECK_EQ(incoming_playback_config->scheduled_cycles[i].end_offset,
existing_playback_config->scheduled_cycles[i].end_offset);
}
DCHECK_EQ(incoming_playback_config->style, existing_playback_config->style);
}
} // namespace ash