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