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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
22 | TimerManager::TimerManager() |
23 | : nextId(no_timer + 1), |
24 | queue(), |
25 | done(false) |
26 | { |
27 | } |
28 | |
29 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
30 | TimerManager::~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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
53 | TimerManager::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
85 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
93 | void TimerManager::clear() |
94 | { |
95 | ScopedLock lock(sync); |
96 | while (!active.empty()) |
97 | destroy_impl(lock, active.begin(), queue.size() == 1); |
98 | } |
99 | |
100 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
101 | std::size_t TimerManager::size() const noexcept |
102 | { |
103 | ScopedLock lock(sync); |
104 | return active.size(); |
105 | } |
106 | |
107 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
108 | bool TimerManager::empty() const noexcept |
109 | { |
110 | ScopedLock lock(sync); |
111 | return active.empty(); |
112 | } |
113 | |
114 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
115 | TimerManager& TimerManager::global() |
116 | { |
117 | static TimerManager singleton; |
118 | return singleton; |
119 | } |
120 | |
121 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
122 | void 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 |
193 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
235 | TimerManager::Timer::Timer(TimerId tid) |
236 | : id(tid), |
237 | running(false) |
238 | { |
239 | } |
240 | |
241 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
242 | TimerManager::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
252 | TimerManager::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 | |