use alloc::collections::BTreeMap;
use crate::core::plan::{DeadlinePlan, WINDOW_MAX};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Outcome {
Exact {
residual_ns: u64,
},
Late {
woke_ns: u64,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PointEstimate {
ring: [u64; WINDOW_MAX],
len: u8,
next: u8,
}
impl Default for PointEstimate {
fn default() -> Self {
Self::new()
}
}
impl PointEstimate {
#[must_use]
pub const fn new() -> Self {
Self {
ring: [0; WINDOW_MAX],
len: 0,
next: 0,
}
}
#[must_use]
pub fn predicted_ns(&self) -> Option<u64> {
self.ring[..usize::from(self.len)].iter().copied().min()
}
#[must_use]
pub fn sleep_ns(&self, plan: &DeadlinePlan) -> Option<u64> {
let s = self.predicted_ns()?.checked_sub(plan.margin_ns)?;
(s >= plan.min_sleep_ns).then_some(s.min(plan.max_sleep_ns))
}
pub fn observe(&mut self, plan: &DeadlinePlan, outcome: Outcome) {
let sample = match outcome {
Outcome::Exact { residual_ns } => residual_ns,
Outcome::Late { woke_ns } => woke_ns.min(self.predicted_ns().unwrap_or(woke_ns)) / 2,
};
let w = plan
.window
.clamp(1, u8::try_from(WINDOW_MAX).unwrap_or(u8::MAX));
if self.next >= w {
self.next = 0;
}
self.ring[usize::from(self.next)] = sample;
self.next += 1;
if self.len < w {
self.len += 1;
}
}
}
#[derive(Clone, Debug)]
pub struct PointTable<K> {
points: BTreeMap<K, PointEstimate>,
}
impl<K: Ord> Default for PointTable<K> {
fn default() -> Self {
Self::new()
}
}
impl<K: Ord> PointTable<K> {
#[must_use]
pub const fn new() -> Self {
Self {
points: BTreeMap::new(),
}
}
#[must_use]
pub fn sleep_ns(&self, key: &K, plan: &DeadlinePlan) -> Option<u64> {
self.points.get(key)?.sleep_ns(plan)
}
pub fn observe(&mut self, key: K, plan: &DeadlinePlan, outcome: Outcome) {
self.points.entry(key).or_default().observe(plan, outcome);
}
#[must_use]
pub fn len(&self) -> usize {
self.points.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
}
#[cfg(test)]
#[path = "estimator_tests.rs"]
mod tests;