OSSIA
Open Scenario System for Interactive Application
graph_utils.hpp
1 #pragma once
2 #include <ossia/dataflow/bench_map.hpp>
3 #include <ossia/dataflow/data_copy.hpp>
4 #include <ossia/dataflow/dataflow.hpp>
5 #include <ossia/dataflow/execution_state.hpp>
6 #include <ossia/dataflow/for_each_port.hpp>
7 #include <ossia/dataflow/graph/breadth_first_search.hpp>
8 #include <ossia/dataflow/graph/graph_interface.hpp>
9 #include <ossia/dataflow/graph/graph_ordering.hpp>
10 #include <ossia/dataflow/graph/small_graph.hpp>
11 #include <ossia/dataflow/graph_edge.hpp>
12 #include <ossia/dataflow/graph_node.hpp>
13 #include <ossia/dataflow/port.hpp>
15 #include <ossia/detail/flat_set.hpp>
16 #include <ossia/detail/ptr_set.hpp>
18 
19 #include <boost/graph/adjacency_list.hpp>
20 #include <boost/predef.h>
21 // broken due to dynamic_property_map requiring rtti...
22 // #include <boost/graph/graphviz.hpp>
23 #include <boost/graph/topological_sort.hpp>
24 
25 #include <ankerl/unordered_dense.h>
26 
27 #if BOOST_LIB_STD_GNU >= BOOST_VERSION_NUMBER(13, 0, 0) \
28  && BOOST_VERSION_NUMBER <= BOOST_VERSION_NUMBER(1, 83, 0)
29 #define OSSIA_SMALL_VECTOR_ALLOCATOR_REBIND_FAILS 1
30 #endif
31 
32 class DataflowTest;
33 namespace ossia
34 {
35 using graph_t = boost::adjacency_list<
36  boost::smallvecS, boost::smallvecS, boost::directedS, node_ptr,
37  std::shared_ptr<graph_edge>>;
38 }
39 namespace boost::detail
40 {
41 
42 template <>
43 class stored_edge_property<unsigned long, std::shared_ptr<ossia::graph_edge>>
44  : public stored_edge<unsigned long>
45 {
46  using Vertex = unsigned long;
47  using Property = std::shared_ptr<ossia::graph_edge>;
48  typedef stored_edge_property self;
49  typedef stored_edge<Vertex> Base;
50 
51 public:
52  typedef Property property_type;
53  inline stored_edge_property() { }
54  inline stored_edge_property(Vertex target, Property p = {})
55  : stored_edge<Vertex>(target)
56  , m_property(std::move(p))
57  {
58  }
59  stored_edge_property(self&& x) noexcept
60  : Base(static_cast<Base&&>(x))
61  , m_property(std::move(x.m_property))
62  {
63  }
64  stored_edge_property(self const& x)
65  : Base(static_cast<Base const&>(x))
66  , m_property(std::move(const_cast<self&>(x).m_property))
67  {
68  }
69  self& operator=(self&& x)
70  {
71  // NOTE: avoid 'Base::operator=(x);' broken on SGI MIPSpro (bug
72  // 55771 of Mozilla).
73  static_cast<Base&>(*this) = static_cast<Base&&>(x);
74  m_property = std::move(x.m_property);
75  return *this;
76  }
77  self& operator=(self const& x)
78  {
79  // NOTE: avoid 'Base::operator=(x);' broken on SGI MIPSpro (bug
80  // 55771 of Mozilla).
81  static_cast<Base&>(*this) = static_cast<Base const&>(x);
82  m_property = std::move(const_cast<self&>(x).m_property);
83  return *this;
84  }
85  inline Property& get_property() noexcept { return m_property; }
86  inline const Property& get_property() const noexcept { return m_property; }
87 
88 protected:
89  Property m_property;
90 };
91 
92 }
93 
94 namespace boost
95 {
96 namespace detail
97 {
98 template <typename G>
99 using DFSVertexInfo = std::pair<
100  typename graph_traits<G>::vertex_descriptor,
101  std::pair<
102  boost::optional<typename graph_traits<G>::edge_descriptor>,
103  std::pair<
104  typename graph_traits<G>::out_edge_iterator,
105  typename graph_traits<G>::out_edge_iterator>>>;
106 
107 template <class IncidenceGraph, class DFSVisitor, class ColorMap>
108 void custom_depth_first_visit_impl(
109  const IncidenceGraph& g, typename graph_traits<IncidenceGraph>::vertex_descriptor u,
110  DFSVisitor& vis, ColorMap color, std::vector<DFSVertexInfo<IncidenceGraph>>& stack)
111 {
112  constexpr detail::nontruth2 func;
113  BOOST_CONCEPT_ASSERT((IncidenceGraphConcept<IncidenceGraph>));
114  BOOST_CONCEPT_ASSERT((DFSVisitorConcept<DFSVisitor, IncidenceGraph>));
115  typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex;
116  typedef typename graph_traits<IncidenceGraph>::edge_descriptor Edge;
117  BOOST_CONCEPT_ASSERT((ReadWritePropertyMapConcept<ColorMap, Vertex>));
118  typedef typename property_traits<ColorMap>::value_type ColorValue;
119  BOOST_CONCEPT_ASSERT((ColorValueConcept<ColorValue>));
120  typedef color_traits<ColorValue> Color;
121  typedef typename graph_traits<IncidenceGraph>::out_edge_iterator Iter;
122  typedef std::pair<Vertex, std::pair<boost::optional<Edge>, std::pair<Iter, Iter>>>
123  VertexInfo;
124 
125  boost::optional<Edge> src_e;
126  Iter ei, ei_end;
127 
128  // Possible optimization for vector
129  stack.clear();
130  stack.reserve(num_vertices(g));
131 
132  put(color, u, Color::gray());
133  vis.discover_vertex(u, g);
134  boost::tie(ei, ei_end) = out_edges(u, g);
135  if(func(u, g))
136  {
137  // If this vertex terminates the search, we push empty range
138  stack.push_back(std::make_pair(
139  u, std::make_pair(boost::optional<Edge>(), std::make_pair(ei_end, ei_end))));
140  }
141  else
142  {
143  stack.push_back(std::make_pair(
144  u, std::make_pair(boost::optional<Edge>(), std::make_pair(ei, ei_end))));
145  }
146  while(!stack.empty())
147  {
148  VertexInfo& back = stack.back();
149  u = back.first;
150  src_e = back.second.first;
151  boost::tie(ei, ei_end) = back.second.second;
152  stack.pop_back();
153  // finish_edge has to be called here, not after the
154  // loop. Think of the pop as the return from a recursive call.
155  if(src_e)
156  {
157  call_finish_edge(vis, src_e.get(), g);
158  }
159  while(ei != ei_end)
160  {
161  Vertex v = target(*ei, g);
162  vis.examine_edge(*ei, g);
163  ColorValue v_color = get(color, v);
164  if(v_color == Color::white())
165  {
166  vis.tree_edge(*ei, g);
167  src_e = *ei;
168  stack.push_back(
169  std::make_pair(u, std::make_pair(src_e, std::make_pair(++ei, ei_end))));
170  u = v;
171  put(color, u, Color::gray());
172  vis.discover_vertex(u, g);
173  boost::tie(ei, ei_end) = out_edges(u, g);
174  if(func(u, g))
175  {
176  ei = ei_end;
177  }
178  }
179  else
180  {
181  if(v_color == Color::gray())
182  {
183  vis.back_edge(*ei, g);
184  }
185  else
186  {
187  vis.forward_or_cross_edge(*ei, g);
188  }
189  call_finish_edge(vis, *ei, g);
190  ++ei;
191  }
192  }
193  put(color, u, Color::black());
194  vis.finish_vertex(u, g);
195  }
196 }
197 }
198 
199 template <class VertexListGraph, class DFSVisitor, class ColorMap>
200 void custom_depth_first_search(
201  const VertexListGraph& g, DFSVisitor vis, ColorMap color,
202  typename graph_traits<VertexListGraph>::vertex_descriptor start_vertex,
203  std::vector<boost::detail::DFSVertexInfo<VertexListGraph>>& stack)
204 {
205  typedef typename graph_traits<VertexListGraph>::vertex_descriptor Vertex;
206  BOOST_CONCEPT_ASSERT((DFSVisitorConcept<DFSVisitor, VertexListGraph>));
207  typedef typename property_traits<ColorMap>::value_type ColorValue;
208  typedef color_traits<ColorValue> Color;
209 
210  typename graph_traits<VertexListGraph>::vertex_iterator ui, ui_end;
211  for(boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui)
212  {
213  Vertex u = implicit_cast<Vertex>(*ui);
214  put(color, u, Color::white());
215  vis.initialize_vertex(u, g);
216  }
217 
218  if(start_vertex != detail::get_default_starting_vertex(g))
219  {
220  vis.start_vertex(start_vertex, g);
221  detail::custom_depth_first_visit_impl(g, start_vertex, vis, color, stack);
222  }
223 
224  for(boost::tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui)
225  {
226  Vertex u = implicit_cast<Vertex>(*ui);
227  ColorValue u_color = get(color, u);
228  if(u_color == Color::white())
229  {
230  vis.start_vertex(u, g);
231  detail::custom_depth_first_visit_impl(g, u, vis, color, stack);
232  }
233  }
234 }
235 
236 }
237 namespace ossia
238 {
239 inline void remove_vertex(typename graph_t::vertex_descriptor v, graph_t& g)
240 {
241  typedef typename graph_t::directed_category Cat;
242  g.removing_vertex(v, boost::graph_detail::iterator_stability(g.m_vertices));
243  boost::detail::remove_vertex_dispatch(g, v, Cat());
244 }
245 
246 template <typename VertexListGraph, typename OutputIterator>
247 void custom_topological_sort(
248  VertexListGraph& g, OutputIterator result,
249  std::vector<boost::default_color_type>& color_map,
250  std::vector<boost::detail::DFSVertexInfo<VertexListGraph>>& stack)
251 {
252  color_map.clear();
253  color_map.resize(boost::num_vertices(g));
254 
255  auto map = boost::make_iterator_property_map(
256  color_map.begin(), boost::get(boost::vertex_index, g), color_map[0]);
257 
258  boost::custom_depth_first_search(
259  g, boost::topo_sort_visitor<OutputIterator>(result), map,
260  boost::detail::get_default_starting_vertex(g), stack);
261 
262  // depth_first_search(g, params.visitor(TopoVisitor(result)));
263 }
264 
265 using graph_vertex_t = graph_t::vertex_descriptor;
266 using graph_edge_t = graph_t::edge_descriptor;
267 
268 #if !defined(OSSIA_NO_FAST_CONTAINERS)
269 template <typename T, typename V>
270 using dense_shared_ptr_map = ankerl::unordered_dense::map<
271  std::shared_ptr<T>, V, egur_hash, pointer_equal,
272 #if defined(OSSIA_SMALL_VECTOR_ALLOCATOR_REBIND_FAILS)
273  std::vector<std::pair<std::shared_ptr<T>, V>>
274 #else
275  ossia::small_vector<std::pair<std::shared_ptr<T>, V>, 1024>
276 #endif
277  >;
278 #else
279 template <typename T, typename V>
280 using dense_shared_ptr_map
281  = ossia::hash_map<std::shared_ptr<T>, V, egur_hash, pointer_equal>;
282 #endif
283 using node_map = ossia::dense_shared_ptr_map<ossia::graph_node, graph_vertex_t>;
284 using edge_map = ossia::dense_shared_ptr_map<ossia::graph_edge, graph_edge_t>;
285 
286 using node_flat_set = ossia::flat_set<graph_node*>;
287 enum class node_ordering
288 {
289  topological,
290  temporal,
291  hierarchical
292 };
293 
294 template <typename T>
295 auto apply_con(const T& visitor, ossia::connection& con)
296 {
297  auto tgt = con.target();
298  switch(con.which().index())
299  {
300  case ossia::connection::index_of<immediate_glutton_connection>().index():
301  return visitor(*reinterpret_cast<immediate_glutton_connection*>(tgt));
302  break;
303  case ossia::connection::index_of<immediate_strict_connection>().index():
304  return visitor(*reinterpret_cast<immediate_strict_connection*>(tgt));
305  break;
306  case ossia::connection::index_of<delayed_glutton_connection>().index():
307  return visitor(*reinterpret_cast<delayed_glutton_connection*>(tgt));
308  break;
309  case ossia::connection::index_of<delayed_strict_connection>().index():
310  return visitor(*reinterpret_cast<delayed_strict_connection*>(tgt));
311  break;
312  case ossia::connection::index_of<dependency_connection>().index():
313  return visitor(*reinterpret_cast<dependency_connection*>(tgt));
314  break;
315  default:
316  return visitor();
317  break;
318  }
319 }
320 template <typename T>
321 auto apply_con(const T& visitor, const ossia::connection& con)
322 {
323  auto tgt = con.target();
324  switch(con.which().index())
325  {
326  case ossia::connection::index_of<immediate_glutton_connection>().index():
327  return visitor(*reinterpret_cast<const immediate_glutton_connection*>(tgt));
328  break;
329  case ossia::connection::index_of<immediate_strict_connection>().index():
330  return visitor(*reinterpret_cast<const immediate_strict_connection*>(tgt));
331  break;
332  case ossia::connection::index_of<delayed_glutton_connection>().index():
333  return visitor(*reinterpret_cast<const delayed_glutton_connection*>(tgt));
334  break;
335  case ossia::connection::index_of<delayed_strict_connection>().index():
336  return visitor(*reinterpret_cast<const delayed_strict_connection*>(tgt));
337  break;
338  case ossia::connection::index_of<dependency_connection>().index():
339  return visitor(*reinterpret_cast<const dependency_connection*>(tgt));
340  break;
341  default:
342  return visitor();
343  break;
344  }
345 }
346 
347 template <typename Graph_T, typename IO>
348 void print_graph(Graph_T& g, IO& stream)
349 {
350 #if 0
351  std::stringstream s;
352  boost::write_graphviz(
353  s, g,
354  [&](auto& out, auto v) {
355  if(g[v] && !g[v]->label().empty())
356  out << "[label=\"" << g[v]->label() << "\"]";
357  else
358  out << "[]";
359  },
360  [](auto&&...) {});
361 
362  stream << s.str() << "\n";
363 #endif
364 }
365 
366 struct OSSIA_EXPORT graph_util
367 {
368  static void pull_from_parameter(inlet& in, execution_state& e)
369  {
370  apply_to_destination(
371  in.address, e.exec_devices(),
372  [&](ossia::net::parameter_base* addr, bool) {
373  if(in.scope & port::scope_t::local)
374  {
375  e.find_and_copy(*addr, in);
376  }
377  else if(in.scope & port::scope_t::global)
378  {
379  e.copy_from_global(*addr, in);
380  }
381  },
382  [&](ossia::net::node_base* node, bool) { e.copy_from_global_node(*node, in); });
383  }
384 
386  static void init_outlet(outlet& out, execution_state&)
387  {
388  out.visit(clear_data{});
389 
390  out.pre_process();
391  }
392 
393  static void init_inlet(inlet& in, execution_state& e)
394  {
395  bool must_copy = in.sources.empty();
396 
397  for(const graph_edge* edge : in.sources)
398  {
399  must_copy |= ossia::apply_con(init_must_copy_visitor{*edge}, edge->con);
400  }
401 
402  if(must_copy)
403  pull_from_parameter(in, e);
404 
405  for(auto edge : in.sources)
406  {
407  ossia::apply_con(init_node_visitor{in, *edge, e}, edge->con);
408  }
409 
410  in.pre_process();
411  }
412 
413  static void init_node(graph_node& n, execution_state& e)
414  {
415  // Clear the outputs of the node
416  for_each_outlet(n, [&](auto& port) { init_outlet(port, e); });
417 
418  // Copy from environment and previous ports to inputs
419  for_each_inlet(n, [&](auto& port) { init_inlet(port, e); });
420  }
421 
423  static void teardown_outlet(outlet& out, execution_state& e)
424  {
425  out.post_process();
426  bool must_copy = out.targets.empty();
427 
428  // If some following glutton nodes aren't enabled, then we copy to the
429  // env.
430  for(const auto& tgt : out.targets)
431  {
432  must_copy |= ossia::apply_con(env_writer{out, *tgt}, tgt->con);
433  }
434 
435  // if there are two outgoing glutton connections, one active, the other
436  // inactive then we want to copy through cable for the first one, and
437  // through env for the second one
438  if(must_copy)
439  out.write(e);
440  }
441 
442  static void teardown_inlet(inlet& in, execution_state&)
443  {
444  in.post_process();
445  in.visit(clear_data{});
446  }
447 
448  static void teardown_node(const graph_node& n, execution_state& e)
449  {
450  // Copy from output ports to environment
451  // Clear the outputs of the node
452  for_each_outlet(n, [&](auto& port) { teardown_outlet(port, e); });
453 
454  // Copy from environment and previous ports to inputs
455  for_each_inlet(n, [&](auto& port) { teardown_inlet(port, e); });
456  }
457 
458  /*
459  static void disable_strict_nodes_bfs(const graph_t& graph)
460  {
461  // while(Find a non-marked disabled node)
462  // Do a BFS from it
463  ossia::flat_map<graph_vertex_t, boost::two_bit_color_type> mark;
464  struct disable_visitor : public boost::default_bfs_visitor
465  {
466  void discover_vertex(graph_vertex_t vtx, graph_t& g) const
467  {
468  auto ptr = g[vtx].get();
469 
470  }
471  };
472  }
473  */
474 
475  static bool disable_strict_nodes(const graph_node* node)
476  {
477  if(node->muted())
478  return true;
479 
480  auto test_disable_inlet = [&](const ossia::inlet& inlet) {
481  for(const auto& edge : inlet.sources)
482  {
483  assert(edge);
484  assert(edge->out_node);
485 
486  if(immediate_strict_connection* sc
487  = edge->con.target<immediate_strict_connection>())
488  {
489  if((sc->required_sides
490  & immediate_strict_connection::required_sides_t::outbound)
491  && node->enabled() && !edge->out_node->enabled())
492  {
493  return true;
494  }
495  }
496  else if(
497  delayed_strict_connection* delay
498  = edge->con.target<delayed_strict_connection>())
499  {
500  const auto n = ossia::apply(data_size{}, delay->buffer);
501  if(n == 0 || delay->pos >= n)
502  {
503  return true;
504  }
505  }
506  }
507  return false;
508  };
509 
510  auto test_disable_outlet = [&](const ossia::outlet& outlet) {
511  for(const auto& edge : outlet.targets)
512  {
513  assert(edge->in_node);
514 
515  if(auto sc = edge->con.target<immediate_strict_connection>())
516  {
517  if((sc->required_sides
518  & immediate_strict_connection::required_sides_t::inbound)
519  && node->enabled() && !edge->in_node->enabled())
520  {
521  return true;
522  }
523  }
524  }
525  return false;
526  };
527 
528  if(any_of_inlet(*node, test_disable_inlet))
529  return true;
530  if(any_of_outlet(*node, test_disable_outlet))
531  return true;
532 
533  return false;
534  }
535 
536  static void
537  disable_strict_nodes(const node_flat_set& enabled_nodes, node_flat_set& ret)
538  {
539  for(graph_node* node : enabled_nodes)
540  {
541  if(disable_strict_nodes(node))
542  ret.insert(node);
543  }
544  }
545 
546  static void disable_strict_nodes_rec(
547  node_flat_set& cur_enabled_node, node_flat_set& disabled_cache)
548  {
549  do
550  {
551  disabled_cache.clear();
552  disable_strict_nodes(cur_enabled_node, disabled_cache);
553  for(ossia::graph_node* n : disabled_cache)
554  {
555  n->disable();
556 
557  cur_enabled_node.erase(n);
558  }
559 
560  } while(!disabled_cache.empty());
561  }
562 
563  static void log_inputs(const graph_node&, ossia::logger_type& logger);
564  static void log_outputs(const graph_node&, ossia::logger_type& logger);
565 
566  static void run_scaled(graph_node& first_node, execution_state& e);
567 
568  static void exec_node(graph_node& first_node, execution_state& e)
569  {
570  init_node(first_node, e);
571  if(!first_node.requested_tokens.empty())
572  {
573  if(first_node.start_discontinuous())
574  {
575  first_node.requested_tokens.front().start_discontinuous = true;
576  first_node.set_start_discontinuous(false);
577  }
578  if(first_node.end_discontinuous())
579  {
580  first_node.requested_tokens.front().end_discontinuous = true;
581  first_node.set_end_discontinuous(false);
582  }
583  }
584 
585  for(const auto& request : first_node.requested_tokens)
586  {
587  first_node.run(request, {&e});
588  }
589 
590  first_node.set_executed(true);
591  teardown_node(first_node, e);
592  }
593 
594  static void
595  exec_node(graph_node& first_node, execution_state& e, ossia::logger_type& logger)
596  {
597  init_node(first_node, e);
598  if(first_node.start_discontinuous())
599  {
600  first_node.requested_tokens.front().start_discontinuous = true;
601  first_node.set_start_discontinuous(false);
602  }
603  if(first_node.end_discontinuous())
604  {
605  first_node.requested_tokens.front().end_discontinuous = true;
606  first_node.set_end_discontinuous(false);
607  }
608 
609  log_inputs(first_node, logger);
610  for(const auto& request : first_node.requested_tokens)
611  {
612  first_node.run(request, {&e});
613  }
614  log_outputs(first_node, logger);
615 
616  first_node.set_executed(true);
617  teardown_node(first_node, e);
618  }
619 
620  // These methods are only accessed by ossia::graph
621  static bool can_execute(graph_node& node, const execution_state&)
622  {
623  return all_of_inlet(node, [](const auto& inlet) {
624  // A port can execute if : - it has source ports and all its source
625  // ports have executed
626 
627  // if there was a strict connection, this node would have been
628  // disabled so we can just do the following check.
629  bool previous_nodes_executed = ossia::all_of(inlet.sources, [&](graph_edge* edge) {
630  return edge->out_node->executed()
631  || (!edge->out_node->enabled() /* && bool(inlet->address) */
632  /* TODO check that it's in scope */);
633  });
634 
635  // it does not have source ports ; we have to check for variables :
636  // find if address available in local / global scope
637  return !inlet.sources.empty() ? previous_nodes_executed : true; // TODO
638  });
639  }
640 
641  static void finish_nodes(const node_map& nodes)
642  {
643  for(auto& node : nodes)
644  {
645  ossia::graph_node& n = *node.first;
646  n.set_executed(false);
647  n.disable();
648  }
649  }
650 
651  template <typename DevicesT>
652  static auto find_address_connection(
653  ossia::graph_node& source, ossia::graph_node& sink, const DevicesT& devices)
654  {
655  return any_of_outlet(source, [&](auto& outlet) {
656  return any_of_inlet(sink, [&](auto& inlet) {
657  bool ok = false;
658  apply_to_destination(
659  outlet.address, devices,
660  [&](ossia::net::parameter_base* p1, bool) {
661  apply_to_destination(
662  inlet.address, devices,
663  [&](ossia::net::parameter_base* p2, bool) {
664  if(p1 == p2)
665  ok = true;
666  },
667  do_nothing_for_nodes{});
668  },
669  do_nothing_for_nodes{});
670  return ok;
671  });
672  });
673  }
674 };
675 
676 struct OSSIA_EXPORT graph_base : graph_interface
677 {
678  graph_base() noexcept
679  {
680  m_nodes.reserve(1024);
681  m_node_list.reserve(1024);
682  m_edges.reserve(1024);
683  }
684  [[nodiscard]] tcb::span<ossia::graph_node* const>
685  get_nodes() const noexcept final override
686  {
687  return tcb::span{m_node_list};
688  }
689 
690  void recompute_maps()
691  {
692  // TODO we should instead mark it for cleaning and do it once per tick ?
693  m_nodes.clear();
694  m_edges.clear();
695  auto vertices = boost::vertices(m_graph);
696  m_nodes.reserve(m_graph.m_vertices.size());
697  for(auto it = vertices.first; it != vertices.second; ++it)
698  {
699  graph_vertex_t k = *it;
700  node_ptr n = m_graph[k];
701  assert(n);
702 
703  m_nodes.insert({n, k});
704  }
705 
706  // https://svn.boost.org/trac10/ticket/5706#no1
707 #if !defined(__clang__)
708 #pragma GCC diagnostic push
709 #pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
710 #endif
711  auto edges = boost::edges(m_graph);
712  m_edges.reserve(std::distance(edges.first, edges.second));
713  for(auto it = edges.first; it != edges.second; ++it)
714  {
715  graph_edge_t k = *it;
716  edge_ptr n = m_graph[k];
717  assert(n);
718 
719  m_edges.insert({n, k});
720  }
721 #if !defined(__clang__)
722 #pragma GCC diagnostic pop
723 #endif
724  }
725 
726  auto add_node_impl(node_ptr n)
727  {
728  // auto& bench = *ossia::bench_ptr();
729  // bench[n.get()];
730 
731  auto vtx = boost::add_vertex(n, m_graph);
732  // m_nodes.insert({std::move(n), vtx});
733  m_node_list.push_back(n.get());
734  m_dirty = true;
735  recompute_maps();
736  return vtx;
737  }
738 
739  void add_node(node_ptr n) final override
740  {
741  if(m_nodes.find(n) == m_nodes.end())
742  {
743  add_node_impl(std::move(n));
744  }
745  }
746 
747  void remove_node(const node_ptr& n) final override
748  {
749  for_each_inlet(*n, [&](auto& port) {
750  auto s = port.sources;
751  for(auto edge : s)
752  disconnect(edge);
753  });
754  for_each_outlet(*n, [&](auto& port) {
755  auto s = port.targets;
756  for(auto edge : s)
757  disconnect(edge);
758  });
759 
760  auto it = m_nodes.find(n);
761  if(it != m_nodes.end())
762  {
763  auto vtx = boost::vertices(m_graph);
764  if(std::find(vtx.first, vtx.second, it->second) != vtx.second)
765  {
766  boost::clear_vertex(it->second, m_graph);
767  remove_vertex(it->second, m_graph);
768 
769  recompute_maps();
770  }
771  // no need to erase it since it won't be here after recompute_maps
772  }
773  ossia::remove_one(m_node_list, n.get());
774  m_dirty = true;
775  }
776 
777  void connect(std::shared_ptr<graph_edge> edge) final override
778  {
779  if(edge)
780  {
781  edge->init();
782 
783  graph_vertex_t in_vtx, out_vtx;
784  auto it1 = m_nodes.find(edge->in_node);
785  if(it1 == m_nodes.end())
786  in_vtx = add_node_impl(edge->in_node);
787  else
788  in_vtx = it1->second;
789 
790  auto it2 = m_nodes.find(edge->out_node);
791  if(it2 == m_nodes.end())
792  out_vtx = add_node_impl(edge->out_node);
793  else
794  out_vtx = it2->second;
795 
796  // TODO check that two edges can be added
797  boost::add_edge(in_vtx, out_vtx, edge, m_graph);
798  recompute_maps();
799  m_dirty = true;
800  }
801  }
802 
803  void disconnect(const std::shared_ptr<graph_edge>& edge) final override
804  {
805  disconnect(edge.get());
806  }
807 
808  void disconnect(graph_edge* edge) final override
809  {
810  if(edge)
811  {
812  edge->clear();
813  auto it = m_edges.find(edge);
814  if(it != m_edges.end())
815  {
816  auto edg = boost::edges(m_graph);
817  if(std::find(edg.first, edg.second, it->second) != edg.second)
818  {
819  boost::remove_edge(it->second, m_graph);
820  recompute_maps();
821  }
822  m_dirty = true;
823  // no need to erase it since it won't be here after recompute_maps
824  }
825  }
826  }
827 
828  void clear() final override
829  {
830  // TODO clear all the connections, ports, etc, to ensure that there is no
831  // shared_ptr loop
832  for(auto& edge : m_edges)
833  {
834  edge.first->clear();
835  }
836  for(auto& node : m_nodes)
837  {
838  node.first->clear();
839  }
840  m_dirty = true;
841  m_nodes.clear();
842  m_node_list.clear();
843  m_edges.clear();
844  m_graph.clear();
845  }
846 
847  void mark_dirty() final override { m_dirty = true; }
848 
849  ~graph_base() override { clear(); }
850 
851  node_map m_nodes;
852  edge_map m_edges;
853  ossia::small_vector<ossia::graph_node*, 1024> m_node_list;
854 
855  graph_t m_graph;
856 
857  bool m_dirty{};
858 };
859 }
The node_base class.
Definition: network/base/node.hpp:48
The parameter_base class.
Definition: ossia/network/base/parameter.hpp:48
Definition: git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition: context.cpp:104