#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Depletion {
threshold: f32,
last: Option<f32>,
}
impl Depletion {
pub fn new(threshold: f32) -> Self {
Self {
threshold,
last: None,
}
}
pub fn update(&mut self, level: f32) -> Option<u32> {
let estimate = if level <= self.threshold {
Some(0)
} else {
match self.last {
Some(previous) => {
let rate = previous - level;
if rate > 0.0 {
Some(ceil_samples((level - self.threshold) / rate))
} else {
None
}
}
None => None,
}
};
self.last = Some(level);
estimate
}
}
fn ceil_samples(value: f32) -> u32 {
if value >= u32::MAX as f32 {
return u32::MAX;
}
let whole = value as u32;
if (whole as f32) < value {
whole + 1
} else {
whole
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_first_reading_has_no_rate() {
let mut tank = Depletion::new(0.0);
assert_eq!(tank.update(10.0), None);
}
#[test]
fn counts_down_as_the_level_falls() {
let mut tank = Depletion::new(2.0);
tank.update(10.0);
assert_eq!(tank.update(8.0), Some(3)); assert_eq!(tank.update(6.0), Some(2)); }
#[test]
fn rounds_partial_samples_up() {
let mut tank = Depletion::new(0.0);
tank.update(10.0);
assert_eq!(tank.update(7.0), Some(3)); }
#[test]
fn a_steady_or_rising_level_does_not_warn() {
let mut tank = Depletion::new(2.0);
tank.update(6.0);
assert_eq!(tank.update(6.0), None); assert_eq!(tank.update(7.0), None); }
#[test]
fn at_or_below_the_threshold_is_zero() {
let mut tank = Depletion::new(2.0);
assert_eq!(tank.update(2.0), Some(0));
assert_eq!(tank.update(1.0), Some(0));
}
#[test]
fn an_enormous_estimate_saturates_rather_than_overflowing() {
let mut tank = Depletion::new(-1e10);
tank.update(2.0);
assert_eq!(tank.update(1.0), Some(u32::MAX));
}
}