Do a topological sort of the route list before passing it to

the graph, as the graph's feedback detection algorithm
depends on the input route list being sorted in such a way.
 Fixes #3924.


git-svn-id: svn://localhost/ardour2/branches/3.0@10471 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
Carl Hetherington 2011-11-07 16:01:46 +00:00
parent b7f195d375
commit ae94dfda5f
4 changed files with 260 additions and 59 deletions

View File

@ -0,0 +1,56 @@
/*
Copyright (C) 2011 Paul Davis
Author: Carl Hetherington <cth@carlh.net>
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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <map>
#include <set>
namespace ARDOUR {
/** A list of edges for a directed acyclic graph for routes */
class DAGEdges
{
public:
typedef std::map<boost::shared_ptr<Route>, std::set<boost::shared_ptr<Route> > > EdgeMap;
void add (boost::shared_ptr<Route> from, boost::shared_ptr<Route> to);
std::set<boost::shared_ptr<Route> > from (boost::shared_ptr<Route> r) const;
void remove (boost::shared_ptr<Route> from, boost::shared_ptr<Route> to);
bool has_none_to (boost::shared_ptr<Route> to) const;
bool empty () const;
void dump () const;
private:
void insert (EdgeMap& e, boost::shared_ptr<Route> a, boost::shared_ptr<Route> b);
/* Keep a map in both directions to speed lookups */
/** 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;
};
boost::shared_ptr<RouteList> topographical_sort (
boost::shared_ptr<RouteList>,
DAGEdges
);
}

193
libs/ardour/route_dag.cc Normal file
View File

@ -0,0 +1,193 @@
/*
Copyright (C) 2011 Paul Davis
Author: Carl Hetherington <cth@carlh.net>
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
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "ardour/route.h"
#include "ardour/route_dag.h"
#include "i18n.h"
using namespace std;
using namespace ARDOUR;
void
DAGEdges::add (boost::shared_ptr<Route> from, boost::shared_ptr<Route> to)
{
insert (_from_to, from, to);
insert (_to_from, to, from);
EdgeMap::iterator i = _from_to.find (from);
if (i != _from_to.end ()) {
i->second.insert (to);
} else {
set<boost::shared_ptr<Route> > v;
v.insert (to);
_from_to.insert (make_pair (from, v));
}
}
set<boost::shared_ptr<Route> >
DAGEdges::from (boost::shared_ptr<Route> r) const
{
EdgeMap::const_iterator i = _from_to.find (r);
if (i == _from_to.end ()) {
return set<boost::shared_ptr<Route> > ();
}
return i->second;
}
void
DAGEdges::remove (boost::shared_ptr<Route> from, boost::shared_ptr<Route> to)
{
EdgeMap::iterator i = _from_to.find (from);
assert (i != _from_to.end ());
i->second.erase (to);
if (i->second.empty ()) {
_from_to.erase (i);
}
EdgeMap::iterator j = _to_from.find (to);
assert (j != _to_from.end ());
j->second.erase (from);
if (j->second.empty ()) {
_to_from.erase (j);
}
}
/** @param to `To' route.
* @return true if there are no edges going to `to'.
*/
bool
DAGEdges::has_none_to (boost::shared_ptr<Route> to) const
{
return _to_from.find (to) == _to_from.end ();
}
bool
DAGEdges::empty () const
{
assert (_from_to.empty () == _to_from.empty ());
return _from_to.empty ();
}
void
DAGEdges::dump () const
{
for (EdgeMap::const_iterator i = _from_to.begin(); i != _from_to.end(); ++i) {
cout << "FROM: " << i->first->name() << " ";
for (set<boost::shared_ptr<Route> >::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
cout << (*j)->name() << " ";
}
cout << "\n";
}
for (EdgeMap::const_iterator i = _to_from.begin(); i != _to_from.end(); ++i) {
cout << "TO: " << i->first->name() << " ";
for (set<boost::shared_ptr<Route> >::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
cout << (*j)->name() << " ";
}
cout << "\n";
}
}
void
DAGEdges::insert (EdgeMap& e, boost::shared_ptr<Route> a, boost::shared_ptr<Route> b)
{
EdgeMap::iterator i = e.find (a);
if (i != e.end ()) {
i->second.insert (b);
} else {
set<boost::shared_ptr<Route> > v;
v.insert (b);
e.insert (make_pair (a, v));
}
}
struct RouteRecEnabledComparator
{
bool operator () (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> r2) const
{
if (r1->record_enabled()) {
if (r2->record_enabled()) {
/* both rec-enabled, just use signal order */
return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
} else {
/* r1 rec-enabled, r2 not rec-enabled, run r2 early */
return false;
}
} else {
if (r2->record_enabled()) {
/* r2 rec-enabled, r1 not rec-enabled, run r1 early */
return true;
} else {
/* neither rec-enabled, use signal order */
return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
}
}
}
};
boost::shared_ptr<RouteList>
ARDOUR::topographical_sort (
boost::shared_ptr<RouteList> routes,
DAGEdges edges
)
{
boost::shared_ptr<RouteList> sorted_routes (new RouteList);
/* queue of routes to process */
RouteList queue;
/* initial queue has routes that are not fed by anything */
for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
if ((*i)->not_fed ()) {
queue.push_back (*i);
}
}
/* Sort the initial queue so that non-rec-enabled routes are run first */
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.
*/
while (!queue.empty ()) {
boost::shared_ptr<Route> r = queue.front ();
queue.pop_front ();
sorted_routes->push_back (r);
set<boost::shared_ptr<Route> > e = edges.from (r);
for (set<boost::shared_ptr<Route> >::iterator i = e.begin(); i != e.end(); ++i) {
edges.remove (r, *i);
if (edges.has_none_to (*i)) {
queue.push_back (*i);
}
}
}
if (!edges.empty ()) {
cout << "Feedback detected.\n";
}
return sorted_routes;
}

