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