From 47a9c01a68f6d40f6835d3507367277ea8edd5de Mon Sep 17 00:00:00 2001 From: joq Date: Thu, 9 Dec 2004 02:30:07 +0000 Subject: [PATCH] [0.99.27] Simon's graph sorting patches git-svn-id: svn+ssh://jackaudio.org/trunk/jack@831 0c269be4-1314-0410-8aa9-9f06e86f4224 --- configure.ac | 2 +- jack/engine.h | 1 + jack/internal.h | 5 +- jackd/clientengine.c | 9 +- jackd/engine.c | 500 ++++++++++++++++++++++++++----------------- libjack/client.c | 2 +- 6 files changed, 316 insertions(+), 203 deletions(-) diff --git a/configure.ac b/configure.ac index 2b62dc0..f2c817a 100644 --- a/configure.ac +++ b/configure.ac @@ -15,7 +15,7 @@ dnl changes are made dnl --- JACK_MAJOR_VERSION=0 JACK_MINOR_VERSION=99 -JACK_MICRO_VERSION=26 +JACK_MICRO_VERSION=27 dnl --- dnl HOWTO: updating the jack protocol version diff --git a/jack/engine.h b/jack/engine.h index 0a26d13..5241561 100644 --- a/jack/engine.h +++ b/jack/engine.h @@ -114,6 +114,7 @@ struct _jack_engine { char temporary; int reordered; int watchdog_check; + int feedbackcount; pid_t wait_pid; pthread_t freewheel_thread; diff --git a/jack/internal.h b/jack/internal.h index 428910b..5f32f16 100644 --- a/jack/internal.h +++ b/jack/internal.h @@ -361,7 +361,10 @@ typedef struct _jack_client_internal { int subgraph_start_fd; int subgraph_wait_fd; JSList *ports; /* protected by engine->client_lock */ - JSList *fed_by; /* protected by engine->client_lock */ + JSList *truefeeds; /* protected by engine->client_lock */ + JSList *sortfeeds; /* protected by engine->client_lock */ + int fedcount; + int tfedcount; jack_shm_info_t control_shm; unsigned long execution_order; struct _jack_client_internal *next_client; /* not a linked list! */ diff --git a/jackd/clientengine.c b/jackd/clientengine.c index ac113b2..0a99a81 100644 --- a/jackd/clientengine.c +++ b/jackd/clientengine.c @@ -57,8 +57,10 @@ jack_client_disconnect_ports (jack_engine_t *engine, } jack_slist_free (client->ports); - jack_slist_free (client->fed_by); - client->fed_by = 0; + jack_slist_free (client->truefeeds); + jack_slist_free (client->sortfeeds); + client->truefeeds = 0; + client->sortfeeds = 0; client->ports = 0; } @@ -422,7 +424,8 @@ jack_setup_client_control (jack_engine_t *engine, int fd, client->request_fd = fd; client->event_fd = -1; client->ports = 0; - client->fed_by = 0; + client->truefeeds = 0; + client->sortfeeds = 0; client->execution_order = UINT_MAX; client->next_client = NULL; client->handle = NULL; diff --git a/jackd/engine.c b/jackd/engine.c index 87f42a5..9957203 100644 --- a/jackd/engine.c +++ b/jackd/engine.c @@ -66,7 +66,9 @@ typedef struct { jack_port_internal_t *source; jack_port_internal_t *destination; - + signed int dir; /* -1 = feedback, 0 = self, 1 = forward */ + jack_client_internal_t *srcclient; + jack_client_internal_t *dstclient; } jack_connection_internal_t; typedef struct _jack_driver_info { @@ -96,8 +98,7 @@ static int jack_do_get_port_connections (jack_engine_t *engine, jack_request_t *req, int reply_fd); static int jack_port_disconnect_internal (jack_engine_t *engine, jack_port_internal_t *src, - jack_port_internal_t *dst, - int sort_graph); + jack_port_internal_t *dst); static int jack_send_connection_notification (jack_engine_t *, jack_client_id_t, jack_port_id_t, @@ -116,6 +117,12 @@ static void jack_engine_driver_exit (jack_engine_t* engine); static int jack_start_freewheeling (jack_engine_t* engine); static int jack_stop_freewheeling (jack_engine_t* engine); static int jack_start_watchdog (jack_engine_t *engine); +static int jack_client_feeds_transitive (jack_client_internal_t *source, + jack_client_internal_t *dest); +static int jack_client_sort (jack_client_internal_t *a, + jack_client_internal_t *b); +static void jack_check_acyclic (jack_engine_t* engine); + static inline int jack_rolling_interval (jack_time_t period_usecs) @@ -1520,6 +1527,7 @@ jack_engine_new (int realtime, int rtpriority, int do_mlock, int do_unlock, engine->server_name = server_name; engine->temporary = temporary; engine->freewheeling = 0; + engine->feedbackcount = 0; engine->wait_pid = wait_pid; jack_engine_reset_rolling_usecs (engine); @@ -2100,8 +2108,7 @@ jack_port_clear_connections (jack_engine_t *engine, engine, ((jack_connection_internal_t *) node->data)->source, ((jack_connection_internal_t *) - node->data)->destination, - FALSE); + node->data)->destination); node = next; } @@ -2393,136 +2400,6 @@ jack_rechain_graph (jack_engine_t *engine) return err; } -static void -jack_trace_terminal (jack_client_internal_t *c1, jack_client_internal_t *rbase) -{ - jack_client_internal_t *c2; - - /* make a copy of the existing list of routes that feed - c1. this provides us with an atomic snapshot of c1's - "fed-by" state, which will be modified as we progress ... - */ - JSList *existing; - JSList *node; - - if (c1->fed_by == NULL) { - return; - } - - existing = jack_slist_copy (c1->fed_by); - - /* for each route that feeds c1, recurse, marking it as - feeding rbase as well. - */ - for (node = existing; node; node = jack_slist_next (node)) { - - c2 = (jack_client_internal_t *) node->data; - - /* c2 is a route that feeds c1 which somehow feeds - base. mark base as being fed by c2, but don't do it - more than once. - */ - if (c2 != rbase && c2 != c1) { - - if (jack_slist_find (rbase->fed_by, c2) == NULL) { - rbase->fed_by = - jack_slist_prepend (rbase->fed_by, c2); - } - - /* FIXME: if c2->fed_by is not up-to-date, we - may end up recursing infinitely - (kaiv) - */ - - if (jack_slist_find (c2->fed_by, c1) == NULL) { - /* now recurse, so that we can mark - base as being fed by all routes - that feed c2 - */ - jack_trace_terminal (c2, rbase); - } - } - } - - jack_slist_free (existing); -} - -static int -jack_client_sort (jack_client_internal_t *a, jack_client_internal_t *b) - -{ - if (jack_slist_find (a->fed_by, b)) { - - if (jack_slist_find (b->fed_by, a)) { - - /* feedback loop: if `a' is the driver - client, let that execute first. - */ - - if (a->control->type == ClientDriver) { - /* b comes after a */ - return -1; - } - } - - /* a comes after b */ - return 1; - - } else if (jack_slist_find (b->fed_by, a)) { - - if (jack_slist_find (a->fed_by, b)) { - - /* feedback loop: if `b' is the driver - client, let that execute first. - */ - - if (b->control->type == ClientDriver) { - /* b comes before a */ - return 1; - } - } - - /* b comes after a */ - return -1; - } else { - /* we don't care */ - return 0; - } -} - -static int -jack_client_feeds (jack_client_internal_t *might, - jack_client_internal_t *target) -{ - JSList *pnode, *cnode; - - /* Check every port of `might' for an outbound connection to - * `target' */ - for (pnode = might->ports; pnode; pnode = jack_slist_next (pnode)) { - - jack_port_internal_t *port; - - port = (jack_port_internal_t *) pnode->data; - - for (cnode = port->connections; cnode; - cnode = jack_slist_next (cnode)) { - - jack_connection_internal_t *c; - - c = (jack_connection_internal_t *) cnode->data; - - if (c->source->shared->client_id - == might->control->id && - c->destination->shared->client_id - == target->control->id) { - return 1; - } - } - } - - return 0; -} - static jack_nframes_t jack_get_port_total_latency (jack_engine_t *engine, jack_port_internal_t *port, int hop_count, @@ -2623,68 +2500,191 @@ jack_compute_all_port_total_latencies (jack_engine_t *engine) } } -/** - * Sorts the network of clients using the following - * algorithm: +/* How the sort works: * - * 1) figure out who is connected to whom: - * - * foreach client1 - * foreach input port - * foreach client2 - * foreach output port - * if client1->input port connected to client2->output port - * mark client1 fed by client 2 + * Each client has a "sortfeeds" list of clients indicating which clients + * it should be considered as feeding for the purposes of sorting the + * graph. This list differs from the clients it /actually/ feeds in the + * following ways: * - * 2) trace the connections as terminal arcs in the graph so that - * if client A feeds client B who feeds client C, mark client C - * as fed by client A as well as client B, and so forth. + * 1. Connections from a client to itself are disregarded * - * 3) now sort according to whether or not client1->fed_by (client2) is true. - * if the condition is true, client2 must execute before client1 + * 2. Connections to a driver client are disregarded * - */ - + * 3. If a connection from A to B is a feedback connection (ie there was + * already a path from B to A when the connection was made) then instead + * of B appearing on A's sortfeeds list, A will appear on B's sortfeeds + * list. + * + * If client A is on client B's sortfeeds list, client A must come after + * client B in the execution order. The above 3 rules ensure that the + * sortfeeds relation is always acyclic so that all ordering constraints + * can actually be met. + * + * Each client also has a "truefeeds" list which is the same as sortfeeds + * except that feedback connections appear normally instead of reversed. + * This is used to detect whether the graph has become acyclic. + * + */ + void jack_sort_graph (jack_engine_t *engine) { - JSList *node, *onode; - jack_client_internal_t *client; - jack_client_internal_t *oclient; - /* called, obviously, must hold engine->client_lock */ - for (node = engine->clients; node; node = jack_slist_next (node)) { + engine->clients = jack_slist_sort (engine->clients, + (JCompareFunc) jack_client_sort); + jack_compute_all_port_total_latencies (engine); + jack_rechain_graph (engine); +} - client = (jack_client_internal_t *) node->data; +static int +jack_client_sort (jack_client_internal_t *a, jack_client_internal_t *b) +{ + /* drivers are forced to the front, ie considered as sources + rather than sinks for purposes of the sort */ - jack_slist_free (client->fed_by); - client->fed_by = 0; + if (jack_client_feeds_transitive (a, b) || + (a->control->type == ClientDriver && + b->control->type != ClientDriver)) { + return -1; + } else if (jack_client_feeds_transitive (b, a) || + (b->control->type == ClientDriver && + a->control->type != ClientDriver)) { + return 1; + } else { + return 0; + } +} - for (onode = engine->clients; onode; - onode = jack_slist_next (onode)) { - - oclient = (jack_client_internal_t *) onode->data; +/* transitive closure of the relation expressed by the sortfeeds lists. */ +static int +jack_client_feeds_transitive (jack_client_internal_t *source, + jack_client_internal_t *dest ) +{ + jack_client_internal_t *med; + JSList *node; + + if (jack_slist_find (source->sortfeeds, dest)) { + return 1; + } - if (jack_client_feeds (oclient, client)) { - client->fed_by = - jack_slist_prepend (client->fed_by, - oclient); - } + for (node = source->sortfeeds; node; node = jack_slist_next (node)) { + + med = (jack_client_internal_t *) node->data; + + if (jack_client_feeds_transitive (med, dest)) { + return 1; } } - for (node = engine->clients; node; node = jack_slist_next (node)) { - jack_trace_terminal ((jack_client_internal_t *) node->data, - (jack_client_internal_t *) node->data); + return 0; +} + +/** + * Checks whether the graph has become acyclic and if so modifies client + * sortfeeds lists to turn leftover feedback connections into normal ones. + * This lowers latency, but at the expense of some data corruption. + */ +static void +jack_check_acyclic (jack_engine_t *engine) +{ + JSList *srcnode, *dstnode, *portnode, *connnode; + jack_client_internal_t *src, *dst; + jack_port_internal_t *port; + jack_connection_internal_t *conn; + int stuck; + int unsortedclients = 0; + + VERBOSE (engine, "checking for graph become acyclic\n"); + + for (srcnode = engine->clients; srcnode; + srcnode = jack_slist_next (srcnode)) { + + src = (jack_client_internal_t *) srcnode->data; + src->tfedcount = src->fedcount; + unsortedclients++; } + + stuck = FALSE; - engine->clients = jack_slist_sort (engine->clients, - (JCompareFunc) jack_client_sort); + /* find out whether a normal sort would have been possible */ + while (unsortedclients && !stuck) { + + stuck = TRUE; - jack_compute_all_port_total_latencies (engine); + for (srcnode = engine->clients; srcnode; + srcnode = jack_slist_next (srcnode)) { - jack_rechain_graph (engine); + src = (jack_client_internal_t *) srcnode->data; + + if (!src->tfedcount) { + + stuck = FALSE; + unsortedclients--; + src->tfedcount = -1; + + for (dstnode = src->truefeeds; dstnode; + dstnode = jack_slist_next (dstnode)) { + + dst = (jack_client_internal_t *) + dstnode->data; + dst->tfedcount--; + } + } + } + } + + if (stuck) { + + VERBOSE (engine, "graph is still cyclic\n" ); + } else { + + VERBOSE (engine, "graph has become acyclic\n"); + + /* turn feedback connections around in sortfeeds */ + for (srcnode = engine->clients; srcnode; + srcnode = jack_slist_next (srcnode)) { + + src = (jack_client_internal_t *) srcnode->data; + + for (portnode = src->ports; portnode; + portnode = jack_slist_next (portnode)) { + + port = (jack_port_internal_t *) portnode->data; + + for (connnode = port->connections; connnode; + connnode = jack_slist_next (connnode)) { + + conn = (jack_connection_internal_t*) + connnode->data; + + if (conn->dir == -1 ) + + /*&& + conn->srcclient == src) */{ + + VERBOSE (engine, + "reversing connection from " + "%s to %s\n", + conn->srcclient->control->name, + conn->dstclient->control->name); + conn->dir = 1; + conn->dstclient->sortfeeds = + jack_slist_remove + (conn->dstclient->sortfeeds, + conn->srcclient); + + conn->srcclient->sortfeeds = + jack_slist_prepend + (conn->srcclient->sortfeeds, + conn->dstclient ); + } + } + } + } + engine->feedbackcount = 0; + } } /** @@ -2710,13 +2710,12 @@ void jack_dump_configuration(jack_engine_t *engine, int take_lock) client = (jack_client_internal_t *) clientnode->data; ctl = client->control; - fprintf (stderr, "client #%d: %s (type: %d, process? %s, fed" - " by %d clients) start=%d wait=%d\n", + fprintf (stderr, "client #%d: %s (type: %d, process? %s," + " start=%d wait=%d\n", ++n, ctl->name, ctl->type, ctl->process ? "yes" : "no", - jack_slist_length(client->fed_by), client->subgraph_start_fd, client->subgraph_wait_fd); @@ -2761,7 +2760,7 @@ jack_port_do_connect (jack_engine_t *engine, jack_connection_internal_t *connection; jack_port_internal_t *srcport, *dstport; jack_port_id_t src_id, dst_id; - jack_client_internal_t *client; + jack_client_internal_t *srcclient, *dstclient; JSList *it; if ((srcport = jack_get_port_by_name (engine, source_port)) == NULL) { @@ -2809,7 +2808,7 @@ jack_port_do_connect (jack_engine_t *engine, return -1; } - if ((client = jack_client_internal_by_id (engine, + if ((srcclient = jack_client_internal_by_id (engine, srcport->shared->client_id)) == 0) { jack_error ("unknown client set as owner of port - " @@ -2817,13 +2816,13 @@ jack_port_do_connect (jack_engine_t *engine, return -1; } - if (!client->control->active) { + if (!srcclient->control->active) { jack_error ("cannot connect ports owned by inactive clients;" - " \"%s\" is not active", client->control->name); + " \"%s\" is not active", srcclient->control->name); return -1; } - if ((client = jack_client_internal_by_id (engine, + if ((dstclient = jack_client_internal_by_id (engine, dstport->shared->client_id)) == 0) { jack_error ("unknown client set as owner of port - cannot " @@ -2831,9 +2830,9 @@ jack_port_do_connect (jack_engine_t *engine, return -1; } - if (!client->control->active) { + if (!dstclient->control->active) { jack_error ("cannot connect ports owned by inactive clients;" - " \"%s\" is not active", client->control->name); + " \"%s\" is not active", dstclient->control->name); return -1; } @@ -2849,6 +2848,8 @@ jack_port_do_connect (jack_engine_t *engine, connection->source = srcport; connection->destination = dstport; + connection->srcclient = srcclient; + connection->dstclient = dstclient; src_id = srcport->shared->id; dst_id = dstport->shared->id; @@ -2864,9 +2865,78 @@ jack_port_do_connect (jack_engine_t *engine, jack_unlock_graph (engine); return -1; } else { - VERBOSE (engine, "connect %s and %s\n", - srcport->shared->name, - dstport->shared->name); + + if (dstclient->control->type == ClientDriver) + { + /* Ignore output connections to drivers for purposes + of sorting. Drivers are executed first in the sort + order anyway, and we don't want to treat graphs + such as driver -> client -> driver as containing + feedback */ + + VERBOSE (engine, + "connect %s and %s (output)\n", + srcport->shared->name, + dstport->shared->name); + + connection->dir = 1; + + } + else if (srcclient != dstclient) { + + srcclient->truefeeds = jack_slist_prepend + (srcclient->truefeeds, dstclient); + + dstclient->fedcount++; + + if (jack_client_feeds_transitive (dstclient, + srcclient ) || + (dstclient->control->type == ClientDriver && + srcclient->control->type != ClientDriver)) { + + /* dest is running before source so + this is a feedback connection */ + + VERBOSE (engine, + "connect %s and %s (feedback)\n", + srcport->shared->name, + dstport->shared->name); + + dstclient->sortfeeds = jack_slist_prepend + (dstclient->sortfeeds, srcclient); + + connection->dir = -1; + engine->feedbackcount++; + VERBOSE (engine, + "feedback count up to %d\n", + engine->feedbackcount); + + } else { + + /* this is not a feedback connection */ + + VERBOSE (engine, + "connect %s and %s (forward)\n", + srcport->shared->name, + dstport->shared->name); + + srcclient->sortfeeds = jack_slist_prepend + (srcclient->sortfeeds, dstclient); + + connection->dir = 1; + } + } + else + { + /* this is a connection to self */ + + VERBOSE (engine, + "connect %s and %s (self)\n", + srcport->shared->name, + dstport->shared->name); + + connection->dir = 0; + } dstport->connections = jack_slist_prepend (dstport->connections, connection); @@ -2892,14 +2962,14 @@ jack_port_do_connect (jack_engine_t *engine, int jack_port_disconnect_internal (jack_engine_t *engine, jack_port_internal_t *srcport, - jack_port_internal_t *dstport, - int sort_graph) + jack_port_internal_t *dstport ) { JSList *node; jack_connection_internal_t *connect; int ret = -1; jack_port_id_t src_id, dst_id; + int check_acyclic = engine->feedbackcount; /* call tree **** MUST HOLD **** engine->client_lock. */ for (node = srcport->connections; node; @@ -2943,15 +3013,51 @@ jack_port_disconnect_internal (jack_engine_t *engine, engine, dstport->shared->client_id, dst_id, src_id, FALSE); + if (connect->dir) { + + jack_client_internal_t *src; + jack_client_internal_t *dst; + + src = jack_client_internal_by_id + (engine, srcport->shared->client_id); + + dst = jack_client_internal_by_id + (engine, dstport->shared->client_id); + + src->truefeeds = jack_slist_remove + (src->truefeeds, dst); + + dst->fedcount--; + + if (connect->dir == 1) { + /* normal connection: remove dest from + source's sortfeeds list */ + src->sortfeeds = jack_slist_remove + (src->sortfeeds, dst); + } else { + /* feedback connection: remove source + from dest's sortfeeds list */ + dst->sortfeeds = jack_slist_remove + (dst->sortfeeds, src); + engine->feedbackcount--; + VERBOSE (engine, + "feedback count down to %d\n", + engine->feedbackcount); + + } + } /* else self-connection: do nothing */ + free (connect); ret = 0; break; } } - if (sort_graph) { - jack_sort_graph (engine); + if (check_acyclic) { + jack_check_acyclic (engine); } + + jack_sort_graph (engine); return ret; } @@ -2993,14 +3099,14 @@ jack_port_do_disconnect (jack_engine_t *engine, if ((dstport = jack_get_port_by_name (engine, destination_port)) == NULL) { - jack_error ("unknown destination port in attempted connection" - " [%s]", destination_port); + jack_error ("unknown destination port in attempted" + " disconnection [%s]", destination_port); return -1; } jack_lock_graph (engine); - ret = jack_port_disconnect_internal (engine, srcport, dstport, TRUE); + ret = jack_port_disconnect_internal (engine, srcport, dstport); jack_unlock_graph (engine); diff --git a/libjack/client.c b/libjack/client.c index 78bd797..5524502 100644 --- a/libjack/client.c +++ b/libjack/client.c @@ -452,8 +452,8 @@ _start_server (void) good = 1; } } + if (!good) { -#define JACK_LOCATION #if defined(USE_CAPABILITIES) command = JACK_LOCATION "/jackstart"; strncpy(arguments, JACK_LOCATION "/jackstart -T -R -d "