tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_LockFreeMultiThreadedNodePlayer.h
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11#pragma once
12
13#ifdef _MSC_VER
14 #pragma warning (push)
15 #pragma warning (disable: 4127)
16#endif
17
18#ifdef _MSC_VER
19 #pragma warning (pop)
20#endif
21
22namespace tracktion { inline namespace graph
23{
24
25//==============================================================================
26//==============================================================================
34{
35private:
36 //==============================================================================
37 template<typename Type>
38 class LockFreeFifo
39 {
40 public:
41 LockFreeFifo (int capacity)
42 : fifo (std::make_unique<rigtorp::MPMCQueue<Type>> (static_cast<size_t> (capacity)))
43 {
44 }
45
46 bool try_enqueue (Type&& item) { return fifo->try_push (std::move (item)); }
47 bool try_dequeue (Type& item) { return fifo->try_pop (item); }
48
49 private:
51 };
52
53 struct PlaybackNode
54 {
55 PlaybackNode (Node& n)
56 : node (n), numInputs (node.getDirectInputNodes().size())
57 {}
58
59 Node& node;
60 const size_t numInputs;
61 std::vector<Node*> outputs;
62 std::atomic<size_t> numInputsToBeProcessed { 0 };
63 std::atomic<bool> hasBeenQueued { true };
64 #if JUCE_DEBUG
65 std::atomic<bool> hasBeenDequeued { false };
66 #endif
67 };
68
69 struct PreparedNode
70 {
73 std::unique_ptr<LockFreeFifo<Node*>> nodesReadyToBeProcessed;
75 };
76
77public:
78 //==============================================================================
84 {
87 : player (p)
88 {
89 }
90
92 virtual ~ThreadPool() = default;
93
95 virtual void createThreads (size_t numThreads, juce::AudioWorkgroup) = 0;
96
98 virtual void clearThreads() = 0;
99
103 virtual void signalOne() = 0;
104
108 virtual void signal (int numToSignal) = 0;
109
113 virtual void signalAll() = 0;
114
119 virtual void waitForFinalNode() = 0;
120
121 //==============================================================================
124 {
125 threadsShouldExit = true;
126 signalAll();
127 }
128
131 {
132 threadsShouldExit = false;
133 }
134
136 bool shouldExit() const
137 {
138 return threadsShouldExit.load (std::memory_order_acquire);
139 }
140
145 {
146 if (shouldExit())
147 return false;
148
149 return player.numNodesQueued == 0;
150 }
151
154 {
155 assert (currentPreparedNode);
156
157 if (! currentPreparedNode.load()->graph)
158 return true;
159
160 return currentPreparedNode.load()->graph->rootNode->hasProcessed();
161 }
162
167 bool process()
168 {
169 if (auto cpn = currentPreparedNode.load())
170 return player.processNextFreeNode (*cpn);
171
172 return false;
173 }
174
176 void setCurrentNode (LockFreeMultiThreadedNodePlayer::PreparedNode* nodeInUse)
177 {
178 currentPreparedNode = nodeInUse;
179
180 // If a nullptr is being set, you must stop the threads first.
181 assert (nodeInUse || shouldExit());
182 }
183
185
186 private:
187 std::atomic<bool> threadsShouldExit { false };
189 };
190
191 //==============================================================================
194
196
199
202
203 //==============================================================================
208 void setNumThreads (size_t);
209
212
214 void setNode (std::unique_ptr<Node> newNode, double sampleRateToUse, int blockSizeToUse);
215
219 void prepareToPlay (double sampleRateToUse, int blockSizeToUse);
220
223 {
224 return rootNode;
225 }
226
228 int process (const Node::ProcessContext&);
229
236 void clearNode();
237
239 double getSampleRate() const
240 {
241 return sampleRate.load (std::memory_order_acquire);
242 }
243
245 int getBlockSize() const
246 {
247 return blockSize.load (std::memory_order_acquire);;
248 }
249
250 //==============================================================================
256
257 /* @internal. */
258 void enableNodeMemorySharing (bool shouldBeEnabled);
259
260private:
261 //==============================================================================
262 std::atomic<size_t> numThreadsToUse { std::max ((size_t) 0, (size_t) std::thread::hardware_concurrency() - 1) };
263 juce::Range<int64_t> referenceSampleRange;
264 choc::buffer::FrameCount numSamplesToProcess = 0;
265 std::atomic<bool> threadsShouldExit { false }, useMemoryPool { false };
266
268 juce::AudioWorkgroup audioWorkgroup;
269
270 LockFreeObject<PreparedNode> preparedNodeObject;
271 Node* rootNode = nullptr;
272 NodeGraph* lastGraphPosted = nullptr;
273 AudioBufferPool* lastAudioBufferPoolPosted = nullptr;
274
275 std::atomic<size_t> numNodesQueued { 0 };
276
277 //==============================================================================
278 std::atomic<double> sampleRate { 44100.0 };
279 std::atomic<int> blockSize { 512 };
280 bool nodeMemorySharingEnabled = false;
281
282 //==============================================================================
285 double sampleRateToUse, int blockSizeToUse,
286 bool useCurrentAudioBufferPool);
287
288 //==============================================================================
289 void clearThreads();
290 void createThreads();
291 void pause();
292
293 //==============================================================================
294 void postNewGraph (std::unique_ptr<NodeGraph>);
295
296 //==============================================================================
297 static void buildNodesOutputLists (PreparedNode&);
298 void resetProcessQueue (PreparedNode&);
299 Node* updateProcessQueueForNode (PreparedNode&, Node&);
300 void processNode (PreparedNode&, Node&);
301
302 //==============================================================================
303 bool processNextFreeNode (PreparedNode&);
304};
305
306}}
assert
void setNode(std::unique_ptr< Node >)
Sets the Node to process.
void enablePooledMemoryAllocations(bool)
Enables or disables the use on an AudioBufferPool to reduce memory consumption.
int process(const Node::ProcessContext &)
Process a block of the Node.
void setNumThreads(size_t)
Sets the number of threads to use for rendering.
void prepareToPlay(double sampleRateToUse, int blockSizeToUse)
Prepares the current Node to be played.
LockFreeMultiThreadedNodePlayer()
Creates an empty LockFreeMultiThreadedNodePlayer.
Main graph Node processor class.
Struct to describe a single iteration of a process call.
T hardware_concurrency(T... args)
T is_pointer_v
T load(T... args)
T make_unique(T... args)
T max(T... args)
Base class for thread pools which can be customised to determine how cooperative threads should behav...
virtual void createThreads(size_t numThreads, juce::AudioWorkgroup)=0
Subclasses should implement this to create the given number of threads.
bool shouldWait()
Returns true if there are no free Nodes to be processed and the calling thread should wait until ther...
virtual void signal(int numToSignal)=0
Called by the player when more than one Node becomes available to process.
virtual void clearThreads()=0
Subclasses should implement this to clear all the threads.
void signalShouldExit()
Signals the pool that all the threads should exit.
virtual void signalOne()=0
Called by the player when a Node becomes available to process.
void setCurrentNode(LockFreeMultiThreadedNodePlayer::PreparedNode *nodeInUse)
Sets the current PreparedNode in use.
bool isFinalNodeReady()
Returns true if all the Nodes have been processed.
void resetExitSignal()
Signals the pool that all the threads should continue to run and not exit.
ThreadPool(LockFreeMultiThreadedNodePlayer &p)
Constructs a ThreadPool for a given LockFreeMultiThreadedNodePlayer.
virtual void waitForFinalNode()=0
Called by the player when the audio thread has no free Nodes to process.
virtual void signalAll()=0
Called by the player when more than one Node becomes available to process.