File
Blob: firmware/vendor/str0m/src/util/value_history.rs
| 1 | use std::collections::VecDeque; |
| 2 | use std::iter::Sum; |
| 3 | use std::ops::{AddAssign, SubAssign}; |
| 4 | use std::time::{Duration, Instant}; |
| 5 | |
| 6 | /// Holds a history values of type T for up to a certain Duration, as well as the |
| 7 | /// cumulated (total) value. |
| 8 | #[derive(Debug)] |
| 9 | pub(crate) struct ValueHistory<T> { |
| 10 | value: T, |
| 11 | history: VecDeque<(Instant, T)>, |
| 12 | max_time: Duration, |
| 13 | } |
| 14 | |
| 15 | const DEFAULT_VALUE_HISTORY_DURATION: Duration = Duration::from_secs(1); |
| 16 | |
| 17 | impl<T: Default> Default for ValueHistory<T> { |
| 18 | fn default() -> Self { |
| 19 | Self { |
| 20 | value: Default::default(), |
| 21 | history: Default::default(), |
| 22 | max_time: DEFAULT_VALUE_HISTORY_DURATION, |
| 23 | } |
| 24 | } |
| 25 | } |
| 26 | |
| 27 | impl<T> ValueHistory<T> |
| 28 | where |
| 29 | T: Copy + AddAssign + SubAssign + Sum, |
| 30 | { |
| 31 | /// Adds a timed value |
| 32 | /// Note: time should always monotonically increase in subsequent calls to add() |
| 33 | pub fn push(&mut self, t: Instant, v: T) { |
| 34 | self.value += v; |
| 35 | self.history.push_back((t, v)); |
| 36 | } |
| 37 | |
| 38 | /// Returns the sum of all values in the history up to max_time. Might |
| 39 | /// return stale value unless [`ValueHistory::purge_old`] is called before. |
| 40 | pub fn sum(&self) -> T { |
| 41 | self.value |
| 42 | } |
| 43 | |
| 44 | /// Recalculates sum purging values older than `now - max_time`. |
| 45 | pub fn purge_old(&mut self, now: Instant) { |
| 46 | while { |
| 47 | let Some(front_t) = self.history.front().map(|v| v.0) else { |
| 48 | return; |
| 49 | }; |
| 50 | now.duration_since(front_t) > self.max_time |
| 51 | } { |
| 52 | if let Some((_, v)) = self.history.pop_front() { |
| 53 | self.value -= v; |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | #[allow(clippy::unchecked_duration_subtraction)] |
| 60 | #[cfg(test)] |
| 61 | mod test { |
| 62 | use std::time::{Duration, Instant}; |
| 63 | |
| 64 | use super::ValueHistory; |
| 65 | |
| 66 | #[test] |
| 67 | fn with_value_test() { |
| 68 | let now = Instant::now(); |
| 69 | |
| 70 | let mut h = ValueHistory { |
| 71 | value: 11, |
| 72 | max_time: Duration::from_secs(1), |
| 73 | ..Default::default() |
| 74 | }; |
| 75 | |
| 76 | assert_eq!(h.sum(), 11); |
| 77 | h.purge_old(now); |
| 78 | assert_eq!(h.sum(), 11); |
| 79 | h.push(now - Duration::from_millis(1500), 22); |
| 80 | h.push(now - Duration::from_millis(500), 22); |
| 81 | assert_eq!(h.sum(), 11 + 22 + 22); |
| 82 | h.purge_old(now); |
| 83 | assert_eq!(h.sum(), 11 + 22); |
| 84 | h.push(now, 0); |
| 85 | assert_eq!(h.sum(), 11 + 22); |
| 86 | } |
| 87 | |
| 88 | #[test] |
| 89 | fn test() { |
| 90 | let now = Instant::now(); |
| 91 | let mut h = ValueHistory::default(); |
| 92 | |
| 93 | assert_eq!(h.sum(), 0); |
| 94 | h.push(now - Duration::from_millis(1500), 22); |
| 95 | assert_eq!(h.sum(), 22); |
| 96 | h.purge_old(now); |
| 97 | assert_eq!(h.sum(), 0); |
| 98 | h.push(now - Duration::from_millis(700), 22); |
| 99 | h.push(now - Duration::from_millis(500), 33); |
| 100 | assert_eq!(h.sum(), 22 + 33); |
| 101 | h.purge_old(now); |
| 102 | assert_eq!(h.sum(), 22 + 33); |
| 103 | |
| 104 | h.purge_old(now + Duration::from_millis(400)); |
| 105 | assert_eq!(h.sum(), 33); |
| 106 | h.purge_old(now + Duration::from_millis(600)); |
| 107 | assert_eq!(h.sum(), 0); |
| 108 | } |
| 109 | } |