Untangle Graph from Route

The process-graph should only be concerned with GraphNodes,
which may or may not be Routes.

This also removes intrinsic connection information from
the graph-node. Connection information is to be kept separate
from the nodes.

When the graph is re-calculated in the background, old information
has to be retained until the new graph becomes active.

Previously *new* information was already stored in the nodes
while the graph is sorted, even though the new graph was not
active.
This commit is contained in:
Robin Gareus 2022-04-28 02:44:08 +02:00
parent 3862d13e21
commit 9ad154f265
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
12 changed files with 285 additions and 337 deletions

View File

@ -59,7 +59,7 @@ public:
Graph (Session& session);
void trigger (GraphNode* n);
void rechain (boost::shared_ptr<RouteList>, GraphEdges const&);
void rechain (GraphNodeList const&, GraphEdges const&);
bool plot (std::string const& file_name) const;
void plot (int chain);

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
* Copyright (C) 2015-2022 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -22,9 +22,15 @@
#include <map>
#include <set>
namespace ARDOUR {
#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<Route> GraphVertex;
#include "ardour/libardour_visibility.h"
#include "ardour/types.h"
namespace ARDOUR {
class GraphNode;
typedef boost::shared_ptr<GraphNode> GraphVertex;
/** A list of edges for a directed graph for routes.
*
@ -38,40 +44,43 @@ typedef boost::shared_ptr<Route> GraphVertex;
class LIBARDOUR_API GraphEdges
{
public:
typedef std::map<GraphVertex, std::set<GraphVertex> > EdgeMap;
void add (GraphVertex from, GraphVertex to, bool via_sends_only);
bool has (GraphVertex from, GraphVertex to, bool* via_sends_only);
bool feeds (GraphVertex from, GraphVertex to);
std::set<GraphVertex> from (GraphVertex r) const;
void remove (GraphVertex from, GraphVertex to);
bool has (GraphVertex from, GraphVertex to, bool* via_sends_only);
bool feeds (GraphVertex from, GraphVertex to) const;
/** @return the vertices that are directly fed from `r' */
std::set<GraphVertex> from (GraphVertex r) const;
/** @return all nodes that feed `r' (`r` is fed-by rv) */
std::set<GraphVertex> to (GraphVertex r, bool via_sends_only = false) const;
bool has_none_to (GraphVertex to) const;
bool empty () const;
void dump () const;
private:
void insert (EdgeMap& e, GraphVertex a, GraphVertex b);
typedef std::map<GraphVertex, std::set<GraphVertex> > EdgeMap;
typedef std::multimap<GraphVertex, std::pair<GraphVertex, bool> > EdgeMapWithSends;
void insert (EdgeMap& e, GraphVertex a, GraphVertex b);
EdgeMapWithSends::iterator find_in_from_to_with_sends (GraphVertex, GraphVertex);
EdgeMapWithSends::iterator find_recursively_in_from_to_with_sends (GraphVertex, GraphVertex);
EdgeMapWithSends::iterator find_in_to_from_with_sends (GraphVertex, GraphVertex);
EdgeMapWithSends::const_iterator find_recursively_in_from_to_with_sends (GraphVertex, GraphVertex) const;
/** map of edges with from as `first' and to as `second' */
EdgeMap _from_to;
/** map of the same edges with to as `first' and from as `second' */
EdgeMap _to_from;
/** map of edges with via-sends information; first part of the map is
the `from' vertex, second is the `to' vertex and a flag which is
true if the edge is via a send only.
*/
* the `from' vertex, second is the `to' vertex and a flag which is
* true if the edge is via a send only.
*/
EdgeMapWithSends _from_to_with_sends;
EdgeMapWithSends _to_from_with_sends;
};
boost::shared_ptr<RouteList> topological_sort (
boost::shared_ptr<RouteList>,
GraphEdges
);
bool topological_sort (GraphNodeList&, GraphEdges&);
}

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2010-2011 David Robillard <d@drobilla.net>
* Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2015-2022 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -65,13 +65,20 @@ public:
finish (chain);
}
private:
void finish (int chain);
void process ();
virtual std::string graph_node_name () const = 0;
virtual bool direct_feeds_according_to_reality (boost::shared_ptr<GraphNode>, bool* via_send_only = 0) = 0;
protected:
virtual void process () = 0;
boost::shared_ptr<Graph> _graph;
GATOMIC_QUAL gint _refcount;
private:
void finish (int chain);
GATOMIC_QUAL gint _refcount;
};
}
#endif

View File

