1//============================================================================
2//
3// SSSS tt lll lll
4// SS SS tt ll ll
5// SS tttttt eeee ll ll aaaa
6// SSSS tt ee ee ll ll aa
7// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8// SS SS tt ee ll ll aa aa
9// SSSS ttt eeeee llll llll aaaaa
10//
11// Copyright (c) 1995-2019 by Bradford W. Mott, Stephen Anthony
12// and the Stella Team
13//
14// See the file "License.txt" for information on usage and redistribution of
15// this file, and for a DISCLAIMER OF ALL WARRANTIES.
16//============================================================================
17
18#include <cassert>
19#include "TimerManager.hxx"
20
21// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
22TimerManager::TimerManager()
23 : nextId(no_timer + 1),
24 queue(),
25 done(false)
26{
27}
28
29// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
30TimerManager::~TimerManager()
31{
32 ScopedLock lock(sync);
33
34 // The worker might not be running
35 if (worker.joinable())
36 {
37 done = true;
38 lock.unlock();
39 wakeUp.notify_all();
40
41 // If a timer handler is running, this
42 // will make sure it has returned before
43 // allowing any deallocations to happen
44 worker.join();
45
46 // Note that any timers still in the queue
47 // will be destructed properly but they
48 // will not be invoked
49 }
50}
51
52// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
53TimerManager::TimerId TimerManager::addTimer(
54 millisec msDelay,
55 millisec msPeriod,
56 const TFunction& func)
57{
58 ScopedLock lock(sync);
59
60 // Lazily start thread when first timer is requested
61 if (!worker.joinable())
62 worker = std::thread(&TimerManager::timerThreadWorker, this);
63
64 // Assign an ID and insert it into function storage
65 auto id = nextId++;
66 auto iter = active.emplace(id, Timer(id, Clock::now() + Duration(msDelay),
67 Duration(msPeriod), std::move(func)));
68
69 // Insert a reference to the Timer into ordering queue
70 Queue::iterator place = queue.emplace(iter.first->second);
71
72 // We need to notify the timer thread only if we inserted
73 // this timer into the front of the timer queue
74 bool needNotify = (place == queue.begin());
75
76 lock.unlock();
77
78 if (needNotify)
79 wakeUp.notify_all();
80
81 return id;
82}
83
84// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
85bool TimerManager::clear(TimerId id)
86{
87 ScopedLock lock(sync);
88 auto i = active.find(id);
89 return destroy_impl(lock, i, true);
90}
91
92// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
93void TimerManager::clear()
94{
95 ScopedLock lock(sync);
96 while (!active.empty())
97 destroy_impl(lock, active.begin(), queue.size() == 1);
98}
99
100// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
101std::size_t TimerManager::size() const noexcept
102{
103 ScopedLock lock(sync);
104 return active.size();
105}
106
107// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
108bool TimerManager::empty() const noexcept
109{
110 ScopedLock lock(sync);
111 return active.empty();
112}
113
114// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
115TimerManager& TimerManager::global()
116{
117 static TimerManager singleton;
118 return singleton;
119}
120
121// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
122void TimerManager::timerThreadWorker()
123{
124 ScopedLock lock(sync);
125
126 while (!done)
127 {
128 if (queue.empty())
129 {
130 // Wait for done or work
131 wakeUp.wait(lock, [this] { return done || !queue.empty(); });
132 continue;
133 }
134
135 auto queueHead = queue.begin();
136 Timer& timer = *queueHead;
137 auto now = Clock::now();
138 if (now >= timer.next)
139 {
140 queue.erase(queueHead);
141
142 // Mark it as running to handle racing destroy
143 timer.running = true;
144
145 // Call the handler outside the lock
146 lock.unlock();
147 timer.handler();
148 lock.lock();
149
150 if (timer.running)
151 {
152 timer.running = false;
153
154 // If it is periodic, schedule a new one
155 if (timer.period.count() > 0)
156 {
157 timer.next = timer.next + timer.period;
158 queue.emplace(timer);
159 }
160 else
161 {
162 // Not rescheduling, destruct it
163 active.erase(timer.id);
164 }
165 }
166 else
167 {
168 // timer.running changed!
169 //
170 // Running was set to false, destroy was called
171 // for this Timer while the callback was in progress
172 // (this thread was not holding the lock during the callback)
173 // The thread trying to destroy this timer is waiting on
174 // a condition variable, so notify it
175 timer.waitCond->notify_all();
176
177 // The clearTimer call expects us to remove the instance
178 // when it detects that it is racing with its callback
179 active.erase(timer.id);
180 }
181 }
182 else
183 {
184 // Wait until the timer is ready or a timer creation notifies
185 Timestamp next = timer.next;
186 wakeUp.wait_until(lock, next);
187 }
188 }
189}
190
191// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
192// NOTE: if notify is true, returns with lock unlocked
193bool TimerManager::destroy_impl(ScopedLock& lock, TimerMap::iterator i,
194 bool notify)
195{
196 assert(lock.owns_lock());
197
198 if (i == active.end())
199 return false;
200
201 Timer& timer = i->second;
202 if (timer.running)
203 {
204 // A callback is in progress for this Timer,
205 // so flag it for deletion in the worker
206 timer.running = false;
207
208 // Assign a condition variable to this timer
209 timer.waitCond.reset(new ConditionVar);
210
211 // Block until the callback is finished
212 if (std::this_thread::get_id() != worker.get_id())
213 timer.waitCond->wait(lock);
214 }
215 else
216 {
217 queue.erase(timer);
218 active.erase(i);
219
220 if (notify)
221 {
222 lock.unlock();
223 wakeUp.notify_all();
224 }
225 }
226
227 return true;
228}
229
230// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
231// TimerManager::Timer implementation
232//
233
234// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
235TimerManager::Timer::Timer(TimerId tid)
236 : id(tid),
237 running(false)
238{
239}
240
241// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
242TimerManager::Timer::Timer(Timer&& r) noexcept
243 : id(std::move(r.id)),
244 next(std::move(r.next)),
245 period(std::move(r.period)),
246 handler(std::move(r.handler)),
247 running(std::move(r.running))
248{
249}
250
251// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
252TimerManager::Timer::Timer(TimerId tid, Timestamp tnext, Duration tperiod,
253 const TFunction& func) noexcept
254 : id(tid),
255 next(tnext),
256 period(tperiod),
257 handler(std::move(func)),
258 running(false)
259{
260}
261