1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-09 23:34:20 +00:00

AudioProcessorGraph: Make prepareToPlay and releaseResources truly synchronous

Previously, these functions would only do a synchronous rebuild of the
graph when called from the main thread. However, in scenarios like
offline rendering, the graph *must* be ready to process after
prepareToPlay() returns, even if the prepare call is made on a
background thread.
This commit is contained in:
reuk 2025-09-24 11:01:47 +01:00
parent a9a99a0a08
commit a38fd6b477
No known key found for this signature in database

View file

@ -1851,24 +1851,18 @@ public:
nodeStates.setState (settings); nodeStates.setState (settings);
topologyChanged (UpdateKind::sync); topologyChanged (RebuildKind::immediate);
} }
void releaseResources() void releaseResources()
{ {
nodeStates.setState (nullopt); nodeStates.setState (nullopt);
topologyChanged (UpdateKind::sync); topologyChanged (RebuildKind::immediate);
} }
void rebuild (UpdateKind updateKind) void rebuild (UpdateKind updateKind)
{ {
if (updateKind == UpdateKind::none) rebuild (getRebuildKind (updateKind));
return;
if (updateKind == UpdateKind::sync && MessageManager::getInstance()->isThisTheMessageThread())
handleAsyncUpdate();
else
updater.triggerAsyncUpdate();
} }
void reset() void reset()
@ -1918,16 +1912,67 @@ public:
auto* getAudioThreadState() const { return renderSequenceExchange.getAudioThreadState(); } auto* getAudioThreadState() const { return renderSequenceExchange.getAudioThreadState(); }
private: private:
enum class RebuildKind
{
none, // no rebuild
async, // always async on main thread
syncIfMainThread, // sync if the rebuild request is on the main thread, async otherwise
immediate, // synchronous regardless of the thread making the rebuild request
};
static RebuildKind getRebuildKind (UpdateKind kind)
{
switch (kind)
{
case UpdateKind::async:
return RebuildKind::async;
case UpdateKind::sync:
return RebuildKind::syncIfMainThread;
case UpdateKind::none:
return RebuildKind::none;
}
jassertfalse;
return RebuildKind::syncIfMainThread;
}
void setParentGraph (AudioProcessor* p) const void setParentGraph (AudioProcessor* p) const
{ {
if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (p)) if (auto* ioProc = dynamic_cast<AudioGraphIOProcessor*> (p))
ioProc->setParentGraph (owner); ioProc->setParentGraph (owner);
} }
void topologyChanged (UpdateKind updateKind) void topologyChanged (UpdateKind kind)
{
topologyChanged (getRebuildKind (kind));
}
void topologyChanged (RebuildKind kind)
{ {
owner->sendChangeMessage(); owner->sendChangeMessage();
rebuild (updateKind); rebuild (kind);
}
void rebuild (RebuildKind kind)
{
if (kind == RebuildKind::none)
return;
const auto immediate = kind == RebuildKind::immediate
|| (kind == RebuildKind::syncIfMainThread
&& MessageManager::getInstance()->isThisTheMessageThread());
if (immediate)
{
updater.cancelPendingUpdate();
handleAsyncUpdate();
}
else
{
updater.triggerAsyncUpdate();
}
} }
void handleAsyncUpdate() void handleAsyncUpdate()
@ -2399,6 +2444,40 @@ public:
} }
} }
beginTest ("graph can be prepared and unprepared from a background thread");
{
using UK = AudioProcessorGraph::UpdateKind;
AudioProcessorGraph graph;
auto nodeA = BasicProcessor::make ({}, MidiIn::no, MidiOut::yes);
auto nodeB = BasicProcessor::make ({}, MidiIn::yes, MidiOut::no);
auto* ptrA = nodeA.get();
auto* ptrB = nodeB.get();
const auto idA = graph.addNode (std::move (nodeA), {}, UK::none)->nodeID;
const auto idB = graph.addNode (std::move (nodeB), {}, UK::none)->nodeID;
expect (graph.addConnection ({ { idA, midiChannel }, { idB, midiChannel } }, UK::none));
expect (! ptrA->isPrepared());
expect (! ptrB->isPrepared());
std::ignore = std::async (std::launch::async, [&]
{
expect (! ptrA->isPrepared());
expect (! ptrB->isPrepared());
graph.prepareToPlay (44100, 512);
expect (ptrA->isPrepared());
expect (ptrB->isPrepared());
graph.releaseResources();
expect (! ptrA->isPrepared());
expect (! ptrB->isPrepared());
});
}
beginTest ("large render sequence can be built"); beginTest ("large render sequence can be built");
{ {
AudioProcessorGraph graph; AudioProcessorGraph graph;
@ -2453,8 +2532,8 @@ private:
void changeProgramName (int, const String&) override {} void changeProgramName (int, const String&) override {}
void getStateInformation (MemoryBlock&) override {} void getStateInformation (MemoryBlock&) override {}
void setStateInformation (const void*, int) override {} void setStateInformation (const void*, int) override {}
void prepareToPlay (double, int) override {} void prepareToPlay (double, int) override { prepared = true; }
void releaseResources() override {} void releaseResources() override { prepared = false; }
bool supportsDoublePrecisionProcessing() const override { return doublePrecisionSupported; } bool supportsDoublePrecisionProcessing() const override { return doublePrecisionSupported; }
bool isMidiEffect() const override { return {}; } bool isMidiEffect() const override { return {}; }
void reset() override {} void reset() override {}
@ -2510,11 +2589,14 @@ private:
ProcessingPrecision getLastBlockPrecision() const { return blockPrecision; } ProcessingPrecision getLastBlockPrecision() const { return blockPrecision; }
bool isPrepared() const { return prepared; }
private: private:
MidiIn midiIn; MidiIn midiIn;
MidiOut midiOut; MidiOut midiOut;
ProcessingPrecision blockPrecision = ProcessingPrecision (-1); // initially invalid ProcessingPrecision blockPrecision = ProcessingPrecision (-1); // initially invalid
bool doublePrecisionSupported = true; bool doublePrecisionSupported = true;
bool prepared = false;
}; };
}; };