From eaa6dfc3ee31de52fa5d85a9f78d535ddf6a87ea Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 19 Jan 2023 13:14:04 +0000 Subject: [PATCH] AudioProcessorGraph: Allow triggering manual rebuild, and avoid rebuilding if nothing has changed --- .../processors/juce_AudioProcessorGraph.cpp | 67 ++++++++++++++++--- .../processors/juce_AudioProcessorGraph.h | 15 ++++- 2 files changed, 72 insertions(+), 10 deletions(-) diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp index 5e615433ab..58913a1713 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.cpp @@ -351,14 +351,14 @@ public: /* Called from prepareToPlay and releaseResources with the PrepareSettings that should be used next time the graph is rebuilt. */ - void setState (Optional newSettings) + void setState (std::optional newSettings) { const std::lock_guard lock (mutex); next = newSettings; } /* Call from the audio thread only. */ - Optional getLastRequestedSettings() const { return next; } + std::optional getLastRequestedSettings() const { return next; } /* Call from the main thread only! @@ -375,7 +375,7 @@ public: Returns the settings that were applied to the nodes. */ - Optional applySettings (const Nodes& n) + std::optional applySettings (const Nodes& n) { const auto settingsChanged = [this] { @@ -411,7 +411,7 @@ public: preparedNodes.clear(); } - if (current.hasValue()) + if (current.has_value()) { for (const auto& node : n.getNodes()) { @@ -433,7 +433,7 @@ public: private: std::mutex mutex; std::set preparedNodes; - Optional current, next; + std::optional current, next; }; //============================================================================== @@ -1447,6 +1447,40 @@ private: int latencySamples = 0; }; +//============================================================================== +/** Holds information about a particular graph configuration, without sharing ownership of any + graph nodes. Can be checked for equality with other RenderSequenceSignature instances to see + whether two graph configurations match. +*/ +class RenderSequenceSignature +{ + auto tie() const { return std::tie (settings, connections, nodes); } + +public: + RenderSequenceSignature (const PrepareSettings s, const Nodes& n, const Connections& c) + : settings (s), connections (c), nodes (getNodeIDs (n)) {} + + bool operator== (const RenderSequenceSignature& other) const { return tie() == other.tie(); } + bool operator!= (const RenderSequenceSignature& other) const { return tie() != other.tie(); } + +private: + static std::vector getNodeIDs (const Nodes& n) + { + const auto& nodeRefs = n.getNodes(); + std::vector result; + result.reserve ((size_t) nodeRefs.size()); + + for (const auto& node : nodeRefs) + result.push_back (node->nodeID); + + return result; + } + + PrepareSettings settings; + Connections connections; + std::vector nodes; +}; + //============================================================================== /* Facilitates wait-free render-sequence updates. @@ -1687,6 +1721,14 @@ public: topologyChanged (UpdateKind::sync); } + void rebuild() + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + handleAsyncUpdate(); + else + triggerAsyncUpdate(); + } + void reset() { for (auto* n : getNodes()) @@ -1757,22 +1799,30 @@ private: for (const auto node : nodes.getNodes()) setParentGraph (node->getProcessor()); - auto sequence = std::make_unique (*newSettings, nodes, connections); - owner->setLatencySamples (sequence->getLatencySamples()); - renderSequenceExchange.set (std::move (sequence)); + const RenderSequenceSignature newSignature (*newSettings, nodes, connections); + + if (std::exchange (lastBuiltSequence, newSignature) != newSignature) + { + auto sequence = std::make_unique (*newSettings, nodes, connections); + owner->setLatencySamples (sequence->getLatencySamples()); + renderSequenceExchange.set (std::move (sequence)); + } } else { + lastBuiltSequence.reset(); renderSequenceExchange.set (nullptr); } } + AudioProcessorGraph* owner = nullptr; Nodes nodes; Connections connections; NodeStates nodeStates; RenderSequenceExchange renderSequenceExchange; NodeID lastNodeID; + std::optional lastBuiltSequence; }; //============================================================================== @@ -1799,6 +1849,7 @@ AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (NodeID x) const bool AudioProcessorGraph::disconnectNode (NodeID nodeID, UpdateKind updateKind) { return pimpl->disconnectNode (nodeID, updateKind); } void AudioProcessorGraph::releaseResources() { return pimpl->releaseResources(); } bool AudioProcessorGraph::removeIllegalConnections (UpdateKind updateKind) { return pimpl->removeIllegalConnections (updateKind); } +void AudioProcessorGraph::rebuild() { return pimpl->rebuild(); } void AudioProcessorGraph::reset() { return pimpl->reset(); } bool AudioProcessorGraph::canConnect (const Connection& c) const { return pimpl->canConnect (c); } bool AudioProcessorGraph::isConnected (const Connection& c) const noexcept { return pimpl->isConnected (c); } diff --git a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h index 8190d754d4..2234d2c5ae 100644 --- a/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h +++ b/modules/juce_audio_processors/processors/juce_AudioProcessorGraph.h @@ -210,8 +210,11 @@ public: */ enum class UpdateKind { - sync, ///< Indicates that the graph should be rebuilt immediately after modification. - async ///< Indicates that the graph rebuild should be deferred. + sync, ///< Graph should be rebuilt immediately after modification. + async, ///< Graph rebuild should be delayed. If you make several changes to the graph + ///< inside the same call stack, these changes will be applied in one go. + none ///< Graph should not be rebuilt automatically. Use rebuild() to trigger a graph + ///< rebuild. }; //============================================================================== @@ -312,6 +315,14 @@ public: */ bool removeIllegalConnections (UpdateKind = UpdateKind::sync); + /** Rebuilds the graph if necessary. + + This function will only ever rebuild the graph on the main thread. If this function is + called from another thread, the rebuild request will be dispatched asynchronously to the + main thread. + */ + void rebuild(); + //============================================================================== /** A special type of AudioProcessor that can live inside an AudioProcessorGraph in order to use the audio that comes into and out of the graph itself.