259 return uuid_from_string<Node>();
266 return static_key() == other || Execution::ProcessComponent::base_key_match(other);
269#if defined(SCORE_PLUGIN_GFX)
270 [[no_unique_address]] ossia::type_if<int, is_gpu<Node>> node_id = score::gfx::invalid_node_index;
278 template <
typename F>
282 f(settings.getExecutionUpdate());
284 &settings, &Scenario::Settings::Model::ExecutionUpdateChanged,
this,
290 element, ctx,
"Executor::ProcessModel<Info>", p}
292 if constexpr(is_gpu<Node>)
295 setup_gpu(element, ctx, p);
300 setup_cpu(element, ctx, p);
303 if constexpr(avnd::tag_process_exec<Node>)
305 this->m_ossia_process = std::make_shared<CustomNodeProcess<Node>>(this->node);
309 this->m_ossia_process = std::make_shared<ossia::node_process>(this->node);
314 setup_cpu(ProcessModel<Node>& element, const ::Execution::Context& ctx, QObject* p)
317 = *ctx.doc.findPlugin<Explorer::DeviceDocumentPlugin>()->networkContext();
321 auto st = ossia::exec_state_facade{ctx.execState.get()};
322 std::shared_ptr<safe_node<Node>> ptr;
323 auto node =
new exec_node_t<Node>{st.setupBufferSize(), (double)st.sampleRate(),
id};
324 node->root_inputs().reserve(element.inlets().size());
325 node->root_outputs().reserve(element.outlets().size());
327 node->prepare(*ctx.execState.get());
329 if_possible(node->impl.effect.ossia_state = st);
330 if_possible(node->impl.effect.io_context = &net_ctx.context);
331 if_possible(node->impl.effect.ossia_document_context = &ctx.doc);
335 if constexpr(
requires { ptr->impl.effect; })
336 if constexpr(std::is_same_v<std::decay_t<
decltype(ptr->impl.effect)>,
Node>)
338 connect_message_bus(element, ctx, ptr->impl.effect, node->wakeups);
339 connect_dynamic_items(element, ptr->impl.effect);
343 if constexpr(
requires { element.setMonitor(ptr->impl.effect.monitor); })
345 [&element, monitor = ptr->impl.effect.monitor](
bool enabled) {
346 element.setMonitor(enabled ? monitor : nullptr);
351 node->dynamic_ports = element.dynamic_ports;
354 connect_controls(element, ctx, ptr);
355 update_controls(ptr);
358 node->audio_configuration_changed(st);
360 if constexpr(oscr::has_dynamic_ports<Node>)
363 &element, &Process::ProcessModel::inletsChanged,
this,
366 &element, &Process::ProcessModel::outletsChanged,
this,
369 this->m_oldInlets = element.inlets();
370 this->m_oldOutlets = element.outlets();
375 setup_gpu(ProcessModel<Node>& element, const ::Execution::Context& ctx, QObject* p)
385 if constexpr(
requires { Node::monitor; })
387 using monitor_type =
typename decltype(Node::monitor)::element_type;
389 [&element, monitor = std::make_shared<monitor_type>()](
bool enabled) {
390 element.setMonitor(enabled ? monitor : nullptr);
399 using Gfx::gfx_exec_node::gfx_exec_node;
400 std::string label() const noexcept
override
402 return std::string(avnd::get_name<Node>());
406 auto node = std::make_shared<named_exec_node>(gfx_exec);
407 node->prepare(*ctx.execState);
414 for(
auto& ctl : element.inlets())
416 if(
auto ctrl = qobject_cast<Process::ControlInlet*>(ctl))
418 auto& p = node->add_control();
419 p->value = gpuControlValue(*ctrl, ctrl->value(), ctx.doc);
423 p->changed = p->value.get_type() != ossia::val_type::IMPULSE;
428 ctrl->setupExecution(*node->root_inputs().back(),
this);
431 ctrl, &Process::ControlInlet::valueChanged,
this,
433 &doc = ctx.doc](
const ossia::value& v)
mutable {
434 con(gpuControlValue(*ctrl, v, doc));
438 else if(
auto ctrl = qobject_cast<Process::ValueInlet*>(ctl))
440 auto& p = node->add_control();
444 else if(
auto ctrl = qobject_cast<Process::AudioInlet*>(ctl))
448 else if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
450 ossia::texture_inlet& inl = *node->add_texture();
451 ctrl->setupExecution(inl,
this);
453 else if(
auto ctrl = qobject_cast<Gfx::GeometryInlet*>(ctl))
455 ossia::geometry_inlet& inl = *node->add_geometry();
456 ctrl->setupExecution(inl,
this);
461 for(
auto* outlet : element.outlets())
463 if(
auto ctrl = qobject_cast<Process::ControlOutlet*>(outlet))
465 node->add_control_out();
467 else if(
auto ctrl = qobject_cast<Process::ValueOutlet*>(outlet))
469 node->add_control_out();
471 else if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
473 node->add_texture_out();
475 else if(
auto out = qobject_cast<Gfx::GeometryOutlet*>(outlet))
477 node->add_geometry_out();
482 std::weak_ptr qex_ptr = std::shared_ptr<Execution::ExecutionCommandQueue>(
483 ctx.alias.lock(), &ctx.executionQueue);
484 std::unique_ptr<score::gfx::Node> ptr;
485 if constexpr(GpuGraphicsNode2<Node>)
487 auto gpu_node =
new CustomGpuNode<Node>(qex_ptr, node->control_outs,
id, ctx.doc);
490 else if constexpr(GpuComputeNode2<Node>)
492 auto gpu_node =
new GpuComputeNode<Node>(qex_ptr, node->control_outs,
id, ctx.doc);
495 else if constexpr(GpuNode<Node>)
498 =
new GfxNode<Node>(element, qex_ptr, node->control_outs,
id, ctx.doc);
503 for(
auto& ctl : element.inlets())
505 if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
507 ossia::texture_inlet& inl
508 =
static_cast<ossia::texture_inlet&
>(*node->root_inputs()[i]);
509 ptr->process(i, inl.data);
513 node->id = gfx_exec.ui->register_node(std::move(ptr));
518 if(!node->control_outs.empty())
520 std::vector<Process::ControlOutlet*> ui_outlets;
521 for(
auto* outlet : element.outlets())
522 if(auto ctl = qobject_cast<
Process::ControlOutlet*>(outlet))
523 ui_outlets.push_back(ctl);
524 else if(qobject_cast<Process::ValueOutlet*>(outlet))
525 ui_outlets.push_back(
nullptr);
527 if(
auto n = weak.lock())
528 n->notify_ui.store(enabled, std::memory_order_relaxed);
530 con(ctx.doc.coarseUpdateTimer, &QTimer::timeout,
this,
531 [weak = std::weak_ptr{node}, ui_outlets = std::move(ui_outlets),
532 v = ossia::value{}]()
mutable {
533 auto n = weak.lock();
534 if(!n || !n->notify_ui.load(std::memory_order_relaxed))
536 const int count = std::min(int(ui_outlets.size()), int(n->ui_control_outs.size()));
537 for(int i = 0; i < count; i++)
538 if(ui_outlets[i] && n->ui_control_outs[i]->latest.consume(v))
539 ui_outlets[i]->setValue(v);
540 }, Qt::QueuedConnection);
544 for(
auto* outlet : element.outlets())
545 if(auto out = qobject_cast<
Gfx::TextureOutlet*>(outlet))
546 out->nodeId = node_id;
564 if constexpr(oscr::has_dynamic_ports<Node> && !
is_gpu<Node>)
567 auto n = std::dynamic_pointer_cast<safe_node<Node>>(this->node);
571 auto& element = this->process();
583 for(
auto port : old_inlets)
584 if(!ossia::contains(new_inlets, port))
585 this->m_connectedControls.erase(port);
591 ossia::small_vector<Process::Cable*, 8> cables;
592 auto collect_cables = [&](
const auto& ports) {
593 for(
auto port : ports)
594 for(
auto& cbl : port->cables())
595 if(
auto c = cbl.try_find(ctx.doc))
596 if(setup.m_cables.find(c->id()) != setup.m_cables.end())
597 if(!ossia::contains(cables, c))
600 collect_cables(old_inlets);
601 collect_cables(old_outlets);
602 collect_cables(new_inlets);
603 collect_cables(new_outlets);
608 ossia::inlets new_inls_buffer;
609 ossia::outlets new_outls_buffer;
610 inlet_reload_storage<T> reload_inlet;
611 outlet_reload_storage<T> reload_outlet;
612 port_storage(std::size_t ins, std::size_t outs)
614 new_inls_buffer.reserve(ins);
615 new_outls_buffer.reserve(outs);
618 auto port_st = std::make_shared<port_storage>(new_inlets.size(), new_outlets.size());
619 auto& inbuf = port_st->new_inls_buffer;
620 auto& outbuf = port_st->new_outls_buffer;
625 n->audio_ports.init(inbuf, outbuf);
626 n->arg_value_ports.init(inbuf, outbuf);
627 n->message_ports.init(inbuf);
629 auto& dp = element.dynamic_ports;
630 if constexpr(avnd::inputs_type<T>::size > 0)
632 using in_info = avnd::input_introspection<T>;
633 using in_type =
typename avnd::inputs_type<T>::type;
634 auto& port_tuple = n->ossia_inlets.ports;
636 [&]<
typename K, K... Index>(std::integer_sequence<K, Index...>) {
637 reload_inlets<safe_node_base_base<T>> init{*n, inbuf, dp};
639 avnd::field_reflection<Index, avnd::pfr::tuple_element_t<Index, in_type>>{},
640 tuplet::get<Index>(port_tuple),
641 tuplet::get<Index>(port_st->reload_inlet.ports)),
643 }(
typename in_info::indices_n{});
646 if constexpr(avnd::outputs_type<T>::size > 0)
648 using out_info = avnd::output_introspection<T>;
649 using out_type =
typename avnd::outputs_type<T>::type;
650 auto& port_tuple = n->ossia_outlets.ports;
652 [&]<
typename K, K... Index>(std::integer_sequence<K, Index...>) {
653 reload_outlets<safe_node_base_base<T>> init{*n, outbuf, dp};
655 avnd::field_reflection<
656 Index, avnd::pfr::tuple_element_t<Index, out_type>>{},
657 tuplet::get<Index>(port_tuple),
658 tuplet::get<Index>(port_st->reload_outlet.ports)),
660 }(
typename out_info::indices_n{});
665 SCORE_SOFT_ASSERT(inbuf.size() == new_inlets.size());
666 SCORE_SOFT_ASSERT(outbuf.size() == new_outlets.size());
672 setup.removeCable(*c, commands);
675 setup.unregister_node_soft(old_inlets, old_outlets, this->node, commands);
679 [node = n, dp, port_st, gcq = ctx.weakGCQueue(),
680 wg = std::weak_ptr{ctx.execGraph}]()
mutable {
681 OSSIA_ENSURE_CURRENT_THREAD_KIND(ossia::thread_type::Audio);
687 if(
auto g = wg.lock())
689 const auto& inbuf = port_st->new_inls_buffer;
690 const auto& outbuf = port_st->new_outls_buffer;
691 for(
auto* p : node->root_inputs())
693 if(ossia::contains(inbuf, p))
695 const auto edges = p->sources;
699 for(
auto* p : node->root_outputs())
701 if(ossia::contains(outbuf, p))
703 const auto edges = p->targets;
709 node->dynamic_ports = dp;
710 node->reload_all_ports(port_st->reload_inlet, port_st->reload_outlet);
712 auto& inbuf = port_st->new_inls_buffer;
713 auto& outbuf = port_st->new_outls_buffer;
714 node->root_inputs().assign(inbuf.begin(), inbuf.end());
715 node->root_outputs().assign(outbuf.begin(), outbuf.end());
720 if constexpr(avnd::dynamic_ports_input_introspection<T>::size > 0)
722 for(
auto state : node->impl.full_state())
724 avnd::dynamic_ports_input_introspection<T>::for_all_n2(
725 state.inputs, [&]<std::size_t Idx>(
726 auto& field,
auto, avnd::field_index<Idx> idx) {
727 field.ports.resize(std::max(0, node->dynamic_ports.num_in_ports(idx)));
731 if constexpr(avnd::dynamic_ports_output_introspection<T>::size > 0)
733 for(
auto state : node->impl.full_state())
735 avnd::dynamic_ports_output_introspection<T>::for_all_n2(
736 state.outputs, [&]<std::size_t Idx>(
737 auto& field,
auto, avnd::field_index<Idx> idx) {
738 field.ports.resize(std::max(0, node->dynamic_ports.num_out_ports(idx)));
744 if(
auto q = gcq.lock())
745 q->enqueue(Execution::gc(std::move(port_st)));
749 setup.proc_map[this->node.get()] = &element;
750 const std::size_t n_in = std::min(new_inlets.size(), inbuf.size());
751 for(std::size_t i = 0; i < n_in; i++)
752 setup.register_inlet(*new_inlets[i], inbuf[i], this->node, commands);
754 const std::size_t n_out = std::min(new_outlets.size(), outbuf.size());
755 for(std::size_t i = 0; i < n_out; i++)
756 setup.register_outlet(*new_outlets[i], outbuf[i], this->node, commands);
761 setup.connectCable(*c, commands);
762 this->portsReplaced(&commands);
766 this->m_oldInlets = new_inlets;
767 this->m_oldOutlets = new_outlets;
770 using dynamic_ports_port_type = avnd::dynamic_ports_input_introspection<Node>;
771 if constexpr(dynamic_ports_port_type::size > 0)
773 avnd::effect_container<Node>& eff = n->impl;
774 for(
auto state : eff.full_state())
776 dynamic_ports_port_type::for_all_n2(
784 void connect_controls(
786 std::shared_ptr<safe_node<Node>>& ptr)
788 using dynamic_ports_port_type = avnd::dynamic_ports_input_introspection<Node>;
789 using control_inputs_type = avnd::control_input_introspection<Node>;
790 using curve_inputs_type = avnd::curve_input_introspection<Node>;
791 using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
792 using midifile_inputs_type = avnd::midifile_input_introspection<Node>;
793 using raw_file_inputs_type = avnd::raw_file_input_introspection<Node>;
794 using control_outputs_type = avnd::control_output_introspection<Node>;
797 safe_node<Node>& node = *ptr;
798 avnd::effect_container<Node>& eff = node.impl;
803 if constexpr(dynamic_ports_port_type::size > 0)
805 for(
auto state : eff.full_state())
807 dynamic_ports_port_type::for_all_n2(
811 if constexpr(control_inputs_type::size > 0)
813 for(
auto state : eff.full_state())
815 control_inputs_type::for_all_n2(
816 state.inputs, dispatch_control_setup<Node>{element, ctx, ptr, *this, this});
819 if constexpr(curve_inputs_type::size > 0)
821 for(
auto state : eff.full_state())
823 curve_inputs_type::for_all_n2(
824 state.inputs, dispatch_control_setup<Node>{element, ctx, ptr, *this, this});
827 if constexpr(soundfile_inputs_type::size > 0)
829 soundfile_inputs_type::for_all_n2(
830 avnd::get_inputs<Node>(eff),
831 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
833 setup_soundfile_task_pool(element, ctx, ptr);
835 if constexpr(midifile_inputs_type::size > 0)
837 midifile_inputs_type::for_all_n2(
838 avnd::get_inputs<Node>(eff),
839 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
841 if constexpr(raw_file_inputs_type::size > 0)
843 raw_file_inputs_type::for_all_n2(
844 avnd::get_inputs<Node>(eff),
845 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
849 if constexpr(control_inputs_type::size > 0 || control_outputs_type::size > 0)
851 std::weak_ptr<safe_node<Node>> weak_node = ptr;
852 update_control_value_in_ui<Node> timer_action{weak_node, &element};
854 [
this, weak_node, timer_action, &ctx,
855 timer = std::make_shared<QMetaObject::Connection>()](
bool enabled) {
859 if constexpr(control_outputs_type::size > 0)
860 if(
auto n = weak_node.lock())
861 n->control.notify_ui.store(enabled, std::memory_order_relaxed);
863 QObject::disconnect(*timer);
867 *timer = QObject::connect(
868 &ctx.doc.coarseUpdateTimer, &QTimer::timeout,
this,
869 [timer_action] { timer_action(); }, Qt::QueuedConnection);
875 void setup_soundfile_task_pool(
876 ProcessModel<Node>& element, const ::Execution::Context& ctx,
877 std::shared_ptr<safe_node<Node>>& ptr)
879 safe_node<Node>& node = *ptr;
881 using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
883 auto& tq = score::TaskPool::instance();
884 node.soundfiles.load_request
885 = [&tq, p = std::weak_ptr{ptr}, &ctx](std::string& str,
int idx) {
886 auto eff_ptr = p.lock();
889 tq.post([eff_ptr = std::move(eff_ptr), filename = str, &ctx, idx]()
mutable {
890 if(
auto file = loadSoundfile(filename, ctx.doc, ctx.execState))
893 QMetaObject::invokeMethod(
894 QCoreApplication::instance(),
895 [&ctx, file = std::move(file), eff_ptr = std::move(eff_ptr), idx = idx] {
896 ctx.executionQueue.enqueue([sf = std::move(file), p = std::weak_ptr{eff_ptr},
897 gcq = ctx.weakGCQueue(), idx]()
mutable {
898 if(
auto eff_ptr = p.lock())
900 avnd::effect_container<Node>& eff = eff_ptr->impl;
901 soundfile_inputs_type::for_nth_mapped_n2(
902 avnd::get_inputs<Node>(eff), idx,
903 [&]<std::size_t NField, std::size_t N>(
904 auto& field, avnd::predicate_index<N> p,
905 avnd::field_index<NField> f) {
906 sf = eff_ptr->soundfile_loaded(sf, p, f);
918 void connect_dynamic_items(ProcessModel<Node>& element, Node& eff)
922 avnd::inputs_is_type<Node>
923 || avnd::control_input_introspection<Node>::size == 0)
926 avnd::control_input_introspection<Node>::for_all_n2(
927 avnd::get_inputs<Node>(eff),
928 [&element]<std::size_t Idx,
typename F>(
929 F& field,
auto pred_index, avnd::field_index<Idx>) {
930 if constexpr(avnd::dynamic_items_parameter<F>)
932 auto ports = element.avnd_input_idx_to_model_ports(Idx);
933 if(ports.size() != 1)
935 if(
auto combo = qobject_cast<Process::ComboBox*>(ports[0]))
937 field.update_items = [p = QPointer<Process::ComboBox>{combo}](
938 std::vector<std::string> items) {
939 std::vector<std::pair<QString, ossia::value>> alts;
940 alts.reserve(items.size());
941 for(std::size_t i = 0; i < items.size(); i++)
942 alts.emplace_back(QString::fromStdString(items[i]), (int)i);
943 QMetaObject::invokeMethod(
945 [p, alts = std::move(alts)]()
mutable {
947 p->setAlternatives(std::move(alts));
949 Qt::QueuedConnection);
956 void connect_message_bus(
957 ProcessModel<Node>& element, const ::Execution::Context& ctx, Node& eff,
961 if constexpr(avnd::has_gui_to_processor_bus<Node>)
971 if constexpr(avnd::has_processor_to_gui_bus<Node>)
973 if constexpr(
requires { eff.send_message = [](
auto&&) { }; })
975 using send_type = std::remove_cvref_t<
decltype(eff.send_message)>;
977 = std::remove_cvref_t<typename function_argument<send_type>::type>;
979 sizeof(QPointer<QObject>) +
sizeof(message_type)
980 < Execution::ExecutionCommand::max_storage)
982 eff.send_message = [proc = QPointer{&this->process()},
983 qed_ptr = weak_edit]<
typename T>(T&& b)
mutable {
984 auto qed = qed_ptr.lock();
987 qed->try_enqueue([proc, bb = message_type(std::forward<T>(b))]()
mutable {
988 if(proc && proc->to_ui)
989 MessageBusSender{proc->to_ui}(std::move(bb));
996 std::is_default_constructible_v<message_type>,
997 "a processor to UI message is stored in a preallocated ring");
998 auto ring = std::make_shared<message_ring<message_type>>();
1000 auto wake = [proc = QPointer{&this->process()}, qed_ptr = weak_edit, ring] {
1001 auto qed = qed_ptr.lock();
1007 if(qed->try_enqueue([proc, ring]()
mutable {
1008 ring->drain([&](message_type&& m) {
1009 if(proc && proc->to_ui)
1010 MessageBusSender{proc->to_ui}(std::move(m));
1017 wakeups.add([ring, wake] {
1018 if(!ring->unpark() || !ring->request())
1023 eff.send_message = [qed_ptr = weak_edit, ring, wake,
1024 wakeups = &wakeups]<
typename T>(T&& b)
mutable {
1025 if(qed_ptr.expired())
1027 if(!ring->push(message_type(std::forward<T>(b))))
1029 if(!ring->request())
1036 else if constexpr(
requires { eff.send_message = []() { }; })
1039 = [proc = QPointer{&this->process()}, qed_ptr = weak_edit]()
mutable {
1042 auto qed = qed_ptr.lock();
1046 qed->try_enqueue([proc]()
mutable {
1047 if(proc && proc->to_ui)
1048 MessageBusSender{proc->to_ui}();
1058 if constexpr(
requires { eff.send_message_from_worker = [](
auto&&) { }; })
1060 using send_type = std::remove_cvref_t<
decltype(eff.send_message_from_worker)>;
1062 = std::remove_cvref_t<typename function_argument<send_type>::type>;
1063 eff.send_message_from_worker = [proc = QPointer{&this->process()}](message_type m) {
1064 ossia::qt::run_async(qApp, [proc, m = std::move(m)]()
mutable {
1065 if(proc && proc->to_ui)
1066 MessageBusSender{proc->to_ui}(std::move(m));
1073 void connect_worker(
const std::shared_ptr<safe_node<Node>>& node_ptr)
1075 if constexpr(avnd::has_worker<Node>)
1077 avnd::effect_container<Node>& eff = node_ptr->impl;
1080 score::TaskPool::instance();
1084 const std::shared_ptr<node_with_worker<Node>> self{
1085 node_ptr,
static_cast<node_with_worker<Node>*
>(node_ptr.get())};
1087 for(
auto& e : eff.effects())
1089 e,
std::weak_ptr{
std::shared_ptr<
Node>(node_ptr, &e)},
1090 exec_worker_delivery<
Node>{self});
1095 void update_controls(std::shared_ptr<safe_node<Node>>& ptr)
1097 avnd::effect_container<Node>& eff = ptr->impl;
1099 for(
auto state : eff.full_state())
1101 avnd::input_introspection<Node>::for_all(state.inputs, [&](
auto& field) {
1102 avnd::update_if_holding_value(state, field);
1108 void cleanup()
override
1110 if constexpr(
requires { this->process().from_ui; })
1112 this->process().from_ui = [](QByteArray arr) {};
1115 for(
auto& c : m_executionUpdate)
1116 QObject::disconnect(c);
1117 m_executionUpdate.clear();
1118 if constexpr(
requires { Node::monitor; })
1119 this->process().setMonitor({});
1122 if constexpr(is_gpu<Node>)
1125 auto& gfx_exec = this->system().doc.template plugin<Gfx::DocumentPlugin>().exec;
1128 gfx_exec.ui->unregister_node(node_id);
1129 node_id = score::gfx::invalid_node_index;
1134 for(
auto* outlet : this->process().outlets())
1136 if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
1142 ::Execution::ProcessComponent::cleanup();