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}