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