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