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 fn interval_hz(frequency: u64) -> Interval {
41 Interval::new(Duration::from_nanos(1_000_000_000 / frequency))
42}
43
44pub 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 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 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 pub fn elapsed_ticks(&self) -> usize {
112 self.ticks.0
113 }
114 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 pub fn period(&self) -> Duration {
122 self.period
123 }
124}
125
126#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
132pub enum MissedTickBehavior {
133 #[default]
134 Burst,
137 Delay,
139 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}