205 return uuid_from_string<Node>();
212 return static_key() == other || Execution::ProcessComponent::base_key_match(other);
215#if defined(SCORE_PLUGIN_GFX)
216 [[no_unique_address]] ossia::type_if<int, is_gpu<Node>> node_id = score::gfx::invalid_node_index;
221 element, ctx,
"Executor::ProcessModel<Info>", p}
226 setup_gpu(element, ctx, p);
231 setup_cpu(element, ctx, p);
234 if constexpr(avnd::tag_process_exec<Node>)
236 this->m_ossia_process = std::make_shared<CustomNodeProcess<Node>>(this->node);
240 this->m_ossia_process = std::make_shared<ossia::node_process>(this->node);
248 = *ctx.doc.findPlugin<Explorer::DeviceDocumentPlugin>()->networkContext();
252 auto st = ossia::exec_state_facade{ctx.execState.get()};
253 std::shared_ptr<safe_node<Node>> ptr;
255 node->root_inputs().reserve(element.inlets().size());
256 node->root_outputs().reserve(element.outlets().size());
258 node->prepare(*ctx.execState.get());
260 if_possible(node->impl.effect.ossia_state = st);
261 if_possible(node->impl.effect.io_context = &net_ctx.context);
262 if_possible(node->impl.effect.ossia_document_context = &ctx.doc);
266 if constexpr(
requires { ptr->impl.effect; })
267 if constexpr(std::is_same_v<std::decay_t<
decltype(ptr->impl.effect)>, Node>)
269 connect_message_bus(element, ctx, ptr->impl.effect);
270 connect_dynamic_items(element, ptr->impl.effect);
274 node->dynamic_ports = element.dynamic_ports;
277 connect_controls(element, ctx, ptr);
278 update_controls(ptr);
281 node->audio_configuration_changed(st);
283 if constexpr(oscr::has_dynamic_ports<Node>)
286 &element, &Process::ProcessModel::inletsChanged,
this,
289 &element, &Process::ProcessModel::outletsChanged,
this,
292 this->m_oldInlets = element.inlets();
293 this->m_oldOutlets = element.outlets();
310 using Gfx::gfx_exec_node::gfx_exec_node;
311 std::string label()
const noexcept override
313 return std::string(avnd::get_name<Node>());
317 auto node = std::make_shared<named_exec_node>(gfx_exec);
318 node->prepare(*ctx.execState);
325 for(
auto& ctl : element.inlets())
327 if(
auto ctrl = qobject_cast<Process::ControlInlet*>(ctl))
329 auto& p = node->add_control();
330 p->value = gpuControlValue(*ctrl, ctrl->value(), ctx.doc);
334 p->changed = p->value.get_type() != ossia::val_type::IMPULSE;
339 ctrl->setupExecution(*node->root_inputs().back(),
this);
342 ctrl, &Process::ControlInlet::valueChanged,
this,
344 &doc = ctx.doc](
const ossia::value& v)
mutable {
345 con(gpuControlValue(*ctrl, v, doc));
349 else if(
auto ctrl = qobject_cast<Process::ValueInlet*>(ctl))
351 auto& p = node->add_control();
355 else if(
auto ctrl = qobject_cast<Process::AudioInlet*>(ctl))
359 else if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
361 ossia::texture_inlet& inl = *node->add_texture();
362 ctrl->setupExecution(inl,
this);
364 else if(
auto ctrl = qobject_cast<Gfx::GeometryInlet*>(ctl))
366 ossia::geometry_inlet& inl = *node->add_geometry();
367 ctrl->setupExecution(inl,
this);
372 for(
auto* outlet : element.outlets())
374 if(
auto ctrl = qobject_cast<Process::ControlOutlet*>(outlet))
376 node->add_control_out();
378 else if(
auto ctrl = qobject_cast<Process::ValueOutlet*>(outlet))
380 node->add_control_out();
382 else if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
384 node->add_texture_out();
385 out->nodeId = node_id;
387 else if(
auto out = qobject_cast<Gfx::GeometryOutlet*>(outlet))
389 node->add_geometry_out();
394 std::weak_ptr qex_ptr = std::shared_ptr<Execution::ExecutionCommandQueue>(
395 ctx.alias.lock(), &ctx.executionQueue);
396 std::unique_ptr<score::gfx::Node> ptr;
399 auto gpu_node =
new CustomGpuNode<Node>(qex_ptr, node->control_outs,
id, ctx.doc);
404 auto gpu_node =
new GpuComputeNode<Node>(qex_ptr, node->control_outs,
id, ctx.doc);
410 =
new GfxNode<Node>(element, qex_ptr, node->control_outs,
id, ctx.doc);
415 for(
auto& ctl : element.inlets())
417 if(
auto ctrl = qobject_cast<Gfx::TextureInlet*>(ctl))
419 ossia::texture_inlet& inl
420 =
static_cast<ossia::texture_inlet&
>(*node->root_inputs()[i]);
421 ptr->process(i, inl.data);
425 node->id = gfx_exec.ui->register_node(std::move(ptr));
444 if constexpr(oscr::has_dynamic_ports<Node> && !
is_gpu<Node>)
447 auto n = std::dynamic_pointer_cast<safe_node<Node>>(this->node);
451 auto& element = this->process();
463 for(
auto port : old_inlets)
464 if(!ossia::contains(new_inlets, port))
465 this->m_connectedControls.erase(port);
471 ossia::small_vector<Process::Cable*, 8> cables;
472 auto collect_cables = [&](
const auto& ports) {
473 for(
auto port : ports)
474 for(
auto& cbl : port->cables())
475 if(
auto c = cbl.try_find(ctx.doc))
476 if(setup.m_cables.find(c->id()) != setup.m_cables.end())
477 if(!ossia::contains(cables, c))
480 collect_cables(old_inlets);
481 collect_cables(old_outlets);
482 collect_cables(new_inlets);
483 collect_cables(new_outlets);
488 ossia::inlets new_inls_buffer;
489 ossia::outlets new_outls_buffer;
490 inlet_reload_storage<T> reload_inlet;
491 outlet_reload_storage<T> reload_outlet;
492 port_storage(std::size_t ins, std::size_t outs)
494 new_inls_buffer.reserve(ins);
495 new_outls_buffer.reserve(outs);
498 auto port_st = std::make_shared<port_storage>(new_inlets.size(), new_outlets.size());
499 auto& inbuf = port_st->new_inls_buffer;
500 auto& outbuf = port_st->new_outls_buffer;
505 n->audio_ports.init(inbuf, outbuf);
506 n->arg_value_ports.init(inbuf, outbuf);
507 n->message_ports.init(inbuf);
509 auto& dp = element.dynamic_ports;
510 if constexpr(avnd::inputs_type<T>::size > 0)
512 using in_info = avnd::input_introspection<T>;
513 using in_type =
typename avnd::inputs_type<T>::type;
514 auto& port_tuple = n->ossia_inlets.ports;
516 [&]<
typename K, K... Index>(std::integer_sequence<K, Index...>) {
517 reload_inlets<safe_node_base_base<T>> init{*n, inbuf, dp};
519 avnd::field_reflection<Index, avnd::pfr::tuple_element_t<Index, in_type>>{},
520 tuplet::get<Index>(port_tuple),
521 tuplet::get<Index>(port_st->reload_inlet.ports)),
523 }(
typename in_info::indices_n{});
526 if constexpr(avnd::outputs_type<T>::size > 0)
528 using out_info = avnd::output_introspection<T>;
529 using out_type =
typename avnd::outputs_type<T>::type;
530 auto& port_tuple = n->ossia_outlets.ports;
532 [&]<
typename K, K... Index>(std::integer_sequence<K, Index...>) {
533 reload_outlets<safe_node_base_base<T>> init{*n, outbuf, dp};
535 avnd::field_reflection<
536 Index, avnd::pfr::tuple_element_t<Index, out_type>>{},
537 tuplet::get<Index>(port_tuple),
538 tuplet::get<Index>(port_st->reload_outlet.ports)),
540 }(
typename out_info::indices_n{});
545 SCORE_SOFT_ASSERT(inbuf.size() == new_inlets.size());
546 SCORE_SOFT_ASSERT(outbuf.size() == new_outlets.size());
552 setup.removeCable(*c, commands);
555 setup.unregister_node_soft(old_inlets, old_outlets, this->node, commands);
559 [node = n, dp, port_st, gcq = ctx.weakGCQueue(),
560 wg = std::weak_ptr{ctx.execGraph}]()
mutable {
561 OSSIA_ENSURE_CURRENT_THREAD_KIND(ossia::thread_type::Audio);
567 if(
auto g = wg.lock())
569 const auto& inbuf = port_st->new_inls_buffer;
570 const auto& outbuf = port_st->new_outls_buffer;
571 for(
auto* p : node->root_inputs())
573 if(ossia::contains(inbuf, p))
575 const auto edges = p->sources;
579 for(
auto* p : node->root_outputs())
581 if(ossia::contains(outbuf, p))
583 const auto edges = p->targets;
589 node->dynamic_ports = dp;
590 node->reload_all_ports(port_st->reload_inlet, port_st->reload_outlet);
592 auto& inbuf = port_st->new_inls_buffer;
593 auto& outbuf = port_st->new_outls_buffer;
594 node->root_inputs().assign(inbuf.begin(), inbuf.end());
595 node->root_outputs().assign(outbuf.begin(), outbuf.end());
600 if constexpr(avnd::dynamic_ports_input_introspection<T>::size > 0)
602 for(
auto state : node->impl.full_state())
604 avnd::dynamic_ports_input_introspection<T>::for_all_n2(
605 state.inputs, [&]<std::size_t Idx>(
606 auto& field,
auto, avnd::field_index<Idx> idx) {
607 field.ports.resize(std::max(0, node->dynamic_ports.num_in_ports(idx)));
611 if constexpr(avnd::dynamic_ports_output_introspection<T>::size > 0)
613 for(
auto state : node->impl.full_state())
615 avnd::dynamic_ports_output_introspection<T>::for_all_n2(
616 state.outputs, [&]<std::size_t Idx>(
617 auto& field,
auto, avnd::field_index<Idx> idx) {
618 field.ports.resize(std::max(0, node->dynamic_ports.num_out_ports(idx)));
624 if(
auto q = gcq.lock())
625 q->enqueue(Execution::gc(std::move(port_st)));
629 setup.proc_map[this->node.get()] = &element;
630 const std::size_t n_in = std::min(new_inlets.size(), inbuf.size());
631 for(std::size_t i = 0; i < n_in; i++)
632 setup.register_inlet(*new_inlets[i], inbuf[i], this->node, commands);
634 const std::size_t n_out = std::min(new_outlets.size(), outbuf.size());
635 for(std::size_t i = 0; i < n_out; i++)
636 setup.register_outlet(*new_outlets[i], outbuf[i], this->node, commands);
641 setup.connectCable(*c, commands);
642 this->portsReplaced(&commands);
646 this->m_oldInlets = new_inlets;
647 this->m_oldOutlets = new_outlets;
650 using dynamic_ports_port_type = avnd::dynamic_ports_input_introspection<Node>;
651 if constexpr(dynamic_ports_port_type::size > 0)
653 avnd::effect_container<Node>& eff = n->impl;
654 for(
auto state : eff.full_state())
656 dynamic_ports_port_type::for_all_n2(
664 void connect_controls(
666 std::shared_ptr<safe_node<Node>>& ptr)
668 using dynamic_ports_port_type = avnd::dynamic_ports_input_introspection<Node>;
669 using control_inputs_type = avnd::control_input_introspection<Node>;
670 using curve_inputs_type = avnd::curve_input_introspection<Node>;
671 using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
672 using midifile_inputs_type = avnd::midifile_input_introspection<Node>;
673 using raw_file_inputs_type = avnd::raw_file_input_introspection<Node>;
674 using control_outputs_type = avnd::control_output_introspection<Node>;
677 safe_node<Node>& node = *ptr;
678 avnd::effect_container<Node>& eff = node.impl;
683 if constexpr(dynamic_ports_port_type::size > 0)
685 for(
auto state : eff.full_state())
687 dynamic_ports_port_type::for_all_n2(
691 if constexpr(control_inputs_type::size > 0)
693 for(
auto state : eff.full_state())
695 control_inputs_type::for_all_n2(
696 state.inputs, dispatch_control_setup<Node>{element, ctx, ptr, *this, this});
699 if constexpr(curve_inputs_type::size > 0)
701 for(
auto state : eff.full_state())
703 curve_inputs_type::for_all_n2(
704 state.inputs, dispatch_control_setup<Node>{element, ctx, ptr, *this, this});
707 if constexpr(soundfile_inputs_type::size > 0)
709 soundfile_inputs_type::for_all_n2(
710 avnd::get_inputs<Node>(eff),
711 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
713 setup_soundfile_task_pool(element, ctx, ptr);
715 if constexpr(midifile_inputs_type::size > 0)
717 midifile_inputs_type::for_all_n2(
718 avnd::get_inputs<Node>(eff),
719 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
721 if constexpr(raw_file_inputs_type::size > 0)
723 raw_file_inputs_type::for_all_n2(
724 avnd::get_inputs<Node>(eff),
725 dispatch_control_setup<Node>{element, ctx, ptr, *
this,
this});
729 if constexpr(control_inputs_type::size > 0 || control_outputs_type::size > 0)
736 if constexpr(control_outputs_type::size > 0)
737 ptr->control.notify_ui.store(
738 settings.getExecutionUpdate(), std::memory_order_relaxed);
740 if(settings.getExecutionUpdate())
743 std::weak_ptr<safe_node<Node>> weak_node = ptr;
744 update_control_value_in_ui<Node> timer_action{weak_node, &element};
747 con(ctx.doc.coarseUpdateTimer, &QTimer::timeout,
this,
748 [timer_action = std::move(timer_action)] { timer_action(); },
749 Qt::QueuedConnection);
754 void setup_soundfile_task_pool(
755 ProcessModel<Node>& element, const ::Execution::Context& ctx,
756 std::shared_ptr<safe_node<Node>>& ptr)
758 safe_node<Node>& node = *ptr;
760 using soundfile_inputs_type = avnd::soundfile_input_introspection<Node>;
762 auto& tq = score::TaskPool::instance();
763 node.soundfiles.load_request
764 = [&tq, p = std::weak_ptr{ptr}, &ctx](std::string& str,
int idx) {
765 auto eff_ptr = p.lock();
768 tq.post([eff_ptr = std::move(eff_ptr), filename = str, &ctx, idx]()
mutable {
769 if(
auto file = loadSoundfile(filename, ctx.doc, ctx.execState))
772 QMetaObject::invokeMethod(
773 QCoreApplication::instance(),
774 [&ctx, file = std::move(file), eff_ptr = std::move(eff_ptr), idx = idx] {
775 ctx.executionQueue.enqueue(
776 [sf = std::move(file), p = std::weak_ptr{eff_ptr}, idx]()
mutable {
777 auto eff_ptr = p.lock();
781 avnd::effect_container<Node>& eff = eff_ptr->impl;
782 soundfile_inputs_type::for_nth_mapped_n2(
783 avnd::get_inputs<Node>(eff), idx,
784 [&]<std::size_t NField, std::size_t N>(
785 auto& field, avnd::predicate_index<N> p,
786 avnd::field_index<NField> f) {
787 sf = eff_ptr->soundfile_loaded(sf, p, f);
796 void connect_dynamic_items(ProcessModel<Node>& element, Node& eff)
800 avnd::inputs_is_type<Node>
801 || avnd::control_input_introspection<Node>::size == 0)
804 avnd::control_input_introspection<Node>::for_all_n2(
805 avnd::get_inputs<Node>(eff),
806 [&element]<std::size_t Idx,
typename F>(
807 F& field,
auto pred_index, avnd::field_index<Idx>) {
808 if constexpr(avnd::dynamic_items_parameter<F>)
810 auto ports = element.avnd_input_idx_to_model_ports(Idx);
811 if(ports.size() != 1)
813 if(
auto combo = qobject_cast<Process::ComboBox*>(ports[0]))
815 field.update_items = [p = QPointer<Process::ComboBox>{combo}](
816 std::vector<std::string> items) {
817 std::vector<std::pair<QString, ossia::value>> alts;
818 alts.reserve(items.size());
819 for(std::size_t i = 0; i < items.size(); i++)
820 alts.emplace_back(QString::fromStdString(items[i]), (int)i);
821 QMetaObject::invokeMethod(
823 [p, alts = std::move(alts)]()
mutable {
825 p->setAlternatives(std::move(alts));
827 Qt::QueuedConnection);
834 void connect_message_bus(
835 ProcessModel<Node>& element, const ::Execution::Context& ctx, Node& eff)
838 if constexpr(avnd::has_gui_to_processor_bus<Node>)
840 element.from_ui = [qex_ptr = weak_exec, &eff](QByteArray b) {
841 auto qex = qex_ptr.lock();
845 qex->enqueue([mess = std::move(b), &eff]()
mutable {
846 using refl = avnd::function_reflection<&Node::process_message>;
847 static_assert(refl::count <= 1);
849 if constexpr(refl::count == 0)
852 eff.process_message();
854 else if constexpr(refl::count == 1)
856 using arg_type = avnd::first_argument<&Node::process_message>;
857 std::decay_t<arg_type> arg;
858 MessageBusReader reader{mess};
860 eff.process_message(std::move(arg));
866 if constexpr(avnd::has_processor_to_gui_bus<Node>)
868 if constexpr(
requires { eff.send_message = [](
auto&&) { }; })
870 eff.send_message = [proc = QPointer{&this->process()},
871 qed_ptr = weak_edit]<
typename T>(T&& b)
mutable {
872 auto qed = qed_ptr.lock();
876 sizeof(QPointer<QObject>) +
sizeof(b)
877 < Execution::ExecutionCommand::max_storage)
879 qed->enqueue([proc, bb = std::move(b)]()
mutable {
880 if(proc && proc->to_ui)
881 MessageBusSender{proc->to_ui}(std::move(bb));
887 [proc, bb = std::make_unique<std::decay_t<T>>(std::move(b))]()
mutable {
888 if(proc && proc->to_ui)
889 MessageBusSender{proc->to_ui}(*std::move(bb));
894 else if constexpr(
requires { eff.send_message = []() { }; })
897 = [proc = QPointer{&this->process()}, qed_ptr = weak_edit]()
mutable {
900 auto qed = qed_ptr.lock();
904 qed->enqueue([proc]()
mutable {
905 if(proc && proc->to_ui)
906 MessageBusSender{proc->to_ui}();
913 void connect_worker(
const std::shared_ptr<safe_node<Node>>& node_ptr)
915 if constexpr(avnd::has_worker<Node>)
917 avnd::effect_container<Node>& eff = node_ptr->impl;
920 score::TaskPool::instance();
924 const std::shared_ptr<node_with_worker<Node>> self{
925 node_ptr,
static_cast<node_with_worker<Node>*
>(node_ptr.get())};
927 for(
auto& e : eff.effects())
929 e,
std::weak_ptr{
std::shared_ptr<
Node>(node_ptr, &e)},
930 exec_worker_delivery<
Node>{self});
935 void update_controls(std::shared_ptr<safe_node<Node>>& ptr)
937 avnd::effect_container<Node>& eff = ptr->impl;
939 for(
auto state : eff.full_state())
941 avnd::input_introspection<Node>::for_all(state.inputs, [&](
auto& field) {
942 avnd::update_if_holding_value(state, field);
948 void cleanup()
override
950 if constexpr(
requires { this->process().from_ui; })
952 this->process().from_ui = [](QByteArray arr) {};
957 if constexpr(is_gpu<Node>)
960 auto& gfx_exec = this->system().doc.template plugin<Gfx::DocumentPlugin>().exec;
963 gfx_exec.ui->unregister_node(node_id);
964 node_id = score::gfx::invalid_node_index;
969 for(
auto* outlet : this->process().outlets())
971 if(
auto out = qobject_cast<Gfx::TextureOutlet*>(outlet))
977 ::Execution::ProcessComponent::cleanup();