View File

@ -86,6 +86,7 @@
#include "ardour/recent_sessions.h"
#include "ardour/region_factory.h"
#include "ardour/return.h"
#include "ardour/route_dag.h"
#include "ardour/route_group.h"
#include "ardour/send.h"
#include "ardour/session.h"
@ -1222,56 +1223,6 @@ Session::set_block_size (pframes_t nframes)
}
}
struct RouteSorter {
/** @return true to run r1 before r2, otherwise false */
bool sort_by_rec_enabled (const boost::shared_ptr<Route>& r1, const boost::shared_ptr<Route>& r2) {
if (r1->record_enabled()) {
if (r2->record_enabled()) {
/* both rec-enabled, just use signal order */
return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
} else {
/* r1 rec-enabled, r2 not rec-enabled, run r2 early */
return false;
}
} else {
if (r2->record_enabled()) {
/* r2 rec-enabled, r1 not rec-enabled, run r1 early */
return true;
} else {
/* neither rec-enabled, use signal order */
return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
}
}
}
bool operator() (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> r2) {
if (r2->feeds (r1)) {
/* r1 fed by r2; run r2 early */
return false;
} else if (r1->feeds (r2)) {
/* r2 fed by r1; run r1 early */
return true;
} else {
if (r1->not_fed ()) {
if (r2->not_fed ()) {
/* no ardour-based connections inbound to either route. */
return sort_by_rec_enabled (r1, r2);
} else {
/* r2 has connections, r1 does not; run r1 early */
return true;
}
} else {
if (r2->not_fed()) {
/* r1 has connections, r2 does not; run r2 early */
return false;
} else {
/* both r1 and r2 have connections, but not to each other. just use signal order */
return r1->order_key(N_("signal")) < r2->order_key(N_("signal"));
}
}
}
}
};
static void
trace_terminal (boost::shared_ptr<Route> r1, boost::shared_ptr<Route> rbase)
@ -1361,16 +1312,17 @@ Session::resort_routes ()
#endif
}
void
Session::resort_routes_using (boost::shared_ptr<RouteList> r)
{
RouteList::iterator i, j;
DAGEdges edges;
for (i = r->begin(); i != r->end(); ++i) {
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
(*i)->clear_fed_by ();
for (j = r->begin(); j != r->end(); ++j) {
for (RouteList::iterator j = r->begin(); j != r->end(); ++j) {
/* although routes can feed themselves, it will
cause an endless recursive descent if we
@ -1385,23 +1337,22 @@ Session::resort_routes_using (boost::shared_ptr<RouteList> r)
bool via_sends_only;
if ((*j)->direct_feeds (*i, &via_sends_only)) {
edges.add (*j, *i);
(*i)->add_fed_by (*j, via_sends_only);
}
}
}
for (i = r->begin(); i != r->end(); ++i) {
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
trace_terminal (*i, *i);
}
RouteSorter cmp;
r->sort (cmp);
route_graph->rechain (r);
boost::shared_ptr<RouteList> sorted_routes = topographical_sort (r, edges);
route_graph->rechain (sorted_routes);
#ifndef NDEBUG
DEBUG_TRACE (DEBUG::Graph, "Routes resorted, order follows:\n");
for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
for (RouteList::iterator i = sorted_routes->begin(); i != sorted_routes->end(); ++i) {
DEBUG_TRACE (DEBUG::Graph, string_compose ("\t%1 signal order %2\n",
(*i)->name(), (*i)->order_key ("signal")));
}

View File

@ -170,6 +170,7 @@ libardour_sources = [
'return.cc',
'reverse.cc',
'route.cc',
'route_dag.cc',
'route_group.cc',
'route_group_member.cc',
'rb_effect.cc',