use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct EdfTask<S: ControlScalar> {
pub id: u8,
pub period: S,
pub deadline: S,
pub budget: S,
pub next_abs_deadline: S,
pub next_release: S,
}
impl<S: ControlScalar> EdfTask<S> {
pub fn new(id: u8, period: S, deadline: S, budget: S) -> Self {
Self {
id,
period,
deadline,
budget,
next_abs_deadline: deadline,
next_release: S::ZERO,
}
}
pub fn advance(&mut self) {
self.next_release += self.period;
self.next_abs_deadline += self.period;
}
pub fn utilization(&self) -> S {
self.budget / self.period
}
}
pub struct EdfScheduler<S: ControlScalar, const N: usize> {
pub tasks: [Option<EdfTask<S>>; N],
pub current_time: S,
pub deadline_miss_count: u32,
}
impl<S: ControlScalar, const N: usize> EdfScheduler<S, N> {
pub fn new() -> Self {
Self {
tasks: core::array::from_fn(|_| None),
current_time: S::ZERO,
deadline_miss_count: 0,
}
}
pub fn add_task(&mut self, task: EdfTask<S>) -> bool {
for slot in self.tasks.iter_mut() {
if slot.is_none() {
*slot = Some(task);
return true;
}
}
false
}
pub fn tick(&mut self, dt: S) -> heapless::Vec<u8, N> {
self.current_time += dt;
let mut ready: heapless::Vec<(S, u8), N> = heapless::Vec::new();
for slot in self.tasks.iter_mut().flatten() {
if self.current_time >= slot.next_release {
if self.current_time > slot.next_abs_deadline {
self.deadline_miss_count += 1;
}
let _ = ready.push((slot.next_abs_deadline, slot.id));
slot.advance();
}
}
let len = ready.len();
for i in 0..len {
for j in 0..len - 1 - i {
if ready[j].0 > ready[j + 1].0 {
ready.swap(j, j + 1);
}
}
}
let mut ids: heapless::Vec<u8, N> = heapless::Vec::new();
for (_, id) in ready {
let _ = ids.push(id);
}
ids
}
pub fn is_schedulable(&self) -> bool {
self.total_utilization() <= S::ONE
}
pub fn total_utilization(&self) -> S {
self.tasks
.iter()
.flatten()
.fold(S::ZERO, |acc, t| acc + t.utilization())
}
}
impl<S: ControlScalar, const N: usize> Default for EdfScheduler<S, N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn schedulability_check() {
let mut sched = EdfScheduler::<f64, 4>::new();
sched.add_task(EdfTask::new(1, 1.0, 1.0, 0.3));
sched.add_task(EdfTask::new(2, 1.0, 1.0, 0.4));
assert!(sched.is_schedulable());
assert!((sched.total_utilization() - 0.7).abs() < 1e-10);
sched.add_task(EdfTask::new(3, 1.0, 1.0, 0.4));
assert!(!sched.is_schedulable());
}
#[test]
fn edf_ordering_by_deadline() {
let mut sched = EdfScheduler::<f64, 4>::new();
sched.add_task(EdfTask::new(10, 1.0, 1.0, 0.1));
sched.add_task(EdfTask::new(20, 2.0, 0.5, 0.1));
let ready = sched.tick(0.1);
assert_eq!(ready.len(), 2);
assert_eq!(ready[0], 20, "task with earlier deadline should be first");
assert_eq!(ready[1], 10);
}
#[test]
fn add_task_full_returns_false() {
let mut sched = EdfScheduler::<f64, 2>::new();
assert!(sched.add_task(EdfTask::new(1, 1.0, 1.0, 0.1)));
assert!(sched.add_task(EdfTask::new(2, 1.0, 1.0, 0.1)));
assert!(!sched.add_task(EdfTask::new(3, 1.0, 1.0, 0.1)));
}
#[test]
fn periodic_release_advances_correctly() {
let mut sched = EdfScheduler::<f64, 2>::new();
sched.add_task(EdfTask::new(1, 1.0, 1.0, 0.1));
let r0 = sched.tick(0.5); assert_eq!(r0.len(), 1);
let r1 = sched.tick(0.5); assert_eq!(r1.len(), 1);
}
}