OpenShot Audio Library | OpenShotAudio: juce_HighResolutionTimer.cpp Source File

OpenShot Audio Library | OpenShotAudio 0.4.0

Loading...
Searching...
No Matches
juce_HighResolutionTimer.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26//==============================================================================
27class HighResolutionTimer::Impl : private PlatformTimerListener
28{
29public:
30 explicit Impl (HighResolutionTimer& o)
31 : owner { o } {}
32
33 void startTimer (int newIntervalMs)
34 {
35 shouldCancelCallbacks.store (true);
36
37 const auto shouldWaitForPendingCallbacks = [&]
38 {
39 const std::scoped_lock lock { timerMutex };
40
41 if (timer.getIntervalMs() > 0)
42 timer.cancelTimer();
43
44 jassert (timer.getIntervalMs() == 0);
45
46 if (newIntervalMs > 0)
47 timer.startTimer (jmax (0, newIntervalMs));
48
49 return callbackThreadId != std::this_thread::get_id()
50 && timer.getIntervalMs() <= 0;
51 }();
52
53 if (shouldWaitForPendingCallbacks)
54 std::scoped_lock lock { callbackMutex };
55 }
56
57 int getIntervalMs() const
58 {
59 const std::scoped_lock lock { timerMutex };
60 return timer.getIntervalMs();
61 }
62
63 bool isTimerRunning() const
64 {
65 return getIntervalMs() > 0;
66 }
67
68private:
69 void onTimerExpired() final
70 {
71 callbackThreadId.store (std::this_thread::get_id());
72
73 {
74 std::scoped_lock lock { callbackMutex };
75
76 if (isTimerRunning())
77 {
78 try
79 {
80 owner.hiResTimerCallback();
81 }
82 catch (...)
83 {
84 // Exceptions thrown in a timer callback won't be
85 // propagated to the main thread, it's best to find
86 // a way to avoid them if possible
87 jassertfalse;
88 }
89 }
90 }
91
92 callbackThreadId.store ({});
93 }
94
95 HighResolutionTimer& owner;
96 mutable std::mutex timerMutex;
97 std::mutex callbackMutex;
98 std::atomic<std::thread::id> callbackThreadId{};
99 std::atomic<bool> shouldCancelCallbacks { false };
100 PlatformTimer timer { *this };
101
102 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Impl)
103 JUCE_DECLARE_NON_MOVEABLE (Impl)
104};
105
106//==============================================================================
107HighResolutionTimer::HighResolutionTimer()
108 : impl (std::make_unique<Impl> (*this)) {}
109
110HighResolutionTimer::~HighResolutionTimer()
111{
112 // You *must* call stopTimer from the derived class destructor to
113 // avoid data races on the timer's vtable
114 jassert (! isTimerRunning());
115 stopTimer();
116}
117
118 void HighResolutionTimer::startTimer (int newIntervalMs)
119{
120 impl->startTimer (newIntervalMs);
121}
122
123 void HighResolutionTimer::stopTimer()
124{
125 impl->startTimer (0);
126}
127
128 int HighResolutionTimer::getTimerInterval() const noexcept
129{
130 return impl->getIntervalMs();
131}
132
133 bool HighResolutionTimer::isTimerRunning() const noexcept
134{
135 return impl->isTimerRunning();
136}
137
138//==============================================================================
139#if JUCE_UNIT_TESTS
140
141class HighResolutionTimerTests final : public UnitTest
142{
143public:
144 HighResolutionTimerTests()
145 : UnitTest ("HighResolutionTimer", UnitTestCategories::threads) {}
146
147 void runTest() override
148 {
149 constexpr int maximumTimeoutMs {30'000};
150
151 beginTest ("Start/stop a timer");
152 {
153 WaitableEvent timerFiredOnce;
154 WaitableEvent timerFiredTwice;
155
156 Timer timer {[&, callbackCount = 0]() mutable
157 {
158 switch (++callbackCount)
159 {
160 case 1: timerFiredOnce.signal(); return;
161 case 2: timerFiredTwice.signal(); return;
162 default: return;
163 }
164 }};
165
166 expect (! timer.isTimerRunning());
167 expect (timer.getTimerInterval() == 0);
168
169 timer.startTimer (1);
170 expect (timer.isTimerRunning());
171 expect (timer.getTimerInterval() == 1);
172 expect (timerFiredOnce.wait (maximumTimeoutMs));
173 expect (timerFiredTwice.wait (maximumTimeoutMs));
174
175 timer.stopTimer();
176 expect (! timer.isTimerRunning());
177 expect (timer.getTimerInterval() == 0);
178 }
179
180 beginTest ("Stop a timer from the timer callback");
181 {
182 WaitableEvent stoppedTimer;
183
184 auto timerCallback = [&] (Timer& timer)
185 {
186 expect (timer.isTimerRunning());
187 timer.stopTimer();
188 expect (! timer.isTimerRunning());
189 stoppedTimer.signal();
190 };
191
192 Timer timer {[&]{ timerCallback (timer); }};
193 timer.startTimer (1);
194 expect (stoppedTimer.wait (maximumTimeoutMs));
195 }
196
197 beginTest ("Restart a timer from the timer callback");
198 {
199 WaitableEvent restartTimer;
200 WaitableEvent timerRestarted;
201 WaitableEvent timerFiredAfterRestart;
202
203 Timer timer {[&, callbackCount = 0]() mutable
204 {
205 switch (++callbackCount)
206 {
207 case 1:
208 expect (restartTimer.wait (maximumTimeoutMs));
209 expect (timer.getTimerInterval() == 1);
210
211 timer.startTimer (2);
212 expect (timer.getTimerInterval() == 2);
213 timerRestarted.signal();
214 return;
215
216 case 2:
217 expect (timer.getTimerInterval() == 2);
218 timerFiredAfterRestart.signal();
219 return;
220
221 default:
222 return;
223 }
224 }};
225
226 timer.startTimer (1);
227 expect (timer.getTimerInterval() == 1);
228
229 restartTimer.signal();
230 expect (timerRestarted.wait (maximumTimeoutMs));
231 expect (timer.getTimerInterval() == 2);
232 expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
233
234 timer.stopTimer();
235 }
236
237 beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish");
238 {
239 WaitableEvent timerCallbackStarted;
240 WaitableEvent stoppingTimer;
241 std::atomic<bool> timerCallbackFinished { false };
242
243 Timer timer {[&, callbackCount = 0]() mutable
244 {
245 switch (++callbackCount)
246 {
247 case 1:
248 timerCallbackStarted.signal();
249 expect (stoppingTimer.wait (maximumTimeoutMs));
250 Thread::sleep (10);
251 timerCallbackFinished = true;
252 return;
253
254 default:
255 return;
256 }
257 }};
258
259 timer.startTimer (1);
260 expect (timerCallbackStarted.wait (maximumTimeoutMs));
261
262 stoppingTimer.signal();
263 timer.stopTimer();
264 expect (timerCallbackFinished);
265 }
266
267 beginTest ("Calling stopTimer on a timer, waits for any timer callbacks to finish, even if the timer callback calls stopTimer first");
268 {
269 WaitableEvent stoppedFromInsideTimerCallback;
270 WaitableEvent stoppingFromOutsideTimerCallback;
271 std::atomic<bool> timerCallbackFinished { false };
272
273 Timer timer {[&]()
274 {
275 timer.stopTimer();
276 stoppedFromInsideTimerCallback.signal();
277 expect (stoppingFromOutsideTimerCallback.wait (maximumTimeoutMs));
278 Thread::sleep (10);
279 timerCallbackFinished = true;
280
281 }};
282
283 timer.startTimer (1);
284 expect (stoppedFromInsideTimerCallback.wait (maximumTimeoutMs));
285
286 stoppingFromOutsideTimerCallback.signal();
287 timer.stopTimer();
288 expect (timerCallbackFinished);
289 }
290
291 beginTest ("Adjusting a timer period from outside the timer callback doesn't cause data races");
292 {
293 WaitableEvent timerCallbackStarted;
294 WaitableEvent timerRestarted;
295 WaitableEvent timerFiredAfterRestart;
296 std::atomic<int> lastCallbackCount {0};
297
298 Timer timer {[&, callbackCount = 0]() mutable
299 {
300 switch (++callbackCount)
301 {
302 case 1:
303 expect (timer.getTimerInterval() == 1);
304 timerCallbackStarted.signal();
305 Thread::sleep (10);
306 lastCallbackCount = 1;
307 return;
308
309 case 2:
310 expect (timerRestarted.wait (maximumTimeoutMs));
311 expect (timer.getTimerInterval() == 2);
312 lastCallbackCount = 2;
313 timerFiredAfterRestart.signal();
314 return;
315
316 default:
317 return;
318 }
319 }};
320
321 timer.startTimer (1);
322 expect (timerCallbackStarted.wait (maximumTimeoutMs));
323
324 timer.startTimer (2);
325 timerRestarted.signal();
326
327 expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
328 expect (lastCallbackCount == 2);
329
330 timer.stopTimer();
331 expect (lastCallbackCount == 2);
332 }
333
334 beginTest ("A timer can be restarted externally, after being stopped internally");
335 {
336 WaitableEvent timerStopped;
337 WaitableEvent timerFiredAfterRestart;
338
339 Timer timer {[&, callbackCount = 0]() mutable
340 {
341 switch (++callbackCount)
342 {
343 case 1:
344 timer.stopTimer();
345 timerStopped.signal();
346 return;
347
348 case 2:
349 timerFiredAfterRestart.signal();
350 return;
351
352 default:
353 return;
354 }
355 }};
356
357 expect (! timer.isTimerRunning());
358 timer.startTimer (1);
359 expect (timer.isTimerRunning());
360
361 expect (timerStopped.wait (maximumTimeoutMs));
362 expect (! timer.isTimerRunning());
363
364 timer.startTimer (1);
365 expect (timer.isTimerRunning());
366 expect (timerFiredAfterRestart.wait (maximumTimeoutMs));
367 }
368
369 beginTest ("Calls to `startTimer` and `getTimerInterval` succeed while a callback is blocked");
370 {
371 WaitableEvent timerBlocked;
372 WaitableEvent unblockTimer;
373
374 Timer timer {[&]
375 {
376 timerBlocked.signal();
377 unblockTimer.wait();
378 timer.stopTimer();
379 }};
380
381 timer.startTimer (1);
382 timerBlocked.wait();
383
384 expect (timer.getTimerInterval() == 1);
385 timer.startTimer (2);
386 expect (timer.getTimerInterval() == 2);
387
388 unblockTimer.signal();
389 timer.stopTimer();
390 }
391
392 beginTest ("Stress test");
393 {
394 constexpr auto maxNumTimers { 100 };
395
396 std::vector<std::unique_ptr<Timer>> timers;
397 timers.reserve (maxNumTimers);
398
399 for (int i = 0; i < maxNumTimers; ++i)
400 {
401 auto timer = std::make_unique<Timer> ([]{});
402 timer->startTimer (1);
403
404 if (! timer->isTimerRunning())
405 break;
406
407 timers.push_back (std::move (timer));
408 }
409
410 expect (timers.size() >= 16);
411 }
412 }
413
414 class Timer final : public HighResolutionTimer
415 {
416 public:
417 explicit Timer (std::function<void()> fn)
418 : callback (std::move (fn)) {}
419
420 ~Timer() override { stopTimer(); }
421
422 void hiResTimerCallback() override { callback(); }
423
424 private:
425 std::function<void()> callback;
426 };
427};
428
429static HighResolutionTimerTests highResolutionTimerTests;
430
431#endif
432
433} // namespace juce
juce::HighResolutionTimer::~HighResolutionTimer
virtual ~HighResolutionTimer()
Definition juce_HighResolutionTimer.cpp:110
juce::HighResolutionTimer::hiResTimerCallback
virtual void hiResTimerCallback()=0
juce::HighResolutionTimer::getTimerInterval
int getTimerInterval() const noexcept
Definition juce_HighResolutionTimer.cpp:128
juce::HighResolutionTimer::HighResolutionTimer
HighResolutionTimer()
Definition juce_HighResolutionTimer.cpp:107
juce::HighResolutionTimer::isTimerRunning
bool isTimerRunning() const noexcept
Definition juce_HighResolutionTimer.cpp:133
juce::HighResolutionTimer::stopTimer
void stopTimer()
Definition juce_HighResolutionTimer.cpp:123
juce::HighResolutionTimer::startTimer
void startTimer(int intervalInMilliseconds)
Definition juce_HighResolutionTimer.cpp:118
juce::UnitTest
Definition juce_UnitTest.h:70