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.

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