OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
graph_static.hpp
1#pragma once
2#include <ossia/dataflow/bench_map.hpp>
3#include <ossia/dataflow/graph/graph_interface.hpp>
4#include <ossia/dataflow/graph/graph_utils.hpp>
5#include <ossia/dataflow/graph/node_executors.hpp>
6#include <ossia/dataflow/graph/transitive_closure.hpp>
7#include <ossia/detail/flat_map.hpp>
9#include <ossia/editor/scenario/execution_log.hpp>
10
11#include <boost/circular_buffer.hpp>
12#include <boost/graph/transitive_closure.hpp>
13
14#include <ossia-config.hpp>
15
16// #define OSSIA_GRAPH_DEBUG
17
18namespace ossia
19{
20using filtered_graph_t = std::decay_t<decltype(boost::filtered_graph(
21 std::declval<graph_t>(), no_delay_edges{nullptr}))>;
22template <typename UpdateImpl, typename TickImpl>
23struct graph_static final
24 : public graph_util
25 , public graph_base
26{
27public:
28 UpdateImpl update_fun;
29 TickImpl tick_fun{*this};
30 std::vector<boost::default_color_type> m_color_map_cache;
31 std::vector<boost::detail::DFSVertexInfo<graph_t, filtered_graph_t>> m_stack_cache;
32 explicit graph_static(const ossia::graph_setup_options& opt = {})
33 : update_fun{*this, opt}
34 {
35#if !defined(OSSIA_FREESTANDING)
36 m_all_nodes.reserve(1024);
37 m_enabled_cache.reserve(1024);
38 m_topo_order_cache.reserve(1024);
39 m_color_map_cache.reserve(1024);
40 m_stack_cache.reserve(1024);
41#endif
42 }
43 ~graph_static() override { clear(); }
44
45 void sort_all_nodes(const graph_t& gr)
46 {
47 try
48 {
49 // Get a total order on nodes
50 m_all_nodes.clear();
51 m_all_nodes.reserve(m_nodes.size());
52
53 // TODO this should be doable with a single vector
54 m_topo_order_cache.clear();
55 m_topo_order_cache.reserve(m_nodes.size());
56 auto view = boost::filtered_graph{gr, no_delay_edges{&gr}};
57 custom_topological_sort<graph_t>(
58 view, std::back_inserter(m_topo_order_cache), m_color_map_cache,
59 m_stack_cache);
60
61 // First put the ones without any I/O (most likely states)
62 for(auto vtx : m_topo_order_cache)
63 {
64 auto node = gr[vtx].get();
65 assert(node);
66 if(node->root_inputs().empty() && node->root_outputs().empty())
67 {
68 m_all_nodes.push_back(node);
69 }
70 }
71 // Then the others
72 for(auto vtx : m_topo_order_cache)
73 {
74 auto node = gr[vtx].get();
75 assert(node);
76
77 if(!(node->root_inputs().empty() && node->root_outputs().empty()))
78 {
79 m_all_nodes.push_back(node);
80 }
81 }
82 }
83 catch(const boost::not_a_dag&)
84 {
85 m_all_nodes.clear();
86 ossia::logger().error(
87 "Execution graph is not a DAG, nothing will execute: {}",
88 describe_immediate_cycle(gr));
89 }
90 catch(const std::exception& e)
91 {
92 m_all_nodes.clear();
93 ossia::logger().error("Execution graph could not be sorted: {}", e.what());
94 }
95 catch(...)
96 {
97 m_all_nodes.clear();
98 ossia::logger().error("Execution graph could not be sorted");
99 }
100 }
101
102 static std::string describe_immediate_cycle(const graph_t& gr)
103 {
104 const auto n = boost::num_vertices(gr);
105 enum color : uint8_t
106 {
107 white,
108 grey,
109 black
110 };
111 std::vector<color> colors(n, white);
112 struct frame
113 {
114 graph_vertex_t vertex;
115 boost::graph_traits<graph_t>::out_edge_iterator it, end;
116 };
117 std::vector<frame> stack;
118
119 auto label = [&](graph_vertex_t v) {
120 const auto& node = gr[v];
121 return node ? node->label() : std::string{"<null>"};
122 };
123
124 for(graph_vertex_t root = 0; root < n; ++root)
125 {
126 if(colors[root] != white)
127 continue;
128 colors[root] = grey;
129 auto [b, e] = boost::out_edges(root, gr);
130 stack.push_back({root, b, e});
131 while(!stack.empty())
132 {
133 auto& f = stack.back();
134 if(f.it == f.end)
135 {
136 colors[f.vertex] = black;
137 stack.pop_back();
138 continue;
139 }
140 const auto edge = *f.it++;
141 if(gr[edge]->delayed())
142 continue;
143 const auto target = boost::target(edge, gr);
144 if(colors[target] == white)
145 {
146 colors[target] = grey;
147 auto [tb, te] = boost::out_edges(target, gr);
148 stack.push_back({target, tb, te});
149 }
150 else if(colors[target] == grey)
151 {
152 std::string res = "immediate cables form a cycle: ";
153 auto it = std::find_if(stack.begin(), stack.end(), [&](const frame& fr) {
154 return fr.vertex == target;
155 });
156 for(; it != stack.end(); ++it)
157 res += label(it->vertex) + " -> ";
158 res += label(target);
159 res += "; make one of these cables delayed";
160 return res;
161 }
162 }
163 }
164 return "no immediate cycle found";
165 }
166
167 void state(execution_state& e) override
168 {
169 try
170 {
171 if(m_dirty)
172 {
173 update_fun(*this, e.exec_devices());
174 m_enabled_cache.clear();
175 m_dirty = false;
176 }
177
178 // Filter disabled nodes (through strict relationships).
179 m_enabled_cache.reserve(m_nodes.size());
180
181 for(auto node : m_all_nodes)
182 {
183 ossia::graph_node& ptr = *node;
184 if(ptr.enabled())
185 {
186 m_enabled_cache.insert(&ptr);
187 }
188 else
189 {
190 auto it = m_enabled_cache.find(&ptr);
191 if(it != m_enabled_cache.end())
192 m_enabled_cache.erase(it);
193 }
194 }
195
196 disable_strict_nodes_rec(m_enabled_cache, m_disabled_cache);
197
198 tick_fun(*this, update_fun, e, m_all_nodes);
199
200#if defined(OSSIA_EXECUTION_LOG)
201 auto log = g_exec_log.log_executed_nodes(m_graph, m_all_nodes);
202#endif
203
204 finish_nodes(m_nodes);
205 }
206 catch(const boost::not_a_dag&)
207 {
208 ossia::logger().error("Execution graph is not a DAG.");
209 return;
210 }
211 }
212
213 [[nodiscard]] const graph_t& impl() const { return m_graph; }
214 graph_t& impl() { return m_graph; }
215 std::vector<graph_node*> m_all_nodes;
216
217protected:
218 void print(std::ostream& stream) override { print_graph(m_graph, stream); }
219
220private:
221 node_flat_set m_enabled_cache;
222 node_flat_set m_disabled_cache;
223 std::vector<graph_vertex_t> m_topo_order_cache;
224
225 friend class ::DataflowTest;
226};
227
228struct simple_update
229{
230 ossia::graph_t& m_sub_graph;
231 template <typename Graph_T>
232 simple_update(Graph_T& g, const ossia::graph_setup_options& opt)
233 : m_sub_graph{g.m_graph}
234 {
235 }
236
237 template <typename Graph_T, typename DevicesT>
238 void operator()(Graph_T& g, const DevicesT& devices)
239 {
240 g.sort_all_nodes(g.m_graph);
241 }
242};
243
244struct bfs_update
245{
246public:
247 template <typename Graph_T>
248 bfs_update(Graph_T& g, const ossia::graph_setup_options& opt)
249 : m_color{boost::make_two_bit_color_map_fast(
250 0, boost::get(boost::vertex_index, g.m_graph))}
251 {
252 }
253
254 template <typename Graph_T, typename DevicesT>
255 void operator()(Graph_T& g, const DevicesT& devices)
256 {
257 auto& m_graph = g.m_graph;
258 auto& m_nodes = g.m_nodes;
259 auto& m_all_nodes = g.m_all_nodes;
260 const auto N = boost::num_vertices(m_graph);
261 // m_color.clear();
262 // m_color.reserve(N);
263 m_sub_graph = m_graph;
264
265 g.sort_all_nodes(m_graph);
266 // m_active_nodes is in topo order
267
268 for(std::size_t i = 0; i < N; i++)
269 {
270 ossia::graph_node* n1 = m_all_nodes[i];
271 for(std::size_t j = i + 1; j < N; j++)
272 {
273 ossia::graph_node* n2 = m_all_nodes[j];
274
275 auto source_vtx = m_nodes.find(n1)->second;
276 auto sink_vtx = m_nodes.find(n2)->second;
277 if(find_path(source_vtx, sink_vtx, m_sub_graph))
278 continue;
279 if(find_path(sink_vtx, source_vtx, m_sub_graph))
280 continue;
281
282 if(graph_util::find_address_connection(*n1, *n2, devices))
283 {
284 auto src_it = m_nodes.find(n1);
285 auto sink_it = m_nodes.find(n2);
286 assert(src_it != m_nodes.end());
287 assert(sink_it != m_nodes.end());
288 auto edge = g.allocate_edge(
289 ossia::dependency_connection{}, ossia::outlet_ptr{}, ossia::inlet_ptr{},
290 src_it->first, sink_it->first);
291 boost::add_edge(sink_it->second, src_it->second, edge, m_sub_graph);
292
293#if defined(OSSIA_GRAPH_DEBUG)
294 auto all_nodes_old = std::move(m_all_nodes);
295 m_all_nodes.clear();
296 sort_all_nodes(sub_graph);
297 m_all_nodes = std::move(all_nodes_old);
298#endif
299 }
300 else if(graph_util::find_address_connection(*n2, *n1, devices))
301 {
302 auto src_it = m_nodes.find(n2);
303 auto sink_it = m_nodes.find(n1);
304 auto edge = g.allocate_edge(
305 ossia::dependency_connection{}, ossia::outlet_ptr{}, ossia::inlet_ptr{},
306 src_it->first, sink_it->first);
307 boost::add_edge(sink_it->second, src_it->second, edge, m_sub_graph);
308
309#if defined(OSSIA_GRAPH_DEBUG)
310 auto all_nodes_old = std::move(m_all_nodes);
311 m_all_nodes.clear();
312 sort_all_nodes(sub_graph);
313 m_all_nodes = std::move(all_nodes_old);
314#endif
315 }
316 }
317 }
318
319 g.sort_all_nodes(m_sub_graph);
320 }
321
322 bool find_path(graph_vertex_t source, graph_vertex_t sink, graph_t& graph)
323 {
324 bool ok = false;
325 struct bfs_find_visitor
326 {
327 graph_vertex_t node_to_find{};
328 bool& ok;
329 bool discover_vertex(graph_vertex_t u, const graph_t&) const noexcept
330 {
331 if(u == node_to_find)
332 ok = true;
333 return ok;
334 }
335 } to_find{sink, ok};
336
337 m_queue.clear();
338
339 const auto N = boost::num_vertices(graph);
340 if(m_queue.capacity() <= N)
341 m_queue.set_capacity(N);
342
343 m_color.resize(N);
344
345 ossia::bfs::breadth_first_search_simple(graph, source, to_find, m_queue, m_color);
346 return ok;
347 }
348 graph_t m_sub_graph;
349
350private:
351 boost::circular_buffer<graph_vertex_t> m_queue;
352
353 using pmap_type = decltype(boost::make_two_bit_color_map_fast(
354 0, get(boost::vertex_index, graph_t{})));
355
356 pmap_type m_color;
357};
358
359template <typename Impl>
360struct tc_update
361{
362 Impl impl;
363
364public:
365 template <typename Graph_T>
366 tc_update(Graph_T& g, const ossia::graph_setup_options& opt)
367 {
368 }
369 template <typename Graph_T, typename DevicesT>
370 void operator()(Graph_T& g, const DevicesT& devices)
371 {
372 m_sub_graph = g.m_graph;
373
374 g.sort_all_nodes(m_sub_graph);
375
376 impl.update(m_sub_graph);
377
378 tc_add_addresses(g, g.m_graph, m_sub_graph, g.m_nodes, g.m_all_nodes, impl, devices);
379
380 g.sort_all_nodes(m_sub_graph);
381 }
382
383 graph_t m_sub_graph;
384
385private:
386 template <
387 typename BaseGraph, typename TCGraph, typename NodeMap, typename AllNodes,
388 typename TC, typename Devices>
389 static void tc_add_addresses(
390 auto& impl, BaseGraph& m_graph, TCGraph& m_sub_graph, NodeMap& m_nodes,
391 AllNodes& m_all_nodes, TC& tc, Devices& devices)
392 {
393 // m_active_nodes is in topo order
394
395 // note: this is not enough.
396 // eg consider
397 // n1
398 // / \ ..
399 // /a->n2->/b /b->n3->/a
400 // depending on the sort the connection may not happen, if n3 happens
401 // before n2
402 //.. is it a problem ? we should just sort every "non-connected" node ?
403 // What we do is : do the topo sort, and only add edges if they don't
404 // create cycles. That is, if there is already path from n2 to n1 we don't
405 // add the edge. The order is defined... by what ? maybe we have to run
406 // this every tick ? :( If using the topo sort order (eg DFS) we can do it
407 // statically, else it's dynamically.
408
409 // another case : [b -> c] [a -> b]
410 // if the first node occurs before the second there won't be any chaining,
411 // while we want to ensure that there will be chainings. So: for each pair:
412 // check if there is a path from one to the other. Problem: [a -> b] [b ->
413 // a] : which comes first ? one has to resolve the ambiguity manually.
414
415 for(std::size_t i = 0; i < m_all_nodes.size(); i++)
416 {
417 ossia::graph_node* n1 = m_all_nodes[i];
418 for(std::size_t j = i + 1; j < m_all_nodes.size(); j++)
419 {
420 ossia::graph_node* n2 = m_all_nodes[j];
421
422 auto source_vtx = m_nodes.find(n1)->second;
423 auto sink_vtx = m_nodes.find(n2)->second;
424 if(tc.has_edge(source_vtx, sink_vtx))
425 continue;
426 if(tc.has_edge(sink_vtx, source_vtx))
427 continue;
428
429 if(graph_util::find_address_connection(*n1, *n2, devices))
430 {
431 auto src_it = m_nodes.find(n1);
432 auto sink_it = m_nodes.find(n2);
433 auto edge = impl.allocate_edge(
434 ossia::dependency_connection{}, ossia::outlet_ptr{}, ossia::inlet_ptr{},
435 src_it->first, sink_it->first);
436 boost::add_edge(sink_it->second, src_it->second, edge, m_sub_graph);
437 tc.update(m_sub_graph);
438
439#if defined(OSSIA_GRAPH_DEBUG)
440 print_graph(transitive_closure, std::cout);
441 auto all_nodes_old = std::move(m_all_nodes);
442 m_all_nodes.clear();
443 sort_all_nodes(sub_graph);
444 m_all_nodes = std::move(all_nodes_old);
445#endif
446 }
447 else if(graph_util::find_address_connection(*n2, *n1, devices))
448 {
449 auto src_it = m_nodes.find(n2);
450 auto sink_it = m_nodes.find(n1);
451 auto edge = impl.allocate_edge(
452 ossia::dependency_connection{}, ossia::outlet_ptr{}, ossia::inlet_ptr{},
453 src_it->first, sink_it->first);
454 boost::add_edge(sink_it->second, src_it->second, edge, m_sub_graph);
455 tc.update(m_sub_graph);
456
457#if defined(OSSIA_GRAPH_DEBUG)
458 auto all_nodes_old = std::move(m_all_nodes);
459 m_all_nodes.clear();
460 sort_all_nodes(sub_graph);
461 m_all_nodes = std::move(all_nodes_old);
462#endif
463 }
464 }
465 }
466 }
467};
468
469struct fast_tc
470{
471public:
472 using transitive_closure_t = boost::adjacency_list<
473 boost::vecS, boost::vecS, boost::directedS, ossia::graph_node*, int64_t>;
474
475 [[nodiscard]] bool has_edge(int source_vtx, int sink_vtx) const
476 {
477 return boost::edge(source_vtx, sink_vtx, m_transitive_closure).second;
478 }
479 void update(const graph_t& sub_graph)
480 {
481 m_transitive_closure = transitive_closure_t{};
482 ossia::transitive_closure(sub_graph, m_transitive_closure, m_tcState);
483
484#if defined(OSSIA_GRAPH_DEBUG)
485 auto vertices = boost::vertices(sub_graph);
486 for(auto i = vertices.first; i != vertices.second; i++)
487 {
488 tclos[*i] = sub_graph[*i].get();
489 assert(tclos[*i]);
490 }
491 print_graph(tclos, std::cout);
492#endif
493 }
494 transitive_closure_t m_transitive_closure;
495 ossia::TransitiveClosureState<graph_t, transitive_closure_t> m_tcState;
496};
497
498struct boost_tc
499{
500public:
501 using transitive_closure_t = boost::adjacency_list<
502 boost::vecS, boost::vecS, boost::directedS, ossia::graph_node*, int64_t>;
503
504 [[nodiscard]] bool has_edge(int source_vtx, int sink_vtx) const
505 {
506 return boost::edge(source_vtx, sink_vtx, m_transitive_closure).second;
507 }
508
509 void update(const graph_t& sub_graph)
510 {
511 m_transitive_closure = transitive_closure_t{};
512 boost::transitive_closure(sub_graph, m_transitive_closure);
513
514#if defined(OSSIA_GRAPH_DEBUG)
515 auto vertices = boost::vertices(sub_graph);
516 for(auto i = vertices.first; i != vertices.second; i++)
517 {
518 tclos[*i] = sub_graph[*i].get();
519 assert(tclos[*i]);
520 }
521 print_graph(tclos, std::cout);
522#endif
523 }
524
525private:
526 transitive_closure_t m_transitive_closure;
527};
528
529using tc_graph = graph_static<tc_update<fast_tc>, static_exec>;
530using bfs_graph = graph_static<bfs_update, static_exec>;
531
532using logged_tc_graph = graph_static<tc_update<fast_tc>, static_exec_logger>;
533}
Definition git_info.h:7
spdlog::logger & logger() noexcept
Where the errors will be logged. Default is stderr.
Definition context.cpp:120
std::ostream & print(std::ostream &out, const state_element &e)
print Print a state_element
Definition state_element.cpp:23