MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Wiring.cpp
Go to the documentation of this file.
1#include "Wiring.hpp"
2
3#include "Fabric.hpp"
4
6
13
17
18namespace MayaFlux::Nexus {
19
20// =============================================================================
21// Scheduling modifiers
22// =============================================================================
23
24Wiring& Wiring::every(double interval_seconds, Vruta::ProcessingToken token)
25{
26 m_interval = interval_seconds;
27 m_metro_token = token;
28 return *this;
29}
30
32{
33 m_duration = seconds;
34 m_duration_token = token;
35 return *this;
36}
37
38Wiring& Wiring::on(std::shared_ptr<Core::Window> window, IO::Keys key)
39{
40 m_trigger = KeyTrigger { .window = std::move(window), .key = key };
41 return *this;
42}
43
44Wiring& Wiring::on(std::shared_ptr<Core::Window> window, IO::Keys key, bool held,
45 std::function<void()> on_release)
46{
48 .window = std::move(window),
49 .key = key,
50 .on_release = on_release ? std::optional<std::function<void()>>(std::move(on_release)) : std::nullopt,
51 .held = held
52 };
53 return *this;
54}
55
56Wiring& Wiring::on(std::shared_ptr<Core::Window> window, IO::MouseButtons button)
57{
58 m_trigger = MouseTrigger { .window = std::move(window), .button = button };
59 return *this;
60}
61
62Wiring& Wiring::on(std::shared_ptr<Core::Window> window, IO::MouseButtons button, bool held, std::function<void(double, double)> on_release)
63{
65 .window = std::move(window),
66 .button = button,
67 .on_release = on_release ? std::optional<std::function<void(double, double)>>(std::move(on_release)) : std::nullopt,
68 .held = held
69 };
70
71 return *this;
72}
73
74Wiring& Wiring::on(std::shared_ptr<Core::Window> window, IO::MouseButtons button,
75 std::function<void(double, double)> on_release)
76{
78 .window = std::move(window),
79 .button = button,
80 .on_release = std::move(on_release)
81 };
82 return *this;
83}
84
86{
87 m_trigger = NetworkTrigger { .source = &source };
88 return *this;
89}
90
92{
93 m_trigger = WindowEventTrigger { .source = &source, .filter = std::move(filter) };
94 return *this;
95}
96
97Wiring& Wiring::on_scroll(std::shared_ptr<Core::Window> window,
98 std::function<void(double, double)> fn)
99{
100 m_trigger = ScrollTrigger { .window = std::move(window), .fn = std::move(fn) };
101 return *this;
102}
103
104Wiring& Wiring::move_to(const glm::vec3& pos, double delay_seconds)
105{
106 m_move_steps.push_back({ .position = pos, .delay_seconds = delay_seconds });
107 return *this;
108}
109
111{
112 m_position_fn = std::move(fn);
113 return *this;
114}
115
117{
118 m_times = count;
119 return *this;
120}
121
123{
124 m_factory = std::move(factory);
125 return *this;
126}
127
129{
130 m_factory = std::move(factory);
131 return *this;
132}
133
135{
136 m_factory = std::move(factory);
137 return *this;
138}
139
141{
142 m_event_factory = std::move(factory);
143 return *this;
144}
145
146Wiring& Wiring::position_from(std::string fn_name, PositionFn fn)
147{
148 m_position_fn_name = std::move(fn_name);
149 m_position_fn = std::move(fn);
150 return *this;
151}
152
153Wiring& Wiring::use(std::string fn_name, SoundFactory factory)
154{
155 m_factory_name = std::move(fn_name);
156 m_factory = std::move(factory);
157 return *this;
158}
159
160Wiring& Wiring::use(std::string fn_name, GraphicsFactory factory)
161{
162 m_factory_name = std::move(fn_name);
163 m_factory = std::move(factory);
164 return *this;
165}
166
167Wiring& Wiring::use(std::string fn_name, CrossFactory factory)
168{
169 m_factory_name = std::move(fn_name);
170 m_factory = std::move(factory);
171 return *this;
172}
173
174Wiring& Wiring::use(std::string fn_name, EventFactory factory)
175{
176 m_factory_name = std::move(fn_name);
177 m_event_factory = std::move(factory);
178 return *this;
179}
180
181// =============================================================================
182// Immediate bind
183// =============================================================================
184
186{
187 m_bind_attach = [this]() { m_fabric.fire(m_entity_id); };
188 return *this;
189}
190
191Wiring& Wiring::bind(std::function<void()> fn)
192{
193 m_bind_attach = std::move(fn);
194 return *this;
195}
196
197Wiring& Wiring::bind(std::function<void()> attach, std::function<void()> detach)
198{
199 m_bind_attach = std::move(attach);
200 m_bind_detach = std::move(detach);
201 return *this;
202}
203
204Wiring& Wiring::bind(std::string fn_name, std::function<void()> fn)
205{
206 m_bind_attach_name = std::move(fn_name);
207 m_bind_attach = std::move(fn);
208 return *this;
209}
210
212 std::string attach_name, std::function<void()> attach,
213 std::string detach_name, std::function<void()> detach)
214{
215 m_bind_attach_name = std::move(attach_name);
216 m_bind_attach = std::move(attach);
217 m_bind_detach_name = std::move(detach_name);
218 m_bind_detach = std::move(detach);
219 return *this;
220}
221
222// =============================================================================
223// make_name
224// =============================================================================
225
226std::string Wiring::make_name(const char* prefix) const
227{
228 return std::string(prefix) + "_" + std::to_string(m_entity_id) + "_" + std::to_string(m_fabric.m_scheduler.get_next_task_id());
229}
230
231namespace {
232
233 // =============================================================================
234 // Network coroutine
235 // =============================================================================
236
237 Vruta::SoundRoutine network_loop(
238 Vruta::NetworkSource& source,
239 Fabric& fabric,
240 uint32_t id)
241 {
242 auto& promise = co_await Kriya::GetAudioPromise {};
243 while (true) {
244 if (promise.should_terminate) {
245 break;
246 }
247 co_await source.next_message();
248 fabric.fire(id);
249 }
250 }
251
252 Vruta::Event scroll_loop(
253 std::shared_ptr<Core::Window> window,
254 std::function<void(double, double)> fn)
255 {
256 auto& promise = co_await Kriya::GetEventPromise {};
257 auto& source = window->get_event_source();
258
259 Vruta::WindowEventFilter filter;
260 filter.event_type = Core::WindowEventType::MOUSE_SCROLLED;
261
262 while (true) {
263 if (promise.should_terminate)
264 break;
265 auto event = co_await Kriya::WindowEventAwaiter(source, filter);
266 if (auto* sd = std::get_if<Core::WindowEvent::ScrollData>(&event.data))
267 fn(sd->x_offset, sd->y_offset);
268 }
269 }
270}
271
272// =============================================================================
273// Terminal
274// =============================================================================
275
277{
278 auto& scheduler = m_fabric.m_scheduler;
279 auto& ev_manager = m_fabric.m_event_manager;
280 const uint32_t id = m_entity_id;
281 auto& reg = m_fabric.m_registrations[id];
282
283 bool has_any_path = !std::holds_alternative<std::monostate>(m_factory)
284 || m_event_factory.has_value()
285 || m_bind_attach.has_value()
286 || !m_move_steps.empty()
287 || !std::holds_alternative<std::monostate>(m_trigger)
288 || m_interval.has_value();
289
290 reg.commit_driven = !has_any_path;
291
292 // ------------------------------------------------------------------
293 // Register named callables before any early return.
294 // ------------------------------------------------------------------
295 std::visit([&](const auto& ptr) {
296 using T = std::decay_t<decltype(*ptr)>;
297 if constexpr (std::is_same_v<T, Emitter>) {
298 if (!ptr->fn_name().empty()) {
299 m_fabric.m_influence_fns.try_emplace(
300 ptr->fn_name(), std::make_shared<Emitter::InfluenceFn>(ptr->fn()));
301 }
302 } else if constexpr (std::is_same_v<T, Sensor>) {
303 if (!ptr->fn_name().empty()) {
304 m_fabric.m_perception_fns.try_emplace(
305 ptr->fn_name(), std::make_shared<Sensor::PerceptionFn>(ptr->fn()));
306 }
307 } else if constexpr (std::is_same_v<T, Agent>) {
308 if (!ptr->influence_fn_name().empty()) {
309 m_fabric.m_influence_fns.try_emplace(
310 ptr->influence_fn_name(), std::make_shared<Emitter::InfluenceFn>(ptr->influence_fn()));
311 }
312 if (!ptr->perception_fn_name().empty()) {
313 m_fabric.m_perception_fns.try_emplace(
314 ptr->perception_fn_name(), std::make_shared<Sensor::PerceptionFn>(ptr->perception_fn()));
315 }
316 }
317 },
318 reg.member);
319
320 // ------------------------------------------------------------------
321 // Routine factory path
322 // ------------------------------------------------------------------
323 if (!std::holds_alternative<std::monostate>(m_factory)) {
324 auto name = make_name("nexus_task");
325 reg.task_name = name;
326 std::visit([&](auto& factory) {
327 using F = std::decay_t<decltype(factory)>;
328 if constexpr (!std::is_same_v<F, std::monostate>) {
329 scheduler.add_task(
330 std::make_shared<std::invoke_result_t<F>>(factory()),
331 name, false);
332 }
333 },
334 m_factory);
335 m_fabric.m_registrations[m_entity_id].wiring.emplace(std::move(*this));
336 return;
337 }
338
339 // ------------------------------------------------------------------
340 // Event factory path
341 // ------------------------------------------------------------------
342 if (m_event_factory.has_value()) {
343 auto name = make_name("nexus_event");
344 reg.event_name = name;
345 ev_manager.add_event(
346 std::make_shared<Vruta::Event>((*m_event_factory)(scheduler)),
347 name);
348 m_fabric.m_registrations[m_entity_id].wiring.emplace(std::move(*this));
349 return;
350 }
351
352 // ------------------------------------------------------------------
353 // Immediate bind path
354 // ------------------------------------------------------------------
355 if (m_bind_attach.has_value()) {
356 (*m_bind_attach)();
357
358 if (m_duration.has_value() && m_bind_detach.has_value()) {
359 auto timer = std::make_shared<Kriya::Timer>(scheduler, m_duration_token);
360 auto detach = *m_bind_detach;
361 timer->schedule(*m_duration, [timer, detach]() {
362 detach();
363 });
364 }
365 m_fabric.m_registrations[m_entity_id].wiring.emplace(std::move(*this));
366 return;
367 }
368
369 // ------------------------------------------------------------------
370 // Choreographed position path
371 // ------------------------------------------------------------------
372 if (!m_move_steps.empty()) {
373 auto name = make_name("nexus_chain");
374 reg.chain_name = name;
375
376 Kriya::EventChain chain(scheduler, name);
377 auto& fab = m_fabric;
378 for (auto& step : m_move_steps) {
379 glm::vec3 pos = step.position;
380 chain.then([&fab, pos, id]() {
381 std::visit([&pos](const auto& ptr) {
382 ptr->set_position(pos);
383 },
384 fab.m_registrations[id].member);
385 fab.fire(id);
386 },
387 step.delay_seconds);
388 }
389 if (m_times > 1) {
390 chain.times(m_times);
391 }
392 chain.start();
393 m_fabric.m_registrations[m_entity_id].wiring.emplace(std::move(*this));
394 return;
395 }
396
397 // ------------------------------------------------------------------
398 // Trigger path
399 // ------------------------------------------------------------------
400 if (!std::holds_alternative<std::monostate>(m_trigger)) {
401 std::visit([&](auto& trig) {
402 using T = std::decay_t<decltype(trig)>;
403
404 if constexpr (std::is_same_v<T, KeyTrigger>) {
405 auto name = make_name("nexus_event");
406 reg.event_name = name;
407 auto& fab = m_fabric;
408 ev_manager.add_event(
409 std::make_shared<Vruta::Event>(
410 trig.held
411 ? Kriya::key_held(trig.window, trig.key,
412 [&fab, id]() { fab.fire(id); })
413 : Kriya::key_pressed(trig.window, trig.key,
414 [&fab, id]() { fab.fire(id); })),
415 name);
416
417 if (trig.on_release) {
418 auto release_name = make_name("nexus_event_release");
419 reg.chain_name = release_name;
420 ev_manager.add_event(
421 std::make_shared<Vruta::Event>(
422 Kriya::key_released(trig.window, trig.key, *trig.on_release)),
423 release_name);
424 }
425
426 } else if constexpr (std::is_same_v<T, MouseTrigger>) {
427 auto name = make_name("nexus_event");
428 reg.event_name = name;
429 Fabric& fab = m_fabric;
430 uint32_t eid = m_entity_id;
431 ev_manager.add_event(
432 std::make_shared<Vruta::Event>(
433 trig.held
434 ? Kriya::mouse_dragged(trig.window, trig.button,
435 [&fab, eid](double px, double py) {
436 fab.m_registrations[eid].pending_cursor = glm::vec2(px, py);
437 fab.fire(eid);
438 })
439 : Kriya::mouse_pressed(trig.window, trig.button,
440 [&fab, eid](double px, double py) {
441 fab.m_registrations[eid].pending_cursor = glm::vec2(px, py);
442 fab.fire(eid);
443 })),
444 name);
445 if (trig.on_release) {
446 auto release_name = make_name("nexus_event_release");
447 reg.chain_name = release_name;
448 ev_manager.add_event(
449 std::make_shared<Vruta::Event>(
450 Kriya::mouse_released(trig.window, trig.button, *trig.on_release)),
451 release_name);
452 }
453
454 } else if constexpr (std::is_same_v<T, ScrollTrigger>) {
455 auto name = make_name("nexus_scroll");
456 reg.event_name = name;
457 ev_manager.add_event(
458 std::make_shared<Vruta::Event>(
459 scroll_loop(trig.window, trig.fn)),
460 name);
461
462 } else if constexpr (std::is_same_v<T, NetworkTrigger>) {
463 auto name = make_name("nexus_task");
464 reg.task_name = name;
465 scheduler.add_task(
466 std::make_shared<Vruta::SoundRoutine>(
467 network_loop(*trig.source, m_fabric, id)),
468 name, false);
469
470 } else if constexpr (std::is_same_v<T, WindowEventTrigger>) {
471 auto name = make_name("nexus_event");
472 reg.event_name = name;
473 Fabric& fab = m_fabric;
474 ev_manager.add_event(
475 std::make_shared<Vruta::Event>(
476 window_event_source_loop(*trig.source, trig.filter, fab, id)),
477 name);
478 }
479 },
480 m_trigger);
481 m_fabric.m_registrations[m_entity_id].wiring.emplace(std::move(*this));
482 return;
483 }
484
485 // ------------------------------------------------------------------
486 // Metro path
487 // ------------------------------------------------------------------
488 if (m_interval.has_value()) {
489 auto pos_fn = m_position_fn;
490 auto name = make_name("nexus_metro");
491 reg.task_name = name;
492 auto& fab = m_fabric;
493
494 scheduler.add_task(
495 Kriya::metro(*m_interval, [&fab, id, pos_fn]() mutable {
496 if (pos_fn.has_value()) {
497 glm::vec3 p = (*pos_fn)();
498 std::visit([&p](const auto& ptr) {
499 ptr->set_position(p);
500 },
501 fab.m_registrations[id].member);
502 }
503 fab.fire(id); }, m_metro_token), name, false);
504
505 if (m_duration.has_value()) {
506 auto cancel_name = make_name("nexus_metro_cancel");
507 auto timer = std::make_shared<Kriya::Timer>(scheduler);
508 timer->schedule(*m_duration, [name, timer, &scheduler]() {
509 scheduler.cancel_task(name);
510 });
511 }
512 }
513
514 m_fabric.m_registrations[m_entity_id].wiring.emplace(std::move(*this));
515}
516
517// =============================================================================
518// Cancel
519// =============================================================================
520
521void Wiring::cancel()
522{
523 auto& reg = m_fabric.m_registrations[m_entity_id];
524
525 if (!reg.task_name.empty()) {
526 m_fabric.m_scheduler.cancel_task(reg.task_name);
527 reg.task_name.clear();
528 }
529 if (!reg.chain_name.empty()) {
530 m_fabric.m_scheduler.cancel_task(reg.chain_name);
531 reg.chain_name.clear();
532 }
533 if (!reg.event_name.empty()) {
534 m_fabric.m_event_manager.cancel_event(reg.event_name);
535 reg.event_name.clear();
536 }
537
538 if (m_bind_detach.has_value()) {
539 (*m_bind_detach)();
540 }
541}
542
543// =============================================================================
544// Internal
545// =============================================================================
546
547Vruta::Event Wiring::window_event_source_loop(
550 Fabric& fabric,
551 uint32_t id)
552{
553 auto& promise = co_await Kriya::GetEventPromise {};
554 while (true) {
555 if (promise.should_terminate)
556 break;
557 auto event = co_await Kriya::WindowEventAwaiter(source, filter);
558 if (auto* pos = std::get_if<Core::WindowEvent::MousePosData>(&event.data))
559 fabric.m_registrations[id].pending_cursor = glm::vec2(pos->x, pos->y);
560 fabric.fire(id);
561 }
562}
563
564} // namespace MayaFlux::Nexus
size_t count
const uint8_t * ptr
A sequential chain of timed events with precise temporal control.
Definition Chain.hpp:43
Awaiter for suspending on GLFW window input events with optional filtering.
void fire(uint32_t id)
Fire a single object against the current snapshot without republishing.
Definition Fabric.cpp:204
std::unordered_map< std::string, std::shared_ptr< Sensor::PerceptionFn > > m_perception_fns
Definition Fabric.hpp:300
Vruta::TaskScheduler & m_scheduler
Definition Fabric.hpp:288
std::unordered_map< uint32_t, Registration > m_registrations
Definition Fabric.hpp:292
std::unordered_map< std::string, std::shared_ptr< Emitter::InfluenceFn > > m_influence_fns
Definition Fabric.hpp:299
Vruta::EventManager & m_event_manager
Definition Fabric.hpp:289
Orchestrates spatial indexing and scheduling for Nexus objects.
Definition Fabric.hpp:38
std::optional< std::function< void()> > m_bind_detach
Definition Wiring.hpp:319
Wiring & position_from(PositionFn fn)
Drive position from a callable evaluated on each every() tick.
Definition Wiring.cpp:110
std::string m_position_fn_name
Definition Wiring.hpp:307
Vruta::Event window_event_source_loop(Vruta::WindowEventSource &source, Vruta::WindowEventFilter filter, Fabric &fabric, uint32_t id)
Definition Wiring.cpp:547
std::string make_name(const char *prefix) const
Definition Wiring.cpp:226
Wiring & use(SoundFactory factory)
Delegate coroutine creation entirely to the caller.
Definition Wiring.cpp:122
Wiring & on_scroll(std::shared_ptr< Core::Window > window, std::function< void(double, double)> fn)
Fire the entity on each mouse scroll event from a window.
Definition Wiring.cpp:97
std::function< Vruta::CrossRoutine()> CrossFactory
Definition Wiring.hpp:46
std::string m_bind_attach_name
Definition Wiring.hpp:320
Wiring & on(std::shared_ptr< Core::Window > window, IO::Keys key)
Fire the entity on a key event from a window.
Definition Wiring.cpp:38
std::function< glm::vec3()> PositionFn
Definition Wiring.hpp:43
std::optional< std::function< void()> > m_bind_attach
Definition Wiring.hpp:318
std::optional< double > m_duration
Definition Wiring.hpp:305
Vruta::ProcessingToken m_metro_token
Definition Wiring.hpp:310
Wiring & every(double interval_seconds, Vruta::ProcessingToken token=Vruta::ProcessingToken::SAMPLE_ACCURATE)
Fire the entity on a recurring interval.
Definition Wiring.cpp:24
Wiring & times(size_t count)
Repeat the configured sequence or choreography N times.
Definition Wiring.cpp:116
Wiring & move_to(const glm::vec3 &pos, double delay_seconds=0.0)
Choreograph a position move as an EventChain step.
Definition Wiring.cpp:104
std::function< Vruta::GraphicsRoutine()> GraphicsFactory
Definition Wiring.hpp:45
std::function< Vruta::Event(Vruta::TaskScheduler &)> EventFactory
Definition Wiring.hpp:47
Vruta::ProcessingToken m_duration_token
Definition Wiring.hpp:311
std::optional< double > m_interval
Definition Wiring.hpp:304
const Factory & factory() const
Active factory variant set by use for non-Event factories.
Definition Wiring.hpp:361
std::string m_bind_detach_name
Definition Wiring.hpp:321
Wiring & for_duration(double seconds, Vruta::ProcessingToken token=Vruta::ProcessingToken::SAMPLE_ACCURATE)
Limit a recurring registration to a fixed duration then cancel.
Definition Wiring.cpp:31
std::vector< MoveStep > m_move_steps
Definition Wiring.hpp:308
std::string m_factory_name
Definition Wiring.hpp:316
std::optional< PositionFn > m_position_fn
Definition Wiring.hpp:306
void finalise()
Apply the configured wiring.
Definition Wiring.cpp:276
Wiring & bind()
Call the entity's influence function once immediately.
Definition Wiring.cpp:185
std::function< Vruta::SoundRoutine()> SoundFactory
Definition Wiring.hpp:44
Fluent builder that wires an entity into Fabric's scheduling infrastructure.
Definition Wiring.hpp:41
Coroutine type for event-driven suspension.
Definition Event.hpp:26
Awaitable broadcast message stream for a network endpoint.
A C++20 coroutine-based audio processing task with sample-accurate timing.
Definition Routine.hpp:316
uint64_t get_next_task_id() const
Generates a unique task ID for new tasks.
Awaitable stream of GLFW window input events.
MouseButtons
Enumeration for mouse buttons.
Definition Keys.hpp:147
Vruta::Event key_released(std::shared_ptr< Core::Window > window, IO::Keys key, std::function< void()> callback)
Creates an Event coroutine that triggers on specific key release.
Vruta::Event key_pressed(std::shared_ptr< Core::Window > window, IO::Keys key, std::function< void()> callback)
Creates an Event coroutine that triggers on specific key press.
Vruta::Event key_held(std::shared_ptr< Core::Window > window, IO::Keys key, std::function< void()> callback)
Creates an Event coroutine that triggers on key press and repeats while held.
Vruta::Event mouse_released(std::shared_ptr< Core::Window > window, IO::MouseButtons button, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on specific mouse button release.
Vruta::Event mouse_pressed(std::shared_ptr< Core::Window > window, IO::MouseButtons button, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on specific mouse button press.
Vruta::Event mouse_dragged(std::shared_ptr< Core::Window > window, IO::MouseButtons button, std::function< void(double, double)> callback)
Creates an Event coroutine that triggers on mouse drag with specific button.
std::shared_ptr< Vruta::Routine > metro(double interval_seconds, std::function< void()> callback, Vruta::ProcessingToken token)
Creates a periodic event generator that executes a callback at regular intervals.
Definition Tasks.cpp:12
Source source()
Begin a Source chain.
Definition Plot.hpp:128
Event-domain promise accessor with optional NetworkSource ownership transfer.
Templated awaitable for accessing a coroutine's promise object.
std::shared_ptr< Core::Window > window
Definition Wiring.hpp:256
std::shared_ptr< Core::Window > window
Definition Wiring.hpp:263
std::shared_ptr< Core::Window > window
Definition Wiring.hpp:270
Vruta::WindowEventSource * source
Definition Wiring.hpp:284
Filter criteria for GLFW window input events.