use crate::core::scalar::ControlScalar;
use crate::scheduler::fixed_rate::FixedRateTask;
pub struct MultiRateScheduler<S: ControlScalar, const N: usize> {
tasks: [FixedRateTask<S>; N],
}
impl<S: ControlScalar, const N: usize> MultiRateScheduler<S, N> {
pub fn new(periods: [S; N]) -> Self {
Self {
tasks: core::array::from_fn(|i| FixedRateTask::new(periods[i])),
}
}
pub fn tick(&mut self, dt: S) -> [bool; N] {
core::array::from_fn(|i| self.tasks[i].tick(dt))
}
pub fn tick_with<F: FnMut(usize)>(&mut self, dt: S, mut callback: F) {
for i in 0..N {
if self.tasks[i].tick(dt) {
callback(i);
}
}
}
pub fn task(&self, index: usize) -> Option<&FixedRateTask<S>> {
self.tasks.get(index)
}
pub fn task_mut(&mut self, index: usize) -> Option<&mut FixedRateTask<S>> {
self.tasks.get_mut(index)
}
pub fn reset_all(&mut self) {
for task in &mut self.tasks {
task.reset();
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum TaskPriority {
Low,
Normal,
High,
Critical,
}
#[derive(Debug, Clone)]
pub struct TaskDescriptor {
pub name: &'static str,
pub priority: TaskPriority,
}
pub struct PriorityScheduler<S: ControlScalar, const N: usize> {
pub scheduler: MultiRateScheduler<S, N>,
pub descriptors: [TaskDescriptor; N],
overrun_counts: [u32; N],
last_execution_time: [S; N],
budget: [S; N],
}
impl<S: ControlScalar, const N: usize> PriorityScheduler<S, N> {
pub fn new(periods: [S; N], descriptors: [TaskDescriptor; N], budgets: [S; N]) -> Self {
Self {
scheduler: MultiRateScheduler::new(periods),
descriptors,
overrun_counts: [0; N],
last_execution_time: core::array::from_fn(|_| S::ZERO),
budget: budgets,
}
}
pub fn report_execution(&mut self, task_index: usize, execution_time: S) -> bool {
if task_index >= N {
return false;
}
self.last_execution_time[task_index] = execution_time;
let overrun = execution_time > self.budget[task_index];
if overrun {
self.overrun_counts[task_index] += 1;
}
overrun
}
pub fn overrun_count(&self, task_index: usize) -> u32 {
if task_index < N {
self.overrun_counts[task_index]
} else {
0
}
}
pub fn last_execution_time(&self, task_index: usize) -> S {
if task_index < N {
self.last_execution_time[task_index]
} else {
S::ZERO
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn multi_rate_fires_at_correct_rates() {
let mut sched = MultiRateScheduler::<f64, 3>::new([0.001, 0.01, 0.1]);
let mut counts = [0u32; 3];
for _ in 0..10000 {
let fired = sched.tick(0.001);
for i in 0..3 {
if fired[i] {
counts[i] += 1;
}
}
}
assert!(
counts[0] >= 9990 && counts[0] <= 10010,
"1kHz: {}",
counts[0]
);
assert!(
counts[1] >= 990 && counts[1] <= 1010,
"100Hz: {}",
counts[1]
);
assert!(counts[2] >= 99 && counts[2] <= 101, "10Hz: {}", counts[2]);
}
#[test]
fn tick_with_calls_callback() {
let mut sched = MultiRateScheduler::<f64, 2>::new([0.01, 0.1]);
let mut task0_count = 0u32;
let mut task1_count = 0u32;
for _ in 0..1000 {
sched.tick_with(0.001, |i| {
if i == 0 {
task0_count += 1;
} else {
task1_count += 1;
}
});
}
assert!((99..=101).contains(&task0_count), "100Hz: {}", task0_count);
assert!((9..=11).contains(&task1_count), "10Hz: {}", task1_count);
}
#[test]
fn priority_scheduler_overrun_detection() {
let descriptors = [
TaskDescriptor {
name: "fast",
priority: TaskPriority::Critical,
},
TaskDescriptor {
name: "slow",
priority: TaskPriority::Low,
},
];
let mut ps = PriorityScheduler::<f64, 2>::new(
[0.001, 0.01],
descriptors,
[0.0005, 0.005], );
let overrun = ps.report_execution(0, 0.001); assert!(overrun);
assert_eq!(ps.overrun_count(0), 1);
let no_overrun = ps.report_execution(1, 0.001);
assert!(!no_overrun);
assert_eq!(ps.overrun_count(1), 0);
}
#[test]
fn reset_all() {
let mut sched = MultiRateScheduler::<f64, 2>::new([0.1, 0.2]);
sched.tick(0.09);
sched.reset_all();
let fired = sched.tick(0.05);
assert!(!fired[0]); }
}