summaryrefslogtreecommitdiff
path: root/src/animator.cc
diff options
context:
space:
mode:
authorJoel Klinghed <the_jk@yahoo.com>2017-09-26 23:44:35 +0200
committerJoel Klinghed <the_jk@yahoo.com>2017-09-26 23:44:35 +0200
commiteff2e4cc49bfadd9716f3ad65854b3b0ca309b74 (patch)
treebafa49f7efcf39e9057b8f006c14d7f3a2a53e2d /src/animator.cc
parent811e04305457108bc32d8895fd9bd274715d02fc (diff)
Animate job count changes
Diffstat (limited to 'src/animator.cc')
-rw-r--r--src/animator.cc262
1 files changed, 262 insertions, 0 deletions
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 <vector>
+
+#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<Looper> 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<Animation> 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> animation_;
+ AnimationObserver* observer_;
+ Observers<AnimationObserver> others_;
+ double started_;
+ bool alive_;
+
+ Entry(std::shared_ptr<Animation> 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<Entry>::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> looper_;
+ uint32_t timer_;
+ Observers<Observer> observers_;
+ std::vector<Entry> delayed_animations_;
+ std::vector<Entry> animations_;
+ bool callback_;
+ double target_;
+ double frame_;
+ unsigned good_;
+ unsigned bad_;
+ unsigned frame_count_;
+};
+
+} // namespace
+
+// static
+Animator* Animator::create(std::shared_ptr<Looper> const& looper) {
+ return new AnimatorImpl(looper);
+}