MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Promise.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <coroutine>
4
6
7namespace MayaFlux::Vruta {
8
9class SoundRoutine;
10class GraphicsRoutine;
11class ComplexRoutine;
12class Event;
13class NetworkSource;
14
15/**
16 * @struct routine_promise
17 * @brief Base coroutine promise type for audio processing tasks
18 *
19 * This promise_type serves as the base class for all coroutine promises in the
20 * MayaFlux engine. It defines the common behavior and interface for
21 * coroutines, including lifecycle management, state storage, and execution flags.
22 *
23 * The promise_type is a crucial component of C++20 coroutines that defines the
24 * behavior of SoundRoutine coroutines. It serves as the control interface between
25 * the coroutine machinery and the audio engine, managing:
26 *
27 * In the coroutine model, the promise object is created first when a coroutine
28 * function is called. It then creates and returns the SoundRoutine object that
29 * the caller receives. The promise remains associated with the coroutine frame
30 * throughout its lifetime, while the RoutineType provides the external interface
31 * to manipulate the coroutine.
32 */
33template <typename RoutineType>
34struct MAYAFLUX_API routine_promise {
35
36 RoutineType get_return_object()
37 {
38 return RoutineType(std::coroutine_handle<routine_promise>::from_promise(*this));
39 }
40
41 /**
42 * @brief Determines whether the coroutine suspends immediately upon creation
43 * @return A suspend never awaitable, meaning the coroutine begins execution immediately
44 *
45 * By returning std::suspend_never, this method indicates that the coroutine
46 * should start executing as soon as it's created, rather than waiting for
47 * an explicit resume call.
48 */
49 std::suspend_never initial_suspend() { return {}; }
50
51 /**
52 * @brief Determines whether the coroutine suspends before destruction
53 * @return A suspend always awaitable, meaning the coroutine suspends before completing
54 *
55 * By returning std::suspend_always, this method ensures that the coroutine
56 * frame isn't destroyed immediately when the coroutine completes. This gives
57 * the scheduler a chance to observe the completion and perform cleanup.
58 */
59 std::suspend_always final_suspend() noexcept { return {}; }
60
61 /**
62 * @brief Handles the coroutine's void return
63 *
64 * This method is called when the coroutine executes a co_return statement
65 * without a value, or reaches the end of its function body.
66 */
67 void return_void() { }
68
69 /**
70 * @brief Handles exceptions thrown from within the coroutine
71 *
72 * This method is called if an unhandled exception escapes from the coroutine.
73 * The current implementation terminates the program, as exceptions in audio
74 * processing code are generally considered fatal errors.
75 */
76 void unhandled_exception() { std::terminate(); }
77
78 /**
79 * @brief Token indicating how this coroutine should be processed
80 */
81 const ProcessingToken processing_token { ProcessingToken::ON_DEMAND };
82
83 /**
84 * @brief Flag indicating whether the coroutine should be automatically resumed
85 *
86 * When true, the scheduler will automatically resume the coroutine when
87 * the current sample position reaches next_sample. When false, the coroutine
88 * must be manually resumed by calling ::try_resume.
89 */
90 bool auto_resume = true;
91
92 /**
93 * @brief Flag indicating whether the coroutine should be terminated
94 *
95 * When set to true, the scheduler will destroy the coroutine rather than
96 * resuming it, even if it hasn't completed naturally. This allows for
97 * early termination of long-running coroutines.
98 */
99 bool should_terminate = false;
100
101 /**
102 * @brief Dictionary for storing arbitrary state data
103 *
104 * This map allows the coroutine to store and retrieve named values of any type.
105 * It serves multiple purposes:
106 * 1. Persistent storage between suspensions
107 * 2. Communication channel between the coroutine and external code
108 * 3. Parameter storage for configurable behaviors
109 *
110 * The use of std::any allows for type-safe heterogeneous storage without
111 * requiring the promise type to be templated.
112 */
113 std::unordered_map<std::string, std::any> state;
114
115 /**
116 * @brief Flag indicating whether the coroutine should synchronize with the audio clock
117 *
118 * When true, the coroutine will be scheduled to run in sync with the specificed clock,
119 * via tokens ensuring sample-accurate timing. When false, it has to be "proceeded" manually
120 */
121 const bool sync_to_clock = false;
122
123 /**
124 * @brief Amount of delay requested by the coroutine
125 *
126 * This value is set when the coroutine co_awaits a delay awaiter (e.g., SampleDelay).
127 * It indicates how many time units the coroutine wishes to wait before resuming.
128 */
129 uint64_t delay_amount = 0;
130
131 /**
132 * @brief Stores a value in the state dictionary
133 * @param key Name of the state value
134 * @param value Value to store
135 *
136 * This method provides a type-safe way to store values of any type in the
137 * state dictionary. The value is wrapped in std::any for type erasure.
138 */
139 template <typename T>
140 inline void set_state(const std::string& key, T value)
141 {
142 state[key] = std::make_any<T>(std::move(value));
143 }
144
145 /**
146 * @brief Retrieves a value from the state dictionary
147 * @param key Name of the state value to retrieve
148 * @return Pointer to the stored value, or nullptr if not found or type mismatch
149 *
150 * This method provides a type-safe way to retrieve values from the state
151 * dictionary. It returns a pointer to the stored value if it exists and
152 * has the requested type, or nullptr otherwise.
153 */
154 template <typename T>
155 inline T* get_state(const std::string& key)
156 {
157 auto it = state.find(key);
158 if (it != state.end()) {
159 try {
160 return std::any_cast<T>(&it->second);
161 } catch (const std::bad_any_cast&) {
162 return nullptr;
163 }
164 }
165 return nullptr;
166 }
167
168 void domain_mismatch_error(const std::string& awaiter_name, const std::string& suggestion)
169 {
170 set_state("domain_error", awaiter_name + ": " + suggestion);
171 should_terminate = true;
172 }
173};
174
175/**
176 * @struct audio_promise
177 * @brief Coroutine promise type for audio processing tasks with sample-accurate timing
178 *
179 * The promise_type is a crucial component of C++20 coroutines that defines the
180 * behavior of SoundRoutine coroutines. It serves as the control interface between
181 * the coroutine machinery and the audio engine, managing:
182 *
183 * 1. Coroutine lifecycle (creation, suspension, resumption, and destruction)
184 * 2. Timing information for sample-accurate scheduling
185 * 3. State storage for persistent data between suspensions
186 * 4. Control flags for execution behavior
187 *
188 * In the coroutine model, the promise object is created first when a coroutine
189 * function is called. It then creates and returns the SoundRoutine object that
190 * the caller receives. The promise remains associated with the coroutine frame
191 * throughout its lifetime, while the SoundRoutine provides the external interface
192 * to manipulate the coroutine.
193 *
194 * This separation of concerns allows the audio engine to schedule and manage
195 * coroutines efficiently while providing a clean API for audio processing code.
196 */
197struct audio_promise : public routine_promise<SoundRoutine> {
198 /**
199 * @brief Creates the SoundRoutine object returned to the caller
200 * @return A new SoundRoutine that wraps this promise
201 *
202 * This method is called by the compiler-generated code when a coroutine
203 * function is invoked. It creates the SoundRoutine object that will be
204 * returned to the caller and associates it with this promise.
205 */
207
209
210 bool sync_to_clock = true;
211
212 /**
213 * @brief The sample position when this coroutine should next execute
214 *
215 * This is the core timing mechanism for sample-accurate scheduling.
216 * When a coroutine co_awaits a SampleDelay, this value is updated to
217 * indicate when the coroutine should be resumed next.
218 */
219 uint64_t next_sample = 0;
220
221 /**
222 * @brief The buffer cycle when this coroutine should next execute
223 * Managed by BufferDelay awaiter. Incremented on each co_await BufferDelay{}.
224 * Starts at 0, incremented to 1 on first await.
225 */
226 uint64_t next_buffer_cycle = 0;
227
228 /**
229 * @brief The active delay context for this coroutine
230 *
231 * This value indicates which type of delay (sample, buffer, event)
232 * is currently being awaited by the coroutine. It helps the scheduler
233 * determine how to manage the coroutine's timing.
234 */
236};
237
238/**
239 * @struct graphics_promise
240 * @brief Coroutine promise type for graphics processing tasks with frame-accurate timing
241 *
242 * graphics_promise is the frame-domain equivalent of audio_promise. It manages the
243 * state and lifecycle of GraphicsRoutine coroutines, providing frame-accurate timing
244 * and scheduling for visual processing tasks.
245 *
246 * Key Architectural Notes:
247 * - Frame timing is managed by FrameClock (self-driven wall-clock based)
248 * - Unlike audio_promise (driven by RTAudio callbacks), graphics_promise observes
249 * the FrameClock which ticks independently in the graphics thread loop
250 * - The promise doesn't care HOW timing advances, only that it receives tick updates
251 * - Mirrors audio_promise architecture but for the visual/frame domain
252 *
253 * Timing Flow:
254 * 1. Graphics thread loop: m_frame_clock->tick() (self-driven)
255 * 2. GraphicsSubsystem::process() called
256 * 3. Scheduler::process_frame_coroutines_impl() checks routines
257 * 4. If current_frame >= next_frame, routine->try_resume(current_frame)
258 * 5. Routine updates next_frame based on FrameDelay amount
259 */
260struct graphics_promise : public routine_promise<GraphicsRoutine> {
261 /**
262 * @brief Creates the GraphicsRoutine object returned to the caller
263 * @return A new GraphicsRoutine that wraps this promise
264 *
265 * This method is called by the compiler-generated code when a coroutine
266 * function is invoked. It creates the GraphicsRoutine object that will be
267 * returned to the caller and associates it with this promise.
268 */
270
271 /**
272 * @brief Processing token indicating frame-accurate scheduling
273 */
275
276 /**
277 * @brief Whether this routine should synchronize with FrameClock
278 */
279 bool sync_to_clock = true;
280
281 /**
282 * @brief The frame position when this coroutine should next execute
283 *
284 * This is the core timing mechanism for frame-accurate scheduling.
285 * When a coroutine co_awaits a FrameDelay, this value is updated to
286 * indicate when the coroutine should be resumed next.
287 *
288 * Example:
289 * - Current frame: 1000
290 * - co_await FrameDelay{5}
291 * - next_frame becomes: 1005
292 * - Routine resumes when FrameClock reaches frame 1005
293 */
294 uint64_t next_frame = 0;
295
296 /**
297 * @brief The active delay context for this coroutine
298 *
299 * This value indicates which type of delay (frame, event, etc.)
300 * is currently being awaited by the coroutine. It helps the scheduler
301 * determine how to manage the coroutine's timing and prevents
302 * cross-domain contamination (e.g., audio delays don't affect graphics routines).
303 *
304 * Valid states for graphics routines:
305 * - NONE: No active delay, can resume immediately
306 * - FRAME_BASED: Waiting for a specific frame (FrameDelay)
307 * - EVENT_BASED: Waiting for a window/input event (EventAwaiter)
308 * - AWAIT: Temporary state during GetPromise awaiter
309 */
311
312 /**
313 * @brief The amount of delay units for incremental delays
314 *
315 * When using delays that accumulate (like BufferDelay for audio),
316 * this stores the increment amount. For graphics, this would be
317 * the frame increment for continuous animations.
318 *
319 * Example:
320 * ```cpp
321 * auto animation = []() -> GraphicsRoutine {
322 * while (true) {
323 * render_frame();
324 * co_await FrameDelay{1}; // delay_amount = 1
325 * }
326 * };
327 * ```
328 */
329 uint64_t delay_amount = 0;
330};
331
332// TODO: Graphics features are not yet implemented, needs GL/Vulkan integration first
333/** * @struct complex_promise
334 * @brief Coroutine promise type for complex processing tasks with multi-rate scheduling
335 */
347
348/**
349 * @struct EventPromise
350 * @brief Promise type for event-driven coroutines
351 *
352 * Unlike time-based promises (SampleClockPromise, FrameClockPromise),
353 * EventPromise has no clock. Coroutines suspend/resume based on
354 * discrete event signals, not periodic ticks.
355 */
356struct event_promise : public routine_promise<Event> {
358
360
362
363 /**
364 * @brief Transfer ownership of a NetworkSource to this coroutine frame.
365 * @param source Source to keep alive for the coroutine's lifetime.
366 */
367 void own(std::shared_ptr<Vruta::NetworkSource> source)
368 {
369 owned_sources.push_back(std::move(source));
370 }
371
372 /**
373 * @brief Coroutine-owned NetworkSource instances.
374 *
375 * Sources deposited here via GetEventPromise live for exactly the
376 * lifetime of the coroutine frame. Populated through co_await
377 * GetEventPromise{ source } at coroutine startup.
378 */
379 std::vector<std::shared_ptr<Vruta::NetworkSource>> owned_sources;
380};
381
382}
Multi-domain coroutine that can handle multiple processing rates.
Definition Routine.hpp:665
Coroutine type for event-driven suspension.
Definition Event.hpp:26
A C++20 coroutine-based graphics processing task with frame-accurate timing.
Definition Routine.hpp:513
A C++20 coroutine-based audio processing task with sample-accurate timing.
Definition Routine.hpp:316
@ MULTI_RATE
Coroutine can handle multiple sample rates. Picks the frame-accurate processing token by default.
@ EVENT_DRIVEN
Event-driven execution - process when events arrive.
@ FRAME_ACCURATE
Coroutine is frame-accurate.
@ SAMPLE_ACCURATE
Coroutine is sample-accurate.
DelayContext
Discriminator for different temporal delay mechanisms.
@ NONE
No active delay, resume immediately.
@ EVENT_BASED
Event-driven delay (user events, etc.)
uint64_t next_sample
The sample position when this coroutine should next execute.
Definition Promise.hpp:219
ProcessingToken processing_token
Definition Promise.hpp:208
uint64_t next_buffer_cycle
The buffer cycle when this coroutine should next execute Managed by BufferDelay awaiter.
Definition Promise.hpp:226
SoundRoutine get_return_object()
Creates the SoundRoutine object returned to the caller.
Definition Routine.cpp:6
DelayContext active_delay_context
The active delay context for this coroutine.
Definition Promise.hpp:235
Coroutine promise type for audio processing tasks with sample-accurate timing.
Definition Promise.hpp:197
ComplexRoutine get_return_object()
Coroutine promise type for complex processing tasks with multi-rate scheduling.
Definition Promise.hpp:336
void own(std::shared_ptr< Vruta::NetworkSource > source)
Transfer ownership of a NetworkSource to this coroutine frame.
Definition Promise.hpp:367
ProcessingToken processing_token
Definition Promise.hpp:359
std::vector< std::shared_ptr< Vruta::NetworkSource > > owned_sources
Coroutine-owned NetworkSource instances.
Definition Promise.hpp:379
GraphicsRoutine get_return_object()
Creates the GraphicsRoutine object returned to the caller.
Definition Routine.cpp:11
DelayContext active_delay_context
The active delay context for this coroutine.
Definition Promise.hpp:310
uint64_t delay_amount
The amount of delay units for incremental delays.
Definition Promise.hpp:329
uint64_t next_frame
The frame position when this coroutine should next execute.
Definition Promise.hpp:294
bool sync_to_clock
Whether this routine should synchronize with FrameClock.
Definition Promise.hpp:279
ProcessingToken processing_token
Processing token indicating frame-accurate scheduling.
Definition Promise.hpp:274
Coroutine promise type for graphics processing tasks with frame-accurate timing.
Definition Promise.hpp:260
void unhandled_exception()
Handles exceptions thrown from within the coroutine.
Definition Promise.hpp:76
void domain_mismatch_error(const std::string &awaiter_name, const std::string &suggestion)
Definition Promise.hpp:168
void set_state(const std::string &key, T value)
Stores a value in the state dictionary.
Definition Promise.hpp:140
T * get_state(const std::string &key)
Retrieves a value from the state dictionary.
Definition Promise.hpp:155
void return_void()
Handles the coroutine's void return.
Definition Promise.hpp:67
std::unordered_map< std::string, std::any > state
Dictionary for storing arbitrary state data.
Definition Promise.hpp:113
std::suspend_never initial_suspend()
Determines whether the coroutine suspends immediately upon creation.
Definition Promise.hpp:49
std::suspend_always final_suspend() noexcept
Determines whether the coroutine suspends before destruction.
Definition Promise.hpp:59
Base coroutine promise type for audio processing tasks.
Definition Promise.hpp:34