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/// Creates a new [`Interval`] with the specified frequency
40pub fn interval_hz(frequency: u64) -> Interval {
41    Interval::new(Duration::from_nanos(1_000_000_000 / frequency))
42}
43
44/// A synchronous interval helper, similar to
45/// <https://docs.rs/tokio/latest/tokio/time/struct.Interval.html>
46pub struct Interval {
47    next_tick: Option<Monotonic>,
48    period: Duration,
49    missing_tick_behavior: MissedTickBehavior,
50    ticks: Wrapping<usize>,
51}
52
53impl Iterator for Interval {
54    type Item = bool;
55
56    fn next(&mut self) -> Option<bool> {
57        Some(self.tick())
58    }
59}
60
61impl Interval {
62    /// Creates a new interval helper with the specified period
63    pub fn new(period: Duration) -> Self {
64        Self {
65            next_tick: None,
66            period,
67            missing_tick_behavior: <_>::default(),
68            ticks: Wrapping(0),
69        }
70    }
71    /// Ticks the interval
72    ///
73    /// Returns false if a tick is missed
74    pub fn tick(&mut self) -> bool {
75        self.ticks += Wrapping(1);
76        let now = Monotonic::now();
77        if let Some(mut next_tick) = self.next_tick {
78            match now.cmp(&next_tick) {
79                std::cmp::Ordering::Less => {
80                    let to_sleep = next_tick - now;
81                    self.next_tick = Some(next_tick + self.period);
82                    thread::sleep(to_sleep);
83                    true
84                }
85                std::cmp::Ordering::Equal => true,
86                std::cmp::Ordering::Greater => {
87                    match self.missing_tick_behavior {
88                        MissedTickBehavior::Burst => {
89                            self.next_tick = Some(next_tick + self.period);
90                        }
91                        MissedTickBehavior::Delay => {
92                            self.next_tick = Some(now + self.period);
93                        }
94                        MissedTickBehavior::Skip => {
95                            while next_tick <= now {
96                                next_tick += self.period;
97                            }
98                            self.next_tick = Some(next_tick);
99                        }
100                    }
101                    false
102                }
103            }
104        } else {
105            self.next_tick = Some(now + self.period);
106            true
107        }
108    }
109    /// Returns the number of ticks elapsed. If a tick is skipped, the counter is not incremented.
110    /// In case if the tick counter reaches `usize::MAX`, it is reset to zero
111    pub fn elapsed_ticks(&self) -> usize {
112        self.ticks.0
113    }
114    /// Sets missing tick behavior policy. Can be used as a build pattern
115    pub fn set_missing_tick_behavior(mut self, missing_tick_behavior: MissedTickBehavior) -> Self {
116        self.missing_tick_behavior = missing_tick_behavior;
117        self
118    }
119
120    /// Returns the period of the interval.
121    pub fn period(&self) -> Duration {
122        self.period
123    }
124}
125
126/// Interval missing tick behavior
127///
128/// The behavior is similar to
129/// <https://docs.rs/tokio/latest/tokio/time/enum.MissedTickBehavior.html>
130/// but may differ in some details
131#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
132pub enum MissedTickBehavior {
133    #[default]
134    /// `[Interval::tick()`] method has no delay for missed intervals, all the missed ones are
135    /// fired instantly
136    Burst,
137    /// The interval is restarted from the current point of time
138    Delay,
139    /// Missed ticks are skipped with no additional effect
140    Skip,
141}
142
143#[cfg(test)]
144mod test {
145    use std::{thread, time::Duration};
146
147    use bma_ts::Monotonic;
148
149    use crate::time::DurationRT as _;
150
151    #[test]
152    fn test_fits() {
153        let first = Monotonic::now();
154        thread::sleep(Duration::from_millis(10));
155        let second = Monotonic::now();
156        thread::sleep(Duration::from_millis(10));
157        let third = Monotonic::now();
158        assert!(Duration::from_millis(100).fits(&[first, second, third]));
159        assert!(Duration::from_millis(55).fits(&[first, second, third]));
160    }
161
162    #[test]
163    fn test_ticks() {
164        let mut int = super::interval(Duration::from_millis(10));
165        for _ in 0..3 {
166            int.tick();
167        }
168        assert_eq!(int.elapsed_ticks(), 3);
169    }
170}