MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Bridge.hpp
Go to the documentation of this file.
1#pragma once
2
4
5namespace MayaFlux::Core {
6class Window;
7}
8
9namespace MayaFlux::Vruta {
10class TaskScheduler;
11}
12
13namespace MayaFlux::Buffers {
14class BufferManager;
15class ShaderProcessor;
16class AudioWriteProcessor;
17class DataWriteProcessor;
18class FormaBindingsProcessor;
19}
20
21namespace MayaFlux::Nodes {
22class Node;
23class Constant;
24}
25
27
28/**
29 * @class Bridge
30 * @brief Two-way binding orchestrator for Forma elements.
31 *
32 * One Bridge instance serves the full application. It owns all inbound
33 * GraphicsRoutine tasks and all outbound FormaBindingsProcessor instances.
34 * Layer and Context are created via the Forma free functions and passed in
35 * — Bridge does not own or create them.
36 *
37 * Inbound (what drives an element's MappedState each frame):
38 * - bind(id, Node) — reads Node::get_last_output() via GraphicsRoutine
39 * - bind(id, lambda) — calls std::function<float()> via GraphicsRoutine
40 *
41 * Outbound (where the element's value goes each frame):
42 * - write(id, ShaderProcessor, offset) — push constant staging
43 * - write(id, descriptor_name, ...) — descriptor binding
44 * - write(id, AudioWriteProcessor) — audio buffer
45 * - write(id, DataWriteProcessor) — vertex buffer
46 * - write(id, Constant) — node graph value carrier
47 *
48 * All overloads also accept shared_ptr<MappedState<T>> in place of the
49 * element id. These resolve to the state->id.
50 * duplicated logic.
51 *
52 * Usage:
53 * @code
54 * auto [layer, ctx] = Forma::create_layer(window, "hud", event_manager);
55 * auto el = Forma::create_element<float>(*layer, window, bm, geom, 0.5f);
56 *
57 * Bridge bridge(scheduler, buffer_manager);
58 * bridge.bind(el.state, envelope);
59 * bridge.write(el.state, compute_proc, offsetof(PC, cutoff));
60 *
61 * ctx->on_press(el.element.id, IO::MouseButtons::LEFT,
62 * [](uint32_t, glm::vec2){});
63 * @endcode
64 */
65class MAYAFLUX_API Bridge {
66public:
67 Bridge(
68 Vruta::TaskScheduler& scheduler,
69 Buffers::BufferManager& buffer_manager);
70
71 ~Bridge();
72
73 Bridge(const Bridge&) = delete;
74 Bridge& operator=(const Bridge&) = delete;
75 Bridge(Bridge&&) = delete;
76 Bridge& operator=(Bridge&&) = delete;
77
78 // =========================================================================
79 // Inbound — id overloads (primary implementation)
80 // =========================================================================
81
82 /**
83 * @brief Drive element value from a Node's output each frame.
84 *
85 * Spawns a GraphicsRoutine that reads node->get_last_output() every tick,
86 * applies @p project, and writes into the element's MappedState.
87 * Node must be processed externally (EXTERNAL mode read).
88 * Replaces any existing inbound binding on this element.
89 *
90 * @param id Element id (from Layer::add / Mapped::element.id).
91 * @param node Node to read from.
92 * @param project Optional double -> float projection. Identity if empty.
93 */
94 void bind(uint32_t id,
95 std::shared_ptr<Nodes::Node> node,
96 std::function<float(double)> project = {});
97
98 /**
99 * @brief Drive element value from an arbitrary per-frame callable.
100 *
101 * Spawns a GraphicsRoutine that calls @p source every tick and writes
102 * the result into the element's MappedState.
103 * Replaces any existing inbound binding on this element.
104 *
105 * @param id Element id.
106 * @param source Callable returning current float value.
107 */
108 void bind(uint32_t id, std::function<float()> source);
109
110 // =========================================================================
111 // Inbound — MappedState overloads
112 // =========================================================================
113
114 template <typename T>
115 void bind(std::shared_ptr<MappedState<T>> state,
116 std::shared_ptr<Nodes::Node> node,
117 std::function<float(double)> project = {})
118 {
119 bind(state->id, std::move(node), std::move(project));
120 }
121
122 template <typename T>
123 void bind(std::shared_ptr<MappedState<T>> state, std::function<float()> source)
124 {
125 bind(state->id, std::move(source));
126 }
127
128 template <typename T>
129 void write(std::shared_ptr<MappedState<T>> state,
130 std::function<void(std::span<const float>)> sink)
131 {
132 write(state->id, std::move(sink));
133 }
134
135 // =========================================================================
136 // Outbound — id overloads (primary implementation)
137 // =========================================================================
138
139 /**
140 * @brief Route element value to a push constant slot on a ShaderProcessor.
141 * @param id Element id.
142 * @param target VKBuffer whose push_constant staging receives the value.
143 * @param offset Byte offset in the push constant struct.
144 * @param size Byte width. Defaults to sizeof(float).
145 */
146 void write(
147 uint32_t id,
148 const std::shared_ptr<Buffers::VKBuffer>& target_buffer,
149 const std::string& shader_path,
150 uint32_t offset,
151 size_t size = sizeof(float));
152
153 /**
154 * @brief Route element value to a descriptor binding on @p target_buffer.
155 *
156 * Creates a FormaBindingsProcessor if one does not yet exist for this element
157 * and attaches it to @p target_buffer. If a processor already exists from a
158 * prior write() call, @p target_buffer must be the same buffer — a second
159 * attachment is not made.
160 *
161 * @param id Element id.
162 * @param target_buffer Buffer whose pipeline context receives the descriptor update.
163 * @param shader_path Shader path used to construct FormaBindingsProcessor if needed.
164 * @param descriptor_name Descriptor name in the shader config.
165 * @param binding_index Vulkan binding index.
166 * @param set Descriptor set index.
167 * @param role UNIFORM or STORAGE.
168 */
169 void write(uint32_t id,
170 const std::shared_ptr<Buffers::VKBuffer>& target_buffer,
171 const std::string& shader_path,
172 const std::string& descriptor_name,
173 uint32_t binding_index,
174 uint32_t set,
175 Portal::Graphics::DescriptorRole role = Portal::Graphics::DescriptorRole::UNIFORM);
176
177 /**
178 * @brief Route element value to an AudioWriteProcessor each frame.
179 * @param id Element id.
180 * @param target AudioWriteProcessor to call set_data() on.
181 */
182 void write(uint32_t id, std::shared_ptr<Buffers::AudioWriteProcessor> target);
183
184 /**
185 * @brief Route element value to a DataWriteProcessor each frame.
186 * @param id Element id.
187 * @param target DataWriteProcessor to call set_data() on.
188 */
189 void write(uint32_t id, std::shared_ptr<Buffers::DataWriteProcessor> target);
190
191 /**
192 * @brief Route element value into a Constant node via set_constant().
193 * @param id Element id.
194 * @param node Constant node updated each frame.
195 */
196 void write(uint32_t id, std::shared_ptr<Nodes::Constant> node);
197
198 /**
199 * @brief Route element bulk value to a caller-supplied sink each frame.
200 *
201 * When the element's MappedState<T> has a bulk_reader (T is vector<float>
202 * or vector<double>), the full vector is forwarded as a span. For scalar
203 * T the sink receives a single-element span of the scalar value.
204 *
205 * Intended for consumers that operate on coefficient arrays or other
206 * N-element state: FIR/IIR coefficient vectors, Random node bounds,
207 * any function accepting a contiguous float range.
208 *
209 * @param id Element id.
210 * @param sink Callable invoked each frame with the current value span.
211 */
212 void write(uint32_t id, std::function<void(std::span<const float>)> sink);
213
214 // =========================================================================
215 // Outbound — MappedState overloads
216 // =========================================================================
217
218 template <typename T>
219 void write(std::shared_ptr<MappedState<T>> state,
220 std::shared_ptr<Buffers::VKBuffer> target_buffer,
221 const std::string& shader_path,
222 uint32_t offset,
223 size_t size = sizeof(float))
224 {
225 write(state->id, std::move(target_buffer), shader_path, offset, size);
226 }
227
228 template <typename T>
229 void write(std::shared_ptr<MappedState<T>> state,
230 std::shared_ptr<Buffers::VKBuffer> target_buffer,
231 const std::string& shader_path,
232 const std::string& descriptor_name,
233 uint32_t binding_index,
234 uint32_t set,
235 Portal::Graphics::DescriptorRole role = Portal::Graphics::DescriptorRole::UNIFORM)
236 {
237 write(state->id, std::move(target_buffer), shader_path,
238 descriptor_name, binding_index, set, role);
239 }
240
241 template <typename T>
242 void write(std::shared_ptr<MappedState<T>> state,
243 std::shared_ptr<Buffers::AudioWriteProcessor> target)
244 {
245 write(state->id, std::move(target));
246 }
247
248 template <typename T>
249 void write(std::shared_ptr<MappedState<T>> state,
250 std::shared_ptr<Buffers::DataWriteProcessor> target)
251 {
252 write(state->id, std::move(target));
253 }
254
255 template <typename T>
256 void write(std::shared_ptr<MappedState<T>> state,
257 std::shared_ptr<Nodes::Constant> node)
258 {
259 write(state->id, std::move(node));
260 }
261
262 // =========================================================================
263 // Registration — called by Forma::create_element to make state findable
264 // =========================================================================
265
266 /**
267 * @brief Register a MappedState<T> so that MappedState overloads and
268 * outbound bindings can resolve to the correct element id and reader.
269 *
270 * Called by Forma::create_element. The reader closes over the MappedState
271 * and is stored type-erased as std::function<float()>.
272 *
273 * @tparam T MappedState value type.
274 * @param state MappedState to register.
275 * @param id Element id from Layer::add.
276 * @param buffer FormaBuffer the element renders into.
277 * @param project Optional T -> float projection. Identity cast if empty.
278 */
279 template <typename T>
281 std::shared_ptr<MappedState<T>> state,
282 uint32_t id,
283 std::shared_ptr<Buffers::FormaBuffer> buffer,
284 std::function<float(T)> project = {})
285 {
286 std::function<float()> reader;
287 if (project) {
288 reader = [s = state, p = std::move(project)] {
289 return p(s->value);
290 };
291 } else if constexpr (std::is_convertible_v<T, float>) {
292 reader = [s = state] {
293 return static_cast<float>(s->value);
294 };
295 } else {
296 reader = [] { return 0.F; };
297 }
298
299 std::function<std::vector<float>()> bulk_reader;
300 if constexpr (std::is_same_v<T, std::vector<float>>) {
301 bulk_reader = [s = state] { return s->value; };
302 } else if constexpr (std::is_same_v<T, std::vector<double>>) {
303 bulk_reader = [s = state] {
304 std::vector<float> out;
305 out.reserve(s->value.size());
306 for (double v : s->value)
307 out.push_back(static_cast<float>(v));
308 return out;
309 };
310 }
311
312 std::function<void(float)> writer;
313 if constexpr (std::is_convertible_v<float, T>) {
314 writer = [s = state](float v) {
315 s->write(static_cast<T>(v));
316 };
317 } else {
318 writer = [](float) { };
319 }
320
321 state->id = id;
322 m_records[id] = ElementRecord {
323 .buffer = std::move(buffer),
324 .bindings = nullptr,
325 .reader = std::move(reader),
326 .bulk_reader = std::move(bulk_reader),
327 .writer = std::move(writer),
328 .inbound_task = {},
329 .outbound_tasks = {},
330 };
331 }
332
333 /**
334 * @brief Register a fully constructed Mapped<T> and own its sync loop.
335 *
336 * Delegates record and reader/writer setup to the existing
337 * register_element overload, then spawns a per-frame GraphicsRoutine
338 * that calls mapped.sync(). The routine is stored in outbound_tasks
339 * and cancelled by unbind().
340 *
341 * @p mapped must outlive the Bridge registration.
342 *
343 * @tparam T MappedState value type.
344 * @param mapped Fully constructed Mapped<T>, typically from create_element.
345 * @param project Optional T -> float projection for outbound readers.
346 * Identity cast used if empty.
347 */
348 template <typename T>
349 void register_element(Mapped<T> mapped, std::function<float(T)> project = {})
350 {
351 register_element(mapped.state, mapped.element.id, mapped.element.buffer, std::move(project));
352 spawn_sync(mapped.element.id, [m = std::move(mapped)]() mutable { m.sync(); });
353 }
354
355 // =========================================================================
356 // Binding lifecycle
357 // =========================================================================
358
359 /**
360 * @brief Cancel all inbound and outbound bindings for an element.
361 * The element remains in its layer.
362 */
363 void unbind(uint32_t id);
364
365 template <typename T>
366 void unbind(std::shared_ptr<MappedState<T>> state)
367 {
368 unbind(state->id);
369 }
370
371 // =========================================================================
372 // Binding handle
373 // =========================================================================
374
375 /**
376 * @class Binding
377 * @brief Non-owning handle for all bind/write/unbind operations on one element.
378 *
379 * Obtained via Bridge::at(id) or Bridge::at(state). Closes over the
380 * Bridge reference and the element id so the caller never repeats either.
381 * Every method forwards directly to the corresponding Bridge overload:
382 * there is no deferred execution, no new mechanism.
383 *
384 * Binding holds a reference to its Bridge and must not outlive it.
385 *
386 * @code
387 * bridge.at(el.element.id)
388 * .bind(envelope_node)
389 * .write(compute_buf, shader_path, offsetof(PC, cutoff))
390 * .write(audio_proc);
391 * @endcode
392 */
393 class Binding {
394 public:
395 Binding(Bridge& bridge, uint32_t id) noexcept
396 : m_bridge(bridge)
397 , m_id(id)
398 {
399 }
400
401 [[nodiscard]] uint32_t id() const noexcept { return m_id; }
402
403 Binding& bind(std::shared_ptr<Nodes::Node> node,
404 std::function<float(double)> project = {})
405 {
406 m_bridge.bind(m_id, std::move(node), std::move(project));
407 return *this;
408 }
409
410 Binding& bind(std::function<float()> source)
411 {
412 m_bridge.bind(m_id, std::move(source));
413 return *this;
414 }
415
417 const std::shared_ptr<Buffers::VKBuffer>& target,
418 const std::string& shader_path,
419 uint32_t offset,
420 size_t size = sizeof(float))
421 {
422 m_bridge.write(m_id, target, shader_path, offset, size);
423 return *this;
424 }
425
427 const std::shared_ptr<Buffers::VKBuffer>& target,
428 const std::string& shader_path,
429 const std::string& descriptor_name,
430 uint32_t binding_index,
431 uint32_t set,
432 Portal::Graphics::DescriptorRole role = Portal::Graphics::DescriptorRole::UNIFORM)
433 {
434 m_bridge.write(m_id, target, shader_path,
435 descriptor_name, binding_index, set, role);
436 return *this;
437 }
438
439 Binding& write(std::shared_ptr<Buffers::AudioWriteProcessor> target)
440 {
441 m_bridge.write(m_id, std::move(target));
442 return *this;
443 }
444
445 Binding& write(std::shared_ptr<Buffers::DataWriteProcessor> target)
446 {
447 m_bridge.write(m_id, std::move(target));
448 return *this;
449 }
450
451 Binding& write(std::shared_ptr<Nodes::Constant> node)
452 {
453 m_bridge.write(m_id, std::move(node));
454 return *this;
455 }
456
457 Binding& write(std::function<void(std::span<const float>)> sink)
458 {
459 m_bridge.write(m_id, std::move(sink));
460 return *this;
461 }
462
463 void unbind()
464 {
465 m_bridge.unbind(m_id);
466 }
467
468 private:
470 uint32_t m_id;
471 };
472
473 // =========================================================================
474 // at() — entry point for the Binding handle
475 // =========================================================================
476
477 /**
478 * @brief Return a Binding handle for the element registered under @p id.
479 *
480 * The handle is lightweight and non-owning. Prefer this over repeated
481 * bind(id, ...) / write(id, ...) calls when wiring one element to
482 * multiple sources or destinations.
483 */
484 [[nodiscard]] Binding at(uint32_t id)
485 {
486 return Binding { *this, id };
487 }
488
489 /**
490 * @brief Return a Binding handle resolved from a MappedState.
491 */
492 template <typename T>
493 [[nodiscard]] Binding at(const std::shared_ptr<MappedState<T>>& state)
494 {
495 return Binding(*this, state->id);
496 }
497
498 /**
499 * @brief Spawn a per-frame GraphicsRoutine that calls @p sync_fn each tick.
500 *
501 * Stores the task name in the outbound_tasks of the record for @p id so
502 * that unbind() cancels it correctly. The coroutine body is type-free --
503 * all type-specific work is captured inside @p sync_fn by the caller.
504 *
505 * @param id Element id whose outbound_tasks receives the task name.
506 * @param sync_fn Callable invoked once per frame. Typically a lambda
507 * capturing Mapped<T> by reference and calling sync().
508 */
509 void spawn_sync(uint32_t id, std::function<void()> sync_fn);
510
511private:
513 std::shared_ptr<Buffers::FormaBuffer> buffer;
514 std::shared_ptr<Buffers::FormaBindingsProcessor> bindings;
515 std::function<float()> reader; ///< reads current value from MappedState (outbound)
516 std::function<std::vector<float>()> bulk_reader; ///< populated for vector<float>/vector<double> T
517 std::function<void(float)> writer; ///< writes new value into MappedState (inbound)
518 std::string inbound_task;
519 std::vector<std::string> outbound_tasks;
520 };
521
524
525 mutable uint32_t m_next_id { 0 };
526
527 std::unordered_map<uint32_t, ElementRecord> m_records;
528
529 std::string make_task_name(uint32_t id, const char* suffix) const;
530 void cancel_inbound(ElementRecord& rec);
531 void cancel_outbound(ElementRecord& rec);
532 void spawn_inbound(uint32_t id, std::function<float()> source);
533};
534
535} // namespace MayaFlux::Portal::Forma
Token-based multimodal buffer management system for unified data stream processing.
Binding(Bridge &bridge, uint32_t id) noexcept
Definition Bridge.hpp:395
Binding & write(std::function< void(std::span< const float >)> sink)
Definition Bridge.hpp:457
Binding & bind(std::shared_ptr< Nodes::Node > node, std::function< float(double)> project={})
Definition Bridge.hpp:403
Binding & write(std::shared_ptr< Buffers::AudioWriteProcessor > target)
Definition Bridge.hpp:439
Binding & bind(std::function< float()> source)
Definition Bridge.hpp:410
Binding & write(const std::shared_ptr< Buffers::VKBuffer > &target, const std::string &shader_path, const std::string &descriptor_name, uint32_t binding_index, uint32_t set, Portal::Graphics::DescriptorRole role=Portal::Graphics::DescriptorRole::UNIFORM)
Definition Bridge.hpp:426
uint32_t id() const noexcept
Definition Bridge.hpp:401
Binding & write(std::shared_ptr< Buffers::DataWriteProcessor > target)
Definition Bridge.hpp:445
Binding & write(std::shared_ptr< Nodes::Constant > node)
Definition Bridge.hpp:451
Binding & write(const std::shared_ptr< Buffers::VKBuffer > &target, const std::string &shader_path, uint32_t offset, size_t size=sizeof(float))
Definition Bridge.hpp:416
Non-owning handle for all bind/write/unbind operations on one element.
Definition Bridge.hpp:393
void write(std::shared_ptr< MappedState< T > > state, std::shared_ptr< Buffers::VKBuffer > target_buffer, const std::string &shader_path, const std::string &descriptor_name, uint32_t binding_index, uint32_t set, Portal::Graphics::DescriptorRole role=Portal::Graphics::DescriptorRole::UNIFORM)
Definition Bridge.hpp:229
void write(std::shared_ptr< MappedState< T > > state, std::function< void(std::span< const float >)> sink)
Definition Bridge.hpp:129
void write(std::shared_ptr< MappedState< T > > state, std::shared_ptr< Buffers::VKBuffer > target_buffer, const std::string &shader_path, uint32_t offset, size_t size=sizeof(float))
Definition Bridge.hpp:219
Binding at(uint32_t id)
Return a Binding handle for the element registered under id.
Definition Bridge.hpp:484
Vruta::TaskScheduler & m_scheduler
Definition Bridge.hpp:522
Bridge & operator=(Bridge &&)=delete
void register_element(std::shared_ptr< MappedState< T > > state, uint32_t id, std::shared_ptr< Buffers::FormaBuffer > buffer, std::function< float(T)> project={})
Register a MappedState<T> so that MappedState overloads and outbound bindings can resolve to the corr...
Definition Bridge.hpp:280
Bridge(const Bridge &)=delete
std::unordered_map< uint32_t, ElementRecord > m_records
Definition Bridge.hpp:527
void write(std::shared_ptr< MappedState< T > > state, std::shared_ptr< Nodes::Constant > node)
Definition Bridge.hpp:256
void bind(std::shared_ptr< MappedState< T > > state, std::shared_ptr< Nodes::Node > node, std::function< float(double)> project={})
Definition Bridge.hpp:115
void write(std::shared_ptr< MappedState< T > > state, std::shared_ptr< Buffers::DataWriteProcessor > target)
Definition Bridge.hpp:249
Buffers::BufferManager & m_buffer_manager
Definition Bridge.hpp:523
void unbind(std::shared_ptr< MappedState< T > > state)
Definition Bridge.hpp:366
void bind(std::shared_ptr< MappedState< T > > state, std::function< float()> source)
Definition Bridge.hpp:123
Binding at(const std::shared_ptr< MappedState< T > > &state)
Return a Binding handle resolved from a MappedState.
Definition Bridge.hpp:493
void register_element(Mapped< T > mapped, std::function< float(T)> project={})
Register a fully constructed Mapped<T> and own its sync loop.
Definition Bridge.hpp:349
void write(std::shared_ptr< MappedState< T > > state, std::shared_ptr< Buffers::AudioWriteProcessor > target)
Definition Bridge.hpp:242
Bridge & operator=(const Bridge &)=delete
Two-way binding orchestrator for Forma elements.
Definition Bridge.hpp:65
Token-based multimodal task scheduling system for unified coroutine processing.
Definition Scheduler.hpp:51
Contains the node-based computational processing system components.
Definition Chronie.hpp:14
DescriptorRole
Semantic descriptor type — maps to Vulkan descriptor types internally.
std::function< std::vector< float >()> bulk_reader
populated for vector<float>/vector<double> T
Definition Bridge.hpp:516
std::shared_ptr< Buffers::FormaBuffer > buffer
Definition Bridge.hpp:513
std::vector< std::string > outbound_tasks
Definition Bridge.hpp:519
std::function< float()> reader
reads current value from MappedState (outbound)
Definition Bridge.hpp:515
std::function< void(float)> writer
writes new value into MappedState (inbound)
Definition Bridge.hpp:517
std::shared_ptr< Buffers::FormaBindingsProcessor > bindings
Definition Bridge.hpp:514
std::shared_ptr< Buffers::FormaBuffer > buffer
Buffer whose rendered output occupies this region.
Definition Element.hpp:72
uint32_t id
Stable id assigned by Layer::add. Never zero.
Definition Element.hpp:60
Value carrier for a Mapped primitive.
Definition Mapped.hpp:36
std::shared_ptr< MappedState< T > > state
Shared value carrier. External systems hold a copy of this ptr.
Definition Mapped.hpp:91
Element element
The Element registered with the Layer.
Definition Mapped.hpp:98
Infrastructure for a continuously-mapped value whose GPU geometry tracks it.
Definition Mapped.hpp:89