use alloc::vec::Vec;
use crate::priority::Priority;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Task {
pub period: u64,
pub wcet: u64,
pub deadline: u64,
pub priority: Priority,
}
impl Task {
#[must_use]
pub fn new(period: u64, wcet: u64, priority: Priority) -> Self {
Self {
period,
wcet,
deadline: 0,
priority,
}
}
#[must_use]
pub fn with_deadline(period: u64, wcet: u64, deadline: u64, priority: Priority) -> Self {
Self {
period,
wcet,
deadline,
priority,
}
}
#[must_use]
pub fn effective_deadline(&self) -> u64 {
if self.deadline == 0 {
self.period
} else {
self.deadline
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TaskSet {
pub tasks: Vec<Task>,
}
#[must_use]
fn nth_root_of_two(n: u32) -> f64 {
if n == 0 {
return 1.0;
}
if n == 1 {
return 2.0;
}
let nf = f64::from(n);
let mut x = 1.0_f64;
let mut iter = 0;
while iter < 100 {
let mut x_nm1 = 1.0_f64; let mut k = 0;
while k < n - 1 {
x_nm1 *= x;
k += 1;
}
let x_n = x_nm1 * x;
let denom = nf * x_nm1;
if denom == 0.0 {
break;
}
let next = x - (x_n - 2.0) / denom;
let delta = if next > x { next - x } else { x - next };
x = next;
if delta < 1e-13 {
break;
}
iter += 1;
}
x
}
impl TaskSet {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, task: Task) {
self.tasks.push(task);
}
#[must_use]
pub fn len(&self) -> usize {
self.tasks.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.tasks.is_empty()
}
#[must_use]
pub fn utilization(&self) -> f64 {
self.tasks
.iter()
.filter(|t| t.period > 0)
.map(|t| t.wcet as f64 / t.period as f64)
.sum()
}
#[must_use]
pub fn liu_layland_bound(n: usize) -> f64 {
if n == 0 {
return 1.0;
}
let nf = n as f64;
nf * (nth_root_of_two(n as u32) - 1.0)
}
pub fn assign_rate_monotonic(&mut self) {
let n = self.tasks.len();
if n == 0 {
return;
}
let mut order: Vec<usize> = (0..n).collect();
order.sort_by_key(|&i| self.tasks[i].period);
for (rank, &idx) in order.iter().enumerate() {
let prio_val = (n - 1 - rank) as i32;
self.tasks[idx].priority = Priority::clamped(prio_val);
}
}
fn higher_priority_than(&self, i: usize) -> impl Iterator<Item = &Task> {
let pi = self.tasks[i].priority;
self.tasks
.iter()
.enumerate()
.filter(move |(j, t)| *j != i && t.priority > pi)
.map(|(_, t)| t)
}
#[must_use]
pub fn response_time(&self, i: usize) -> Option<u64> {
if i >= self.tasks.len() {
return None;
}
let ti = self.tasks[i];
let deadline = ti.effective_deadline();
let mut r = ti.wcet;
loop {
let interference: u64 = self
.higher_priority_than(i)
.map(|t| r.div_ceil(t.period) * t.wcet)
.sum();
let next = ti.wcet + interference;
if next == r {
return if r <= deadline { Some(r) } else { None };
}
if next > deadline {
return None; }
r = next;
}
}
#[must_use]
pub fn response_times(&self) -> Vec<Option<u64>> {
(0..self.tasks.len())
.map(|i| self.response_time(i))
.collect()
}
#[must_use]
pub fn is_schedulable_rta(&self) -> bool {
(0..self.tasks.len()).all(|i| self.response_time(i).is_some())
}
#[must_use]
pub fn is_schedulable_ll(&self) -> bool {
self.utilization() <= Self::liu_layland_bound(self.tasks.len()) + 1e-9
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::panic, clippy::float_cmp)]
mod tests {
use super::*;
fn p(v: i16) -> Priority {
Priority::new(v).unwrap()
}
#[test]
fn implicit_deadline_defaults_to_period() {
let t = Task::new(10, 3, p(5));
assert_eq!(t.effective_deadline(), 10);
let t2 = Task::with_deadline(10, 3, 7, p(5));
assert_eq!(t2.effective_deadline(), 7);
}
#[test]
fn rate_monotonic_assigns_shortest_period_highest() {
let mut ts = TaskSet::new();
ts.push(Task::new(20, 5, p(0)));
ts.push(Task::new(4, 1, p(0)));
ts.push(Task::new(5, 2, p(0)));
ts.assign_rate_monotonic();
assert!(ts.tasks[1].priority > ts.tasks[2].priority);
assert!(ts.tasks[2].priority > ts.tasks[0].priority);
}
#[test]
fn response_time_analysis_exact_values() {
let mut ts = TaskSet::new();
ts.push(Task::new(4, 1, p(0)));
ts.push(Task::new(5, 2, p(0)));
ts.push(Task::new(20, 5, p(0)));
ts.assign_rate_monotonic();
let r = ts.response_times();
assert_eq!(r[0], Some(1)); assert_eq!(r[1], Some(3)); assert_eq!(r[2], Some(15)); assert!(ts.is_schedulable_rta());
}
#[test]
fn rta_stronger_than_liu_layland() {
let mut ts = TaskSet::new();
ts.push(Task::new(4, 1, p(0)));
ts.push(Task::new(5, 2, p(0)));
ts.push(Task::new(20, 5, p(0)));
ts.assign_rate_monotonic();
assert!((ts.utilization() - 0.9).abs() < 1e-9);
assert!(!ts.is_schedulable_ll());
assert!(ts.is_schedulable_rta());
}
#[test]
fn overloaded_set_is_unschedulable() {
let mut ts = TaskSet::new();
ts.push(Task::new(4, 3, p(0)));
ts.push(Task::new(5, 3, p(0)));
ts.assign_rate_monotonic();
assert!(!ts.is_schedulable_rta());
assert!(ts.response_time(1).is_none());
}
#[test]
fn liu_layland_bound_known_points() {
assert!((TaskSet::liu_layland_bound(1) - 1.0).abs() < 1e-9);
assert!((TaskSet::liu_layland_bound(2) - 0.8284271).abs() < 1e-6);
assert!((TaskSet::liu_layland_bound(3) - 0.7797631).abs() < 1e-6);
let big = TaskSet::liu_layland_bound(1000);
assert!((big - core::f64::consts::LN_2).abs() < 1e-3, "big={big}");
}
#[test]
fn constrained_deadline_tightens_test() {
let mut ts = TaskSet::new();
ts.push(Task::new(4, 1, p(0)));
ts.push(Task::new(5, 2, p(0)));
ts.push(Task::with_deadline(20, 5, 12, p(0)));
ts.assign_rate_monotonic();
assert!(!ts.is_schedulable_rta());
}
}