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