@ -431,54 +431,41 @@ public:
int add_foldback_send (boost::shared_ptr<Route>, bool post_fader);
void remove_monitor_send ();
/**
* return true if this route feeds the first argument via at least one
* (arbitrarily long) signal pathway.
*/
bool feeds (boost::shared_ptr<Route>, bool* via_send_only = 0);
/**
* return true if this route feeds the first argument directly, via
* either its main outs or a send. This is checked by the actual
* connections, rather than by what the graph is currently doing.
*/
bool direct_feeds_according_to_reality (boost::shared_ptr<Route>, bool* via_send_only = 0);
bool direct_feeds_according_to_reality (boost::shared_ptr<GraphNode>, bool* via_send_only = 0);
std::string graph_node_name () const {
return name ();
}
/**
* return true if this route feeds the first argument directly, via
* @return true if this route feeds the first argument directly, via
* either its main outs or a send, according to the graph that
* is currently being processed.
*/
bool direct_feeds_according_to_graph (boost::shared_ptr<Route>, bool* via_send_only = 0);
bool feeds_according_to_graph (boost::shared_ptr<Route>);
/**
* @return true if this node feeds the first argument via at least one
* (arbitrarily long) signal pathway.
*/
bool feeds (boost::shared_ptr<Route>);
/**
* @return a list of all routes that eventually may feed a signal
* into this route.
*/
std::set<boost::shared_ptr<Route>> signal_sources (bool via_sends_only = false);
/** Follow output port connections and check if the output *port*
* of any downstream routes is connected.
*/
bool output_effectively_connected () const;
struct FeedRecord {
boost::weak_ptr<Route> r;
bool sends_only;
FeedRecord (boost::shared_ptr<Route> rp, bool sendsonly)
: r (rp)
, sends_only (sendsonly) {}
};
struct FeedRecordCompare {
bool operator() (const FeedRecord& a, const FeedRecord& b) const {
return a.r < b.r;
}
};
typedef std::set<FeedRecord,FeedRecordCompare> FedBy;
const FedBy& fed_by() const { return _fed_by; }
void clear_fed_by ();
bool add_fed_by (boost::shared_ptr<Route>, bool sends_only);
/* Controls (not all directly owned by the Route) */
boost::shared_ptr<AutomationControl> get_control (const Evoral::Parameter& param);
@ -604,6 +591,8 @@ public:
protected:
friend class Session;
void process ();
void catch_up_on_solo_mute_override ();
void set_listen (bool);
@ -684,7 +673,6 @@ protected:
std::string _comment;
bool _have_internal_generator;
DataType _default_type;
FedBy _fed_by;
InstrumentInfo _instrument_info;
bool _instrument_fanned_out;

View File

@ -88,7 +88,7 @@
#include "ardour/plugin.h"
#include "ardour/presentation_info.h"
#include "ardour/route.h"
#include "ardour/route_graph.h"
#include "ardour/graph_edges.h"
#include "ardour/transport_api.h"
#include "ardour/triggerbox.h"
@ -760,7 +760,6 @@ public:
void remove_route (boost::shared_ptr<Route>);
void resort_routes ();
void resort_routes_using (boost::shared_ptr<RouteList>);
AudioEngine & engine() { return _engine; }
AudioEngine const & engine () const { return _engine; }
@ -1903,8 +1902,6 @@ private:
/* routes stuff */
boost::shared_ptr<Graph> _process_graph;
SerializedRCUManager<RouteList> routes;
void add_routes (RouteList&, bool input_auto_connect, bool output_auto_connect, PresentationInfo::order_t);
@ -2252,10 +2249,15 @@ private:
void load_nested_sources (const XMLNode& node);
/** The directed graph of routes that is currently being used for audio processing
and solo/mute computations.
*/
* and solo/mute computations.
*/
GraphEdges _current_route_graph;
boost::shared_ptr<Graph> _process_graph;
void resort_routes_using (boost::shared_ptr<RouteList>);
bool rechain_process_graph (GraphNodeList&);
void ensure_route_presentation_info_gap (PresentationInfo::order_t, uint32_t gap_size);
friend class ProcessorChangeBlocker;

View File

