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.

350 lines
10KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE examples.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. The code included in this file is provided under the terms of the ISC license
  6. http://www.isc.org/downloads/software-support-policy/isc-license. Permission
  7. To use, copy, modify, and/or distribute this software for any purpose with or
  8. without fee is hereby granted provided that the above copyright notice and
  9. this permission notice appear in all copies.
  10. THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
  11. WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
  12. PURPOSE, ARE DISCLAIMED.
  13. ==============================================================================
  14. */
  15. /*******************************************************************************
  16. The block below describes the properties of this PIP. A PIP is a short snippet
  17. of code that can be read by the Projucer and used to generate a JUCE project.
  18. BEGIN_JUCE_PIP_METADATA
  19. name: MultithreadingDemo
  20. version: 1.0.0
  21. vendor: JUCE
  22. website: http://juce.com
  23. description: Demonstrates multi-threading.
  24. dependencies: juce_core, juce_data_structures, juce_events, juce_graphics,
  25. juce_gui_basics
  26. exporters: xcode_mac, vs2019, linux_make, androidstudio, xcode_iphone
  27. moduleFlags: JUCE_STRICT_REFCOUNTEDPOINTER=1
  28. type: Component
  29. mainClass: MultithreadingDemo
  30. useLocalCopy: 1
  31. END_JUCE_PIP_METADATA
  32. *******************************************************************************/
  33. #pragma once
  34. #include "../Assets/DemoUtilities.h"
  35. //==============================================================================
  36. class BouncingBallComp : public Component
  37. {
  38. public:
  39. BouncingBallComp()
  40. {
  41. auto speed = 5.0f; // give each ball a fixed speed so we can
  42. // see the effects of thread priority on how fast
  43. // they actually go.
  44. auto angle = Random::getSystemRandom().nextFloat() * MathConstants<float>::twoPi;
  45. dx = std::sin (angle) * speed;
  46. dy = std::cos (angle) * speed;
  47. colour = Colour ((juce::uint32) Random::getSystemRandom().nextInt())
  48. .withAlpha (0.5f)
  49. .withBrightness (0.7f);
  50. }
  51. void paint (Graphics& g) override
  52. {
  53. g.setColour (colour);
  54. g.fillEllipse (innerX, innerY, size, size);
  55. g.setColour (Colours::black);
  56. g.setFont (10.0f);
  57. g.drawText (String::toHexString ((int64) threadId), getLocalBounds(), Justification::centred, false);
  58. }
  59. void parentSizeChanged() override
  60. {
  61. parentWidth = getParentWidth() - size;
  62. parentHeight = getParentHeight() - size;
  63. }
  64. void moveBall()
  65. {
  66. threadId = Thread::getCurrentThreadId(); // this is so the component can print the thread ID inside the ball
  67. x += dx;
  68. y += dy;
  69. if (x < 0)
  70. dx = std::abs (dx);
  71. if (x > parentWidth)
  72. dx = -std::abs (dx);
  73. if (y < 0)
  74. dy = std::abs (dy);
  75. if (y > parentHeight)
  76. dy = -std::abs (dy);
  77. setBounds (((int) x) - 2,
  78. ((int) y) - 2,
  79. ((int) size) + 4,
  80. ((int) size) + 4);
  81. innerX = x - getX();
  82. innerY = y - getY();
  83. repaint();
  84. }
  85. private:
  86. float x = Random::getSystemRandom().nextFloat() * 200.0f,
  87. y = Random::getSystemRandom().nextFloat() * 200.0f,
  88. size = Random::getSystemRandom().nextFloat() * 30.0f + 30.0f,
  89. dx = 0.0f, dy = 0.0f, innerX = 0.0f, innerY = 0.0f,
  90. parentWidth = 50.0f, parentHeight = 50.0f;
  91. Colour colour;
  92. Thread::ThreadID threadId = {};
  93. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BouncingBallComp)
  94. };
  95. //==============================================================================
  96. class DemoThread : public BouncingBallComp,
  97. public Thread
  98. {
  99. public:
  100. DemoThread()
  101. : Thread ("JUCE Demo Thread")
  102. {
  103. // give the threads a random priority, so some will move more
  104. // smoothly than others..
  105. startThread (Random::getSystemRandom().nextInt (3) + 3);
  106. }
  107. ~DemoThread() override
  108. {
  109. // allow the thread 2 seconds to stop cleanly - should be plenty of time.
  110. stopThread (2000);
  111. }
  112. void run() override
  113. {
  114. // this is the code that runs this thread - we'll loop continuously,
  115. // updating the coordinates of our blob.
  116. // threadShouldExit() returns true when the stopThread() method has been
  117. // called, so we should check it often, and exit as soon as it gets flagged.
  118. while (! threadShouldExit())
  119. {
  120. // sleep a bit so the threads don't all grind the CPU to a halt..
  121. wait (interval);
  122. // because this is a background thread, we mustn't do any UI work without
  123. // first grabbing a MessageManagerLock..
  124. const MessageManagerLock mml (Thread::getCurrentThread());
  125. if (! mml.lockWasGained()) // if something is trying to kill this job, the lock
  126. return; // will fail, in which case we'd better return..
  127. // now we've got the UI thread locked, we can mess about with the components
  128. moveBall();
  129. }
  130. }
  131. private:
  132. int interval = Random::getSystemRandom().nextInt (50) + 6;
  133. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoThread)
  134. };
  135. //==============================================================================
  136. class DemoThreadPoolJob : public BouncingBallComp,
  137. public ThreadPoolJob
  138. {
  139. public:
  140. DemoThreadPoolJob()
  141. : ThreadPoolJob ("Demo Threadpool Job")
  142. {}
  143. JobStatus runJob() override
  144. {
  145. // this is the code that runs this job. It'll be repeatedly called until we return
  146. // jobHasFinished instead of jobNeedsRunningAgain.
  147. Thread::sleep (30);
  148. // because this is a background thread, we mustn't do any UI work without
  149. // first grabbing a MessageManagerLock..
  150. const MessageManagerLock mml (this);
  151. // before moving the ball, we need to check whether the lock was actually gained, because
  152. // if something is trying to stop this job, it will have failed..
  153. if (mml.lockWasGained())
  154. moveBall();
  155. return jobNeedsRunningAgain;
  156. }
  157. void removedFromQueue()
  158. {
  159. // This is called to tell us that our job has been removed from the pool.
  160. // In this case there's no need to do anything here.
  161. }
  162. private:
  163. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DemoThreadPoolJob)
  164. };
  165. //==============================================================================
  166. class MultithreadingDemo : public Component,
  167. private Timer
  168. {
  169. public:
  170. MultithreadingDemo()
  171. {
  172. setOpaque (true);
  173. addAndMakeVisible (controlButton);
  174. controlButton.changeWidthToFitText (24);
  175. controlButton.setTopLeftPosition (20, 20);
  176. controlButton.setTriggeredOnMouseDown (true);
  177. controlButton.setAlwaysOnTop (true);
  178. controlButton.onClick = [this] { showMenu(); };
  179. setSize (500, 500);
  180. }
  181. ~MultithreadingDemo() override
  182. {
  183. pool.removeAllJobs (true, 2000);
  184. }
  185. void resetAllBalls()
  186. {
  187. stopTimer();
  188. pool.removeAllJobs (true, 4000);
  189. balls.clear();
  190. if (isShowing())
  191. {
  192. while (balls.size() < 5)
  193. addABall();
  194. startTimer (300);
  195. }
  196. }
  197. void paint (Graphics& g) override
  198. {
  199. g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground));
  200. }
  201. private:
  202. ThreadPool pool { 3 };
  203. TextButton controlButton { "Thread type" };
  204. bool isUsingPool = false;
  205. OwnedArray<Component> balls;
  206. void setUsingPool (bool usePool)
  207. {
  208. isUsingPool = usePool;
  209. resetAllBalls();
  210. }
  211. void addABall()
  212. {
  213. if (isUsingPool)
  214. {
  215. auto* newBall = new DemoThreadPoolJob();
  216. balls.add (newBall);
  217. addAndMakeVisible (newBall);
  218. newBall->parentSizeChanged();
  219. pool.addJob (newBall, false);
  220. }
  221. else
  222. {
  223. auto* newBall = new DemoThread();
  224. balls.add (newBall);
  225. addAndMakeVisible (newBall);
  226. newBall->parentSizeChanged();
  227. }
  228. }
  229. void removeABall()
  230. {
  231. if (balls.size() > 0)
  232. {
  233. auto indexToRemove = Random::getSystemRandom().nextInt (balls.size());
  234. if (isUsingPool)
  235. pool.removeJob (dynamic_cast<DemoThreadPoolJob*> (balls[indexToRemove]), true, 4000);
  236. balls.remove (indexToRemove);
  237. }
  238. }
  239. // this gets called when a component is added or removed from a parent component.
  240. void parentHierarchyChanged() override
  241. {
  242. // we'll use this as an opportunity to start and stop the threads, so that
  243. // we don't leave them going when the component's not actually visible.
  244. resetAllBalls();
  245. }
  246. void timerCallback() override
  247. {
  248. if (Random::getSystemRandom().nextBool())
  249. {
  250. if (balls.size() <= 10)
  251. addABall();
  252. }
  253. else
  254. {
  255. if (balls.size() > 3)
  256. removeABall();
  257. }
  258. }
  259. void showMenu()
  260. {
  261. PopupMenu m;
  262. m.addItem (1, "Use one thread per ball", true, ! isUsingPool);
  263. m.addItem (2, "Use a thread pool", true, isUsingPool);
  264. m.showMenuAsync (PopupMenu::Options().withTargetComponent (controlButton),
  265. ModalCallbackFunction::forComponent (menuItemChosenCallback, this));
  266. }
  267. static void menuItemChosenCallback (int result, MultithreadingDemo* demoComponent)
  268. {
  269. if (result != 0 && demoComponent != nullptr)
  270. demoComponent->setUsingPool (result == 2);
  271. }
  272. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultithreadingDemo)
  273. };