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