Loading...
Searching...
No Matches
ExecutorPortSetup.hpp
1#pragma once
2#include <AvndProcesses/Utils.hpp>
3#include <Process/Process.hpp>
4
5#include <Crousti/File.hpp>
6#include <Crousti/ProcessModel.hpp>
7
8#include <ossia/detail/flat_map.hpp>
9
10#include <avnd/binding/ossia/node.hpp>
11
12namespace oscr
13{
14template <typename T>
18
23template <oscr::has_dynamic_ports T>
25{
26 Process::Inlets m_oldInlets;
27 Process::Outlets m_oldOutlets;
28 ossia::flat_map<Process::Inlet*, std::pair<int, QMetaObject::Connection>>
29 m_connectedControls;
30};
31
32// A halp::folder_port: a std::string control whose widget is a directory picker
33// (`enum widget { folder };`). Like the file ports, its value is a path and must
34// go through score::locateFilePath so that <LIBRARY>:, <PROJECT>: and
35// document-relative paths are resolved before the object reads it.
36template <typename T>
37concept folder_control_port = (requires { T::folder; } || requires { T::file; }
38 || requires { T::placeholders(); })
39 && requires(T t) {
40 { t.value } -> std::convertible_to<std::string_view>;
41 };
42
45template <typename Field>
46ossia::value resolveControlPath(const ossia::value& v, const score::DocumentContext& ctx)
47{
48 if constexpr(requires { Field::placeholders(); })
49 {
50 if(auto str = v.target<std::string>(); str && !str->empty())
51 return ossia::value{avnd_tools::filter_filename(*str, ctx).toStdString()};
52 return v;
53 }
54 else
55 return resolvePathValue(v, ctx);
56}
57
58template <typename Node, typename Field, std::size_t NPred, std::size_t NField>
60{
61 using ExecNode = safe_node<Node>;
62 const Execution::Context& ctx;
63 std::weak_ptr<ExecNode> weak_node;
64 Field& field;
65
66 // NPred is the index of the port in whichever list it was dispatched from in
67 // Executor::connect_controls -- the controls, but also e.g. the curve ports.
68 // control_updated_from_ui indexes the control list, so the two only coincide
69 // when the port happens to be at the same position in both. Recompute it from
70 // the field index, which is unambiguous.
71 static constexpr std::size_t control_index
72 = avnd::control_input_introspection<Node>::field_index_to_index(
73 avnd::field_index<NField>{});
74
75 void operator()(const ossia::value& val)
76 {
77 using control_value_type = std::decay_t<decltype(Field::value)>;
78
79 if(auto node = weak_node.lock())
80 {
81 control_value_type v;
82 node->from_ossia_value(field, val, v, avnd::field_index<NField>{});
83 ctx.executionQueue.enqueue([weak_node = weak_node, v = std::move(v)]() mutable {
84 if(auto n = weak_node.lock())
85 {
86 n->template control_updated_from_ui<control_value_type, control_index>(
87 std::move(v));
88 }
89 });
90 }
91 }
92};
93
97template <typename Node, typename Field, std::size_t NPred, std::size_t NField>
99{
100 using ExecNode = safe_node<Node>;
101 const Execution::Context& ctx;
102 std::weak_ptr<ExecNode> weak_node;
103 int port_index;
104
105 void operator()(const ossia::value& val)
106 {
107 using control_type = avnd::dynamic_port_type<Field>;
108 using control_value_type = std::decay_t<decltype(control_type::value)>;
109
110 if(auto node = weak_node.lock())
111 {
112 // The group's port vector belongs to the execution thread, which resizes
113 // it: it must not be read from here, not even to look up the port. The
114 // conversion only depends on the port type, so it is done against a
115 // local instance; the bounds check happens in the execution thread,
116 // in control_updated_from_ui.
117 control_type witness{};
118 control_value_type v;
119 node->from_ossia_value(witness, val, v, avnd::field_index<NField>{});
120 ctx.executionQueue.enqueue([weak_node = weak_node, port_index = port_index,
121 v = std::move(v)]() mutable {
122 if(auto n = weak_node.lock())
123 {
124 n->template control_updated_from_ui<control_value_type, NPred>(
125 std::move(v), port_index);
126 }
127 });
128 }
129 }
130};
131
132template <typename Node, typename Field>
134{
135 using ExecNode = safe_node<Node>;
137
138 Model& element;
139 const Execution::Context& ctx;
140 const std::shared_ptr<ExecNode>& node_ptr;
141 QObject* parent;
142
143 void invoke_update(Field& param, int k)
144 {
145 avnd::effect_container<Node>& eff = node_ptr->impl;
146 {
147 for(auto state : eff.full_state())
148 {
149 if constexpr(avnd::dynamic_ports_port<Field>)
150 {
151 avnd::update_if_holding_value(state, param.ports[k]);
152 }
153 else
154 {
155 avnd::update_if_holding_value(state, param);
156 }
157 }
158 }
159 }
160};
161template <typename Node, typename Field, std::size_t N, std::size_t NField>
163
164template <typename Node, typename Field, std::size_t N, std::size_t NField>
166{
167 using ExecNode = safe_node<Node>;
169
170 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
171 {
172 // Initialize the control with the current value of the inlet if it is not an optional
173 if constexpr(avnd::dynamic_ports_port<Field>)
174 {
175 using port_type = avnd::dynamic_port_type<Field>;
176 if constexpr(avnd::parameter_port<port_type>)
177 {
178 if constexpr(!requires { param.ports[0].value.reset(); })
179 {
180 this->node_ptr->from_ossia_value(
181 param, inlet->value(), param.ports[k].value, avnd::field_index<NField>{});
182 }
183 }
184 }
185 else
186 {
187 if constexpr(!requires { param.value.reset(); })
188 {
189 this->node_ptr->from_ossia_value(
190 param, inlet->value(), param.value, avnd::field_index<NField>{});
191 }
192 }
193 }
194
195 void update_controller(Field& param, Process::ControlInlet* inlet)
196 {
197 // FIXME proper tag
198 if constexpr(requires { param.update_controller; })
199 {
200 param.update_controller
201 = [inlet = QPointer{inlet}, self = QPointer{&this->element}](auto&& value) {
202 if(!self || !inlet)
203 return;
204
205 // Notify the UI if the object has the power
206 // to actually change the value of the control
207 ossia::qt::run_async(qApp, [self, inlet, val = std::move(value)] {
208 if(!self || !inlet)
209 return;
210
211 // FIXME better to use in_edit queue ?
212 // FIXME not too efficient but which choice do we have ?
213 static const Field field;
214 oscr::to_ossia_value(field, val);
215 inlet->setValue(val);
216 });
217 };
218 }
219 }
220
225 dynamic_ports_component_data<Node>& control_data, Field& param,
226 Process::ControlInlet* inlet, int k)
227 {
228 if constexpr(requires { control_data.m_connectedControls; })
229 {
230 auto it = control_data.m_connectedControls.find(inlet);
231 if(it != control_data.m_connectedControls.end())
232 {
233 if constexpr(!avnd::dynamic_ports_port<Field>)
234 return false;
235 if(it->second.first == k)
236 return false;
237 QObject::disconnect(it->second.second);
238 control_data.m_connectedControls.erase(it);
239 }
240 }
241
242 // Connect to changes
243 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
244 QMetaObject::Connection connection;
245 if constexpr(avnd::dynamic_ports_port<Field>)
246 {
247 connection = QObject::connect(
248 inlet, &Process::ControlInlet::valueChanged, this->parent,
250 }
251 else
252 {
253 connection = QObject::connect(
254 inlet, &Process::ControlInlet::valueChanged, this->parent,
255 con_unvalidated<Node, Field, N, NField>{this->ctx, weak_node, param});
256 }
257
258 if constexpr(requires { control_data.m_connectedControls; })
259 control_data.m_connectedControls.emplace(inlet, std::pair{k, connection});
260 return true;
261 }
262
266 void push_current_value(Field& param, Process::ControlInlet* inlet, int k)
267 {
268 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
269 if constexpr(avnd::dynamic_ports_port<Field>)
270 {
272 inlet->value());
273 }
274 else
275 {
276 con_unvalidated<Node, Field, N, NField>{this->ctx, weak_node, param}(
277 inlet->value());
278 }
279 }
280
281 // Used on initial creation
282 void connect_control_to_ui(
283 dynamic_ports_component_data<Node>& control_data, Field& param,
284 Process::ControlInlet* inlet, int k)
285 {
286 reconnect_control_to_ui(control_data, param, inlet, k);
287 this->update_controller(param, inlet);
288 }
289};
290
291template <typename Node, avnd::soundfile_port Field, std::size_t N, std::size_t NField>
292struct setup_control_for_exec<Node, Field, N, NField>
293 : setup_control_for_exec_base<Node, Field>
294{
295 using ExecNode = safe_node<Node>;
297
298 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
299 {
300 // FIXME handle dynamic ports correctly
301 // First we can load it directly since execution hasn't started yet
302 if(auto hdl = loadSoundfile(inlet->value(), this->ctx.doc, this->ctx.execState))
303 this->node_ptr->soundfile_loaded(
304 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
305 }
306
307 void connect_control_to_ui(
309 int k)
310 {
311 // Connect to changes
312 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
313 std::weak_ptr<ossia::execution_state> weak_st = this->ctx.execState;
314 QObject::connect(
315 inlet, &Process::ControlInlet::valueChanged, this->parent,
316 [&ctx = this->ctx, weak_node = std::move(weak_node),
317 weak_st = std::move(weak_st)](const ossia::value& v) {
318 if(auto n = weak_node.lock())
319 if(auto st = weak_st.lock())
320 if(auto file = loadSoundfile(v, ctx.doc, st))
321 {
322 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
323 auto n = weak_node.lock();
324 if(!n)
325 return;
326
327 // We store the sound file handle returned in this lambda so that it gets
328 // GC'd in the main thread
329 f = n->soundfile_loaded(
330 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
331 });
332 }
333 });
334 }
335};
336
337template <typename Node, avnd::midifile_port Field, std::size_t N, std::size_t NField>
338struct setup_control_for_exec<Node, Field, N, NField>
339 : setup_control_for_exec_base<Node, Field>
340{
341 using ExecNode = safe_node<Node>;
342 using Model = ProcessModel<Node>;
343
344 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
345 {
346 // FIXME handle dynamic ports correctly
347
348 // First we can load it directly since execution hasn't started yet
349 if(auto hdl = loadMidifile(inlet->value(), this->ctx.doc))
350 this->node_ptr->midifile_loaded(
351 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
352 }
353
354 void connect_control_to_ui(
355 dynamic_ports_component_data<Node>&, Field& param, Process::ControlInlet* inlet,
356 int k)
357 {
358 // Connect to changes
359 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
360 std::weak_ptr<ossia::execution_state> weak_st = this->ctx.execState;
361 QObject::connect(
362 inlet, &Process::ControlInlet::valueChanged, this->parent,
363 [inlet, &ctx = this->ctx,
364 weak_node = std::move(weak_node)](const ossia::value& v) {
365 if(auto n = weak_node.lock())
366 if(auto file = loadMidifile(v, ctx.doc))
367 {
368 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
369 auto n = weak_node.lock();
370 if(!n)
371 return;
372
373 // We store the sound file handle returned in this lambda so that it gets
374 // GC'd in the main thread
375 f = n->midifile_loaded(
376 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
377 });
378 }
379 });
380 }
381};
382
383template <typename Node, avnd::raw_file_port Field, std::size_t N, std::size_t NField>
384struct setup_control_for_exec<Node, Field, N, NField>
385 : setup_control_for_exec_base<Node, Field>
386{
387 using ExecNode = safe_node<Node>;
388 using Model = ProcessModel<Node>;
389
390 static constexpr bool has_text = requires { decltype(Field::file)::text; };
391 static constexpr bool has_mmap = requires { decltype(Field::file)::mmap; };
392
393 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
394 {
395 // FIXME handle dynamic ports correctly
396
397 // First we can load it directly since execution hasn't started yet
398 if(auto hdl = loadRawfile(inlet->value(), this->ctx.doc, has_text, has_mmap))
399 {
400 if constexpr(avnd::port_can_process<Field>)
401 {
402 // FIXME also do it when we get a run-time message from the exec engine,
403 // OSC, etc
404 auto func = executePortPreprocess<Field>(*hdl);
405 this->node_ptr->file_loaded(
406 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
407 if(func)
408 func(this->node_ptr->impl.effect);
409 }
410 else
411 {
412 this->node_ptr->file_loaded(
413 hdl, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
414 }
415 }
416 }
417
418 void connect_control_to_ui(
419 dynamic_ports_component_data<Node>&, Field& param, Process::ControlInlet* inlet,
420 int k)
421 {
422 // Connect to changes
423 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
424 std::weak_ptr<ossia::execution_state> weak_st = this->ctx.execState;
425 QObject::connect(
426 inlet, &Process::ControlInlet::valueChanged, this->parent,
427 [inlet, &ctx = this->ctx, weak_node = std::move(weak_node)] {
428 if(auto n = weak_node.lock())
429 if(auto file = loadRawfile(inlet->value(), ctx.doc, has_text, has_mmap))
430 {
431 if constexpr(avnd::port_can_process<Field>)
432 {
433 auto func = executePortPreprocess<Field>(*file);
434 ctx.executionQueue.enqueue(
435 [f = std::move(file), weak_node, ff = std::move(func)]() mutable {
436 auto n = weak_node.lock();
437 if(!n)
438 return;
439
440 // We store the sound file handle returned in this lambda so that it gets
441 // GC'd in the main thread
442 f = n->file_loaded(
443 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
444 if(ff)
445 ff(n->impl.effect);
446 });
447 }
448 else
449 {
450 ctx.executionQueue.enqueue([f = std::move(file), weak_node]() mutable {
451 auto n = weak_node.lock();
452 if(!n)
453 return;
454
455 // We store the sound file handle returned in this lambda so that it gets
456 // GC'd in the main thread
457 f = n->file_loaded(
458 f, avnd::predicate_index<N>{}, avnd::field_index<NField>{});
459 });
460 }
461 }
462 });
463 }
464};
465
466// folder_port: a path-valued string control. Resolve <LIBRARY>: / <PROJECT>: /
467// document-relative paths through score::locateFilePath before the object reads
468// them -- exactly like the file ports above -- otherwise the object receives the
469// raw library-relative string and cannot find the directory.
470template <typename Node, folder_control_port Field, std::size_t N, std::size_t NField>
471struct setup_control_for_exec<Node, Field, N, NField>
472 : setup_control_for_exec_base<Node, Field>
473{
474 using ExecNode = safe_node<Node>;
475 using Model = ProcessModel<Node>;
476
477 void initialize_control(Field& param, Process::ControlInlet* inlet, int k)
478 {
479 if constexpr(!requires { param.value.reset(); })
480 {
481 this->node_ptr->from_ossia_value(
482 param, resolveControlPath<Field>(inlet->value(), this->ctx.doc), param.value,
483 avnd::field_index<NField>{});
484 }
485 }
486
487 void connect_control_to_ui(
488 dynamic_ports_component_data<Node>&, Field& param, Process::ControlInlet* inlet,
489 int k)
490 {
491 std::weak_ptr<ExecNode> weak_node = this->node_ptr;
492 QObject::connect(
493 inlet, &Process::ControlInlet::valueChanged, this->parent,
494 [&ctx = this->ctx, weak_node = std::move(weak_node),
495 field = &param](const ossia::value& val) {
496 // Resolve the path on every change, then run the normal control-update
497 // path (from_ossia_value + control_updated_from_ui) via con_unvalidated.
498 con_unvalidated<Node, Field, N, NField>{ctx, weak_node, *field}(
499 resolveControlPath<Field>(val, ctx.doc));
500 });
501 }
502};
503
507template <typename Field>
509 = !ossia_port<avnd::concrete_port_type<Field>>
510 && (!avnd::dynamic_ports_port<Field>
511 || avnd::parameter_port<avnd::concrete_port_type<Field>>);
512
513template <typename Node>
515{
516 using ExecNode = safe_node<Node>;
518
519 Model& element;
520 const Execution::Context& ctx;
521 const std::shared_ptr<ExecNode>& node_ptr;
523 QObject* parent;
524
525 // Main function being invoked, which dispatches to all the actual implementations
526 template <typename Field, std::size_t N, std::size_t NField>
527 constexpr void
528 operator()(Field& param, avnd::predicate_index<N> np, avnd::field_index<NField> nf)
529 {
530 const auto ports = element.avnd_input_idx_to_model_ports(NField);
531
532 if constexpr(avnd::dynamic_ports_port<Field>)
533 {
534 param.ports.resize(ports.size());
535 }
536
537 if constexpr(field_has_ui_controls<Field>)
538 {
539 int k = 0;
540 for(auto p : ports)
541 {
542 if(auto inlet = qobject_cast<Process::ControlInlet*>(p))
543 {
545 element, ctx, node_ptr, parent};
546
547 setup.initialize_control(param, inlet, k);
548
549 setup.invoke_update(param, k);
550
551 setup.connect_control_to_ui(control_data, param, inlet, k);
552 }
553 k++;
554 // Else it's an unhandled value inlet
555 }
556 }
557 }
558};
559
560// Only used when dynamic ports are added
561template <typename Node>
563{
564 using ExecNode = safe_node<Node>;
566
567 Model& element;
568 const Execution::Context& ctx;
569 const std::shared_ptr<ExecNode>& node_ptr;
571 QObject* parent;
572
573 // Main function being invoked, which dispatches to all the actual implementations
574 template <typename Field, std::size_t N, std::size_t NField>
575 constexpr void
576 operator()(Field& param, avnd::predicate_index<N> np, avnd::field_index<NField> nf)
577 {
578 if constexpr(field_has_ui_controls<Field>)
579 {
580 const auto ports = element.avnd_input_idx_to_model_ports(NField);
581 int k = 0;
582 for(auto p : ports)
583 {
584 if(auto inlet = qobject_cast<Process::ControlInlet*>(p))
585 {
587 element, ctx, node_ptr, parent};
588
589 // A port that was just added: connect it and give the exec side its
590 // current value. Ports that were already there keep their connection
591 // and their value.
592 if(setup.reconnect_control_to_ui(control_data, param, inlet, k))
593 setup.push_current_value(param, inlet, k);
594 }
595 k++;
596 // Else it's an unhandled value inlet
597 }
598 }
599 }
600};
601}
Definition Port.hpp:242
Definition score-plugin-avnd/Crousti/ProcessModel.hpp:88
Definition ExecutorPortSetup.hpp:37
TreeNode< DeviceExplorerNode > Node
Definition DeviceNode.hpp:74
Definition Controls.hpp:27
ossia::value resolveControlPath(const ossia::value &v, const score::DocumentContext &ctx)
Definition ExecutorPortSetup.hpp:46
constexpr bool field_has_ui_controls
Definition ExecutorPortSetup.hpp:509
Definition ExecutionContext.hpp:201
Definition PortForward.hpp:23
Definition PortForward.hpp:27
Definition ExecutorPortSetup.hpp:99
Definition ExecutorPortSetup.hpp:60
Definition ExecutorPortSetup.hpp:563
Definition ExecutorPortSetup.hpp:515
Definition ExecutorPortSetup.hpp:16
Definition ExecutorPortSetup.hpp:134
Definition ExecutorPortSetup.hpp:166
bool reconnect_control_to_ui(dynamic_ports_component_data< Node > &control_data, Field &param, Process::ControlInlet *inlet, int k)
Definition ExecutorPortSetup.hpp:224
void push_current_value(Field &param, Process::ControlInlet *inlet, int k)
Definition ExecutorPortSetup.hpp:266
Definition DocumentContext.hpp:18