MayaFlux 1.0.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
9namespace {
10
11 /**
12 * @brief Counts a submission call as in flight and reports whether it may proceed
13 *
14 * The count is entered before the terminating flag is read, so
15 * terminate_all_tasks can wait for every accepted call to finish.
16 */
17 struct SubmissionGuard {
18 SubmissionGuard(std::atomic<uint32_t>& inflight, const std::atomic<bool>& terminating)
19 : m_inflight(inflight)
20 {
21 m_inflight.fetch_add(1);
22 accepted = !terminating.load();
23 }
24
25 ~SubmissionGuard() { m_inflight.fetch_sub(1); }
26
27 SubmissionGuard(const SubmissionGuard&) = delete;
28 SubmissionGuard& operator=(const SubmissionGuard&) = delete;
29
31
32 private:
33 std::atomic<uint32_t>& m_inflight;
34 };
35
36 /**
37 * @brief Scheduler whose queue drain is running on this thread, if any
38 *
39 * Completion callbacks fire inside the drains while they hold iterators
40 * into the task lists, so terminate_all_tasks must not run from them.
41 */
42 thread_local const TaskScheduler* t_draining = nullptr;
43
44 struct DrainScope {
45 explicit DrainScope(const TaskScheduler* scheduler)
46 : m_previous(t_draining)
47 {
48 t_draining = scheduler;
49 }
50
51 ~DrainScope() { t_draining = m_previous; }
52
53 DrainScope(const DrainScope&) = delete;
54 DrainScope& operator=(const DrainScope&) = delete;
55
56 private:
57 const TaskScheduler* m_previous;
58 };
59
60 /**
61 * @brief Clears the terminating flag on every exit, including exceptions
62 */
63 struct TerminationScope {
64 explicit TerminationScope(std::atomic<bool>& flag)
65 : m_flag(flag)
66 {
67 }
68
69 ~TerminationScope() { m_flag.store(false); }
70
71 TerminationScope(const TerminationScope&) = delete;
72 TerminationScope& operator=(const TerminationScope&) = delete;
73
74 private:
75 std::atomic<bool>& m_flag;
76 };
77
78}
79
80TaskScheduler::TaskScheduler(uint32_t default_sample_rate, uint32_t default_frame_rate)
81 : m_clock(default_sample_rate)
82 , m_cleanup_threshold(512)
83 , m_registered_sample_rate(default_sample_rate)
84 , m_registered_frame_rate(default_frame_rate)
85{
86 s_registered_sample_rate = default_sample_rate;
87 s_registered_frame_rate = default_frame_rate;
90 ensure_domain(ProcessingToken::MULTI_RATE, default_sample_rate);
92}
93
94void TaskScheduler::add_task(const std::shared_ptr<Routine>& routine, const std::string& name, bool initialize)
95{
96 if (!routine) {
97 MF_ERROR(Journal::Component::Vruta, Journal::Context::CoroutineScheduling, "Failed to initiate routine; routine is null. Exiting add_task.");
98 return;
99 }
100
101 SubmissionGuard guard(m_inflight_submissions, m_terminating);
102 if (!guard.accepted) {
104 "Rejected task '{}': scheduler is terminating", name);
105 return;
106 }
107
108 std::string task_name = name.empty() ? auto_generate_name(routine) : name;
109 ProcessingToken token = routine->get_processing_token();
110
111 ensure_domain(token);
112
113 if (initialize)
114 initialize_routine_state(routine, token);
115
116 if (token == ProcessingToken::CONDITIONAL) {
117 routine->set_auto_resume(true);
118 for (auto& op : m_conditional_pending_ops) {
119 bool expected = false;
120 if (op.active.compare_exchange_strong(expected, true,
121 std::memory_order_relaxed, std::memory_order_relaxed)) {
122 op.is_addition = true;
123 op.entry = { routine, task_name };
124 m_conditional_pending_count.fetch_add(1, std::memory_order_release);
126 return;
127 }
128 }
130 "Conditional pending queue full, could not add task '{}'", task_name);
131 return;
132 }
133
134 for (auto& op : m_pending_ops) {
135 bool expected = false;
136 if (op.active.compare_exchange_strong(expected, true,
137 std::memory_order_acquire, std::memory_order_relaxed)) {
138 op.entry = { routine, task_name };
139 op.is_addition = true;
140 m_pending_count.fetch_add(1, std::memory_order_relaxed);
141 return;
142 }
143 }
144
146 "Pending task queue full, could not add task '{}'", task_name);
147}
148
149bool TaskScheduler::cancel_task(const std::string& name)
150{
151 SubmissionGuard guard(m_inflight_submissions, m_terminating);
152 if (!guard.accepted)
153 return false;
154
155 bool queued = false;
156
157 if (m_conditional_thread.joinable()) {
158 for (auto& op : m_conditional_pending_ops) {
159 bool expected = false;
160 if (op.active.compare_exchange_strong(expected, true,
161 std::memory_order_acquire, std::memory_order_relaxed)) {
162 op.entry = { nullptr, name };
163 op.is_addition = false;
164 m_conditional_pending_count.fetch_add(1, std::memory_order_relaxed);
165 queued = true;
166 break;
167 }
168 }
169 }
170
171 for (auto& op : m_pending_ops) {
172 bool expected = false;
173 if (op.active.compare_exchange_strong(expected, true,
174 std::memory_order_acquire, std::memory_order_relaxed)) {
175 op.entry = { nullptr, name };
176 op.is_addition = false;
177 m_pending_count.fetch_add(1, std::memory_order_relaxed);
178 queued = true;
179 break;
180 }
181 }
182
183 if (!queued) {
185 "Pending task queue full, could not cancel task '{}'", name);
186 }
187
188 return queued;
189}
190
191bool TaskScheduler::cancel_task(const std::shared_ptr<Routine>& routine)
192{
193 if (!routine)
194 return false;
195
196 SubmissionGuard guard(m_inflight_submissions, m_terminating);
197 if (!guard.accepted)
198 return false;
199
200 if (routine->get_processing_token() == ProcessingToken::CONDITIONAL) {
201 auto it = std::ranges::find_if(m_conditional_tasks,
202 [&routine](const TaskEntry& e) { return e.routine == routine; });
203 const std::string name = (it != m_conditional_tasks.end()) ? it->name : "";
204 return cancel_task(name);
205 }
206
207 auto it = find_task_by_routine(routine);
208 const std::string name = (it != m_tasks.end()) ? it->name : "";
209
210 for (auto& op : m_pending_ops) {
211 bool expected = false;
212 if (op.active.compare_exchange_strong(expected, true,
213 std::memory_order_acquire, std::memory_order_relaxed)) {
214 op.entry = { routine, name };
215 op.is_addition = false;
216 m_pending_count.fetch_add(1, std::memory_order_relaxed);
217 return true;
218 }
219 }
220
222 "Pending task queue full, could not cancel task '{}'", name);
223 return false;
224}
225
226bool TaskScheduler::restart_task(const std::string& name)
227{
228 SubmissionGuard guard(m_inflight_submissions, m_terminating);
229 if (!guard.accepted)
230 return false;
231
232 auto cit = std::ranges::find_if(m_conditional_tasks,
233 [&name](const TaskEntry& e) { return e.name == name; });
234 if (cit != m_conditional_tasks.end()) {
235 if (cit->routine && cit->routine->is_active())
236 cit->routine->restart();
237 return true;
238 }
239
240 auto it = find_task_by_name(name);
241 if (it != m_tasks.end()) {
242 if (it->routine && it->routine->is_active()) {
243 it->routine->restart();
244 }
245 }
246 return false;
247}
248
249std::shared_ptr<Routine> TaskScheduler::get_task(const std::string& name) const
250{
251 auto cit = std::ranges::find_if(m_conditional_tasks,
252 [&name](const TaskEntry& e) { return e.name == name; });
253 if (cit != m_conditional_tasks.end())
254 return cit->routine;
255
256 auto it = find_task_by_name(name);
257 return (it != m_tasks.end()) ? it->routine : nullptr;
258}
259
260std::vector<std::shared_ptr<Routine>> TaskScheduler::get_tasks_for_token(ProcessingToken token) const
261{
262 if (token == ProcessingToken::CONDITIONAL) {
263 std::vector<std::shared_ptr<Routine>> result;
264 for (const auto& e : m_conditional_tasks) {
265 if (e.routine)
266 result.push_back(e.routine);
267 }
268 return result;
269 }
270
271 std::vector<std::shared_ptr<Routine>> result;
272 for (const auto& entry : m_tasks) {
273 if (entry.routine && entry.routine->get_processing_token() == token) {
274 result.push_back(entry.routine);
275 }
276 }
277 return result;
278}
279
280std::vector<std::string> TaskScheduler::get_task_names() const
281{
282 std::vector<std::string> names;
283 for (const auto& entry : m_tasks) {
284 if (entry.routine)
285 names.push_back(entry.name);
286 }
287 return names;
288}
289
290std::vector<std::string> TaskScheduler::get_task_names(ProcessingToken token) const
291{
292 if (token == ProcessingToken::CONDITIONAL) {
293 std::vector<std::string> names;
294 for (const auto& e : m_conditional_tasks) {
295 if (e.routine)
296 names.push_back(e.name);
297 }
298 return names;
299 }
300
301 std::vector<std::string> names;
302 for (const auto& entry : m_tasks) {
303 if (entry.routine && entry.routine->get_processing_token() == token)
304 names.push_back(entry.name);
305 }
306 return names;
307}
308
309const std::vector<TaskEntry>& TaskScheduler::get_all_tasks()
310{
312 return m_tasks;
313}
314
315void TaskScheduler::process_token(ProcessingToken token, uint64_t processing_units)
316{
318
319 auto processor_it = m_token_processors.find(token);
320 if (processor_it != m_token_processors.end()) {
321 auto tasks = get_tasks_for_token(token);
322 processor_it->second(tasks, processing_units);
323 } else {
324 process_default(token, processing_units);
325 }
326
329 } else if (token == ProcessingToken::FRAME_ACCURATE) {
331 }
332
333 static uint64_t cleanup_counter = 0;
334 if (++cleanup_counter % (static_cast<uint64_t>(m_cleanup_threshold * 2)) == 0) {
336 }
337}
338
340{
341 for (const auto& token : m_token_clocks) {
342 process_token(token.first, 0);
343 }
344
345 static uint64_t cleanup_counter = 0;
346 if (++cleanup_counter % m_cleanup_threshold == 0) {
348 }
349}
350
352{
353 ensure_domain(token);
354 m_token_processors[token] = std::move(processor);
355}
356
357void TaskScheduler::register_clock(ProcessingToken token, std::shared_ptr<IClock> clock)
358{
359 ensure_domain(token);
360 m_token_clocks[token] = std::move(clock);
361}
362
364{
365 auto clock_it = m_token_clocks.find(token);
366 if (clock_it != m_token_clocks.end()) {
367 return *clock_it->second;
368 }
369
370 auto audio_clock_it = m_token_clocks.find(ProcessingToken::SAMPLE_ACCURATE);
371 if (audio_clock_it != m_token_clocks.end()) {
372 return *audio_clock_it->second;
373 }
374
375 error<std::runtime_error>(Journal::Component::Vruta, Journal::Context::CoroutineScheduling, std::source_location::current(),
376 "No clock found for token {}, and no sample-accurate clock available as fallback", static_cast<int>(token));
377}
378
379uint64_t TaskScheduler::seconds_to_units(double seconds, ProcessingToken token) const
380{
381 unsigned int rate = get_rate(token);
382 return static_cast<uint64_t>(seconds * rate);
383}
384
385uint64_t TaskScheduler::seconds_to_samples(double seconds) const
386{
387 return static_cast<uint64_t>(seconds * get_rate(ProcessingToken::SAMPLE_ACCURATE));
388}
389
391{
392 const auto& clock = get_clock(token);
393 return clock.current_position();
394}
395
397{
398 auto clock_it = m_token_clocks.find(token);
399 if (clock_it != m_token_clocks.end()) {
400 return clock_it->second->rate();
401 }
402
403 return get_default_rate(token);
404}
405
407{
408 return std::ranges::any_of(m_tasks,
409 [token](const TaskEntry& entry) {
410 return entry.routine && entry.routine->is_active() && entry.routine->get_processing_token() == token;
411 });
412}
413
415{
416 return m_next_task_id.fetch_add(1);
417}
418
419std::string TaskScheduler::auto_generate_name(const std::shared_ptr<Routine>& /*routine*/) const
420{
421 return "task_" + std::to_string(get_next_task_id());
422}
423
424std::vector<TaskEntry>::iterator TaskScheduler::find_task_by_name(const std::string& name)
425{
426 return std::ranges::find_if(m_tasks,
427 [&name](const TaskEntry& entry) { return entry.name == name; });
428}
429
430std::vector<TaskEntry>::const_iterator TaskScheduler::find_task_by_name(const std::string& name) const
431{
432 return std::ranges::find_if(m_tasks,
433 [&name](const TaskEntry& entry) { return entry.name == name; });
434}
435
436std::vector<TaskEntry>::iterator TaskScheduler::find_task_by_routine(const std::shared_ptr<Routine>& routine)
437{
438 return std::ranges::find_if(m_tasks,
439 [&routine](const TaskEntry& entry) { return entry.routine == routine; });
440}
441
443{
444 switch (token) {
452 return 1;
454 return 1000;
456 return 0;
457 default:
459 }
460}
461
463{
464 if (token == ProcessingToken::CONDITIONAL)
465 return;
466
467 auto clock_it = m_token_clocks.find(token);
468 if (clock_it == m_token_clocks.end()) {
469 unsigned int domain_rate = (rate > 0) ? rate : get_default_rate(token);
470
471 switch (token) {
473 m_token_clocks[token] = std::make_shared<FrameClock>(domain_rate);
474 break;
477 default:
478 m_token_clocks[token] = std::make_shared<SampleClock>(domain_rate);
479 break;
480 }
481 }
482}
483
484void TaskScheduler::process_default(ProcessingToken token, uint64_t processing_units)
485{
486 auto clock_it = m_token_clocks.find(token);
487 if (clock_it == m_token_clocks.end()) {
488 return;
489 }
490
491 auto tasks = get_tasks_for_token(token);
492 if (tasks.empty()) {
493 auto& clock = *clock_it->second;
494 clock.tick(processing_units);
495 return;
496 }
497
498 auto& clock = *clock_it->second;
499
500 for (uint64_t i = 0; i < processing_units; i++) {
501 uint64_t current_context = clock.current_position();
502
503 for (auto& routine : tasks) {
504 if (routine && routine->is_active()) {
505 if (routine->requires_clock_sync()) {
506 if (current_context >= routine->next_execution()) {
507 routine->try_resume_with_context(current_context, DelayContext::SAMPLE_BASED);
508 }
509 } else {
510 routine->try_resume_with_context(current_context, DelayContext::SAMPLE_BASED);
511 }
512 }
513 }
514
515 clock.tick(1);
516 }
517}
518
520{
521 std::erase_if(m_tasks, [](const TaskEntry& entry) {
522 return !entry.routine || !entry.routine->is_active();
523 });
524}
525
526bool TaskScheduler::initialize_routine_state(const std::shared_ptr<Routine>& routine, ProcessingToken token)
527{
528 if (!routine) {
529 return false;
530 }
531
532 auto clock_it = m_token_clocks.find(token);
533 if (clock_it == m_token_clocks.end()) {
534 return false;
535 }
536
537 uint64_t current_context = clock_it->second->current_position();
538 return routine->initialize_state(current_context);
539}
540
542{
543 if (m_conditional_thread.joinable())
544 return;
545
546 m_conditional_thread = std::jthread([this](const std::stop_token& st) {
547 while (!st.stop_requested())
549 });
550}
551
553{
554 for (auto& entry : m_conditional_tasks) {
555 if (entry.routine && entry.routine->is_active()) {
556 bool current_auto_resume = entry.routine->get_auto_resume();
557 entry.routine->set_state<bool>("was_auto_resume", current_auto_resume);
558 entry.routine->set_auto_resume(false);
559 }
560 }
561
562 for (auto& entry : m_tasks) {
563 if (entry.routine && entry.routine->is_active()) {
564 bool current_auto_resume = entry.routine->get_auto_resume();
565 entry.routine->set_state<bool>("was_auto_resume", current_auto_resume);
566 entry.routine->set_auto_resume(false);
567 }
568 }
569}
570
572{
573 for (auto& entry : m_conditional_tasks) {
574 if (entry.routine && entry.routine->is_active()) {
575 auto was_auto_resume = entry.routine->get_state<bool>("was_auto_resume");
576 if (was_auto_resume) {
577 entry.routine->set_auto_resume(*was_auto_resume);
578 } else {
579 entry.routine->set_auto_resume(true);
580 }
581 }
582 }
583
584 for (auto& entry : m_tasks) {
585 if (entry.routine && entry.routine->is_active()) {
586 auto was_auto_resume = entry.routine->get_state<bool>("was_auto_resume");
587 if (was_auto_resume) {
588 entry.routine->set_auto_resume(*was_auto_resume);
589 } else {
590 entry.routine->set_auto_resume(true);
591 }
592 }
593 }
594}
595
597{
598 if (m_conditional_thread.joinable() && m_conditional_thread.get_id() == std::this_thread::get_id()) {
599 return;
600 }
601
602 if (t_draining == this) {
604 "terminate_all_tasks ignored: called from a callback during a queue drain");
605 return;
606 }
607
608 if (m_terminating.exchange(true)) {
609 return;
610 }
611 TerminationScope terminating(m_terminating);
612
613 while (m_inflight_submissions.load() != 0) {
614 std::this_thread::yield();
615 }
616
617 if (m_conditional_thread.joinable()) {
618 m_conditional_thread.request_stop();
620 }
621
624
625 for (auto& entry : m_tasks) {
626 if (entry.routine && entry.routine->is_active()) {
627 entry.routine->set_should_terminate(true);
628 }
629 }
630
631 for (auto& entry : m_tasks) {
632 if (entry.routine && entry.routine->is_active()) {
633 entry.routine->force_resume();
634 }
635 }
636
637 constexpr int MAX_ATTEMPTS = 3;
638 for (int attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
639 std::this_thread::sleep_for(std::chrono::milliseconds(5));
640
641 bool any_active = false;
642 for (auto& entry : m_tasks) {
643 if (entry.routine && entry.routine->is_active()) {
644 any_active = true;
645 entry.routine->force_resume();
646 }
647 }
648
649 if (!any_active) {
650 break;
651 }
652 }
653
654 bool all_done = true;
655 for (const auto& entry : m_tasks) {
656 if (entry.routine && entry.routine->is_active()) {
657 all_done = false;
659 "Coroutine '{}' stuck after {} attempts - forcing destruction",
660 entry.name, MAX_ATTEMPTS);
661 }
662 }
663
664 if (!all_done) {
666 "Some coroutines did not complete - forcing destruction");
667 } else {
669 "All coroutines terminated successfully");
670 }
671
672 for (auto& entry : m_conditional_tasks) {
673 if (entry.routine && entry.routine->is_active())
674 entry.routine->set_should_terminate(true);
675 }
676 for (auto& entry : m_conditional_tasks) {
677 if (entry.routine && entry.routine->is_active())
678 entry.routine->force_resume();
679 }
680 m_conditional_tasks.clear();
681
682 m_tasks.clear();
683}
684
686{
688
691
692 for (auto& task : tasks) {
693 if (task && task->is_active()) {
694 if (task->requires_clock_sync()) {
695 if (m_current_buffer_cycle >= task->next_execution()) {
696 task->try_resume_with_context(m_current_buffer_cycle, DelayContext::BUFFER_BASED);
697 }
698 } else {
699 task->try_resume_with_context(m_current_buffer_cycle, DelayContext::BUFFER_BASED);
700 }
701 }
702 }
703}
704
706{
707 if (m_pending_count.load(std::memory_order_relaxed) == 0)
708 return;
709
710 DrainScope drain_scope(this);
711
712 for (auto& op : m_pending_ops) {
713 if (!op.active.load(std::memory_order_acquire))
714 continue;
715
716 if (op.is_addition) {
717 auto existing_it = find_task_by_name(op.entry.name);
718 if (existing_it != m_tasks.end()) {
719 if (existing_it->routine && existing_it->routine->is_active())
720 existing_it->routine->set_should_terminate(true);
721 m_tasks.erase(existing_it);
722 }
723 m_tasks.push_back(std::move(op.entry));
724 } else {
725 auto it = (op.entry.name.empty() && op.entry.routine)
726 ? find_task_by_routine(op.entry.routine)
727 : find_task_by_name(op.entry.name);
728 if (it != m_tasks.end()) {
729 if (it->routine && it->routine->is_active())
730 it->routine->set_should_terminate(true);
731 m_tasks.erase(it);
732 }
733 }
734
735 op.entry = { nullptr, "" };
736 op.active.store(false, std::memory_order_release);
737 m_pending_count.fetch_sub(1, std::memory_order_relaxed);
738 }
739}
740
742{
743 if (m_conditional_pending_count.load(std::memory_order_acquire) == 0)
744 return;
745
746 DrainScope drain_scope(this);
747
748 for (auto& op : m_conditional_pending_ops) {
749 if (!op.active.load(std::memory_order_acquire))
750 continue;
751
752 if (op.is_addition) {
753 auto it = std::ranges::find_if(m_conditional_tasks,
754 [&](const TaskEntry& e) { return e.name == op.entry.name; });
755 if (it != m_conditional_tasks.end()) {
756 if (it->routine && it->routine->is_active())
757 it->routine->set_should_terminate(true);
758 m_conditional_tasks.erase(it);
759 }
760 m_conditional_tasks.push_back(std::move(op.entry));
761 } else {
762 auto it = std::ranges::find_if(m_conditional_tasks,
763 [&](const TaskEntry& e) { return e.name == op.entry.name; });
764 if (it != m_conditional_tasks.end()) {
765 if (it->routine && it->routine->is_active())
766 it->routine->set_should_terminate(true);
767 m_conditional_tasks.erase(it);
768 }
769 }
770
771 op.entry = { nullptr, "" };
772 op.active.store(false, std::memory_order_release);
773 m_conditional_pending_count.fetch_sub(1, std::memory_order_relaxed);
774 }
775}
776
777void TaskScheduler::pump_cross(DelayContext context, ProcessingToken clock_token, uint64_t processing_units)
778{
780 if (cross_tasks.empty()) {
781 return;
782 }
783
784 auto clock_it = m_token_clocks.find(clock_token);
785 if (clock_it == m_token_clocks.end()) {
786 return;
787 }
788
789 if (processing_units == 0) {
790 processing_units = 1;
791 }
792
793 uint64_t base = clock_it->second->current_position();
794
795 for (uint64_t i = 0; i < processing_units; ++i) {
796 uint64_t pos = base + i;
797 for (auto& routine : cross_tasks) {
798 if (routine && routine->is_active()) {
799 routine->try_resume_with_context(pos, context);
800 }
801 }
802 }
803}
804
806{
808
809 if (m_conditional_tasks.empty()) {
810 std::this_thread::yield();
811 return;
812 }
813
814 std::erase_if(m_conditional_tasks, [](const TaskEntry& e) {
815 return !e.routine || !e.routine->is_active();
816 });
817
818 for (auto& entry : m_conditional_tasks) {
819 if (entry.routine && entry.routine->is_active())
820 entry.routine->try_resume(0);
821 }
822}
823
824}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
float rate
const TaskScheduler * m_previous
Definition Scheduler.cpp:57
std::atomic< bool > & m_flag
Definition Scheduler.cpp:75
std::atomic< uint32_t > & m_inflight
Definition Scheduler.cpp:33
bool accepted
Definition Scheduler.cpp:30
std::string name
Definition VKDevice.cpp:123
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:94
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.
std::atomic< bool > m_terminating
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:80
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.
std::atomic< uint32_t > m_inflight_submissions
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.
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:10
@ 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.
MAYAFLUX_API uint32_t s_registered_frame_rate
Definition Clock.cpp:10
@ 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.
MAYAFLUX_API uint32_t s_registered_sample_rate
Definition Clock.cpp:9
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::atomic< bool > terminating
Definition Core.cpp:22
std::shared_ptr< Routine > routine
Definition Scheduler.hpp:9