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.

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