1// Aseprite UI Library
2// Copyright (C) 2018-2022 Igara Studio S.A.
3// Copyright (C) 2001-2017 David Capello
4//
5// This file is released under the terms of the MIT license.
6// Read LICENSE.txt for more information.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "ui/timer.h"
13
14#include "base/time.h"
15#include "ui/manager.h"
16#include "ui/message.h"
17#include "ui/system.h"
18#include "ui/widget.h"
19
20#include <algorithm>
21#include <limits>
22#include <vector>
23
24namespace ui {
25
26typedef std::vector<Timer*> Timers;
27
28static Timers timers; // Registered timers
29static int running_timers = 0;
30
31Timer::Timer(int interval, Widget* owner)
32 : m_owner(owner ? owner: Manager::getDefault())
33 , m_interval(interval)
34 , m_running(false)
35 , m_lastTick(0)
36{
37 ASSERT(m_owner != nullptr);
38 assert_ui_thread();
39
40 timers.push_back(this);
41}
42
43Timer::~Timer()
44{
45 assert_ui_thread();
46
47 auto it = std::find(timers.begin(), timers.end(), this);
48 ASSERT(it != timers.end());
49 if (it != timers.end())
50 timers.erase(it);
51
52 // Stop the timer and remove it from the message queue.
53 stop();
54}
55
56void Timer::start()
57{
58 assert_ui_thread();
59
60 // Infinite timer? Do nothing.
61 if (m_interval == 0)
62 return;
63
64 m_lastTick = base::current_tick();
65 if (!m_running) {
66 m_running = true;
67 ++running_timers;
68 }
69}
70
71void Timer::stop()
72{
73 assert_ui_thread();
74
75 if (m_running) {
76 m_running = false;
77 --running_timers;
78
79 // Remove messages of this timer in the queue. The expected behavior
80 // is that when we stop a timer, we'll not receive more messages
81 // about it (even if there are enqueued messages waiting in the
82 // message queue).
83 Manager::getDefault()->removeMessagesForTimer(this);
84 }
85}
86
87void Timer::tick()
88{
89 assert_ui_thread();
90
91 onTick();
92}
93
94void Timer::setInterval(int interval)
95{
96 m_interval = interval;
97}
98
99void Timer::onTick()
100{
101 // Fire Tick signal.
102 Tick();
103}
104
105void Timer::pollTimers()
106{
107 assert_ui_thread();
108
109 // Generate messages for timers
110 if (running_timers != 0) {
111 ASSERT(!timers.empty());
112 base::tick_t t = base::current_tick();
113
114 for (auto timer : timers) {
115 if (timer && timer->isRunning()) {
116 ASSERT(timer->interval() > 0);
117
118 int64_t count = ((t - timer->m_lastTick) / timer->m_interval);
119 if (count > 0) {
120 timer->m_lastTick += count * timer->m_interval;
121
122 ASSERT(timer->m_owner != nullptr);
123
124 Message* msg = new TimerMessage(count, timer);
125 msg->setRecipient(timer->m_owner);
126 Manager::getDefault()->enqueueMessage(msg);
127 }
128 }
129 }
130 }
131}
132
133bool Timer::haveTimers()
134{
135 return !timers.empty();
136}
137
138bool Timer::getNextTimeout(double& timeout)
139{
140 if (running_timers == 0)
141 return false;
142
143 base::tick_t t = base::current_tick();
144 bool result = false;
145 timeout = std::numeric_limits<double>::max();
146 for (auto timer : timers) {
147 if (timer && timer->isRunning()) {
148 int64_t diff = (timer->m_lastTick + timer->m_interval) - t;
149 if (diff < 0) {
150 timeout = 0.0; // Right-now
151 return true;
152 }
153 else {
154 timeout = std::min<double>(timeout, diff / 1000.0);
155 result = true;
156 }
157 }
158 }
159 return result;
160}
161
162} // namespace ui
163