score-plugin-avnd/Crousti/ProcessModel.hpp
1 #pragma once
2 #include <Process/GenericProcessFactory.hpp>
3 #include <Process/Process.hpp>
4 #include <Process/ProcessFactory.hpp>
5 
6 #include <Crousti/Attributes.hpp>
7 #include <Crousti/Concepts.hpp>
8 #include <Crousti/MessageBus.hpp>
9 #include <Crousti/Metadata.hpp>
10 #include <Crousti/Metadatas.hpp>
11 #include <Crousti/ProcessModelPortInit.hpp>
12 #include <Dataflow/Commands/CableHelpers.hpp>
13 
14 #include <score/serialization/MapSerialization.hpp>
15 #include <score/tools/std/HashMap.hpp>
16 
17 #include <ossia/detail/string_map.hpp>
18 #include <ossia/detail/type_if.hpp>
19 #include <ossia/detail/typelist.hpp>
20 
21 #include <boost/pfr.hpp>
22 
23 #include <QTimer>
24 
25 #include <avnd/binding/ossia/data_node.hpp>
26 #include <avnd/binding/ossia/dynamic_ports.hpp>
27 #include <avnd/common/for_nth.hpp>
28 #include <avnd/concepts/gfx.hpp>
29 #include <avnd/concepts/ui.hpp>
30 #include <avnd/introspection/messages.hpp>
31 #include <avnd/wrappers/bus_host_process_adapter.hpp>
32 
33 #include <score_plugin_engine.hpp>
34 
39 namespace oscr
40 {
41 
42 template <typename Info>
44 {
45 };
46 
47 template <typename Info>
49 {
50 };
51 
52 namespace
53 {
54 struct dummy_ui_callback
55 {
56  void operator()(const QByteArray& arr) noexcept { }
57 };
58 }
59 
60 template <avnd::has_processor_to_gui_bus Info>
62 {
63  std::function<void(QByteArray)> to_ui = dummy_ui_callback{};
64 };
65 
66 template <avnd::has_gui_to_processor_bus Info>
68 {
69  std::function<void(QByteArray)> from_ui = dummy_ui_callback{};
70 };
71 
72 template <typename Info>
73 class ProcessModel final
74  : public Process::ProcessModel
75  , public MessageBusWrapperFromUi<Info>
76  , public MessageBusWrapperToUi<Info>
77 {
78  SCORE_SERIALIZE_FRIENDS
79  PROCESS_METADATA_IMPL(ProcessModel<Info>)
80  friend struct TSerializer<DataStream, oscr::ProcessModel<Info>>;
81  friend struct TSerializer<JSONObject, oscr::ProcessModel<Info>>;
82 
83 public:
84  [[no_unique_address]]
85  oscr::dynamic_ports_storage<Info> dynamic_ports;
86 
87  [[no_unique_address]]
88  ossia::type_if<Info, oscr::has_dynamic_ports<Info>> object_storage_for_ports_callbacks;
89 
91  const TimeVal& duration, const Id<Process::ProcessModel>& id,
92  const score::DocumentContext& ctx, QObject* parent)
94  duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
95  {
96  metadata().setInstanceName(*this);
97 
98  init_common();
99  init_before_port_creation();
100  init_all_ports();
101  init_after_port_creation();
102  }
103 
104  ProcessModel(
105  const TimeVal& duration, const QString& custom,
106  const Id<Process::ProcessModel>& id, QObject* parent)
108  duration, id, Metadata<ObjectKey_k, ProcessModel>::get(), parent}
109  {
110  metadata().setInstanceName(*this);
111 
112  init_common();
113  init_before_port_creation();
114  init_all_ports();
115 
116  if constexpr(avnd::file_input_introspection<Info>::size > 0)
117  {
118  static constexpr auto idx
119  = avnd::file_input_introspection<Info>::index_to_field_index(0);
120  setupInitialStringPort(idx, custom);
121  }
122  else if constexpr(avnd::control_input_introspection<Info>::size > 0)
123  {
124  static constexpr auto idx
125  = avnd::control_input_introspection<Info>::index_to_field_index(0);
126  using type =
127  typename avnd::control_input_introspection<Info>::template nth_element<0>;
128  if constexpr(avnd::string_ish<decltype(type::value)>)
129  setupInitialStringPort(idx, custom);
130  }
131  init_after_port_creation();
132  }
133 
134  void setupInitialStringPort(int idx, const QString& custom) noexcept
135  {
136  Process::Inlet* port = avnd_input_idx_to_model_ports(idx)[0];
137  auto pp = safe_cast<Process::ControlInlet*>(port);
138 
139  if(pp->value().target<std::string>())
140  {
141  pp->setValue(custom.toStdString());
142  }
143  }
144 
145  template <typename Impl>
146  explicit ProcessModel(Impl& vis, QObject* parent)
147  : Process::ProcessModel{vis, parent}
148  {
149  init_common();
150  init_before_port_creation();
151  vis.writeTo(*this);
152  check_all_ports();
153  init_after_port_creation();
154  }
155 
156  ~ProcessModel() override { }
157 
158 private:
159  void init_common()
160  {
161  if constexpr(avnd::tag_loops_by_default<Info>)
162  setLoops(true);
163  }
164 
165  void init_before_port_creation() { init_dynamic_ports(); }
166  void init_after_port_creation() { init_controller_ports(); }
167  void check_all_ports()
168  {
169  if(std::ssize(m_inlets) != expected_input_ports()
170  || std::ssize(m_outlets) != expected_output_ports())
171  {
172  qDebug() << "Warning : process does not match spec: I " << m_inlets.size()
173  << "but expected: " << expected_input_ports() << " ; O "
174  << m_outlets.size() << "but expected: " << expected_output_ports();
175 
176  std::vector<Dataflow::SavedPort> m_oldInlets, m_oldOutlets;
177  for(auto& port : m_inlets)
178  m_oldInlets.emplace_back(
179  Dataflow::SavedPort{port->name(), port->type(), port->saveData()});
180  for(auto& port : m_outlets)
181  m_oldOutlets.emplace_back(
182  Dataflow::SavedPort{port->name(), port->type(), port->saveData()});
183 
184  qDeleteAll(m_inlets);
185  m_inlets.clear();
186  qDeleteAll(m_outlets);
187  m_outlets.clear();
188 
189  init_all_ports();
190 
191  Dataflow::reloadPortsInNewProcess(m_oldInlets, m_oldOutlets, *this);
192  }
193  }
194 
195  void init_controller_ports()
196  {
197  if constexpr(
198  avnd::dynamic_ports_input_introspection<Info>::size > 0
199  || avnd::dynamic_ports_output_introspection<Info>::size > 0)
200  {
201  avnd::control_input_introspection<Info>::for_all_n2(
202  avnd::get_inputs<Info>((Info&)this->object_storage_for_ports_callbacks),
203  [this]<std::size_t Idx, typename F>(
204  F& field, auto pred_index, avnd::field_index<Idx>) {
205  Info& obj = this->object_storage_for_ports_callbacks;
206  if constexpr(requires { F::on_controller_setup(); })
207  {
208  auto controller_inlets = avnd_input_idx_to_model_ports(Idx);
209  SCORE_ASSERT(controller_inlets.size() == 1);
210  auto inlet = qobject_cast<Process::ControlInlet*>(controller_inlets[0]);
211 
212  oscr::from_ossia_value(inlet->value(), field.value);
213 
214  if_possible(field.update(obj));
215 
216  F::on_controller_setup()(obj, field.value);
217  }
218  if constexpr(requires { F::on_controller_interaction(); })
219  {
220  auto controller_inlets = avnd_input_idx_to_model_ports(Idx);
221  SCORE_ASSERT(controller_inlets.size() == 1);
222  auto inlet = qobject_cast<Process::ControlInlet*>(controller_inlets[0]);
223  inlet->noValueChangeOnMove = true;
224  connect(
225  inlet, &Process::ControlInlet::valueChanged,
226  [this, &field](const ossia::value& val) {
227  Info& obj = this->object_storage_for_ports_callbacks;
228  oscr::from_ossia_value(val, field.value);
229 
230  if_possible(field.update(obj));
231 
232  F::on_controller_interaction()(obj, field.value);
233  });
234  }
235  });
236 
237  if constexpr(avnd::has_gui_to_processor_bus<Info>)
238  {
239  // FIXME needs to be a list of callbacks?
240  }
241 
242  if constexpr(avnd::has_processor_to_gui_bus<Info>)
243  {
244  Info& obj = this->object_storage_for_ports_callbacks;
245 
246  obj.send_message = [this]<typename T>(T&& b) mutable {
247  if(this->to_ui)
248  MessageBusSender{this->to_ui}(std::move(b));
249  };
250  }
251  }
252  }
253 
254  void init_dynamic_ports()
255  {
256  // To check:
257 
258  // - serialization
259  // - OK: uses of total_input_count / total_output_count
260 
261  // - OK: uses of oscr::modelPort (btw this does not seem to match total_input_count)
262 
263  // - execution, resize the avnd object with the number of ports
264  // -> connect controls so that they change in the ui
265 
266  // - OK: Layer::createControl: uses index_in_struct and assumes it's a ProcessModel::inlet index
267 
268  // How are things going to happen UI-wise: all controls created one after each other ?
269  // what if we want to create separate tabs with e.g.
270  // control A 1, control B 1
271  // control A 2, control B 2
272  // we need to know the current state of dynamic ports
273 
274  // UI: recreating UI causes crash when editing e.g. context menu of spinbox
275  // UI: recreating UI causes bug when editing e.g. spinbox
276 
277  // -> we have to find a way to detect that the currently edited object is being deleted
278 
279  // other option: mark widget as "unsafe", do not have it send value changes until mouse is released
280 
281  // REloading on crash: does crash, because the control change does not have
282  // the time to be applied. In DocumentBuilder::loadCommandStack we have
283  // to process the event loop in-between events.
284  // FIxed if we put zero but then changing the widget with the mouses crashes because of the comment
285  // below...
286  // ->
287  // terminate called after throwing an instance of 'std::runtime_error'
288  // what(): Ongoing command mismatch: current command SetControlValue does not match new command MoveNodes
289 
290  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
291  {
292  Info& obj = object_storage_for_ports_callbacks;
293  avnd::dynamic_ports_input_introspection<Info>::for_all_n2(
294  avnd::get_inputs(obj),
295  [&]<std::size_t N>(auto& port, auto pred_idx, avnd::field_index<N> field_idx) {
296  port.request_port_resize = [this, &port](int new_count) {
297  // We're in the ui thread, we can just push the request directly.
298  // With some delay as otherwise we may be deleting the widget we
299  // are clicking on before mouse release and Qt really doesn't like that
300  QTimer::singleShot(0, this, [self = QPointer{this}, &port, new_count] {
301  if(self)
302  self->request_new_dynamic_input_count(
303  port, avnd::field_index<N>{}, new_count);
304  });
305  };
306  });
307  }
308 
309  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
310  {
311  Info& obj = object_storage_for_ports_callbacks;
312  avnd::dynamic_ports_output_introspection<Info>::for_all_n2(
313  avnd::get_outputs(obj),
314  [&]<std::size_t N>(auto& port, auto pred_idx, avnd::field_index<N> field_idx) {
315  port.request_port_resize = [this, &port](int new_count) {
316  // We're in the ui thread, we can just push the request directly.
317  // With some delay as otherwise we may be deleting the widget we
318  // are clicking on before mouse release and Qt really doesn't like that
319  QTimer::singleShot(0, this, [self = QPointer{this}, &port, new_count] {
320  if(self)
321  self->request_new_dynamic_output_count(
322  port, avnd::field_index<N>{}, new_count);
323  });
324  };
325  });
326  }
327  }
328 
329  template <typename P, std::size_t N>
330  void request_new_dynamic_input_count(P& port, avnd::field_index<N> idx, int count)
331  {
332  const int current_model_ports = dynamic_ports.num_in_ports(idx);
333  if(current_model_ports == count || count < 0 || count > 512)
334  return;
335 
336  ossia::small_pod_vector<Process::Inlet*, 4> to_delete;
337  if(current_model_ports < count)
338  {
339  // Add new ports
340  // 1. Find the location where to add them
341  auto res = avnd_input_idx_to_iterator(idx);
342  res += current_model_ports;
343  Process::Inlets inlets_to_add;
344  InletInitFunc<Info> inlets{*this, inlets_to_add};
345  inlets.inlet = 10000 + N * 1000 + current_model_ports;
346  int sz = 0;
347  for(int i = current_model_ports; i < count; i++)
348  {
349  inlets(port, idx);
350  if(std::ssize(inlets_to_add) > sz)
351  {
352  sz = std::ssize(inlets_to_add);
353  auto new_inlet = inlets_to_add.back();
354  if(auto nm = new_inlet->name(); nm.contains("{}"))
355  {
356  nm.replace("{}", QString::number(i));
357  new_inlet->setName(nm);
358  }
359  }
360  }
361  m_inlets.insert(res, inlets_to_add.begin(), inlets_to_add.end());
362  }
363  else if(current_model_ports > count)
364  {
365  // Delete the ports
366  auto res = avnd_input_idx_to_iterator(idx);
367  res += count;
368  auto begin_deleted = res;
369  for(int i = 0; i < (current_model_ports - count); i++)
370  {
371  to_delete.push_back(*res);
372  ++res;
373  }
374  m_inlets.erase(begin_deleted, res);
375  }
376 
377  dynamic_ports.num_in_ports(idx) = count;
378 
379  inletsChanged();
380  for(auto port : to_delete)
381  delete port;
382  }
383 
384  template <typename P, std::size_t N>
385  void request_new_dynamic_output_count(P& port, avnd::field_index<N> idx, int count)
386  {
387  const int current_model_ports = dynamic_ports.num_out_ports(idx);
388  if(current_model_ports == count || count < 0 || count > 512)
389  return;
390 
391  ossia::small_pod_vector<Process::Outlet*, 4> to_delete;
392  if(current_model_ports < count)
393  {
394  // Add new ports
395  // 1. Find the location where to add them
396  auto res = avnd_output_idx_to_iterator(idx);
397  res += current_model_ports;
398  Process::Outlets outlets_to_add;
399  OutletInitFunc<Info> outlets{*this, outlets_to_add};
400  outlets.outlet = 1000000 + N * 1000 + current_model_ports;
401  int sz = 0;
402  for(int i = current_model_ports; i < count; i++)
403  {
404  outlets(port, idx);
405  if(std::ssize(outlets_to_add) > sz)
406  {
407  sz = std::ssize(outlets_to_add);
408  auto new_outlet = outlets_to_add.back();
409  if(auto nm = new_outlet->name(); nm.contains("{}"))
410  {
411  nm.replace("{}", QString::number(i));
412  new_outlet->setName(nm);
413  }
414  }
415  }
416  m_outlets.insert(res, outlets_to_add.begin(), outlets_to_add.end());
417  }
418  else if(current_model_ports > count)
419  {
420  // Delete the ports
421  auto res = avnd_output_idx_to_iterator(idx);
422  res += count;
423  auto begin_deleted = res;
424  for(int i = 0; i < (current_model_ports - count); i++)
425  {
426  to_delete.push_back(*res);
427  ++res;
428  }
429  m_outlets.erase(begin_deleted, res);
430  }
431 
432  dynamic_ports.num_out_ports(idx) = count;
433 
434  outletsChanged();
435  for(auto port : to_delete)
436  delete port;
437  }
438 
439  void init_all_ports()
440  {
441  InletInitFunc<Info> inlets{*this, m_inlets};
442  OutletInitFunc<Info> outlets{*this, m_outlets};
443  avnd::port_visit_dispatcher<Info>([&inlets]<typename P>(P&& port, auto idx) {
444  if constexpr(!avnd::dynamic_ports_port<P>)
445  inlets(port, idx);
446  }, [&outlets]<typename P>(P&& port, auto idx) {
447  if constexpr(!avnd::dynamic_ports_port<P>)
448  outlets(port, idx);
449  });
450  }
451 
452 public:
453  int expected_input_ports() const noexcept
454  {
455  int count = 0;
456 
457  // We have to adjust before accessing a port as there is the first "fake"
458  // port if the processor takes audio by argument
459  if constexpr(avnd::audio_argument_processor<Info>)
460  count += 1;
461  else if constexpr(avnd::tag_cv<Info>)
462  count += 1;
463 
464  // The "messages" ports also go before
465  count += avnd::messages_introspection<Info>::size;
466 
467  avnd::input_introspection<Info>::for_all([this, &count]<std::size_t Idx, typename P>(
468  avnd::field_reflection<Idx, P> field) {
469  int num_ports = 1;
470  if constexpr(avnd::dynamic_ports_port<P>)
471  num_ports = dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
472  count += num_ports;
473  });
474 
475  return count;
476  }
477 
478  int expected_output_ports() const noexcept
479  {
480  int count = 0;
481 
482  // We have to adjust before accessing a port as there is the first "fake"
483  // port if the processor takes audio by argument
484  if constexpr(avnd::audio_argument_processor<Info>)
485  count += 1;
486  else if constexpr(avnd::tag_cv<Info>)
487  {
488  using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
489  if constexpr(!std::is_void_v<operator_ret>)
490  count += 1;
491  }
492 
493  avnd::output_introspection<Info>::for_all(
494  [this,
495  &count]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
496  int num_ports = 1;
497  if constexpr(avnd::dynamic_ports_port<P>)
498  num_ports = dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
499  count += num_ports;
500  });
501 
502  return count;
503  }
504 
505  std::span<Process::Inlet*> avnd_input_idx_to_model_ports(int index) const noexcept
506  {
507  int model_index = 0;
508 
509  // We have to adjust before accessing a port as there is the first "fake"
510  // port if the processor takes audio by argument
511  if constexpr(avnd::audio_argument_processor<Info>)
512  model_index += 1;
513  else if constexpr(avnd::tag_cv<Info>)
514  model_index += 1;
515 
516  // The "messages" ports also go before
517  model_index += avnd::messages_introspection<Info>::size;
518 
519  std::span<Process::Inlet*> ret;
520  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size == 0)
521  {
522  ret = std::span<Process::Inlet*>(
523  const_cast<Process::Inlet**>(this->m_inlets.data()) + model_index + index, 1);
524  }
525  else
526  {
527  avnd::input_introspection<Info>::for_all(
528  [this, index, &model_index,
529  &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
530  if(Idx == index)
531  {
532  int num_ports = 1;
533  if constexpr(avnd::dynamic_ports_port<P>)
534  {
535  num_ports = dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
536  if(num_ports == 0)
537  {
538  ret = {};
539  return;
540  }
541  }
542  ret = std::span<Process::Inlet*>(
543  const_cast<Process::Inlet**>(this->m_inlets.data()) + model_index,
544  num_ports);
545  }
546  else
547  {
548  if constexpr(avnd::dynamic_ports_port<P>)
549  {
550  model_index += dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
551  }
552  else
553  {
554  model_index += 1;
555  }
556  }
557  });
558  }
559 
560  return ret;
561  }
562 
563  std::span<Process::Outlet*> avnd_output_idx_to_model_ports(int index) const noexcept
564  {
565  int model_index = 0;
566 
567  // We have to adjust before accessing a port as there is the first "fake"
568  // port if the processor takes audio by argument
569  if constexpr(avnd::audio_argument_processor<Info>)
570  model_index += 1;
571  else if constexpr(avnd::tag_cv<Info>)
572  {
573  using operator_ret = typename avnd::function_reflection_o<Info>::return_type;
574  if constexpr(!std::is_void_v<operator_ret>)
575  model_index += 1;
576  }
577 
578  // The "messages" ports also go before
579  model_index += avnd::messages_introspection<Info>::size;
580 
581  std::span<Process::Outlet*> ret;
582  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size == 0)
583  {
584  ret = std::span<Process::Outlet*>(
585  const_cast<Process::Outlet**>(this->m_outlets.data()) + model_index + index,
586  1);
587  }
588  else
589  {
590  avnd::output_introspection<Info>::for_all(
591  [this, index, &model_index,
592  &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
593  if(Idx == index)
594  {
595  int num_ports = 1;
596  if constexpr(avnd::dynamic_ports_port<P>)
597  {
598  num_ports = dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
599  if(num_ports == 0)
600  {
601  ret = {};
602  return;
603  }
604  }
605  ret = std::span<Process::Outlet*>(
606  const_cast<Process::Outlet**>(this->m_outlets.data()) + model_index,
607  num_ports);
608  }
609  else
610  {
611  if constexpr(avnd::dynamic_ports_port<P>)
612  {
613  model_index += dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
614  }
615  else
616  {
617  model_index += 1;
618  }
619  }
620  });
621  }
622 
623  return ret;
624  }
625 
626  Process::Inlets::iterator avnd_input_idx_to_iterator(int index) const noexcept
627  {
628  int model_index = 0;
629 
630  // We have to adjust before accessing a port as there is the first "fake"
631  // port if the processor takes audio by argument
632  if constexpr(avnd::audio_argument_processor<Info>)
633  model_index += 1;
634 
635  // The "messages" ports also go before
636  model_index += avnd::messages_introspection<Info>::size;
637 
638  Process::Inlets::iterator ret;
639  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size == 0)
640  {
641  ret = const_cast<ProcessModel*>(this)->m_inlets.begin() + model_index;
642  }
643  else
644  {
645  avnd::input_introspection<Info>::for_all(
646  [this, index, &model_index,
647  &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
648  if(Idx == index)
649  {
650  ret = const_cast<ProcessModel*>(this)->m_inlets.begin() + model_index;
651  }
652  else
653  {
654  if constexpr(avnd::dynamic_ports_port<P>)
655  {
656  model_index += dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
657  }
658  else
659  {
660  model_index += 1;
661  }
662  }
663  });
664  }
665  return ret;
666  }
667 
668  Process::Outlets::iterator avnd_output_idx_to_iterator(int index) const noexcept
669  {
670  int model_index = 0;
671 
672  // We have to adjust before accessing a port as there is the first "fake"
673  // port if the processor takes audio by argument
674  if constexpr(avnd::audio_argument_processor<Info>)
675  model_index += 1;
676 
677  // The "messages" ports also go before
678  model_index += avnd::messages_introspection<Info>::size;
679 
680  Process::Outlets::iterator ret;
681  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size == 0)
682  {
683  ret = const_cast<ProcessModel*>(this)->m_outlets.begin() + model_index;
684  }
685  else
686  {
687  avnd::output_introspection<Info>::for_all(
688  [this, index, &model_index,
689  &ret]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
690  if(Idx == index)
691  {
692  ret = const_cast<ProcessModel*>(this)->m_outlets.begin() + model_index;
693  }
694  else
695  {
696  if constexpr(avnd::dynamic_ports_port<P>)
697  {
698  model_index += dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
699  }
700  else
701  {
702  model_index += 1;
703  }
704  }
705  });
706  }
707  return ret;
708  }
709 };
710 }
711 
712 template <typename Info>
713 struct is_custom_serialized<oscr::ProcessModel<Info>> : std::true_type
714 {
715 };
716 
717 template <typename Info>
718 struct TSerializer<DataStream, oscr::ProcessModel<Info>>
719 {
721  static void readFrom(DataStream::Serializer& s, const model_type& obj)
722  {
723  Process::readPorts(s, obj.m_inlets, obj.m_outlets);
724 
725  // Save the recorded amount of dynamic ports for each port
726  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
727  {
728  avnd::dynamic_ports_input_introspection<Info>::for_all(
729  [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
730  if constexpr(avnd::dynamic_ports_port<P>)
731  s.stream() << obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
732  });
733  }
734  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
735  {
736  avnd::dynamic_ports_output_introspection<Info>::for_all(
737  [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
738  if constexpr(avnd::dynamic_ports_port<P>)
739  s.stream() << obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
740  });
741  }
742 
743  s.insertDelimiter();
744  }
745 
746  static void writeTo(DataStream::Deserializer& s, model_type& obj)
747  {
748  Process::writePorts(
749  s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
750  obj.m_outlets, &obj);
751 
752  // Read the recorded amount of dynamic ports for each port
753  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
754  {
755  avnd::dynamic_ports_input_introspection<Info>::for_all(
756  [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
757  if constexpr(avnd::dynamic_ports_port<P>)
758  s.stream() >> obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
759  });
760  }
761  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
762  {
763  avnd::dynamic_ports_output_introspection<Info>::for_all(
764  [&obj, &s]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
765  if constexpr(avnd::dynamic_ports_port<P>)
766  s.stream() >> obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
767  });
768  }
769  s.checkDelimiter();
770  }
771 };
772 
773 template <typename Info>
774 struct TSerializer<JSONObject, oscr::ProcessModel<Info>>
775 {
777  static void readFrom(JSONObject::Serializer& s, const model_type& obj)
778  {
779  Process::readPorts(s, obj.m_inlets, obj.m_outlets);
780  // Save the recorded amount of dynamic ports for each port
781  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
782  {
783  ossia::string_map<int> indices;
784  avnd::dynamic_ports_input_introspection<Info>::for_all(
785  [&obj,
786  &indices]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
787  if constexpr(avnd::dynamic_ports_port<P>)
788  {
789  indices[std::string(avnd::get_c_identifier<P>())]
790  = obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{});
791  }
792  });
793  s.obj["DynamicInlets"] = indices;
794  }
795  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
796  {
797  ossia::string_map<int> indices;
798  avnd::dynamic_ports_output_introspection<Info>::for_all(
799  [&obj,
800  &indices]<std::size_t Idx, typename P>(avnd::field_reflection<Idx, P> field) {
801  if constexpr(avnd::dynamic_ports_port<P>)
802  indices[std::string(avnd::get_c_identifier<P>())]
803  = obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{});
804  });
805  s.obj["DynamicOutlets"] = indices;
806  }
807  }
808 
809  static void writeTo(JSONObject::Deserializer& s, model_type& obj)
810  {
811  Process::writePorts(
812  s, s.components.interfaces<Process::PortFactoryList>(), obj.m_inlets,
813  obj.m_outlets, &obj);
814  if constexpr(avnd::dynamic_ports_input_introspection<Info>::size > 0)
815  {
816  if(auto val = s.obj.tryGet("DynamicInlets"))
817  {
818  ossia::string_map<int> indices;
819  indices <<= *val;
820  avnd::dynamic_ports_input_introspection<Info>::for_all(
821  [&obj, &indices]<std::size_t Idx, typename P>(
822  avnd::field_reflection<Idx, P> field) {
823  if constexpr(avnd::dynamic_ports_port<P>)
824  obj.dynamic_ports.num_in_ports(avnd::field_index<Idx>{})
825  = indices[std::string(avnd::get_c_identifier<P>())];
826  });
827  }
828  }
829  if constexpr(avnd::dynamic_ports_output_introspection<Info>::size > 0)
830  {
831  if(auto val = s.obj.tryGet("DynamicOutlets"))
832  {
833  ossia::string_map<int> indices;
834  indices <<= *val;
835  avnd::dynamic_ports_output_introspection<Info>::for_all(
836  [&obj, &indices]<std::size_t Idx, typename P>(
837  avnd::field_reflection<Idx, P> field) {
838  if constexpr(avnd::dynamic_ports_port<P>)
839  obj.dynamic_ports.num_out_ports(avnd::field_index<Idx>{})
840  = indices[std::string(avnd::get_c_identifier<P>())];
841  });
842  }
843  }
844  }
845 };
Definition: VisitorInterface.hpp:53
Definition: DataStreamVisitor.hpp:27
Definition: DataStreamVisitor.hpp:202
Definition: VisitorInterface.hpp:61
Definition: JSONVisitor.hpp:52
Definition: JSONVisitor.hpp:423
Definition: Port.hpp:177
Definition: Port.hpp:273
Definition: PortFactory.hpp:74
The Process class.
Definition: score-lib-process/Process/Process.hpp:61
The id_base_t class.
Definition: Identifier.hpp:57
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:77
Definition: Factories.hpp:19
Definition: CableHelpers.hpp:66
Static metadata implementation.
Definition: lib/score/tools/Metadata.hpp:36
Definition: PortForward.hpp:23
Definition: PortForward.hpp:27
Definition: VisitorInterface.hpp:13
Definition: TimeValue.hpp:21
Definition: ProcessModelPortInit.hpp:55
Definition: MessageBus.hpp:37
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:49
Definition: score-plugin-avnd/Crousti/ProcessModel.hpp:44
Definition: ProcessModelPortInit.hpp:277