#include "common.hh" #include #include "animator.hh" #include "animation.hh" #include "clock.hh" #include "looper.hh" #include "observers.hh" namespace { double const FRAME = 1.0 / 15.0; unsigned const NEED_BAD = 2; unsigned const NEED_GOOD = 12; class AnimatorImpl : public Animator { public: explicit AnimatorImpl(std::shared_ptr const& looper) : looper_(looper), timer_(0), callback_(false), target_(-1.0), frame_(FRAME), good_(0), bad_(0), frame_count_(1) { } ~AnimatorImpl() override { if (timer_) { looper_->cancel(timer_); } } void start(std::shared_ptr const& animation, AnimationObserver* observer) override { delayed_animations_.emplace_back(animation, observer); if (!timer_) { timer_ = looper_->schedule(0.0, std::bind(&AnimatorImpl::tick, this, std::placeholders::_1, std::placeholders::_2)); } } bool observe(Animation* animation, AnimationObserver* observer) override { for (auto& entry : animations_) { if (entry.match(animation)) { add_observer(entry, observer); return true; } } for (auto& entry : delayed_animations_) { if (entry.match(animation)) { add_observer(entry, observer); return true; } } return false; } bool stop(Animation* animation) override { for (auto it = delayed_animations_.begin(); it != delayed_animations_.end(); ++it) { if (it->match(animation)) { notify_stopped(*it); delayed_animations_.erase(it); return true; } } for (auto it = animations_.begin(); it != animations_.end(); ++it) { if (it->match(animation)) { notify_stopped(*it); if (!callback_) animations_.erase(it); return true; } } return false; } bool active(Animation* animation) const override { for (auto& entry : animations_) { if (entry.match(animation)) return true; } for (auto& entry : delayed_animations_) { if (entry.match(animation)) return true; } return false; } bool active() const override { return !animations_.empty() || !delayed_animations_.empty(); } void add_observer(Observer* observer) override { observers_.add(observer); } void remove_observer(Observer* observer) override { observers_.remove(observer); } private: struct Entry { std::shared_ptr animation_; AnimationObserver* observer_; Observers others_; double started_; bool alive_; Entry(std::shared_ptr const& animation, AnimationObserver* observer) : animation_(animation), observer_(observer), started_(0.0), alive_(true) { } bool match(Animation* animation) const { return alive_ && animation_.get() == animation; } }; void add_observer(Entry& entry, AnimationObserver* observer) { if (!observer) { assert(false); return; } if (entry.observer_) { entry.others_.add(observer); } else { entry.observer_ = observer; } } void tick_animations(std::vector::iterator start, double now) { assert(callback_); double value; auto it = start; while (it != animations_.end()) { if (it->alive_) { auto stopped = !it->animation_->tick(now - it->started_, &value); notify_ticked(*it, value); if (it->alive_) { if (!stopped) { ++it; continue; } notify_stopped(*it); } } it = animations_.erase(it); } } void tick(Looper*, uint32_t timer) { auto now = clk::steady(); assert(timer_ == timer); callback_ = true; tick_animations(animations_.begin(), now); auto const base = animations_.size(); for (auto& entry : delayed_animations_) { animations_.push_back(entry); animations_.back().started_ = now; } delayed_animations_.clear(); tick_animations(animations_.begin() + base, now); callback_ = false; auto observer = observers_.first(); while (observer) { observer->tick(this); observer = observers_.next(); } if (animations_.empty()) { target_ = -1.0; timer_ = 0; return; } if (target_ < 0.0) { target_ = now + frame_; } auto t = clk::steady(); auto diff = target_ - t; if (diff < 0.0) { ++bad_; // If we miss N frames in a row, double the frame length if (bad_ >= NEED_BAD) { frame_count_ *= 2; frame_ = frame_count_ * FRAME; bad_ = 0; } // "Fix" this delay with FRAME steps and not frame_ to give a bit // of a basic stepping between do { diff += FRAME; } while (diff < 0.0); good_ = 0; } else { bad_ = 0; // If we have an increased frame length, check if we have enough // timer over now to go back down if (frame_count_ > 1) { auto d = frame_ - diff; if (d <= frame_ / 2.0) { ++good_; // If N frames in a row has been good reduce the frame length if (good_ >= NEED_GOOD) { frame_count_ /= 2; frame_ = frame_count_ * FRAME; diff -= FRAME; good_ = 0; } } else { good_ = 0; } } } target_ += frame_; timer_ = looper_->schedule(diff, std::bind(&AnimatorImpl::tick, this, std::placeholders::_1, std::placeholders::_2)); } void notify_stopped(Entry& entry) { entry.alive_ = false; if (!entry.observer_) return; entry.observer_->stopped(this, entry.animation_.get()); auto observer = entry.others_.first(); while (observer) { observer->stopped(this, entry.animation_.get()); observer = entry.others_.next(); } } void notify_ticked(Entry& entry, double value) { if (!entry.observer_) return; entry.observer_->ticked(this, entry.animation_.get(), value); auto observer = entry.others_.first(); while (observer) { observer->ticked(this, entry.animation_.get(), value); observer = entry.others_.next(); } } std::shared_ptr looper_; uint32_t timer_; Observers observers_; std::vector delayed_animations_; std::vector animations_; bool callback_; double target_; double frame_; unsigned good_; unsigned bad_; unsigned frame_count_; }; } // namespace // static std::unique_ptr Animator::create( std::shared_ptr const& looper) { return std::make_unique(looper); }