1use std::sync::atomic::{AtomicBool, Ordering};
4use std::sync::Arc;
5use std::time::Duration;
6
7use crate::Deadline;
8
9#[derive(Debug)]
16pub struct Timer {
17 state: State,
19
20 deadline: Deadline,
22}
23
24impl Timer {
25 pub fn new(delay: Duration) -> Self {
27 Self {
28 state: State::new(),
29 deadline: Deadline::repeat(delay),
30 }
31 }
32
33 pub fn watcher(&self) -> Watcher {
35 Watcher::new(self.state.clone())
36 }
37
38 pub fn tick(&mut self) {
40 self.deadline.wait();
41 self.state.toggle();
42 }
43}
44
45pub struct Watcher {
52 state: State,
54
55 prev_state: bool,
57}
58
59impl Watcher {
60 fn new(state: State) -> Self {
62 let prev_state = state.value();
63 Self { state, prev_state }
64 }
65
66 pub fn has_ticked(&mut self) -> bool {
68 if self.state.value() != self.prev_state {
69 self.prev_state = !self.prev_state;
70 return true;
71 }
72
73 false
74 }
75}
76
77impl Clone for Watcher {
78 fn clone(&self) -> Self {
79 let state = self.state.clone();
83 let prev_state = state.value();
84
85 Self { state, prev_state }
86 }
87}
88
89#[derive(Debug, Default, Clone)]
93struct State(Arc<AtomicBool>);
94
95impl State {
96 #[inline]
98 fn new() -> Self {
99 Self::default()
100 }
101
102 #[inline]
104 fn toggle(&self) {
105 self.0.fetch_xor(true, Ordering::Release);
106 }
107
108 #[inline]
110 fn value(&self) -> bool {
111 self.0.load(Ordering::Acquire)
112 }
113}
114
115#[cfg(test)]
116impl PartialEq<bool> for State {
117 #[inline]
118 fn eq(&self, other: &bool) -> bool {
119 self.value() == *other
120 }
121}
122
123#[cfg(test)]
126mod state {
127 use super::*;
128
129 #[test]
130 fn new() {
131 let new = State::new();
132 assert_eq!(new, false);
133 }
134
135 #[test]
136 fn toggle() {
137 let new = State::new();
138 assert_eq!(new, false);
139
140 new.toggle();
141 assert_eq!(new, true);
142 }
143}
144
145#[cfg(test)]
146#[allow(clippy::module_inception)]
147mod timer {
148 use std::time::Instant;
149
150 use super::*;
151
152 #[test]
153 fn tick_delay() {
154 let now = Instant::now();
155 let mut timer = Timer::new(Duration::from_millis(100));
156
157 for count in 0..5 {
158 timer.tick();
159
160 let elapsed = now.elapsed();
161 assert!(
162 now.elapsed() >= Duration::from_millis(100 * count),
163 "elapsed = {elapsed:?}"
164 )
165 }
166 }
167}
168
169#[cfg(test)]
170mod watcher {
171 use std::time::Instant;
172
173 use super::*;
174
175 #[test]
176 fn new() {
177 let mut timer = Timer::new(Duration::from_millis(100));
178 let mut watcher = timer.watcher();
179
180 assert!(
181 !watcher.has_ticked(),
182 "watcher shouldn't have been notified yet"
183 );
184
185 timer.tick();
186 assert!(watcher.has_ticked(), "watcher should have been notified");
187 assert!(
188 !watcher.has_ticked(),
189 "watcher shouldn't have been notified instantly"
190 );
191 }
192
193 #[test]
194 fn cloned() {
195 let mut timer = Timer::new(Duration::from_millis(100));
196
197 let mut watcher = timer.watcher();
198 assert!(
199 !watcher.has_ticked(),
200 "watcher shouldn't have been notified yet"
201 );
202
203 let mut watcher_clone = watcher.clone();
204 assert!(
205 !watcher_clone.has_ticked(),
206 "watcher clone shouldn't have been notified yet"
207 );
208
209 timer.tick();
210 assert!(watcher.has_ticked(), "watcher should have been notified");
211 assert!(
212 watcher_clone.has_ticked(),
213 "watcher clone should have been notified"
214 );
215
216 let mut watcher_clone = watcher.clone();
217 assert!(
218 !watcher_clone.has_ticked(),
219 "2dn watcher clone shouldn't have been notified yet"
220 );
221 }
222
223 #[test]
224 fn thread_sync() {
225 let stop = Arc::new(AtomicBool::default());
226 let now = Instant::now();
227 let mut timer = Timer::new(Duration::from_millis(100));
228 let mut watcher = timer.watcher();
229
230 let stop_clone = Arc::clone(&stop);
231 let watcher_thread = std::thread::spawn(move || {
232 let mut loops = 1;
233
234 while !stop_clone.load(Ordering::Acquire) {
235 if watcher.has_ticked() {
236 let elapsed = now.elapsed();
237 let expected = Duration::from_millis(100 * loops);
238
239 if now.elapsed() < expected {
240 return Some((elapsed, expected));
241 }
242
243 loops += 1;
244 }
245 }
246
247 None
248 });
249
250 for _ in 0..5 {
251 timer.tick();
252 }
253
254 stop.store(true, Ordering::Release);
255 let test_result = watcher_thread.join().unwrap();
256 assert_eq!(
257 test_result, None,
258 "watcher detected a tick before the expected time"
259 );
260 }
261}