diff --git a/libs/ardour/ardour/graph.h b/libs/ardour/ardour/graph.h index 6558d4209e..d7798f4cd7 100644 --- a/libs/ardour/ardour/graph.h +++ b/libs/ardour/ardour/graph.h @@ -30,9 +30,9 @@ #include +#include "pbd/g_atomic_compat.h" #include "pbd/mpmc_queue.h" #include "pbd/semutils.h" -#include "pbd/g_atomic_compat.h" #include "ardour/audio_backend.h" #include "ardour/libardour_visibility.h" @@ -53,31 +53,38 @@ typedef boost::shared_ptr node_ptr_t; typedef std::list node_list_t; typedef std::set node_set_t; +struct GraphChain { + GraphChain (GraphNodeList const&, GraphEdges const&); + ~GraphChain (); + void dump () const; + bool plot (std::string const&) const; + + node_list_t _nodes_rt; + /** Nodes that are not fed by any other nodes */ + node_list_t _init_trigger_list; + /** The number of nodes that do not feed any other node */ + int _n_terminal_nodes; +}; + class LIBARDOUR_API Graph : public SessionHandleRef { public: Graph (Session& session); - void trigger (GraphNode* n); - void rechain (GraphNodeList const&, GraphEdges const&); - bool plot (std::string const& file_name) const; + /* public API for use by session-process */ + int process_routes (boost::shared_ptr chain, pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool& need_butler); + int routes_no_roll (boost::shared_ptr chain, pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool non_rt_pending); - void plot (int chain); + bool in_process_thread () const; + uint32_t n_threads () const; + + /* called by GraphNode */ + void trigger (GraphNode* n); void reached_terminal_node (); - void helper_thread (); - - int process_routes (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool& need_butler); - - int routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool non_rt_pending); - + /* called by virtual GraphNode::process() */ void process_one_route (Route* route); - void clear_other_chain (); - void swap_process_chain (); - - bool in_process_thread () const; - protected: virtual void session_going_away (); @@ -87,10 +94,8 @@ private: void run_one (); void main_thread (); void prep (); - void dump (int chain) const; - node_list_t _nodes_rt[2]; - node_list_t _init_trigger_list[2]; + void helper_thread (); PBD::MPMCQueue _trigger_queue; ///< nodes that can be processed GATOMIC_QUAL guint _trigger_queue_size; ///< number of entries in trigger-queue @@ -108,9 +113,7 @@ private: /** The number of unprocessed nodes that do not feed any other node; updated during processing */ GATOMIC_QUAL guint _terminal_refcnt; - /** The initial number of nodes that do not feed any other node (for each chain) */ - guint _n_terminal_nodes[2]; - bool _graph_empty; + bool _graph_empty; /* number of background worker threads >= 0 */ GATOMIC_QUAL guint _n_workers; @@ -118,13 +121,8 @@ private: /* flag to terminate background threads */ GATOMIC_QUAL gint _terminate; - /* chain swapping */ - Glib::Threads::Cond _cleanup_cond; - mutable Glib::Threads::Mutex _swap_mutex; - - GATOMIC_QUAL gint _current_chain; - GATOMIC_QUAL gint _pending_chain; - GATOMIC_QUAL gint _setup_chain; + /* graph chain */ + GraphChain const* _graph_chain; /* parameter caches */ pframes_t _process_nframes; @@ -142,6 +140,6 @@ private: void engine_stopped (); }; -} // namespace +} // namespace ARDOUR #endif /* __ardour_graph_h__ */ diff --git a/libs/ardour/ardour/graphnode.h b/libs/ardour/ardour/graphnode.h index 171269fe56..673fce1d4c 100644 --- a/libs/ardour/ardour/graphnode.h +++ b/libs/ardour/ardour/graphnode.h @@ -22,17 +22,21 @@ #define __ardour_graphnode_h__ #include +#include #include -#include #include #include "pbd/g_atomic_compat.h" +#include "pbd/rcu.h" + +#include "ardour/libardour_visibility.h" namespace ARDOUR { class Graph; class GraphNode; +struct GraphChain; typedef boost::shared_ptr node_ptr_t; typedef std::set node_set_t; @@ -40,12 +44,23 @@ typedef std::list node_list_t; class LIBARDOUR_API GraphActivision { +public: + GraphActivision (); + virtual ~GraphActivision () {} + + typedef std::map ActivationMap; + typedef std::map RefCntMap; + + node_set_t const& activation_set (GraphChain const* const g) const; + int init_refcount (GraphChain const* const g) const; + protected: - friend class Graph; + friend struct GraphChain; + /** Nodes that we directly feed */ - node_set_t _activation_set[2]; + SerializedRCUManager _activation_set; /** The number of nodes that we directly feed us (one count for each chain) */ - gint _init_refcount[2]; + SerializedRCUManager _init_refcount; }; /** A node on our processing graph, ie a Route */ @@ -53,19 +68,15 @@ class LIBARDOUR_API GraphNode : public GraphActivision { public: GraphNode (boost::shared_ptr Graph); - virtual ~GraphNode (); - void prep (int chain); + /* API used by Graph */ + void prep (GraphChain const*); void trigger (); + void run (GraphChain const* chain); - void - run (int chain) - { - process (); - finish (chain); - } - + /* API used to sort Nodes and create GraphChain */ virtual std::string graph_node_name () const = 0; + virtual bool direct_feeds_according_to_reality (boost::shared_ptr, bool* via_send_only = 0) = 0; protected: @@ -74,11 +85,11 @@ protected: boost::shared_ptr _graph; private: - void finish (int chain); + void finish (GraphChain const*); GATOMIC_QUAL gint _refcount; }; -} +} // namespace ARDOUR #endif diff --git a/libs/ardour/ardour/session.h b/libs/ardour/ardour/session.h index 8cc72d0932..a6b77d66c7 100644 --- a/libs/ardour/ardour/session.h +++ b/libs/ardour/ardour/session.h @@ -141,6 +141,7 @@ class CoreSelection; class ExportHandler; class ExportStatus; class Graph; +struct GraphChain; class IO; class IOProcessor; class ImportStatus; @@ -2253,7 +2254,8 @@ private: */ GraphEdges _current_route_graph; - boost::shared_ptr _process_graph; + boost::shared_ptr _process_graph; + boost::shared_ptr _graph_chain; void resort_routes_using (boost::shared_ptr); bool rechain_process_graph (GraphNodeList&); diff --git a/libs/ardour/graph.cc b/libs/ardour/graph.cc index 48dffa32d9..b4f4614946 100644 --- a/libs/ardour/graph.cc +++ b/libs/ardour/graph.cc @@ -65,9 +65,7 @@ Graph::Graph (Session& session) , _callback_start_sem ("graph_start", 0) , _callback_done_sem ("graph_done", 0) , _graph_empty (true) - , _current_chain (0) - , _pending_chain (0) - , _setup_chain (1) + , _graph_chain (0) { g_atomic_int_set (&_terminal_refcnt, 0); g_atomic_int_set (&_terminate, 0); @@ -75,9 +73,6 @@ Graph::Graph (Session& session) g_atomic_int_set (&_idle_thread_cnt, 0); g_atomic_int_set (&_trigger_queue_size, 0); - _n_terminal_nodes[0] = 0; - _n_terminal_nodes[1] = 0; - /* pre-allocate memory */ _trigger_queue.reserve (1024); @@ -111,9 +106,6 @@ Graph::reset_thread_list () uint32_t num_threads = how_many_dsp_threads (); guint n_workers = g_atomic_uint_get (&_n_workers); - /* For now, we shouldn't be using the graph code if we only have 1 DSP thread */ - assert (num_threads > 1); - /* don't bother doing anything here if we already have the right * number of threads. */ @@ -146,25 +138,26 @@ Graph::reset_thread_list () } } +uint32_t +Graph::n_threads () const +{ + return 1 + g_atomic_uint_get (&_n_workers); +} + void Graph::session_going_away () { drop_threads (); - // now drop all references on the nodes. - _nodes_rt[0].clear (); - _nodes_rt[1].clear (); - _init_trigger_list[0].clear (); - _init_trigger_list[1].clear (); + /* now drop all references on the nodes. */ g_atomic_int_set (&_trigger_queue_size, 0); _trigger_queue.clear (); + _graph_chain = 0; } void Graph::drop_threads () { - Glib::Threads::Mutex::Lock ls (_swap_mutex); - /* Flag threads to terminate */ g_atomic_int_set (&_terminate, 1); @@ -203,90 +196,27 @@ Graph::drop_threads () #endif } -/* special case route removal -- called from Session::remove_routes */ -void -Graph::clear_other_chain () -{ - Glib::Threads::Mutex::Lock ls (_swap_mutex); - - while (1) { - if (_setup_chain != _pending_chain) { - for (node_list_t::iterator ni = _nodes_rt[_setup_chain].begin (); ni != _nodes_rt[_setup_chain].end (); ++ni) { - (*ni)->_activation_set[_setup_chain].clear (); - } - - _nodes_rt[_setup_chain].clear (); - _init_trigger_list[_setup_chain].clear (); - break; - } - /* setup chain == pending chain - we have - * to wait till this is no longer true. - */ - _cleanup_cond.wait (_swap_mutex); - } -} - -void -Graph::swap_process_chain () -{ - /* Intended to be called Session::process_audition. - * Must not be called while the graph is processing. - */ - if (_swap_mutex.trylock ()) { - /* swap mutex acquired */ - if (_current_chain != _pending_chain) { - DEBUG_TRACE (DEBUG::Graph, string_compose ("Graph::swap_process_chain = %1)\n", _pending_chain)); - /* use new chain */ - _setup_chain = _current_chain; - _current_chain = _pending_chain; - printf ("Graph::swap to %d\n", _current_chain); - _trigger_queue.clear (); - /* ensure that all nodes can be queued */ - _trigger_queue.reserve (_nodes_rt[_current_chain].size ()); - g_atomic_int_set (&_trigger_queue_size, 0); - _cleanup_cond.signal (); - } - _swap_mutex.unlock (); - } -} - void Graph::prep () { - if (_swap_mutex.trylock ()) { - /* swap mutex acquired */ - if (_current_chain != _pending_chain) { - /* use new chain */ - DEBUG_TRACE (DEBUG::Graph, string_compose ("Graph::prep chain = %1)\n", _pending_chain)); - _setup_chain = _current_chain; - _current_chain = _pending_chain; - /* ensure that all nodes can be queued */ - _trigger_queue.reserve (_nodes_rt[_current_chain].size ()); - assert (g_atomic_uint_get (&_trigger_queue_size) == 0); - _cleanup_cond.signal (); - } - _swap_mutex.unlock (); - } - + assert (_graph_chain); _graph_empty = true; - int chain = _current_chain; - node_list_t::iterator i; - for (i = _nodes_rt[chain].begin (); i != _nodes_rt[chain].end (); ++i) { - (*i)->prep (chain); + for (auto const& i : _graph_chain->_nodes_rt) { + i->prep (_graph_chain); _graph_empty = false; } assert (g_atomic_uint_get (&_trigger_queue_size) == 0); - assert (_graph_empty != (_n_terminal_nodes[chain] > 0)); + assert (_graph_empty != (_graph_chain->_n_terminal_nodes > 0)); - g_atomic_int_set (&_terminal_refcnt, _n_terminal_nodes[chain]); + g_atomic_int_set (&_terminal_refcnt, _graph_chain->_n_terminal_nodes); /* Trigger the initial nodes for processing, which are the ones at the `input' end */ - for (i = _init_trigger_list[chain].begin (); i != _init_trigger_list[chain].end (); i++) { + for (auto const& i : _graph_chain->_init_trigger_list) { g_atomic_int_inc (&_trigger_queue_size); - _trigger_queue.push_back (i->get ()); + _trigger_queue.push_back (i.get ()); } } @@ -339,7 +269,7 @@ Graph::reached_terminal_node () * - Reset terminal reference count * - queue initial nodes */ - prep (); + prep (); // XXX if (_graph_empty && !g_atomic_int_get (&_terminate)) { goto again; @@ -348,76 +278,6 @@ Graph::reached_terminal_node () } } -/** Rechain our stuff using a list of routes (which can be in any order) and - * a directed graph of their interconnections, which is guaranteed to be - * acyclic. - */ -void -Graph::rechain (GraphNodeList const& nodelist, GraphEdges const& edges) -{ - Glib::Threads::Mutex::Lock ls (_swap_mutex); - - int chain = _setup_chain; - DEBUG_TRACE (DEBUG::Graph, string_compose ("============== setup %1 (current = %2 pending = %3) thread %4\n", chain, _current_chain, _pending_chain, pthread_name())); - - /* This will become the number of nodes that do not feed any other node; - * once we have processed this number of those nodes, we have finished. - */ - _n_terminal_nodes[chain] = 0; - - /* This will become a list of nodes that are not fed by another node, ie - * those at the `input' end. - */ - _init_trigger_list[chain].clear (); - - _nodes_rt[chain].clear (); - - /* 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 (auto const& ni : _nodes_rt[chain]) { - /* The routes that are directly fed by r */ - set 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::iterator i = fed_from_r.begin (); i != fed_from_r.end (); ++i) { - ni->_activation_set[chain].insert (*i); - } - - /* 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 (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); - } - - if (!has_output) { - /* no output, so this is one of the nodes that we can count off to decide - * if we've finished - */ - _n_terminal_nodes[chain] += 1; - } - } - - _pending_chain = chain; - dump (chain); -} - /** Called by both the main thread and all helpers. */ void Graph::run_one () @@ -470,7 +330,7 @@ Graph::run_one () /* Process the graph-node */ g_atomic_int_dec_and_test (&_trigger_queue_size); - to_run->run (_current_chain); + to_run->run (_graph_chain); DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("%1 has finished run_one()\n", pthread_name ())); } @@ -562,82 +422,8 @@ again: delete (pt); } -void -Graph::dump (int chain) const -{ -#ifndef NDEBUG - node_list_t::const_iterator ni; - node_set_t::const_iterator ai; - - chain = _pending_chain; - - DEBUG_TRACE (DEBUG::Graph, "--------------------------------------------Graph dump:\n"); - 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 (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])); -#endif -} - -bool -Graph::plot (std::string const& file_name) const -{ - Glib::Threads::Mutex::Lock ls (_swap_mutex); - int chain = _current_chain; - - node_list_t::const_iterator ni; - node_set_t::const_iterator ai; - stringstream ss; - - ss << "digraph {\n"; - ss << " node [shape = ellipse];\n"; - - 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) { - ss << " \"" << sn << "\"[style=filled,fillcolor=lightskyblue1];\n"; - } else if (ni->_activation_set[chain].size() == 0) { - ss << " \"" << sn << "\"[style=filled,fillcolor=aquamarine2];\n"; - } - 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; - ni->direct_feeds_according_to_reality (ai, &sends_only); - if (sends_only) { - ss << " edge [style=dashed];\n"; - } - ss << " \"" << sn << "\" -> \"" << dn << "\"\n"; - if (sends_only) { - ss << " edge [style=solid];\n"; - } - } - } - ss << "}\n"; - - GError *err = NULL; - if (!g_file_set_contents (file_name.c_str(), ss.str().c_str(), -1, &err)) { - if (err) { - error << string_compose (_("Could not graph to file (%1)"), err->message) << endmsg; - g_error_free (err); - } - return false; - } - return true; -} - int -Graph::process_routes (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool& need_butler) +Graph::process_routes (boost::shared_ptr chain, pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool& need_butler) { DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("graph execution from %1 to %2 = %3\n", start_sample, end_sample, nframes)); @@ -645,6 +431,7 @@ Graph::process_routes (pframes_t nframes, samplepos_t start_sample, samplepos_t return 0; } + _graph_chain = chain.get (); _process_nframes = nframes; _process_start_sample = start_sample; _process_end_sample = end_sample; @@ -664,7 +451,7 @@ Graph::process_routes (pframes_t nframes, samplepos_t start_sample, samplepos_t } int -Graph::routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool non_rt_pending) +Graph::routes_no_roll (boost::shared_ptr chain, pframes_t nframes, samplepos_t start_sample, samplepos_t end_sample, bool non_rt_pending) { DEBUG_TRACE (DEBUG::ProcessThreads, string_compose ("no-roll graph execution from %1 to %2 = %3\n", start_sample, end_sample, nframes)); @@ -672,6 +459,7 @@ Graph::routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t return 0; } + _graph_chain = chain.get (); _process_nframes = nframes; _process_start_sample = start_sample; _process_end_sample = end_sample; @@ -688,6 +476,7 @@ Graph::routes_no_roll (pframes_t nframes, samplepos_t start_sample, samplepos_t return _process_retval; } + void Graph::process_one_route (Route* route) { @@ -709,7 +498,7 @@ Graph::process_one_route (Route* route) } if (need_butler) { - _process_need_butler = true; + _process_need_butler = true; // -> atomic } } @@ -718,3 +507,151 @@ Graph::in_process_thread () const { return AudioEngine::instance ()->in_process_thread (); } + +/* ****************************************************************************/ + +GraphChain::GraphChain (GraphNodeList const& nodelist, GraphEdges const& edges) +{ + DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphChain constructed in thread:%1\n", pthread_name ())); + /* This will become the number of nodes that do not feed any other node; + * once we have processed this number of those nodes, we have finished. + */ + _n_terminal_nodes = 0; + + /* This will become a list of nodes that are not fed by another node, ie + * those at the `input' end. + */ + _init_trigger_list.clear (); + + _nodes_rt.clear (); + + /* copy nodelist to _nodes_rt, prepare GraphNodes for this graph */ + for (auto const& ri : nodelist) { + RCUWriter wa (ri->_activation_set); + RCUWriter wr (ri->_init_refcount); + boost::shared_ptr ma (wa.get_copy ()); + boost::shared_ptr mr (wr.get_copy ()); + (*mr)[this] = 0; + (*ma)[this].clear (); + _nodes_rt.push_back (ri); + } + + /* now add refs for the connections. */ + for (auto const& ni : _nodes_rt) { + /* The nodes that are directly fed by ni */ + set fed_from_r = edges.from (ni); + + /* Hence whether ni has an output, or is otherwise a terminal node */ + bool const has_output = !fed_from_r.empty (); + + /* Set up ni's activation set */ + if (has_output) { + boost::shared_ptr m (ni->_activation_set.reader ()); + for (auto const& i : fed_from_r) { + auto it = (*m)[this].insert (i); + assert (it.second); + + /* Increment the refcount of any node that we directly feed */ + boost::shared_ptr a ((*it.first)->_init_refcount.reader ()); + (*a)[this] += 1; + } + } + + /* ni has an input if there are some incoming edges to r in the graph */ + bool const has_input = !edges.has_none_to (ni); + + if (!has_input) { + /* no input, so this node needs to be triggered initially to get things going */ + _init_trigger_list.push_back (ni); + } + + if (!has_output) { + /* no output, so this is one of the nodes that we can count off to decide + * if we've finished + */ + _n_terminal_nodes += 1; + } + } + dump (); +} + +GraphChain::~GraphChain () +{ + /* clear chain */ + DEBUG_TRACE (DEBUG::Graph, string_compose ("~GraphChain destroyed in thread:%1\n", pthread_name ())); + for (auto const& ni : _nodes_rt) { + RCUWriter wa (ni->_activation_set); + RCUWriter wr (ni->_init_refcount); + boost::shared_ptr ma (wa.get_copy ()); + boost::shared_ptr mr (wr.get_copy ()); + mr->erase (this); + ma->erase (this); + } +} + +bool +GraphChain::plot (std::string const& file_name) const +{ + node_list_t::const_iterator ni; + node_set_t::const_iterator ai; + stringstream ss; + + ss << "digraph {\n"; + ss << " node [shape = ellipse];\n"; + + for (auto const& ni : _nodes_rt) { + std::string sn = string_compose ("%1 (%2)", ni->graph_node_name (), ni->init_refcount (this)); + if (ni->init_refcount (this) == 0 && ni->activation_set (this).size () == 0) { + ss << " \"" << sn << "\"[style=filled,fillcolor=gold1];\n"; + } else if (ni->init_refcount (this) == 0) { + ss << " \"" << sn << "\"[style=filled,fillcolor=lightskyblue1];\n"; + } else if (ni->activation_set (this).size () == 0) { + ss << " \"" << sn << "\"[style=filled,fillcolor=aquamarine2];\n"; + } + for (auto const& ai : ni->activation_set (this)) { + std::string dn = string_compose ("%1 (%2)", ai->graph_node_name (), ai->init_refcount (this)); + bool sends_only = false; + ni->direct_feeds_according_to_reality (ai, &sends_only); + if (sends_only) { + ss << " edge [style=dashed];\n"; + } + ss << " \"" << sn << "\" -> \"" << dn << "\"\n"; + if (sends_only) { + ss << " edge [style=solid];\n"; + } + } + } + ss << "}\n"; + + GError* err = NULL; + if (!g_file_set_contents (file_name.c_str (), ss.str ().c_str (), -1, &err)) { + if (err) { + error << string_compose (_("Could not graph to file (%1)"), err->message) << endmsg; + g_error_free (err); + } + return false; + } + return true; +} + +void +GraphChain::dump () const +{ +#ifndef NDEBUG + DEBUG_TRACE (DEBUG::Graph, "--8<-- Graph dump ----------------------------\n"); + for (auto const& ni : _nodes_rt) { + DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", ni->graph_node_name (), ni->init_refcount (this))); + for (auto const& ai : ni->activation_set (this)) { + DEBUG_TRACE (DEBUG::Graph, string_compose (" triggers: %1\n", ai->graph_node_name ())); + } + } + + DEBUG_TRACE (DEBUG::Graph, " --- trigger list ---\n"); + for (auto const& ni : _init_trigger_list) { + DEBUG_TRACE (DEBUG::Graph, string_compose ("GraphNode: %1 refcount: %2\n", ni->graph_node_name (), ni->init_refcount (this))); + } + + DEBUG_TRACE (DEBUG::Graph, string_compose ("final activation refcount: %1\n", _n_terminal_nodes)); + DEBUG_TRACE (DEBUG::Graph, "-->8-- END Graph dump ------------------------\n"); +#endif +} diff --git a/libs/ardour/graphnode.cc b/libs/ardour/graphnode.cc index 146616b0f9..8d8885956c 100644 --- a/libs/ardour/graphnode.cc +++ b/libs/ardour/graphnode.cc @@ -18,27 +18,52 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "ardour/graph.h" #include "ardour/graphnode.h" +#include "ardour/graph.h" #include "ardour/route.h" using namespace ARDOUR; +GraphActivision::GraphActivision () + : _activation_set (new ActivationMap) + , _init_refcount (new RefCntMap) +{ +} + +node_set_t const& +GraphActivision::activation_set (GraphChain const* const g) const +{ + boost::shared_ptr m (_activation_set.reader ()); + return m->at (g); +} + +int +GraphActivision::init_refcount (GraphChain const* const g) const +{ + boost::shared_ptr m (_init_refcount.reader ()); + return m->at (g); +} + +/* ****************************************************************************/ + GraphNode::GraphNode (boost::shared_ptr graph) : _graph (graph) { g_atomic_int_set (&_refcount, 0); } -GraphNode::~GraphNode () +void +GraphNode::prep (GraphChain const* chain) { + /* This is the number of nodes that directly feed us */ + g_atomic_int_set (&_refcount, init_refcount (chain)); } void -GraphNode::prep (int chain) +GraphNode::run (GraphChain const* chain) { - /* This is the number of nodes that directly feed us */ - g_atomic_int_set (&_refcount, _init_refcount[chain]); + process (); + finish (chain); } /** Called by an upstream node, when it has completed processing */ @@ -57,14 +82,14 @@ GraphNode::trigger () } void -GraphNode::finish (int chain) +GraphNode::finish (GraphChain const* chain) { node_set_t::iterator i; bool feeds = false; /* Notify downstream nodes that depend on this node */ - for (i = _activation_set[chain].begin (); i != _activation_set[chain].end (); ++i) { - (*i)->trigger (); + for (auto const& i : activation_set (chain)) { + i->trigger (); feeds = true; } diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index f650afae29..af252c70db 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -547,13 +547,7 @@ Session::immediately_post_engine () */ _rt_tasklist.reset (new RTTaskList ()); - - if (how_many_dsp_threads () > 1) { - /* For now, only create the graph if we are using >1 DSP threads, as - it is a bit slower than the old code with 1 thread. - */ - _process_graph.reset (new Graph (*this)); - } + _process_graph.reset (new Graph (*this)); /* every time we reconnect, recompute worst case output latencies */ @@ -676,6 +670,9 @@ Session::destroy () /* reset dynamic state version back to default */ Stateful::loading_state_version = 0; + /* drop GraphNode references */ + _graph_chain.reset (); + _butler->drop_references (); delete _butler; _butler = 0; @@ -2218,8 +2215,18 @@ Session::rechain_process_graph (GraphNodeList& g) * Note: the process graph chain does not require a * topologically-sorted list, but hey ho. */ - if (_process_graph) { - _process_graph->rechain (g, edges); + if (_process_graph->n_threads () > 1) { + /* Ideally we'd use a memory pool to allocate the GraphChain, however node_lists + * inside the change are STL list/set. It was never rt-safe to re-chain the graph. + * Furthermore graph-changes are usually caused by connection changes, which are not + * rt-safe either. + * + * However, the graph-chain may be in use (session process), and the last reference + * be helf by the process-callback. So we delegate deletion to the butler thread. + */ + _graph_chain = boost::shared_ptr (new GraphChain (g, edges), boost::bind (&rt_safe_delete, this, _1)); + } else { + _graph_chain.reset (); } _current_route_graph = edges; @@ -3119,7 +3126,7 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool * we will resort when done. */ - if (!_monitor_out && !loading()) { + if (!_monitor_out && !loading() && !input_auto_connect && !output_auto_connect) { resort_routes_using (r); } } @@ -3444,10 +3451,6 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) routes.flush (); // maybe unsafe, see below. resort_routes (); - if (_process_graph && !deletion_in_progress() && _engine.running()) { - _process_graph->clear_other_chain (); - } - /* get rid of it from the dead wood collection in the route list manager */ /* XXX i think this is unsafe as it currently stands, but i am not sure. (pd, october 2nd, 2006) */ @@ -6115,7 +6118,7 @@ Session::nstripables (bool with_monitor) const bool Session::plot_process_graph (std::string const& file_name) const { - return _process_graph ? _process_graph->plot (file_name) : false; + return _graph_chain ? _graph_chain->plot (file_name) : false; } void diff --git a/libs/ardour/session_process.cc b/libs/ardour/session_process.cc index 422c01bc06..3c287543b5 100644 --- a/libs/ardour/session_process.cc +++ b/libs/ardour/session_process.cc @@ -203,9 +203,10 @@ Session::no_roll (pframes_t nframes) _global_locate_pending = locate_pending (); - if (_process_graph) { + boost::shared_ptr graph_chain = _graph_chain; + if (graph_chain) { DEBUG_TRACE(DEBUG::ProcessThreads,"calling graph/no-roll\n"); - _process_graph->routes_no_roll( nframes, _transport_sample, end_sample, non_realtime_work_pending()); + _process_graph->routes_no_roll(graph_chain, nframes, _transport_sample, end_sample, non_realtime_work_pending()); } else { PT_TIMING_CHECK (10); for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { @@ -250,9 +251,10 @@ Session::process_routes (pframes_t nframes, bool& need_butler) _global_locate_pending = locate_pending(); - if (_process_graph) { + boost::shared_ptr graph_chain = _graph_chain; + if (graph_chain) { DEBUG_TRACE(DEBUG::ProcessThreads,"calling graph/process-routes\n"); - if (_process_graph->process_routes (nframes, start_sample, end_sample, need_butler) < 0) { + if (_process_graph->process_routes (graph_chain, nframes, start_sample, end_sample, need_butler) < 0) { stop_transport (); return -1; } @@ -747,10 +749,6 @@ Session::process_audition (pframes_t nframes) } } - if (_process_graph) { - _process_graph->swap_process_chain (); - } - /* handle pending events */ while (pending_events.read (&ev, 1) == 1) {