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