17 struct SubmissionGuard {
18 SubmissionGuard(std::atomic<uint32_t>& inflight,
const std::atomic<bool>& terminating)
25 ~SubmissionGuard() {
m_inflight.fetch_sub(1); }
27 SubmissionGuard(
const SubmissionGuard&) =
delete;
28 SubmissionGuard& operator=(
const SubmissionGuard&) =
delete;
42 thread_local const TaskScheduler* t_draining =
nullptr;
45 explicit DrainScope(
const TaskScheduler* scheduler)
48 t_draining = scheduler;
53 DrainScope(
const DrainScope&) =
delete;
54 DrainScope& operator=(
const DrainScope&) =
delete;
63 struct TerminationScope {
64 explicit TerminationScope(std::atomic<bool>& flag)
69 ~TerminationScope() {
m_flag.store(
false); }
71 TerminationScope(
const TerminationScope&) =
delete;
72 TerminationScope& operator=(
const TerminationScope&) =
delete;
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)
102 if (!guard.accepted) {
104 "Rejected task '{}': scheduler is terminating",
name);
117 routine->set_auto_resume(
true);
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 };
130 "Conditional pending queue full, could not add task '{}'", task_name);
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;
146 "Pending task queue full, could not add task '{}'", task_name);
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;
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;
185 "Pending task queue full, could not cancel task '{}'",
name);
208 const std::string
name = (it !=
m_tasks.end()) ? it->name :
"";
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;
222 "Pending task queue full, could not cancel task '{}'",
name);
235 if (cit->routine && cit->routine->is_active())
236 cit->routine->restart();
242 if (it->routine && it->routine->is_active()) {
243 it->routine->restart();
257 return (it !=
m_tasks.end()) ? it->routine :
nullptr;
263 std::vector<std::shared_ptr<Routine>> result;
266 result.push_back(e.routine);
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);
282 std::vector<std::string> names;
283 for (
const auto& entry :
m_tasks) {
285 names.push_back(entry.name);
293 std::vector<std::string> names;
296 names.push_back(e.name);
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);
322 processor_it->second(tasks, processing_units);
333 static uint64_t cleanup_counter = 0;
345 static uint64_t cleanup_counter = 0;
367 return *clock_it->second;
372 return *audio_clock_it->second;
376 "No clock found for token {}, and no sample-accurate clock available as fallback",
static_cast<int>(token));
382 return static_cast<uint64_t
>(seconds *
rate);
393 return clock.current_position();
400 return clock_it->second->rate();
408 return std::ranges::any_of(
m_tasks,
410 return entry.
routine && entry.
routine->is_active() && entry.
routine->get_processing_token() == token;
426 return std::ranges::find_if(
m_tasks,
432 return std::ranges::find_if(
m_tasks,
438 return std::ranges::find_if(
m_tasks,
473 m_token_clocks[token] = std::make_shared<FrameClock>(domain_rate);
478 m_token_clocks[token] = std::make_shared<SampleClock>(domain_rate);
493 auto& clock = *clock_it->second;
494 clock.tick(processing_units);
498 auto& clock = *clock_it->second;
500 for (uint64_t i = 0; i < processing_units; i++) {
501 uint64_t current_context = clock.current_position();
503 for (
auto& routine : tasks) {
504 if (routine && routine->is_active()) {
505 if (routine->requires_clock_sync()) {
506 if (current_context >= routine->next_execution()) {
537 uint64_t current_context = clock_it->second->current_position();
538 return routine->initialize_state(current_context);
547 while (!st.stop_requested())
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);
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);
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);
579 entry.routine->set_auto_resume(
true);
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);
590 entry.routine->set_auto_resume(
true);
602 if (t_draining ==
this) {
604 "terminate_all_tasks ignored: called from a callback during a queue drain");
614 std::this_thread::yield();
626 if (entry.routine && entry.routine->is_active()) {
627 entry.routine->set_should_terminate(
true);
632 if (entry.routine && entry.routine->is_active()) {
633 entry.routine->force_resume();
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));
641 bool any_active =
false;
643 if (entry.routine && entry.routine->is_active()) {
645 entry.routine->force_resume();
654 bool all_done =
true;
655 for (
const auto& entry :
m_tasks) {
656 if (entry.routine && entry.routine->is_active()) {
659 "Coroutine '{}' stuck after {} attempts - forcing destruction",
660 entry.name, MAX_ATTEMPTS);
666 "Some coroutines did not complete - forcing destruction");
669 "All coroutines terminated successfully");
673 if (entry.routine && entry.routine->is_active())
674 entry.routine->set_should_terminate(
true);
677 if (entry.routine && entry.routine->is_active())
678 entry.routine->force_resume();
692 for (
auto& task : tasks) {
693 if (task && task->is_active()) {
694 if (task->requires_clock_sync()) {
710 DrainScope drain_scope(
this);
713 if (!op.active.load(std::memory_order_acquire))
716 if (op.is_addition) {
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);
723 m_tasks.push_back(std::move(op.entry));
725 auto it = (op.entry.name.empty() && op.entry.routine)
729 if (it->routine && it->routine->is_active())
730 it->routine->set_should_terminate(
true);
735 op.entry = {
nullptr,
"" };
736 op.active.store(
false, std::memory_order_release);
746 DrainScope drain_scope(
this);
749 if (!op.active.load(std::memory_order_acquire))
752 if (op.is_addition) {
754 [&](
const TaskEntry& e) {
return e.
name == op.entry.name; });
756 if (it->routine && it->routine->is_active())
757 it->routine->set_should_terminate(
true);
763 [&](
const TaskEntry& e) {
return e.
name == op.entry.name; });
765 if (it->routine && it->routine->is_active())
766 it->routine->set_should_terminate(
true);
771 op.entry = {
nullptr,
"" };
772 op.active.store(
false, std::memory_order_release);
780 if (cross_tasks.empty()) {
789 if (processing_units == 0) {
790 processing_units = 1;
793 uint64_t base = clock_it->second->current_position();
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);
810 std::this_thread::yield();
819 if (entry.routine && entry.routine->is_active())
820 entry.routine->try_resume(0);
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
#define MF_WARN(comp, ctx,...)
const TaskScheduler * m_previous
std::atomic< bool > & m_flag
std::atomic< uint32_t > & m_inflight
Abstract base interface for all clock types in the multimodal scheduling system.
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.
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 process_buffer_cycle_tasks()
void start_conditional_thread()
Start the conditional task processing thread if not already running.
uint64_t m_registered_sample_rate
std::unordered_map< ProcessingToken, std::shared_ptr< IClock > > m_token_clocks
Clock instances for each processing domain.
uint32_t m_registered_frame_rate
std::string auto_generate_name(const std::shared_ptr< Routine > &routine) const
Generate automatic name for a routine based on its type.
std::jthread m_conditional_thread
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.
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.
uint64_t m_current_buffer_cycle
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.
@ 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
@ 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
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
std::shared_ptr< Routine > routine