Loading...
Searching...
No Matches
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
39namespace oscr
40{
41
42template <typename Info>
44{
45};
46
47template <typename Info>
51
52namespace
53{
54struct dummy_ui_callback
55{
56 void operator()(const QByteArray& arr) noexcept { }
57};
58}
59
60template <avnd::has_processor_to_gui_bus Info>
62{
63 std::function<void(QByteArray)> to_ui = dummy_ui_callback{};
64};
65
66template <avnd::has_gui_to_processor_bus Info>
68{
69 std::function<void(QByteArray)> from_ui = dummy_ui_callback{};
70};
71
72template <typename Info>
73class ProcessModel final
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
83public:
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
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
158private:
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
452public:
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
712template <typename Info>
713struct is_custom_serialized<oscr::ProcessModel<Info>> : std::true_type
714{
715};
716
717template <typename Info>
718struct 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
773template <typename Info>
774struct 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
void insertDelimiter()
insertDelimiter
Definition DataStreamVisitor.hpp:156
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 score-plugin-avnd/Crousti/ProcessModel.hpp:49
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:44