| // Copyright 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/animation/keyframe_model.h" |
| |
| #include <cmath> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/stl_util.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/animation/animation_curve.h" |
| |
| namespace { |
| |
| // This should match the RunState enum. |
| static const char* const s_runStateNames[] = {"WAITING_FOR_TARGET_AVAILABILITY", |
| "WAITING_FOR_DELETION", |
| "STARTING", |
| "RUNNING", |
| "PAUSED", |
| "FINISHED", |
| "ABORTED", |
| "ABORTED_BUT_NEEDS_COMPLETION"}; |
| |
| static_assert(static_cast<int>(cc::KeyframeModel::LAST_RUN_STATE) + 1 == |
| base::size(s_runStateNames), |
| "RunStateEnumSize should equal the number of elements in " |
| "s_runStateNames"); |
| |
| static const char* const s_curveTypeNames[] = { |
| "COLOR", "FLOAT", "TRANSFORM", "FILTER", "SCROLL_OFFSET", "SIZE"}; |
| |
| static_assert(static_cast<int>(cc::AnimationCurve::LAST_CURVE_TYPE) + 1 == |
| base::size(s_curveTypeNames), |
| "CurveType enum should equal the number of elements in " |
| "s_runStateNames"); |
| |
| } // namespace |
| |
| namespace cc { |
| |
| std::string KeyframeModel::ToString(RunState state) { |
| return s_runStateNames[state]; |
| } |
| |
| std::unique_ptr<KeyframeModel> KeyframeModel::Create( |
| std::unique_ptr<AnimationCurve> curve, |
| int keyframe_model_id, |
| int group_id, |
| int target_property_id) { |
| return base::WrapUnique(new KeyframeModel(std::move(curve), keyframe_model_id, |
| group_id, target_property_id)); |
| } |
| |
| std::unique_ptr<KeyframeModel> KeyframeModel::CreateImplInstance( |
| RunState initial_run_state) const { |
| // Should never clone a model that is the controlling instance as it ends up |
| // creating multiple controlling instances. |
| DCHECK(!is_controlling_instance_); |
| std::unique_ptr<KeyframeModel> to_return( |
| new KeyframeModel(curve_->Clone(), id_, group_, target_property_id_)); |
| to_return->element_id_ = element_id_; |
| to_return->run_state_ = initial_run_state; |
| to_return->iterations_ = iterations_; |
| to_return->iteration_start_ = iteration_start_; |
| to_return->start_time_ = start_time_; |
| to_return->pause_time_ = pause_time_; |
| to_return->total_paused_duration_ = total_paused_duration_; |
| to_return->time_offset_ = time_offset_; |
| to_return->direction_ = direction_; |
| to_return->playback_rate_ = playback_rate_; |
| to_return->fill_mode_ = fill_mode_; |
| DCHECK(!to_return->is_controlling_instance_); |
| to_return->is_controlling_instance_ = true; |
| return to_return; |
| } |
| |
| KeyframeModel::KeyframeModel(std::unique_ptr<AnimationCurve> curve, |
| int keyframe_model_id, |
| int group_id, |
| int target_property_id) |
| : curve_(std::move(curve)), |
| id_(keyframe_model_id), |
| group_(group_id), |
| target_property_id_(target_property_id), |
| run_state_(WAITING_FOR_TARGET_AVAILABILITY), |
| iterations_(1), |
| iteration_start_(0), |
| direction_(Direction::NORMAL), |
| playback_rate_(1), |
| fill_mode_(FillMode::BOTH), |
| needs_synchronized_start_time_(false), |
| received_finished_event_(false), |
| is_controlling_instance_(false), |
| is_impl_only_(false), |
| affects_active_elements_(true), |
| affects_pending_elements_(true) {} |
| |
| KeyframeModel::~KeyframeModel() { |
| if (run_state_ == RUNNING || run_state_ == PAUSED) |
| SetRunState(ABORTED, base::TimeTicks()); |
| } |
| |
| void KeyframeModel::SetRunState(RunState run_state, |
| base::TimeTicks monotonic_time) { |
| char name_buffer[256]; |
| base::snprintf(name_buffer, sizeof(name_buffer), "%s-%d-%d", |
| s_curveTypeNames[curve_->Type()], target_property_id_, group_); |
| |
| bool is_waiting_to_start = |
| run_state_ == WAITING_FOR_TARGET_AVAILABILITY || run_state_ == STARTING; |
| |
| if (is_controlling_instance_ && is_waiting_to_start && run_state == RUNNING) { |
| TRACE_EVENT_ASYNC_BEGIN1("cc", "KeyframeModel", this, "Name", |
| TRACE_STR_COPY(name_buffer)); |
| } |
| |
| bool was_finished = is_finished(); |
| |
| const char* old_run_state_name = s_runStateNames[run_state_]; |
| |
| if (run_state == RUNNING && run_state_ == PAUSED) |
| total_paused_duration_ += (monotonic_time - pause_time_); |
| else if (run_state == PAUSED) |
| pause_time_ = monotonic_time; |
| run_state_ = run_state; |
| |
| const char* new_run_state_name = s_runStateNames[run_state]; |
| |
| if (is_controlling_instance_ && !was_finished && is_finished()) |
| TRACE_EVENT_ASYNC_END0("cc", "KeyframeModel", this); |
| |
| char state_buffer[256]; |
| base::snprintf(state_buffer, sizeof(state_buffer), "%s->%s", |
| old_run_state_name, new_run_state_name); |
| |
| TRACE_EVENT_INSTANT2( |
| "cc", "ElementAnimations::SetRunState", TRACE_EVENT_SCOPE_THREAD, "Name", |
| TRACE_STR_COPY(name_buffer), "State", TRACE_STR_COPY(state_buffer)); |
| } |
| |
| void KeyframeModel::Pause(base::TimeDelta pause_offset) { |
| // Convert pause offset which is in local time to monotonic time. |
| // TODO(yigu): This should be scaled by playbackrate. http://crbug.com/912407 |
| base::TimeTicks monotonic_time = |
| pause_offset + start_time_ + total_paused_duration_; |
| SetRunState(PAUSED, monotonic_time); |
| } |
| |
| bool KeyframeModel::IsFinishedAt(base::TimeTicks monotonic_time) const { |
| if (is_finished()) |
| return true; |
| |
| if (needs_synchronized_start_time_) |
| return false; |
| |
| if (playback_rate_ == 0) |
| return false; |
| |
| return run_state_ == RUNNING && iterations_ >= 0 && |
| (curve_->Duration() * (iterations_ / std::abs(playback_rate_))) <= |
| (ConvertMonotonicTimeToLocalTime(monotonic_time) + time_offset_); |
| } |
| |
| KeyframeModel::Phase KeyframeModel::CalculatePhaseForTesting( |
| base::TimeDelta local_time) const { |
| return CalculatePhase(local_time); |
| } |
| |
| KeyframeModel::Phase KeyframeModel::CalculatePhase( |
| base::TimeDelta local_time) const { |
| base::TimeDelta opposite_time_offset = time_offset_ == base::TimeDelta::Min() |
| ? base::TimeDelta::Max() |
| : -time_offset_; |
| base::TimeDelta before_active_boundary_time = |
| std::max(opposite_time_offset, base::TimeDelta()); |
| if (local_time < before_active_boundary_time || |
| (local_time == before_active_boundary_time && playback_rate_ < 0)) { |
| return KeyframeModel::Phase::BEFORE; |
| } |
| // Scaling the duration is against spec but needed to comply with the cc |
| // implementation. By spec (in blink) the playback rate is an Animation level |
| // concept but in cc it's per KeyframeModel. We grab the active time |
| // calculated here and later scale it with the playback rate in order to get a |
| // proper progress. Therefore we need to un-scale it here. This can be fixed |
| // once we scale the local time by playback rate. See |
| // https://crbug.com/912407. |
| base::TimeDelta active_duration = |
| curve_->Duration() * iterations_ / std::abs(playback_rate_); |
| // TODO(crbug.com/909794): By spec end time = max(start delay + duration + |
| // end delay, 0). The logic should be updated once "end delay" is supported. |
| base::TimeDelta active_after_boundary_time = |
| // Negative iterations_ represents "infinite iterations". |
| iterations_ >= 0 |
| ? std::max(opposite_time_offset + active_duration, base::TimeDelta()) |
| : base::TimeDelta::Max(); |
| if (local_time > active_after_boundary_time || |
| (local_time == active_after_boundary_time && playback_rate_ > 0)) { |
| return KeyframeModel::Phase::AFTER; |
| } |
| return KeyframeModel::Phase::ACTIVE; |
| } |
| |
| base::Optional<base::TimeDelta> KeyframeModel::CalculateActiveTime( |
| base::TimeTicks monotonic_time) const { |
| base::TimeDelta local_time = ConvertMonotonicTimeToLocalTime(monotonic_time); |
| KeyframeModel::Phase phase = CalculatePhase(local_time); |
| DCHECK(playback_rate_); |
| switch (phase) { |
| case KeyframeModel::Phase::BEFORE: |
| if (fill_mode_ == FillMode::BACKWARDS || fill_mode_ == FillMode::BOTH) |
| return std::max(local_time + time_offset_, base::TimeDelta()); |
| return base::nullopt; |
| case KeyframeModel::Phase::ACTIVE: |
| return local_time + time_offset_; |
| case KeyframeModel::Phase::AFTER: |
| if (fill_mode_ == FillMode::FORWARDS || fill_mode_ == FillMode::BOTH) { |
| DCHECK_GE(iterations_, 0); |
| base::TimeDelta active_duration = |
| curve_->Duration() * iterations_ / std::abs(playback_rate_); |
| return std::max(std::min(local_time + time_offset_, active_duration), |
| base::TimeDelta()); |
| } |
| return base::nullopt; |
| default: |
| NOTREACHED(); |
| return base::nullopt; |
| } |
| } |
| |
| bool KeyframeModel::InEffect(base::TimeTicks monotonic_time) const { |
| return CalculateActiveTime(monotonic_time).has_value(); |
| } |
| |
| // TODO(yigu): Local time should be scaled by playback rate by spec. |
| // https://crbug.com/912407. |
| base::TimeDelta KeyframeModel::ConvertMonotonicTimeToLocalTime( |
| base::TimeTicks monotonic_time) const { |
| // When waiting on receiving a start time, then our global clock is 'stuck' at |
| // the initial state. |
| if ((run_state_ == STARTING && !has_set_start_time()) || |
| needs_synchronized_start_time()) |
| return base::TimeDelta(); |
| |
| // If we're paused, time is 'stuck' at the pause time. |
| base::TimeTicks time = (run_state_ == PAUSED) ? pause_time_ : monotonic_time; |
| return time - start_time_ - total_paused_duration_; |
| } |
| |
| base::TimeDelta KeyframeModel::TrimTimeToCurrentIteration( |
| base::TimeTicks monotonic_time) const { |
| DCHECK(playback_rate_); |
| DCHECK_GE(iteration_start_, 0); |
| |
| DCHECK(InEffect(monotonic_time)); |
| base::TimeDelta active_time = CalculateActiveTime(monotonic_time).value(); |
| base::TimeDelta start_offset = curve_->Duration() * iteration_start_; |
| |
| // Return start offset if we are before the start of the keyframe model |
| if (active_time < base::TimeDelta()) |
| return start_offset; |
| // Always return zero if we have no iterations. |
| if (!iterations_) |
| return base::TimeDelta(); |
| |
| // Don't attempt to trim if we have no duration. |
| if (curve_->Duration() <= base::TimeDelta()) |
| return base::TimeDelta(); |
| |
| base::TimeDelta repeated_duration = curve_->Duration() * iterations_; |
| base::TimeDelta active_duration = |
| repeated_duration / std::abs(playback_rate_); |
| |
| // Calculate the scaled active time |
| base::TimeDelta scaled_active_time; |
| if (playback_rate_ < 0) { |
| scaled_active_time = |
| ((active_time - active_duration) * playback_rate_) + start_offset; |
| } else { |
| scaled_active_time = (active_time * playback_rate_) + start_offset; |
| } |
| |
| // Calculate the iteration time |
| base::TimeDelta iteration_time; |
| if (scaled_active_time - start_offset == repeated_duration && |
| fmod(iterations_ + iteration_start_, 1) == 0) |
| iteration_time = curve_->Duration(); |
| else |
| iteration_time = scaled_active_time % curve_->Duration(); |
| |
| // Calculate the current iteration |
| int iteration; |
| if (scaled_active_time <= base::TimeDelta()) |
| iteration = 0; |
| else if (iteration_time == curve_->Duration()) |
| iteration = ceil(iteration_start_ + iterations_ - 1); |
| else |
| iteration = static_cast<int>(scaled_active_time / curve_->Duration()); |
| |
| // Check if we are running the keyframe model in reverse direction for the |
| // current iteration |
| bool reverse = |
| (direction_ == Direction::REVERSE) || |
| (direction_ == Direction::ALTERNATE_NORMAL && iteration % 2 == 1) || |
| (direction_ == Direction::ALTERNATE_REVERSE && iteration % 2 == 0); |
| |
| // If we are running the keyframe model in reverse direction, reverse the |
| // result |
| if (reverse) |
| iteration_time = curve_->Duration() - iteration_time; |
| |
| return iteration_time; |
| } |
| |
| void KeyframeModel::PushPropertiesTo(KeyframeModel* other) const { |
| other->element_id_ = element_id_; |
| if (run_state_ == KeyframeModel::PAUSED || |
| other->run_state_ == KeyframeModel::PAUSED) { |
| other->run_state_ = run_state_; |
| other->pause_time_ = pause_time_; |
| other->total_paused_duration_ = total_paused_duration_; |
| } |
| } |
| |
| std::string KeyframeModel::ToString() const { |
| return base::StringPrintf( |
| "KeyframeModel{id=%d, group=%d, target_property_id=%d, " |
| "run_state=%s}", |
| id_, group_, target_property_id_, |
| KeyframeModel::ToString(run_state_).c_str()); |
| } |
| |
| void KeyframeModel::SetIsImplOnly() { |
| is_impl_only_ = true; |
| // Impl only animations have a single instance which by definition is the |
| // controlling instance. |
| is_controlling_instance_ = true; |
| } |
| } // namespace cc |