@ -69,6 +69,7 @@ namespace ARDOUR {
class Source;
class AudioSource;
class GraphNode;
class Route;
class Region;
class Stripable;
@ -625,6 +626,7 @@ typedef std::list<samplepos_t> AnalysisFeatureList;
typedef std::vector<samplepos_t> XrunPositions;
typedef std::list<boost::shared_ptr<Route> > RouteList;
typedef std::list<boost::shared_ptr<GraphNode> > GraphNodeList;
typedef std::list<boost::shared_ptr<Stripable> > StripableList;
typedef std::list<boost::weak_ptr <Route> > WeakRouteList;
typedef std::list<boost::weak_ptr <Stripable> > WeakStripableList;

View File

@ -353,7 +353,7 @@ Graph::reached_terminal_node ()
* acyclic.
*/
void
Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const& edges)
Graph::rechain (GraphNodeList const& nodelist, GraphEdges const& edges)
{
Glib::Threads::Mutex::Lock ls (_swap_mutex);
@ -372,40 +372,38 @@ Graph::rechain (boost::shared_ptr<RouteList> routelist, GraphEdges const& edges)
_nodes_rt[chain].clear ();
/* Clear things out, and make _nodes_rt[chain] a copy of routelist */
for (RouteList::iterator ri = routelist->begin (); ri != routelist->end (); ri++) {
(*ri)->_init_refcount[chain] = 0;
(*ri)->_activation_set[chain].clear ();
_nodes_rt[chain].push_back (*ri);
/* Clear things out, and make _nodes_rt[chain] a copy of nodelist */
for (auto const& ri : nodelist) {
ri->_init_refcount[chain] = 0;
ri->_activation_set[chain].clear ();
_nodes_rt[chain].push_back (ri);
}
// now add refs for the connections.
for (node_list_t::iterator ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (*ni);
for (auto const& ni : _nodes_rt[chain]) {
/* The routes that are directly fed by r */
set<GraphVertex> fed_from_r = edges.from (r);
set<GraphVertex> fed_from_r = edges.from (ni);
/* Hence whether r has an output */
bool const has_output = !fed_from_r.empty ();
/* Set up r's activation set */
for (set<GraphVertex>::iterator i = fed_from_r.begin (); i != fed_from_r.end (); ++i) {
r->_activation_set[chain].insert (*i);
ni->_activation_set[chain].insert (*i);
}
/* r has an input if there are some incoming edges to r in the graph */
bool const has_input = !edges.has_none_to (r);
/* ni has an input if there are some incoming edges to r in the graph */
bool const has_input = !edges.has_none_to (ni);
/* Increment the refcount of any route that we directly feed */
for (node_set_t::iterator ai = r->_activation_set[chain].begin (); ai != r->_activation_set[chain].end (); ai++) {
(*ai)->_init_refcount[chain] += 1;
for (auto const& ai : ni->_activation_set[chain]) {
ai->_init_refcount[chain] += 1;
}
if (!has_input) {
/* no input, so this node needs to be triggered initially to get things going */
_init_trigger_list[chain].push_back (*ni);
_init_trigger_list[chain].push_back (ni);
}
if (!has_output) {
@ -574,17 +572,16 @@ Graph::dump (int chain) const
chain = _pending_chain;
DEBUG_TRACE (DEBUG::Graph, "--------------------------------------------Graph dump:\n");
for (ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
boost::shared_ptr<Route> rp = boost::dynamic_pointer_cast<Route> (*ni);
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", rp->name ().c_str (), (*ni)->_init_refcount[chain]));
for (ai = (*ni)->_activation_set[chain].begin (); ai != (*ni)->_activation_set[chain].end (); ai++) {
DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", boost::dynamic_pointer_cast<Route> (*ai)->name ().c_str ()));
for (auto const& ni : _nodes_rt[chain]) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", ni->graph_node_name (), ni->_init_refcount[chain]));
for (auto const& ai : ni->_activation_set[chain]) {
DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", ai->graph_node_name ()));
}
}
DEBUG_TRACE (DEBUG::Graph, "------------- trigger list:\n");
for (ni = _init_trigger_list[chain].begin (); ni != _init_trigger_list[chain].end (); ni++) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", boost::dynamic_pointer_cast<Route> (*ni)->name ().c_str (), (*ni)->_init_refcount[chain]));
for (auto const& ni : _init_trigger_list[chain]) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", ni->graph_node_name (), ni->_init_refcount[chain]));
}
DEBUG_TRACE (DEBUG::Graph, string_compose ("final activation refcount: %1\n", _n_terminal_nodes[chain]));
@ -604,21 +601,19 @@ Graph::plot (std::string const& file_name) const
ss << "digraph {\n";
ss << " node [shape = ellipse];\n";
for (ni = _nodes_rt[chain].begin (); ni != _nodes_rt[chain].end (); ni++) {
boost::shared_ptr<Route> sr = boost::dynamic_pointer_cast<Route> (*ni);
std::string sn = string_compose ("%1 (%2)", sr->name (), (*ni)->_init_refcount[chain]);
if ((*ni)->_init_refcount[chain] == 0 && (*ni)->_activation_set[chain].size() == 0) {
for (auto const& ni : _nodes_rt[chain]) {
std::string sn = string_compose ("%1 (%2)", ni->graph_node_name (), ni->_init_refcount[chain]);
if (ni->_init_refcount[chain] == 0 && ni->_activation_set[chain].size() == 0) {
ss << " \"" << sn << "\"[style=filled,fillcolor=gold1];\n";
} else if ((*ni)->_init_refcount[chain] == 0) {
} else if (ni->_init_refcount[chain] == 0) {
ss << " \"" << sn << "\"[style=filled,fillcolor=lightskyblue1];\n";
} else if ((*ni)->_activation_set[chain].size() == 0) {
} else if (ni->_activation_set[chain].size() == 0) {
ss << " \"" << sn << "\"[style=filled,fillcolor=aquamarine2];\n";
}
for (ai = (*ni)->_activation_set[chain].begin (); ai != (*ni)->_activation_set[chain].end (); ai++) {
boost::shared_ptr<Route> dr = boost::dynamic_pointer_cast<Route> (*ai);
std::string dn = string_compose ("%1 (%2)", dr->name (), (*ai)->_init_refcount[chain]);
for (auto const& ai : ni->_activation_set[chain]) {
std::string dn = string_compose ("%1 (%2)", ai->graph_node_name (), ai->_init_refcount[chain]);
bool sends_only = false;
sr->direct_feeds_according_to_reality (dr, &sends_only);
ni->direct_feeds_according_to_reality (ai, &sends_only);
if (sends_only) {
ss << " edge [style=dashed];\n";
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2012-2016 Paul Davis <paul@linuxaudiosystems.com>
* Copyright (C) 2015-2016 Robin Gareus <robin@gareus.org>
* Copyright (C) 2015-2022 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,8 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "ardour/graph_edges.h"
#include "ardour/route.h"
#include "ardour/route_graph.h"
#include "ardour/track.h"
#include "pbd/i18n.h"
@ -34,11 +34,16 @@ GraphEdges::add (GraphVertex from, GraphVertex to, bool via_sends_only)
EdgeMapWithSends::iterator i = find_in_from_to_with_sends (from, to);
if (i != _from_to_with_sends.end ()) {
i->second.second = via_sends_only;
i->second.second |= via_sends_only;
} else {
_from_to_with_sends.insert (
make_pair (from, make_pair (to, via_sends_only))
);
_from_to_with_sends.insert (make_pair (from, make_pair (to, via_sends_only)));
}
i = find_in_to_from_with_sends (to, from);
if (i != _to_from_with_sends.end ()) {
i->second.second |= via_sends_only;
} else {
_to_from_with_sends.insert (make_pair (to, make_pair (from, via_sends_only)));
}
}
@ -55,20 +60,32 @@ GraphEdges::find_in_from_to_with_sends (GraphVertex from, GraphVertex to)
return i;
}
}
return _from_to_with_sends.end ();
}
GraphEdges::EdgeMapWithSends::iterator
GraphEdges::find_recursively_in_from_to_with_sends (GraphVertex from, GraphVertex to)
GraphEdges::find_in_to_from_with_sends (GraphVertex to, GraphVertex from)
{
typedef EdgeMapWithSends::iterator Iter;
pair<Iter, Iter> r = _to_from_with_sends.equal_range (to);
for (Iter i = r.first; i != r.second; ++i) {
if (i->second.first == from) {
return i;
}
}
return _to_from_with_sends.end ();
}
GraphEdges::EdgeMapWithSends::const_iterator
GraphEdges::find_recursively_in_from_to_with_sends (GraphVertex from, GraphVertex to) const
{
typedef EdgeMapWithSends::const_iterator Iter;
pair<Iter, Iter> r = _from_to_with_sends.equal_range (from);
for (Iter i = r.first; i != r.second; ++i) {
if (i->second.first == to) {
return i;
}
GraphEdges::EdgeMapWithSends::iterator t = find_recursively_in_from_to_with_sends (i->second.first, to);
Iter t = find_recursively_in_from_to_with_sends (i->second.first, to);
if (t != _from_to_with_sends.end ()) {
return t;
}
@ -97,16 +114,15 @@ GraphEdges::has (GraphVertex from, GraphVertex to, bool* via_sends_only)
}
bool
GraphEdges::feeds (GraphVertex from, GraphVertex to)
GraphEdges::feeds (GraphVertex from, GraphVertex to) const
{
EdgeMapWithSends::iterator i = find_recursively_in_from_to_with_sends (from, to);
EdgeMapWithSends::const_iterator i = find_recursively_in_from_to_with_sends (from, to);
if (i == _from_to_with_sends.end ()) {
return false;
}
return true;
}
/** @return the vertices that are fed from `r' */
set<GraphVertex>
GraphEdges::from (GraphVertex r) const
{
@ -118,6 +134,26 @@ GraphEdges::from (GraphVertex r) const
return i->second;
}
set<GraphVertex>
GraphEdges::to (GraphVertex t, bool via_sends_only) const
{
set<GraphVertex> rv;
typedef EdgeMapWithSends::const_iterator Iter;
pair<Iter, Iter> r = _to_from_with_sends.equal_range (t);
for (Iter i = r.first; i != r.second; ++i) {
if (via_sends_only) {
if (!i->second.second) {
continue;
}
}
rv.insert (i->second.first);
for (auto const& j: GraphEdges::to (i->second.first, i->second.second ? false : via_sends_only)) {
rv.insert (j);
}
}
return rv;
}
void
GraphEdges::remove (GraphVertex from, GraphVertex to)
{
@ -160,18 +196,18 @@ GraphEdges::empty () const
void
GraphEdges::dump () const
{
for (EdgeMap::const_iterator i = _from_to.begin(); i != _from_to.end(); ++i) {
cout << "FROM: " << i->first->name() << " ";
for (set<GraphVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
cout << (*j)->name() << " ";
for (auto const& i : _from_to) {
cout << "FROM: " << i.first->graph_node_name () << " ";
for (auto const& j : i.second) {
cout << j->graph_node_name () << " ";
}
cout << "\n";
}
for (EdgeMap::const_iterator i = _to_from.begin(); i != _to_from.end(); ++i) {
cout << "TO: " << i->first->name() << " ";
for (set<GraphVertex>::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
cout << (*j)->name() << " ";
for (auto const& i : _to_from) {
cout << "TO: " << i.first->graph_node_name () << " ";
for (auto const& j : i.second) {
cout << j->graph_node_name () << " ";
}
cout << "\n";
}
@ -193,12 +229,15 @@ GraphEdges::insert (EdgeMap& e, GraphVertex a, GraphVertex b)
struct RouteRecEnabledComparator
{
bool operator () (GraphVertex r1, GraphVertex r2) const
bool operator () (GraphVertex n1, GraphVertex n2) const
{
boost::shared_ptr<Track> t1 (boost::dynamic_pointer_cast<Track>(r1));
boost::shared_ptr<Track> t2 (boost::dynamic_pointer_cast<Track>(r2));
PresentationInfo::order_t r1o = r1->presentation_info().order();
PresentationInfo::order_t r2o = r2->presentation_info().order();
boost::shared_ptr<Track> t1 (boost::dynamic_pointer_cast<Track>(n1));
boost::shared_ptr<Track> t2 (boost::dynamic_pointer_cast<Track>(n2));
boost::shared_ptr<Route> r1 (boost::dynamic_pointer_cast<Route>(n1));
boost::shared_ptr<Route> r2 (boost::dynamic_pointer_cast<Route>(n2));
PresentationInfo::order_t r1o = r1 ? r1->presentation_info().order() : 0;
PresentationInfo::order_t r2o = r2 ? r2->presentation_info().order() : 0;
if (!t1) {
if (!t2) {
@ -238,52 +277,70 @@ struct RouteRecEnabledComparator
/** Perform a topological sort of a list of routes using a directed graph representing connections.
* @return Sorted list of routes, or 0 if the graph contains cycles (feedback loops).
*/
boost::shared_ptr<RouteList>
ARDOUR::topological_sort (
boost::shared_ptr<RouteList> routes,
GraphEdges edges
)
bool
ARDOUR::topological_sort (GraphNodeList& nodes, GraphEdges& edges)
{
boost::shared_ptr<RouteList> sorted_routes (new RouteList);
/* Collect the edges of the graph. Each of these edges
* is a pair of nodes, one of which directly feeds the other
* either by a port connection or by an internal send.
*/
/* queue of routes to process */
RouteList queue;
for (auto const& i : nodes) {
for (auto const& j : nodes) {
bool via_sends_only = false;
/* See if this *j feeds *i according to the current state of
* port connections and internal sends.
*/
if (j->direct_feeds_according_to_reality (i, &via_sends_only)) {
/* add the edge to the graph (part #1) */
edges.add (j, i, via_sends_only);
}
}
}
GraphNodeList queue;
/* initial queue has routes that are not fed by anything */
for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
if (edges.has_none_to (*i)) {
queue.push_back (*i);
for (auto const& i : nodes) {
if (edges.has_none_to (i)) {
queue.push_back (i);
}
}
/* Sort the initial queue so that non-rec-enabled routes are run first.
This is so that routes can record things coming from other routes
via external connections.
*/
* This is so that routes can record things coming from other routes
* via external connections.
*/
queue.sort (RouteRecEnabledComparator ());
/* Do the sort: algorithm is Kahn's from Wikipedia.
`Topological sorting of large networks', Communications of the ACM 5(11):558-562.
*/
* `Topological sorting of large networks', Communications of the ACM 5(11):558-562.
*/
nodes.clear ();
GraphEdges remaining_edges (edges);
while (!queue.empty ()) {
GraphVertex r = queue.front ();
queue.pop_front ();
sorted_routes->push_back (r);
set<GraphVertex> e = edges.from (r);
nodes.push_back (r);
set<GraphVertex> e = remaining_edges.from (r);
for (set<GraphVertex>::iterator i = e.begin(); i != e.end(); ++i) {
edges.remove (r, *i);
if (edges.has_none_to (*i)) {
remaining_edges.remove (r, *i);
if (remaining_edges.has_none_to (*i)) {
queue.push_back (*i);
}
}
}
if (!edges.empty ()) {
edges.dump ();
if (!remaining_edges.empty ()) {
remaining_edges.dump ();
/* There are cycles in the graph, so we can't do a topological sort */
return boost::shared_ptr<RouteList> ();
return false;
}
return sorted_routes;
return true;
}

View File

@ -1,7 +1,7 @@
/*
* Copyright (C) 2010-2011 David Robillard <d@drobilla.net>
* Copyright (C) 2011 Carl Hetherington <carl@carlh.net>
* Copyright (C) 2017-2019 Robin Gareus <robin@gareus.org>
* Copyright (C) 2017-2022 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -74,9 +74,3 @@ GraphNode::finish (int chain)
_graph->reached_terminal_node ();
}
}
void
GraphNode::process ()
{
_graph->process_one_route (dynamic_cast<Route*> (this));
}

View File

@ -65,6 +65,7 @@
#include "ardour/disk_writer.h"
#include "ardour/event_type_map.h"
#include "ardour/gain_control.h"
#include "ardour/graph.h"
#include "ardour/internal_return.h"
#include "ardour/internal_send.h"
#include "ardour/meter.h"
@ -697,6 +698,12 @@ Route::monitor_run (samplepos_t start_sample, samplepos_t end_sample, pframes_t
run_route (start_sample, end_sample, nframes, true, false);
}
void
Route::process ()
{
_graph->process_one_route (this);
}
void
Route::run_route (samplepos_t start_sample, samplepos_t end_sample, pframes_t nframes, bool gain_automation_ok, bool run_disk_reader)
{
@ -780,10 +787,10 @@ void
Route::push_solo_upstream (int delta)
{
DEBUG_TRACE (DEBUG::Solo, string_compose("\t ... INVERT push from %1\n", _name));
for (FedBy::iterator i = _fed_by.begin(); i != _fed_by.end(); ++i) {
boost::shared_ptr<Route> sr (i->r.lock());
if (sr) {
sr->solo_control()->mod_solo_by_others_downstream (-delta);
for (auto const& i : _session._current_route_graph.to (boost::dynamic_pointer_cast<Route> (shared_from_this ()))) {
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (i);
if (r) {
r->solo_control()->mod_solo_by_others_downstream (-delta);
}
}
}
@ -3525,52 +3532,6 @@ Route::set_comment (string cmt, void *src)
_session.set_dirty ();
}
bool
Route::add_fed_by (boost::shared_ptr<Route> other, bool via_sends_only)
{
FeedRecord fr (other, via_sends_only);
pair<FedBy::iterator,bool> result = _fed_by.insert (fr);
if (!result.second) {
/* already a record for "other" - make sure sends-only information is correct */
if (!via_sends_only && result.first->sends_only) {
FeedRecord* frp = const_cast<FeedRecord*>(&(*result.first));
frp->sends_only = false;
}
}
return result.second;
}
void
Route::clear_fed_by ()
{
_fed_by.clear ();
}
bool
Route::feeds (boost::shared_ptr<Route> other, bool* via_sends_only)
{
const FedBy& fed_by (other->fed_by());
for (FedBy::const_iterator f = fed_by.begin(); f != fed_by.end(); ++f) {
boost::shared_ptr<Route> sr = f->r.lock();
if (sr && (sr.get() == this)) {
if (via_sends_only) {
*via_sends_only = f->sends_only;
}
return true;
}
}
return false;
}
IOVector
Route::all_inputs () const
{
@ -3614,8 +3575,11 @@ Route::all_outputs () const
}
bool
Route::direct_feeds_according_to_reality (boost::shared_ptr<Route> other, bool* via_send_only)
Route::direct_feeds_according_to_reality (boost::shared_ptr<GraphNode> node, bool* via_send_only)
{
boost::shared_ptr<Route> other (boost::dynamic_pointer_cast<Route> (node));
assert (other);
DEBUG_TRACE (DEBUG::Graph, string_compose ("Feeds from %1 (-> %2)?\n", _name, other->name()));
if (other->all_inputs().fed_by (_output)) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\tdirect FEEDS to %1\n", other->name()));
@ -3667,11 +3631,24 @@ Route::direct_feeds_according_to_graph (boost::shared_ptr<Route> other, bool* vi
}
bool
Route::feeds_according_to_graph (boost::shared_ptr<Route> other)
Route::feeds (boost::shared_ptr<Route> other)
{
return _session._current_route_graph.feeds (boost::dynamic_pointer_cast<Route> (shared_from_this ()), other);
}
std::set<boost::shared_ptr<Route>>
Route::signal_sources (bool via_sends_only)
{
std::set<boost::shared_ptr<Route>> rv;
for (auto const& i : _session._current_route_graph.to (boost::dynamic_pointer_cast<Route> (shared_from_this ()), via_sends_only)) {
boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (i);
if (r) {
rv.insert (r);
}
}
return rv;
}
bool
Route::output_effectively_connected () const
{

View File

@ -101,7 +101,6 @@
#include "ardour/region.h"
#include "ardour/region_factory.h"
#include "ardour/revision.h"
#include "ardour/route_graph.h"
#include "ardour/route_group.h"
#include "ardour/rt_tasklist.h"
#include "ardour/rt_safe_delete.h"
@ -2134,56 +2133,6 @@ Session::set_block_size (pframes_t nframes)
}
static void
trace_terminal (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> rbase, bool sends_only)
{
boost::shared_ptr<Route> r2;
if (r1->feeds (rbase) && rbase->feeds (r1)) {
info << string_compose(_("feedback loop setup between %1 and %2"), r1->name(), rbase->name()) << endmsg;
return;
}
/* make a copy of the existing list of routes that feed r1 */
Route::FedBy existing (r1->fed_by());
/* for each route that feeds r1, recurse, marking it as feeding
rbase as well.
*/
for (Route::FedBy::iterator i = existing.begin(); i != existing.end(); ++i) {
if (!(r2 = i->r.lock ())) {
/* (*i) went away, ignore it */
continue;
}
/* r2 is a route that feeds r1 which somehow feeds base. mark
base as being fed by r2
*/
rbase->add_fed_by (r2, i->sends_only || sends_only);
if (r2 != rbase) {
/* 2nd level feedback loop detection. if r1 feeds or is fed by r2,
stop here.
*/
if (r1->feeds (r2) && r2->feeds (r1)) {
continue;
}
/* now recurse, so that we can mark base as being fed by
all routes that feed r2
*/
trace_terminal (r2, rbase, i->sends_only || sends_only);
}
}
}
void
Session::resort_routes ()
{
@ -2212,22 +2161,16 @@ Session::resort_routes ()
#ifndef NDEBUG
if (DEBUG_ENABLED(DEBUG::Graph)) {
boost::shared_ptr<RouteList> rl = routes.reader ();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("%1 fed by ...\n", (*i)->name()));
const Route::FedBy& fb ((*i)->fed_by());
for (Route::FedBy::const_iterator f = fb.begin(); f != fb.end(); ++f) {
boost::shared_ptr<Route> sf = f->r.lock();
if (sf) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (sends only ? %2)\n", sf->name(), f->sends_only));
}
DEBUG_TRACE (DEBUG::Graph, "---- Session::resort_routes ----\n");
for (auto const& i : *routes.reader ()) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("%1 fed by ...\n", i->name()));
for (auto const& f : i->signal_sources ()) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1\n", f->graph_node_name ()));
}
}
DEBUG_TRACE (DEBUG::Graph, "---- EOF ----\n");
}
#endif
}
/** This is called whenever we need to rebuild the graph of how we will process
@ -2238,91 +2181,61 @@ Session::resort_routes ()
void
Session::resort_routes_using (boost::shared_ptr<RouteList> r)
{
/* We are going to build a directed graph of our routes;
this is where the edges of that graph are put.
*/
GraphNodeList gnl;
for (auto const& rt : *r) {
gnl.push_back (rt);
}
GraphEdges edges;
/* Go through all routes doing two things:
*
* 1. Collect the edges of the route graph. Each of these edges
* is a pair of routes, one of which directly feeds the other
* either by a JACK connection or by an internal send.
*
* 2. Begin the process of making routes aware of which other
* routes directly or indirectly feed them. This information
* is used by the solo code.
*/
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
/* Clear out the route's list of direct or indirect feeds */
(*i)->clear_fed_by ();
for (RouteList::iterator j = r->begin(); j != r->end(); ++j) {
bool via_sends_only = false;
/* See if this *j feeds *i according to the current state of the JACK
connections and internal sends.
*/
if ((*j)->direct_feeds_according_to_reality (*i, &via_sends_only)) {
/* add the edge to the graph (part #1) */
edges.add (*j, *i, via_sends_only);
/* tell the route (for part #2) */
(*i)->add_fed_by (*j, via_sends_only);
}
if (rechain_process_graph (gnl)) {
r->clear ();
for (auto const& nd : gnl) {
r->push_back (boost::dynamic_pointer_cast<Route> (nd));
}
}
/* Attempt a topological sort of the route graph */
boost::shared_ptr<RouteList> sorted_routes = topological_sort (r, edges);
#ifndef NDEBUG
DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
for (auto const& i : *r) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 (presentation order %2)\n", i->name(), i->presentation_info().order()));
}
#endif
}
if (sorted_routes) {
bool
Session::rechain_process_graph (GraphNodeList& g)
{
/* This may be called from the GUI thread (concurrrently with processing),
* when a user adds/removes routes.
*
* Or it may be called from the engine when connections are changed.
* In that case processing is blocked until the graph change is handled.
*/
GraphEdges edges;
if (topological_sort (g, edges)) {
/* We got a satisfactory topological sort, so there is no feedback;
use this new graph.
Note: the process graph rechain does not require a
topologically-sorted list, but hey ho.
*/
* use this new graph.
*
* Note: the process graph chain does not require a
* topologically-sorted list, but hey ho.
*/
if (_process_graph) {
_process_graph->rechain (sorted_routes, edges);
_process_graph->rechain (g, edges);
}
_current_route_graph = edges;
/* Complete the building of the routes' lists of what directly
or indirectly feeds them.
*/
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
trace_terminal (*i, *i, false);
}
*r = *sorted_routes;
#ifndef NDEBUG
DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 presentation order %2\n", (*i)->name(), (*i)->presentation_info().order()));
}
#endif
SuccessfulGraphSort (); /* EMIT SIGNAL */
} else {
/* The topological sort failed, so we have a problem. Tell everyone
and stick to the old graph; this will continue to be processed, so
until the feedback is fixed, what is played back will not quite
reflect what is actually connected. Note also that we do not
do trace_terminal here, as it would fail due to an endless recursion,
so the solo code will think that everything is still connected
as it was before.
*/
FeedbackDetected (); /* EMIT SIGNAL */
return true;
}
/* The topological sort failed, so we have a problem. Tell everyone
* and stick to the old graph; this will continue to be processed, so
* until the feedback is fixed, what is played back will not quite
* reflect what is actually connected.
*/
FeedbackDetected (); /* EMIT SIGNAL */
return false;
}
/** Find a route name starting with \a base, maybe followed by the
@ -5412,6 +5325,10 @@ Session::graph_reordered (bool called_from_backend)
/* force all diskstreams to update their capture offset values to
* reflect any changes in latencies within the graph.
*
* XXX: Is this required? When the graph-order callback
* is initiated by the backend, it is always followed by
* a latency callback.
*/
update_latency_compensation (true, called_from_backend);
}

View File

@ -106,6 +106,7 @@ libardour_sources = [
'globals.cc',
'graph.cc',
'graphnode.cc',
'graph_edges.cc',
'iec1ppmdsp.cc',
'iec2ppmdsp.cc',
'import.cc',
@ -203,7 +204,6 @@ libardour_sources = [
'return.cc',
'reverse.cc',
'route.cc',
'route_graph.cc',
'route_group.cc',
'route_group_member.cc',
'rb_effect.cc',