Fix endless recursion when creating cyclic connection graphs

This is mainly relevant for Mixbus, which allows cyclic-connections
to record Master-out on a Track (which unconditionally has Mixbus send
and feeds master).

In Ardour it may also cause issues when creating loopback connections,
however latency is not usually updated with invalid graphs (old
process graph remains in use).

Otherwise it fixes a crash connecting Track 1 -> Track 2 -> Track 1.

This also optimizes Route::output_effectively_connected by caching
any prior lookup. This helps e.g. Track 1 -> Track 2 -> Master.
The connection "Track 2 -> Master" now only need to be looked up once.

See also 7958031287 and a556e96ed0
This commit is contained in:
Robin Gareus 2022-04-23 14:53:56 +02:00
parent b416a4f168
commit 95693f9364
Signed by: rgareus
GPG Key ID: A090BCE02CF57F04
2 changed files with 29 additions and 5 deletions

View File

@ -744,6 +744,9 @@ private:
bool input_port_count_changing (ChanCount);
bool output_port_count_changing (ChanCount);
bool output_effectively_connected_real () const;
mutable std::map<Route*, bool> _connection_cache;
int configure_processors_unlocked (ProcessorStreams*, Glib::Threads::RWLock::WriterLock*);
bool set_meter_point_unlocked ();
void apply_processor_order (const ProcessorList& new_order);

View File

@ -3674,6 +3674,13 @@ Route::feeds_according_to_graph (boost::shared_ptr<Route> other)
bool
Route::output_effectively_connected () const
{
_connection_cache.clear ();
return output_effectively_connected_real ();
}
bool
Route::output_effectively_connected_real () const
{
if (!_output->connected ()) {
return false;
@ -3687,15 +3694,29 @@ Route::output_effectively_connected () const
return true;
}
/* now follow connections downstream */
boost::shared_ptr<RouteList> routes = _session.get_routes ();
for (RouteList::iterator i = routes->begin(); i != routes->end(); ++i) {
if ((*i).get() == this) {
Route* rp = (*i).get();
if (rp == this) {
continue;
}
if ((*i)->input()->connected_to (_output)) {
if ((*i)->output_effectively_connected ()) {
return true;
}
if (!(*i)->input()->connected_to (_output)) {
continue;
}
if (_connection_cache.find (rp) != _connection_cache.end ()) {
return _connection_cache[rp];
}
/* First mark node a traversed to prevent endless recursion.
* Othewise graph loops A -> B -> A will cause a stack overflow.
*/
_connection_cache[rp] = false;
/* recurse downstream, check connected route */
bool rv = (*i)->output_effectively_connected_real ();
_connection_cache[rp] = rv;
if (rv) {
return true;
}
}
return false;