zentime_rs_timer/timer.rs
1//! Timer implementation
2//! The timer hast the ability to toggle playback and end.
3//! It will call a given `on_tick` closure on every tick update and
4//! an `on_timer_end`-closure, when it's done (either by receiving a [TimerAction::End]) or
5//! when the internal timer is down to 0
6
7use serde::{Deserialize, Serialize};
8use std::fmt::{Debug, Display};
9
10use crate::timer_action::TimerAction;
11use crate::util::seconds_to_time;
12use std::time::{Duration, Instant};
13
14// NOTE: I tried to use the typestate approach, like it's described here:
15// https://cliffle.com/blog/rust-typestate/
16
17/// Information that will be handed to the [on_tick] closure continously
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct CurrentTime(String);
20
21impl Display for CurrentTime {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 write!(f, "{}", &self.0)
24 }
25}
26
27/// Status which is continouusly handed to the callback function on each tick
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct TimerStatus {
30 /// Current time of the timer
31 pub current_time: CurrentTime,
32
33 /// Denotes if timer is paused or running
34 pub is_paused: bool,
35}
36
37/// Empty trait implemented by structs (e.g. Paused, Running)
38pub trait TimerState {}
39
40/// State specific to a paused timer
41#[derive(Clone, Copy, Debug)]
42pub struct Paused {
43 remaining_time: Duration,
44}
45
46/// State specific to a running timer
47#[derive(Clone, Copy, Debug)]
48pub struct Running {
49 target_time: Instant,
50}
51
52impl TimerState for Paused {}
53impl TimerState for Running {}
54
55/// Handler which is called whenever a timer ends by running out
56pub trait TimerEndHandler {
57 /// Callback
58 fn call(&mut self);
59}
60
61/// Handler which is called on each timer tick
62pub trait TimerTickHandler {
63 /// Callback
64 fn call(&mut self, status: TimerStatus) -> Option<TimerAction>;
65}
66
67/// Timer which can either be in a paused state or a running state.
68/// To instantiate the timer run `Timer::new()`.
69/// To actually start it call `Timer::init()`
70///
71/// ## Example
72///
73/// ```
74/// use zentime_rs_timer::timer::{Timer, TimerEndHandler, TimerTickHandler, TimerStatus, Paused};
75/// use zentime_rs_timer::TimerAction;
76/// use std::thread;
77///
78/// // Handler which is passed to our timer implementation
79/// pub struct OnEndHandler { }
80///
81/// impl TimerEndHandler for OnEndHandler {
82/// fn call(&mut self) {
83/// println!("Hi from timer");
84/// }
85/// }
86///
87/// pub struct OnTickHandler {}
88///
89/// impl TimerTickHandler for OnTickHandler {
90/// fn call(&mut self, status: TimerStatus) -> Option<TimerAction> {
91/// println!("{}", status.current_time);
92/// None
93/// }
94/// }
95///
96/// // Run timer in its own thread so it does not block the current one
97/// thread::spawn(move || {
98/// Timer::<Paused>::new(
99/// 10,
100/// Some(OnEndHandler {}),
101/// Some(OnTickHandler {})
102/// )
103/// .init();
104/// });
105/// ```
106pub struct Timer<S: TimerState> {
107 time: u64,
108
109 /// Callback closure which is called at the end of each timer
110 on_timer_end: Option<Box<dyn TimerEndHandler>>,
111
112 /// Callback closure which is being run on each tick
113 on_tick: Option<Box<dyn TimerTickHandler>>,
114
115 /// Internal state data associated with a certain timer state (e.g. [Paused] or [Running])
116 internal_state: S,
117}
118
119impl<S: TimerState + std::fmt::Debug> Debug for Timer<S> {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 f.debug_struct("Timer")
122 .field("time", &self.time)
123 .field("on_timer_end", &"[closure] without context")
124 .field("internal_state", &self.internal_state)
125 .field("on_tick", &"[closure] without context")
126 .finish()
127 }
128}
129
130impl<S: TimerState> Timer<S> {}
131
132/// Implementation of the [Paused] state for [Timer]
133impl Timer<Paused> {
134 /// Creates a new timer in paused state.
135 /// You have to call [Self::init()] to start the timer
136 pub fn new<E, T>(time: u64, on_timer_end: Option<E>, on_tick: Option<T>) -> Self
137 where
138 E: TimerEndHandler + 'static,
139 T: TimerTickHandler + 'static,
140 {
141 let remaining_time = Duration::from_secs(time);
142
143 Self {
144 time,
145 on_timer_end: on_timer_end.map(|x| Box::new(x) as Box<dyn TimerEndHandler>),
146 on_tick: on_tick.map(|x| Box::new(x) as Box<dyn TimerTickHandler>),
147 internal_state: Paused { remaining_time },
148 }
149 }
150
151 /// Puts the paused timer into a waiting state waiting for input (e.g. to unpause the timer
152 /// and transition it into a running state).
153 pub fn init(mut self) {
154 loop {
155 let time = self.internal_state.remaining_time.as_secs();
156
157 let Some(ref mut callback) = self.on_tick else { continue };
158 if let Some(action) = callback.call(TimerStatus {
159 is_paused: true,
160 current_time: CurrentTime(seconds_to_time(time)),
161 }) {
162 match action {
163 TimerAction::SetTimer(time) => {
164 self.internal_state.remaining_time = Duration::from_secs(time)
165 }
166
167 TimerAction::PlayPause => {
168 self.unpause();
169 break;
170 }
171
172 // Returns from the blocking loop, so that the calling code
173 // can resume execution
174 TimerAction::End => return,
175 }
176 }
177 }
178 }
179
180 /// Transitions the paused timer into a running timer
181 fn unpause(self) {
182 Timer {
183 on_timer_end: self.on_timer_end,
184 on_tick: self.on_tick,
185 time: self.time,
186 internal_state: Running {
187 target_time: Instant::now() + self.internal_state.remaining_time,
188 },
189 }
190 .init()
191 }
192}
193
194impl Timer<Running> {
195 /// Creates a new timer in paused state.
196 /// You have to call [Self::init()] to start the timer
197 pub fn new<E, T>(time: u64, on_timer_end: Option<E>, on_tick: Option<T>) -> Self
198 where
199 E: TimerEndHandler + 'static,
200 T: TimerTickHandler + 'static,
201 {
202 let remaining_time = Duration::from_secs(time);
203
204 Self {
205 time,
206 on_timer_end: on_timer_end.map(|x| Box::new(x) as Box<dyn TimerEndHandler>),
207 on_tick: on_tick.map(|x| Box::new(x) as Box<dyn TimerTickHandler>),
208 internal_state: Running {
209 target_time: Instant::now() + remaining_time,
210 },
211 }
212 }
213
214 /// Transitions the running timer into a paused timer state and calls `init()` on_interval_end
215 /// it, so that the new timer is ready to receive an [TimerInputAction]
216 fn pause(self) {
217 Timer {
218 time: self.time,
219 on_tick: self.on_tick,
220 on_timer_end: self.on_timer_end,
221 internal_state: Paused {
222 remaining_time: self.internal_state.target_time - Instant::now(),
223 },
224 }
225 .init();
226 }
227
228 /// Runs the timer and awaits input.
229 /// Depending on the input [TimerInputAction] the timer might transition into a paused state or skip to the next interval.
230 pub fn init(mut self) {
231 while self.internal_state.target_time > Instant::now() {
232 let time = (self.internal_state.target_time - Instant::now()).as_secs();
233
234 let Some(ref mut callback) = self.on_tick else { continue };
235 if let Some(action) = callback.call(TimerStatus {
236 is_paused: false,
237 current_time: CurrentTime(seconds_to_time(time)),
238 }) {
239 match action {
240 TimerAction::PlayPause => {
241 return self.pause();
242 }
243
244 // Returns from the blocking loop, so that the calling code
245 // can resume execution
246 TimerAction::End => return,
247 TimerAction::SetTimer(time) => {
248 self.internal_state.target_time = Instant::now() + Duration::from_secs(time)
249 }
250 }
251 }
252 }
253
254 if let Some(mut on_timer_end) = self.on_timer_end {
255 on_timer_end.call()
256 }
257 }
258}