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