1use std::{num::Wrapping, thread, time::Duration};
2
3use bma_ts::Monotonic;
4
5pub trait DurationRT {
8 fn fits(&self, t: &[Monotonic]) -> bool;
10 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
34pub fn interval(period: Duration) -> Interval {
36 Interval::new(period)
37}
38
39pub 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 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 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 pub fn elapsed_ticks(&self) -> usize {
107 self.ticks.0
108 }
109 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#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
122pub enum MissedTickBehavior {
123 #[default]
124 Burst,
127 Delay,
129 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}