From eff2e4cc49bfadd9716f3ad65854b3b0ca309b74 Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Tue, 26 Sep 2017 23:44:35 +0200 Subject: Animate job count changes --- src/animator.cc | 262 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 src/animator.cc (limited to 'src/animator.cc') diff --git a/src/animator.cc b/src/animator.cc new file mode 100644 index 0000000..7ed412c --- /dev/null +++ b/src/animator.cc @@ -0,0 +1,262 @@ +#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 / 60.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 +Animator* Animator::create(std::shared_ptr const& looper) { + return new AnimatorImpl(looper); +} -- cgit v1.3