The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

432 lines
13KB

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