MayaFlux 0.4.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
Scheduler.cpp
Go to the documentation of this file.
1#include "Scheduler.hpp"
2
3#include "ChronUtils.hpp"
4
6
7namespace MayaFlux::Vruta {
8
9TaskScheduler::TaskScheduler(uint32_t default_sample_rate, uint32_t default_frame_rate)
10 : m_clock(default_sample_rate)
11 , m_cleanup_threshold(512)
12 , m_registered_sample_rate(default_sample_rate)
13 , m_registered_frame_rate(default_frame_rate)
14{
15 s_registered_sample_rate = default_sample_rate;
16 s_registered_frame_rate = default_frame_rate;
19 ensure_domain(ProcessingToken::MULTI_RATE, default_sample_rate);
21}
22
23void TaskScheduler::add_task(const std::shared_ptr<Routine>& routine, const std::string& name, bool initialize)
24{
25 if (!routine) {
26 MF_ERROR(Journal::Component::Vruta, Journal::Context::CoroutineScheduling, "Failed to initiate routine; routine is null. Exiting add_task.");
27 return;
28 }
29
30 std::string task_name = name.empty() ? auto_generate_name(routine) : name;
31 ProcessingToken token = routine->get_processing_token();
32
33 ensure_domain(token);
34
35 if (initialize)
36 initialize_routine_state(routine, token);
37
38 if (token == ProcessingToken::CONDITIONAL) {
39 routine->set_auto_resume(true);
40 for (auto& op : m_conditional_pending_ops) {
41 bool expected = false;
42 if (op.active.compare_exchange_strong(expected, true,
43 std::memory_order_relaxed, std::memory_order_relaxed)) {
44 op.is_addition = true;
45 op.entry = { routine, task_name };
46 m_conditional_pending_count.fetch_add(1, std::memory_order_release);
48 return;
49 }
50 }
52 "Conditional pending queue full, could not add task '{}'", task_name);
53 return;
54 }
55
56 for (auto& op : m_pending_ops) {
57 bool expected = false;
58 if (op.active.compare_exchange_strong(expected, true,
59 std::memory_order_acquire, std::memory_order_relaxed)) {
60 op.entry = { routine, task_name };
61 op.is_addition = true;
62 m_pending_count.fetch_add(1, std::memory_order_relaxed);
63 return;
64 }
65 }
66
68 "Pending task queue full, could not add task '{}'", task_name);
69}
70
71bool TaskScheduler::cancel_task(const std::string& name)
72{
73 for (auto& op : m_conditional_pending_ops) {
74 bool expected = false;
75 if (op.active.compare_exchange_strong(expected, true,
76 std::memory_order_acquire, std::memory_order_relaxed)) {
77 op.entry = { nullptr, name };
78 op.is_addition = false;
79 m_conditional_pending_count.fetch_add(1, std::memory_order_relaxed);
80 return true;
81 }
82 }
83
84 for (auto& op : m_pending_ops) {
85 bool expected = false;
86 if (op.active.compare_exchange_strong(expected, true,
87 std::memory_order_acquire, std::memory_order_relaxed)) {
88 op.entry = { nullptr, name };
89 op.is_addition = false;
90 m_pending_count.fetch_add(1, std::memory_order_relaxed);
91 return true;
92 }
93 }
94
96 "Pending task queue full, could not cancel task '{}'", name);
97
98 return false;
99}
100
101bool TaskScheduler::cancel_task(const std::shared_ptr<Routine>& routine)
102{
103 if (!routine)
104 return false;
105
106 if (routine && routine->get_processing_token() == ProcessingToken::CONDITIONAL) {
107 auto it = std::ranges::find_if(m_conditional_tasks,
108 [&routine](const TaskEntry& e) { return e.routine == routine; });
109 const std::string name = (it != m_conditional_tasks.end()) ? it->name : "";
110 return cancel_task(name);
111 }
112
113 auto it = find_task_by_routine(routine);
114 const std::string name = (it != m_tasks.end()) ? it->name : "";
115
116 for (auto& op : m_pending_ops) {
117 bool expected = false;
118 if (op.active.compare_exchange_strong(expected, true,
119 std::memory_order_acquire, std::memory_order_relaxed)) {
120 op.entry = { routine, name };
121 op.is_addition = false;
122 m_pending_count.fetch_add(1, std::memory_order_relaxed);
123 return true;
124 }
125 }
126
128 "Pending task queue full, could not cancel task '{}'", name);
129 return false;
130}
131
132bool TaskScheduler::restart_task(const std::string& name)
133{
134 auto cit = std::ranges::find_if(m_conditional_tasks,
135 [&name](const TaskEntry& e) { return e.name == name; });
136 if (cit != m_conditional_tasks.end()) {
137 if (cit->routine && cit->routine->is_active())
138 cit->routine->restart();
139 return true;
140 }
141
142 auto it = find_task_by_name(name);
143 if (it != m_tasks.end()) {
144 if (it->routine && it->routine->is_active()) {
145 it->routine->restart();
146 }
147 }
148 return false;
149}
150
151std::shared_ptr<Routine> TaskScheduler::get_task(const std::string& name) const
152{
153 auto cit = std::ranges::find_if(m_conditional_tasks,
154 [&name](const TaskEntry& e) { return e.name == name; });
155 if (cit != m_conditional_tasks.end())
156 return cit->routine;
157
158 auto it = find_task_by_name(name);
159 return (it != m_tasks.end()) ? it->routine : nullptr;
160}
161
162std::vector<std::shared_ptr<Routine>> TaskScheduler::get_tasks_for_token(ProcessingToken token) const
163{
164 if (token == ProcessingToken::CONDITIONAL) {
165 std::vector<std::shared_ptr<Routine>> result;
166 for (const auto& e : m_conditional_tasks) {
167 if (e.routine)
168 result.push_back(e.routine);
169 }
170 return result;
171 }
172
173 std::vector<std::shared_ptr<Routine>> result;
174 for (const auto& entry : m_tasks) {
175 if (entry.routine && entry.routine->get_processing_token() == token) {
176 result.push_back(entry.routine);
177 }
178 }
179 return result;
180}
181
182std::vector<std::string> TaskScheduler::get_task_names() const
183{
184 std::vector<std::string> names;
185 for (const auto& entry : m_tasks) {
186 if (entry.routine)
187 names.push_back(entry.name);
188 }
189 return names;
190}
191
192std::vector<std::string> TaskScheduler::get_task_names(ProcessingToken token) const
193{
194 if (token == ProcessingToken::CONDITIONAL) {
195 std::vector<std::string> names;
196 for (const auto& e : m_conditional_tasks) {
197 if (e.routine)
198 names.push_back(e.name);
199 }
200 return names;
201 }
202
203 std::vector<std::string> names;
204 for (const auto& entry : m_tasks) {
205 if (entry.routine && entry.routine->get_processing_token() == token)
206 names.push_back(entry.name);
207 }
208 return names;
209}
210
211const std::vector<TaskEntry>& TaskScheduler::get_all_tasks()
212{
214 return m_tasks;
215}
216
217void TaskScheduler::process_token(ProcessingToken token, uint64_t processing_units)
218{
220
221 auto processor_it = m_token_processors.find(token);
222 if (processor_it != m_token_processors.end()) {
223 auto tasks = get_tasks_for_token(token);
224 processor_it->second(tasks, processing_units);
225 } else {
226 process_default(token, processing_units);
227 }
228
231 } else if (token == ProcessingToken::FRAME_ACCURATE) {
233 }
234
235 static uint64_t cleanup_counter = 0;
236 if (++cleanup_counter % (static_cast<uint64_t>(m_cleanup_threshold * 2)) == 0) {
238 }
239}
240
242{
243 for (const auto& token : m_token_clocks) {
244 process_token(token.first, 0);
245 }
246
247 static uint64_t cleanup_counter = 0;
248 if (++cleanup_counter % m_cleanup_threshold == 0) {
250 }
251}
252
254{
255 ensure_domain(token);
256 m_token_processors[token] = std::move(processor);
257}
258
259void TaskScheduler::register_clock(ProcessingToken token, std::shared_ptr<IClock> clock)
260{
261 ensure_domain(token);
262 m_token_clocks[token] = std::move(clock);
263}
264
266{
267 auto clock_it = m_token_clocks.find(token);
268 if (clock_it != m_token_clocks.end()) {
269 return *clock_it->second;
270 }
271
272 auto audio_clock_it = m_token_clocks.find(ProcessingToken::SAMPLE_ACCURATE);
273 if (audio_clock_it != m_token_clocks.end()) {
274 return *audio_clock_it->second;
275 }
276
277 error<std::runtime_error>(Journal::Component::Vruta, Journal::Context::CoroutineScheduling, std::source_location::current(),
278 "No clock found for token {}, and no sample-accurate clock available as fallback", static_cast<int>(token));
279}
280
281uint64_t TaskScheduler::seconds_to_units(double seconds, ProcessingToken token) const
282{
283 unsigned int rate = get_rate(token);
284 return static_cast<uint64_t>(seconds * rate);
285}
286
287uint64_t TaskScheduler::seconds_to_samples(double seconds) const
288{
289 return static_cast<uint64_t>(seconds * get_rate(ProcessingToken::SAMPLE_ACCURATE));
290}
291
293{
294 const auto& clock = get_clock(token);
295 return clock.current_position();
296}
297
299{
300 auto clock_it = m_token_clocks.find(token);
301 if (clock_it != m_token_clocks.end()) {
302 return clock_it->second->rate();
303 }
304
305 return get_default_rate(token);
306}
307
309{
310 return std::ranges::any_of(m_tasks,
311 [token](const TaskEntry& entry) {
312 return entry.routine && entry.routine->is_active() && entry.routine->get_processing_token() == token;
313 });
314}
315
317{
318 return m_next_task_id.fetch_add(1);
319}
320
321std::string TaskScheduler::auto_generate_name(const std::shared_ptr<Routine>& /*routine*/) const
322{
323 return "task_" + std::to_string(get_next_task_id());
324}
325
326std::vector<TaskEntry>::iterator TaskScheduler::find_task_by_name(const std::string& name)
327{
328 return std::ranges::find_if(m_tasks,
329 [&name](const TaskEntry& entry) { return entry.name == name; });
330}
331
332std::vector<TaskEntry>::const_iterator TaskScheduler::find_task_by_name(const std::string& name) const
333{
334 return std::ranges::find_if(m_tasks,
335 [&name](const TaskEntry& entry) { return entry.name == name; });
336}
337
338std::vector<TaskEntry>::iterator TaskScheduler::find_task_by_routine(const std::shared_ptr<Routine>& routine)
339{
340 return std::ranges::find_if(m_tasks,
341 [&routine](const TaskEntry& entry) { return entry.routine == routine; });
342}
343
345{
346 switch (token) {
354 return 1;
356 return 1000;
358 return 0;
359 default:
361 }
362}
363
364void TaskScheduler::ensure_domain(ProcessingToken token, unsigned int rate)
365{
366 if (token == ProcessingToken::CONDITIONAL)
367 return;
368
369 auto clock_it = m_token_clocks.find(token);
370 if (clock_it == m_token_clocks.end()) {
371 unsigned int domain_rate = (rate > 0) ? rate : get_default_rate(token);
372
373 switch (token) {
375 m_token_clocks[token] = std::make_shared<FrameClock>(domain_rate);
376 break;
379 default:
380 m_token_clocks[token] = std::make_shared<SampleClock>(domain_rate);
381 break;
382 }
383 }
384}
385
386void TaskScheduler::process_default(ProcessingToken token, uint64_t processing_units)
387{
388 auto clock_it = m_token_clocks.find(token);
389 if (clock_it == m_token_clocks.end()) {
390 return;
391 }
392
393 auto tasks = get_tasks_for_token(token);
394 if (tasks.empty()) {
395 auto& clock = *clock_it->second;
396 clock.tick(processing_units);
397 return;
398 }
399
400 auto& clock = *clock_it->second;
401
402 for (uint64_t i = 0; i < processing_units; i++) {
403 uint64_t current_context = clock.current_position();
404
405 for (auto& routine : tasks) {
406 if (routine && routine->is_active()) {
407 if (routine->requires_clock_sync()) {
408 if (current_context >= routine->next_execution()) {
409 routine->try_resume_with_context(current_context, DelayContext::SAMPLE_BASED);
410 }
411 } else {
412 routine->try_resume_with_context(current_context, DelayContext::SAMPLE_BASED);
413 }
414 }
415 }
416
417 clock.tick(1);
418 }
419}
420
422{
423 std::erase_if(m_tasks, [](const TaskEntry& entry) {
424 return !entry.routine || !entry.routine->is_active();
425 });
426}
427
428bool TaskScheduler::initialize_routine_state(const std::shared_ptr<Routine>& routine, ProcessingToken token)
429{
430 if (!routine) {
431 return false;
432 }
433
434 auto clock_it = m_token_clocks.find(token);
435 if (clock_it == m_token_clocks.end()) {
436 return false;
437 }
438
439 uint64_t current_context = clock_it->second->current_position();
440 return routine->initialize_state(current_context);
441}
442
444{
445 if (m_conditional_thread.joinable())
446 return;
447
448#if MAYAFLUX_USE_JTHREAD
449 m_conditional_thread = std::jthread([this](const std::stop_token& st) {
450 while (!st.stop_requested())
452 });
453#else
454 m_conditional_stop.store(false, std::memory_order_release);
455 m_conditional_thread = std::thread([this]() {
456 while (!m_conditional_stop.load(std::memory_order_acquire))
458 });
459#endif
460}
461
463{
464 for (auto& entry : m_conditional_tasks) {
465 if (entry.routine && entry.routine->is_active()) {
466 bool current_auto_resume = entry.routine->get_auto_resume();
467 entry.routine->set_state<bool>("was_auto_resume", current_auto_resume);
468 entry.routine->set_auto_resume(false);
469 }
470 }
471
472 for (auto& entry : m_tasks) {
473 if (entry.routine && entry.routine->is_active()) {
474 bool current_auto_resume = entry.routine->get_auto_resume();
475 entry.routine->set_state<bool>("was_auto_resume", current_auto_resume);
476 entry.routine->set_auto_resume(false);
477 }
478 }
479}
480
482{
483 for (auto& entry : m_conditional_tasks) {
484 if (entry.routine && entry.routine->is_active()) {
485 auto was_auto_resume = entry.routine->get_state<bool>("was_auto_resume");
486 if (was_auto_resume) {
487 entry.routine->set_auto_resume(*was_auto_resume);
488 } else {
489 entry.routine->set_auto_resume(true);
490 }
491 }
492 }
493
494 for (auto& entry : m_tasks) {
495 if (entry.routine && entry.routine->is_active()) {
496 auto was_auto_resume = entry.routine->get_state<bool>("was_auto_resume");
497 if (was_auto_resume) {
498 entry.routine->set_auto_resume(*was_auto_resume);
499 } else {
500 entry.routine->set_auto_resume(true);
501 }
502 }
503 }
504}
505
507{
509
510 for (auto& entry : m_tasks) {
511 if (entry.routine && entry.routine->is_active()) {
512 entry.routine->set_should_terminate(true);
513 }
514 }
515
516 for (auto& entry : m_tasks) {
517 if (entry.routine && entry.routine->is_active()) {
518 entry.routine->force_resume();
519 }
520 }
521
522 constexpr int MAX_ATTEMPTS = 3;
523 for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
524 std::this_thread::sleep_for(std::chrono::milliseconds(5));
525
526 bool any_active = false;
527 for (auto& entry : m_tasks) {
528 if (entry.routine && entry.routine->is_active()) {
529 any_active = true;
530 entry.routine->force_resume();
531 }
532 }
533
534 if (!any_active) {
535 break;
536 }
537 }
538
539 bool all_done = true;
540 for (const auto& entry : m_tasks) {
541 if (entry.routine && entry.routine->is_active()) {
542 all_done = false;
544 "Coroutine '{}' stuck after {} attempts - forcing destruction",
545 entry.name, MAX_ATTEMPTS);
546 }
547 }
548
549 if (!all_done) {
551 "Some coroutines did not complete - forcing destruction");
552 } else {
554 "All coroutines terminated successfully");
555 }
556
558 for (auto& entry : m_conditional_tasks) {
559 if (entry.routine && entry.routine->is_active())
560 entry.routine->set_should_terminate(true);
561 }
562 for (auto& entry : m_conditional_tasks) {
563 if (entry.routine && entry.routine->is_active())
564 entry.routine->force_resume();
565 }
566 m_conditional_tasks.clear();
567
568 m_tasks.clear();
569}
570
572{
574
577
578 for (auto& task : tasks) {
579 if (task && task->is_active()) {
580 if (task->requires_clock_sync()) {
581 if (m_current_buffer_cycle >= task->next_execution()) {
582 task->try_resume_with_context(m_current_buffer_cycle, DelayContext::BUFFER_BASED);
583 }
584 } else {
585 task->try_resume_with_context(m_current_buffer_cycle, DelayContext::BUFFER_BASED);
586 }
587 }
588 }
589}
590
592{
593 if (m_pending_count.load(std::memory_order_relaxed) == 0)
594 return;
595
596 for (auto& op : m_pending_ops) {
597 if (!op.active.load(std::memory_order_acquire))
598 continue;
599
600 if (op.is_addition) {
601 auto existing_it = find_task_by_name(op.entry.name);
602 if (existing_it != m_tasks.end()) {
603 if (existing_it->routine && existing_it->routine->is_active())
604 existing_it->routine->set_should_terminate(true);
605 m_tasks.erase(existing_it);
606 }
607 m_tasks.push_back(std::move(op.entry));
608 } else {
609 auto it = find_task_by_name(op.entry.name);
610 if (it != m_tasks.end()) {
611 if (it->routine && it->routine->is_active())
612 it->routine->set_should_terminate(true);
613 m_tasks.erase(it);
614 }
615 }
616
617 op.entry = { nullptr, "" };
618 op.active.store(false, std::memory_order_release);
619 m_pending_count.fetch_sub(1, std::memory_order_relaxed);
620 }
621}
622
624{
625 if (m_conditional_pending_count.load(std::memory_order_acquire) == 0)
626 return;
627
628 for (auto& op : m_conditional_pending_ops) {
629 if (!op.active.load(std::memory_order_acquire))
630 continue;
631
632 if (op.is_addition) {
633 auto it = std::ranges::find_if(m_conditional_tasks,
634 [&](const TaskEntry& e) { return e.name == op.entry.name; });
635 if (it != m_conditional_tasks.end()) {
636 if (it->routine && it->routine->is_active())
637 it->routine->set_should_terminate(true);
638 m_conditional_tasks.erase(it);
639 }
640 m_conditional_tasks.push_back(std::move(op.entry));
641 } else {
642 auto it = std::ranges::find_if(m_conditional_tasks,
643 [&](const TaskEntry& e) { return e.name == op.entry.name; });
644 if (it != m_conditional_tasks.end()) {
645 if (it->routine && it->routine->is_active())
646 it->routine->set_should_terminate(true);
647 m_conditional_tasks.erase(it);
648 }
649 }
650
651 op.entry = { nullptr, "" };
652 op.active.store(false, std::memory_order_release);
653 m_conditional_pending_count.fetch_sub(1, std::memory_order_relaxed);
654 }
655}
656
657void TaskScheduler::pump_cross(DelayContext context, ProcessingToken clock_token, uint64_t processing_units)
658{
660 if (cross_tasks.empty()) {
661 return;
662 }
663
664 auto clock_it = m_token_clocks.find(clock_token);
665 if (clock_it == m_token_clocks.end()) {
666 return;
667 }
668
669 if (processing_units == 0) {
670 processing_units = 1;
671 }
672
673 uint64_t base = clock_it->second->current_position();
674
675 for (uint64_t i = 0; i < processing_units; ++i) {
676 uint64_t pos = base + i;
677 for (auto& routine : cross_tasks) {
678 if (routine && routine->is_active()) {
679 routine->try_resume_with_context(pos, context);
680 }
681 }
682 }
683}
684
686{
688
689 if (m_conditional_tasks.empty()) {
690 std::this_thread::yield();
691 return;
692 }
693
694 std::erase_if(m_conditional_tasks, [](const TaskEntry& e) {
695 return !e.routine || !e.routine->is_active();
696 });
697
698 for (auto& entry : m_conditional_tasks) {
699 if (entry.routine && entry.routine->is_active())
700 entry.routine->try_resume(0);
701 }
702}
703
704}
#define MF_LOG(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
Abstract base interface for all clock types in the multimodal scheduling system.
Definition Clock.hpp:23
std::atomic< uint64_t > m_next_task_id
Task ID counter for unique identification.
void add_task(const std::shared_ptr< Routine > &routine, const std::string &name="", bool initialize=false)
Add a routine to the scheduler based on its processing token.
Definition Scheduler.cpp:23
bool initialize_routine_state(const std::shared_ptr< Routine > &routine, ProcessingToken token)
Initialize a routine's state for a specific domain.
void register_token_processor(ProcessingToken token, token_processing_func_t processor)
Register a custom processor for a specific token domain.
std::atomic< uint32_t > m_pending_count
uint64_t seconds_to_units(double seconds, ProcessingToken token=ProcessingToken::SAMPLE_ACCURATE) const
Convert seconds to processing units for a specific domain.
unsigned int get_default_rate(ProcessingToken token) const
Get the default rate for a processing token.
std::shared_ptr< Routine > get_task(const std::string &name) const
Get a named task.
void cleanup_completed_tasks()
Clean up completed tasks in a domain.
std::atomic< uint32_t > m_conditional_pending_count
void start_conditional_thread()
Start the conditional task processing thread if not already running.
std::unordered_map< ProcessingToken, std::shared_ptr< IClock > > m_token_clocks
Clock instances for each processing domain.
std::string auto_generate_name(const std::shared_ptr< Routine > &routine) const
Generate automatic name for a routine based on its type.
void pump_conditional()
Pump the conditional task list in a separate thread.
TaskScheduler(uint32_t default_sample_rate=48000, uint32_t default_frame_rate=60)
Constructs a TaskScheduler with the specified sample rate.
Definition Scheduler.cpp:9
std::vector< TaskEntry > m_tasks
const std::vector< TaskEntry > & get_all_tasks()
Get all tasks for inspection/debugging.
PendingTaskOp m_pending_ops[MAX_PENDING_TASKS]
void process_token(ProcessingToken token, uint64_t processing_units=1)
Process all tasks for a specific processing domain.
std::vector< TaskEntry >::iterator find_task_by_name(const std::string &name)
Find task entry by name.
void ensure_domain(ProcessingToken token, unsigned int rate=0)
Initialize a processing domain if it doesn't exist.
void pause_all_tasks()
Pause all active tasks.
std::vector< TaskEntry > m_conditional_tasks
uint64_t get_next_task_id() const
Generates a unique task ID for new tasks.
uint32_t m_cleanup_threshold
Threshold for task cleanup.
uint64_t seconds_to_samples(double seconds) const
Converts a time in seconds to a number of samples.
std::vector< std::shared_ptr< Routine > > get_tasks_for_token(ProcessingToken token) const
Get all tasks for a specific processing domain.
std::unordered_map< ProcessingToken, token_processing_func_t > m_token_processors
Custom processors for specific domains.
void drain_pending_tasks()
Drain pending task operations (additions/removals) before processing.
void pump_cross(DelayContext context, ProcessingToken clock_token, uint64_t processing_units)
Pump the MULTI_RATE list from one clock's context.
uint64_t current_units(ProcessingToken token=ProcessingToken::SAMPLE_ACCURATE) const
Get current processing units for a domain.
void register_clock(ProcessingToken token, std::shared_ptr< IClock > clock)
Register an externally-owned clock as the authoritative source for a token domain.
void process_default(ProcessingToken token, uint64_t processing_units)
Process tasks in a specific domain with default algorithm.
void process_all_tokens()
Process all active domains.
unsigned int get_rate(ProcessingToken token=ProcessingToken::SAMPLE_ACCURATE) const
Get processing rate for a domain.
bool restart_task(const std::string &name)
Restart a named task.
PendingTaskOp m_conditional_pending_ops[MAX_PENDING_CONDITIONAL]
std::vector< std::string > get_task_names() const
Get all task names for debugging/inspection.
std::atomic< bool > m_conditional_stop
void terminate_all_tasks()
Terminate and clear all tasks.
void resume_all_tasks()
Resume all previously paused tasks.
bool has_active_tasks(ProcessingToken token) const
Check if a processing domain has any active tasks.
const SampleClock & get_clock() const
Gets the primary clock (audio domain for legacy compatibility)
bool cancel_task(const std::shared_ptr< Routine > &routine)
Cancels and removes a task from the scheduler.
void drain_conditional_pending()
Drain pending conditional task operations before processing.
std::vector< TaskEntry >::iterator find_task_by_routine(const std::shared_ptr< Routine > &routine)
Find task entry by routine pointer.
void initialize()
Definition main.cpp:11
@ CoroutineScheduling
Coroutine scheduling and temporal coordination (Vruta::TaskScheduler)
@ Vruta
Coroutines, schedulers, clocks, task management.
std::function< void(const std::vector< std::shared_ptr< Routine > > &, uint64_t)> token_processing_func_t
Function type for processing tasks in a specific token domain.
uint32_t s_registered_frame_rate
Definition ChronUtils.hpp:6
@ MULTI_RATE
Coroutine can handle multiple sample rates. Picks the frame-accurate processing token by default.
@ CONDITIONAL
Condition-driven execution - resume when a caller-supplied predicate returns true.
@ FRAME_ACCURATE
Coroutine is frame-accurate.
@ SAMPLE_ACCURATE
Coroutine is sample-accurate.
@ ON_DEMAND
Coroutine is executed on demand, not scheduled.
uint32_t s_registered_sample_rate
Definition ChronUtils.hpp:5
DelayContext
Discriminator for different temporal delay mechanisms.
@ FRAME_BASED
Frame-rate delay (Graphics domain)
@ SAMPLE_BASED
Sample-accurate delay (audio domain)
@ BUFFER_BASED
Buffer-cycle delay (audio hardware boundary)
std::shared_ptr< Routine > routine
Definition Scheduler.hpp:9