rtsc/
time.rs

1use std::{num::Wrapping, thread, time::Duration};
2
3use bma_ts::Monotonic;
4
5/// A trait which extends the standard [`Duration`] and similar types with additional methods
6///
7pub trait DurationRT {
8    /// Returns true if all provided [`Monotonic`] times fit the duration
9    fn fits(&self, t: &[Monotonic]) -> bool;
10    /// Returns the absolute difference between two durations (provided until abs_diff become
11    /// stable)
12    fn diff_abs(&self, other: Self) -> Duration;
13}
14
15impl DurationRT for Duration {
16    fn fits(&self, t: &[Monotonic]) -> bool {
17        if t.is_empty() {
18            true
19        } else {
20            let min_ts = t.iter().min().unwrap();
21            let max_ts = t.iter().max().unwrap();
22            max_ts.as_duration() - min_ts.as_duration() <= *self
23        }
24    }
25    fn diff_abs(&self, other: Self) -> Duration {
26        if *self > other {
27            *self - other
28        } else {
29            other - *self
30        }
31    }
32}
33
34/// Creates a new [`Interval`]
35pub fn interval(period: Duration) -> Interval {
36    Interval::new(period)
37}
38
39/// A synchronous interval helper, similar to
40/// <https://docs.rs/tokio/latest/tokio/time/struct.Interval.html>
41pub struct Interval {
42    next_tick: Option<Monotonic>,
43    period: Duration,
44    missing_tick_behavior: MissedTickBehavior,
45    ticks: Wrapping<usize>,
46}
47
48impl Iterator for Interval {
49    type Item = bool;
50
51    fn next(&mut self) -> Option<bool> {
52        Some(self.tick())
53    }
54}
55
56impl Interval {
57    /// Creates a new interval helper with the specified period
58    pub fn new(period: Duration) -> Self {
59        Self {
60            next_tick: None,
61            period,
62            missing_tick_behavior: <_>::default(),
63            ticks: Wrapping(0),
64        }
65    }
66    /// Ticks the interval
67    ///
68    /// Returns false if a tick is missed
69    pub fn tick(&mut self) -> bool {
70        self.ticks += Wrapping(1);
71        let now = Monotonic::now();
72        if let Some(mut next_tick) = self.next_tick {
73            match now.cmp(&next_tick) {
74                std::cmp::Ordering::Less => {
75                    let to_sleep = next_tick - now;
76                    self.next_tick = Some(next_tick + self.period);
77                    thread::sleep(to_sleep);
78                    true
79                }
80                std::cmp::Ordering::Equal => true,
81                std::cmp::Ordering::Greater => {
82                    match self.missing_tick_behavior {
83                        MissedTickBehavior::Burst => {
84                            self.next_tick = Some(next_tick + self.period);
85                        }
86                        MissedTickBehavior::Delay => {
87                            self.next_tick = Some(now + self.period);
88                        }
89                        MissedTickBehavior::Skip => {
90                            while next_tick <= now {
91                                next_tick += self.period;
92                            }
93                            self.next_tick = Some(next_tick);
94                        }
95                    }
96                    false
97                }
98            }
99        } else {
100            self.next_tick = Some(now + self.period);
101            true
102        }
103    }
104    /// Returns the number of ticks elapsed. If a tick is skipped, the counter is not incremented.
105    /// In case if the tick counter reaches `usize::MAX`, it is reset to zero
106    pub fn elapsed_ticks(&self) -> usize {
107        self.ticks.0
108    }
109    /// Sets missing tick behavior policy. Can be used as a build pattern
110    pub fn set_missing_tick_behavior(mut self, missing_tick_behavior: MissedTickBehavior) -> Self {
111        self.missing_tick_behavior = missing_tick_behavior;
112        self
113    }
114}
115
116/// Interval missing tick behavior
117///
118/// The behavior is similar to
119/// <https://docs.rs/tokio/latest/tokio/time/enum.MissedTickBehavior.html>
120/// but may differ in some details
121#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
122pub enum MissedTickBehavior {
123    #[default]
124    /// `[Interval::tick()`] method has no delay for missed intervals, all the missed ones are
125    /// fired instantly
126    Burst,
127    /// The interval is restarted from the current point of time
128    Delay,
129    /// Missed ticks are skipped with no additional effect
130    Skip,
131}
132
133#[cfg(test)]
134mod test {
135    use std::{thread, time::Duration};
136
137    use bma_ts::Monotonic;
138
139    use crate::time::DurationRT as _;
140
141    #[test]
142    fn test_fits() {
143        let first = Monotonic::now();
144        thread::sleep(Duration::from_millis(10));
145        let second = Monotonic::now();
146        thread::sleep(Duration::from_millis(10));
147        let third = Monotonic::now();
148        assert!(Duration::from_millis(100).fits(&[first, second, third]));
149        assert!(Duration::from_millis(25).fits(&[first, second, third]));
150    }
151
152    #[test]
153    fn test_ticks() {
154        let mut int = super::interval(Duration::from_millis(10));
155        for _ in 0..3 {
156            int.tick();
157        }
158        assert_eq!(int.elapsed_ticks(), 3);
159    }
160}