bones_framework/time/
timer.rs

1use std::time::Duration;
2
3use crate::prelude::*;
4
5use super::stopwatch::Stopwatch;
6
7/// Tracks elapsed time. Enters the finished state once `duration` is reached.
8///
9/// Non repeating timers will stop tracking and stay in the finished state until reset.
10/// Repeating timers will only be in the finished state on each tick `duration` is reached or
11/// exceeded, and can still be reset at any given point.
12///
13/// Paused timers will not have elapsed time increased.
14#[derive(Clone, Debug, Default, HasSchema)]
15pub struct Timer {
16    finished: bool,
17    mode: TimerMode,
18    duration: Duration,
19    stopwatch: Stopwatch,
20    times_finished_this_tick: u32,
21}
22
23impl Timer {
24    /// Creates a new timer with a given duration.
25    ///
26    /// See also [`Timer::from_seconds`](Timer::from_seconds).
27    pub fn new(duration: Duration, mode: TimerMode) -> Self {
28        Self {
29            duration,
30            mode,
31            ..Default::default()
32        }
33    }
34
35    /// Creates a new timer with a given duration in seconds.
36    ///
37    /// # Example
38    /// ```no_run
39    /// # use bones_framework::prelude::*;
40    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
41    /// ```
42    pub fn from_seconds(duration: f32, mode: TimerMode) -> Self {
43        Self {
44            duration: Duration::from_secs_f32(duration),
45            mode,
46            ..Default::default()
47        }
48    }
49
50    /// Returns `true` if the timer has reached its duration at least once.
51    /// See also [`Timer::just_finished`](Timer::just_finished).
52    ///
53    /// # Examples
54    /// ```no_run
55    /// # use bones_framework::prelude::*;
56    /// use std::time::Duration;
57    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
58    /// timer.tick(Duration::from_secs_f32(1.5));
59    /// assert!(timer.finished());
60    /// timer.tick(Duration::from_secs_f32(0.5));
61    /// assert!(timer.finished());
62    /// ```
63    #[inline]
64    pub fn finished(&self) -> bool {
65        self.finished
66    }
67
68    /// Returns `true` only on the tick the timer reached its duration.
69    ///
70    /// # Examples
71    /// ```no_run
72    /// # use bones_framework::prelude::*;
73    /// use std::time::Duration;
74    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
75    /// timer.tick(Duration::from_secs_f32(1.5));
76    /// assert!(timer.just_finished());
77    /// timer.tick(Duration::from_secs_f32(0.5));
78    /// assert!(!timer.just_finished());
79    /// ```
80    #[inline]
81    pub fn just_finished(&self) -> bool {
82        self.times_finished_this_tick > 0
83    }
84
85    /// Returns the time elapsed on the timer. Guaranteed to be between 0.0 and `duration`.
86    /// Will only equal `duration` when the timer is finished and non repeating.
87    ///
88    /// See also [`Stopwatch::elapsed`](Stopwatch::elapsed).
89    ///
90    /// # Examples
91    /// ```no_run
92    /// # use bones_framework::prelude::*;
93    /// use std::time::Duration;
94    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
95    /// timer.tick(Duration::from_secs_f32(0.5));
96    /// assert_eq!(timer.elapsed(), Duration::from_secs_f32(0.5));
97    /// ```
98    #[inline]
99    pub fn elapsed(&self) -> Duration {
100        self.stopwatch.elapsed()
101    }
102
103    /// Returns the time elapsed on the timer as an `f32`.
104    /// See also [`Timer::elapsed`](Timer::elapsed).
105    #[inline]
106    pub fn elapsed_secs(&self) -> f32 {
107        self.stopwatch.elapsed_secs()
108    }
109
110    /// Sets the elapsed time of the timer without any other considerations.
111    ///
112    /// # Example
113    /// ```no_run
114    /// # use bones_framework::prelude::*;
115    /// use std::time::Duration;
116    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
117    /// timer.set_elapsed(Duration::from_secs(2));
118    /// assert_eq!(timer.elapsed(), Duration::from_secs(2));
119    /// // the timer is not finished even if the elapsed time is greater than the duration.
120    /// assert!(!timer.finished());
121    /// ```
122    #[inline]
123    pub fn set_elapsed(&mut self, time: Duration) {
124        self.stopwatch.set_elapsed(time);
125    }
126
127    /// Returns the duration of the timer.
128    ///
129    /// # Examples
130    /// ```no_run
131    /// # use bones_framework::prelude::*;
132    /// use std::time::Duration;
133    /// let timer = Timer::new(Duration::from_secs(1), TimerMode::Once);
134    /// assert_eq!(timer.duration(), Duration::from_secs(1));
135    /// ```
136    #[inline]
137    pub fn duration(&self) -> Duration {
138        self.duration
139    }
140
141    /// Sets the duration of the timer.
142    ///
143    /// # Examples
144    /// ```no_run
145    /// # use bones_framework::prelude::*;
146    /// use std::time::Duration;
147    /// let mut timer = Timer::from_seconds(1.5, TimerMode::Once);
148    /// timer.set_duration(Duration::from_secs(1));
149    /// assert_eq!(timer.duration(), Duration::from_secs(1));
150    /// ```
151    #[inline]
152    pub fn set_duration(&mut self, duration: Duration) {
153        self.duration = duration;
154    }
155
156    /// Returns the mode of the timer.
157    ///
158    /// # Examples
159    /// ```no_run
160    /// # use bones_framework::prelude::*;
161    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
162    /// assert_eq!(timer.mode(), TimerMode::Repeating);
163    /// ```
164    #[inline]
165    pub fn mode(&self) -> TimerMode {
166        self.mode
167    }
168
169    /// Sets the mode of the timer.
170    ///
171    /// # Examples
172    /// ```no_run
173    /// # use bones_framework::prelude::*;
174    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
175    /// timer.set_mode(TimerMode::Once);
176    /// assert_eq!(timer.mode(), TimerMode::Once);
177    /// ```
178    #[doc(alias = "repeating")]
179    #[inline]
180    pub fn set_mode(&mut self, mode: TimerMode) {
181        if self.mode != TimerMode::Repeating && mode == TimerMode::Repeating && self.finished {
182            self.stopwatch.reset();
183            self.finished = self.just_finished();
184        }
185        self.mode = mode;
186    }
187
188    /// Advance the timer by `delta` seconds.
189    /// Non repeating timer will clamp at duration.
190    /// Repeating timer will wrap around.
191    /// Will not affect paused timers.
192    ///
193    /// See also [`Stopwatch::tick`](Stopwatch::tick).
194    ///
195    /// # Examples
196    /// ```no_run
197    /// # use bones_framework::prelude::*;
198    /// use std::time::Duration;
199    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
200    /// let mut repeating = Timer::from_seconds(1.0, TimerMode::Repeating);
201    /// timer.tick(Duration::from_secs_f32(1.5));
202    /// repeating.tick(Duration::from_secs_f32(1.5));
203    /// assert_eq!(timer.elapsed_secs(), 1.0);
204    /// assert_eq!(repeating.elapsed_secs(), 0.5);
205    /// ```
206    pub fn tick(&mut self, delta: Duration) -> &Self {
207        if self.paused() {
208            self.times_finished_this_tick = 0;
209            if self.mode == TimerMode::Repeating {
210                self.finished = false;
211            }
212            return self;
213        }
214
215        if self.mode != TimerMode::Repeating && self.finished() {
216            self.times_finished_this_tick = 0;
217            return self;
218        }
219
220        self.stopwatch.tick(delta);
221        self.finished = self.elapsed() >= self.duration();
222
223        if self.finished() {
224            if self.mode == TimerMode::Repeating {
225                self.times_finished_this_tick =
226                    (self.elapsed().as_nanos() / self.duration().as_nanos()) as u32;
227                // Duration does not have a modulo
228                self.set_elapsed(self.elapsed() - self.duration() * self.times_finished_this_tick);
229            } else {
230                self.times_finished_this_tick = 1;
231                self.set_elapsed(self.duration());
232            }
233        } else {
234            self.times_finished_this_tick = 0;
235        }
236
237        self
238    }
239
240    /// Pauses the Timer. Disables the ticking of the timer.
241    ///
242    /// See also [`Stopwatch::pause`](Stopwatch::pause).
243    ///
244    /// # Examples
245    /// ```no_run
246    /// # use bones_framework::prelude::*;
247    /// use std::time::Duration;
248    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
249    /// timer.pause();
250    /// timer.tick(Duration::from_secs_f32(0.5));
251    /// assert_eq!(timer.elapsed_secs(), 0.0);
252    /// ```
253    #[inline]
254    pub fn pause(&mut self) {
255        self.stopwatch.pause();
256    }
257
258    /// Unpauses the Timer. Resumes the ticking of the timer.
259    ///
260    /// See also [`Stopwatch::unpause()`](Stopwatch::unpause).
261    ///
262    /// # Examples
263    /// ```no_run
264    /// # use bones_framework::prelude::*;
265    /// use std::time::Duration;
266    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
267    /// timer.pause();
268    /// timer.tick(Duration::from_secs_f32(0.5));
269    /// timer.unpause();
270    /// timer.tick(Duration::from_secs_f32(0.5));
271    /// assert_eq!(timer.elapsed_secs(), 0.5);
272    /// ```
273    #[inline]
274    pub fn unpause(&mut self) {
275        self.stopwatch.unpause();
276    }
277
278    /// Returns `true` if the timer is paused.
279    ///
280    /// See also [`Stopwatch::paused`](Stopwatch::paused).
281    ///
282    /// # Examples
283    /// ```no_run
284    /// # use bones_framework::prelude::*;
285    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
286    /// assert!(!timer.paused());
287    /// timer.pause();
288    /// assert!(timer.paused());
289    /// timer.unpause();
290    /// assert!(!timer.paused());
291    /// ```
292    #[inline]
293    pub fn paused(&self) -> bool {
294        self.stopwatch.paused()
295    }
296
297    /// Resets the timer. The reset doesn't affect the `paused` state of the timer.
298    ///
299    /// See also [`Stopwatch::reset`](Stopwatch::reset).
300    ///
301    /// Examples
302    /// ```no_run
303    /// # use bones_framework::prelude::*;
304    /// use std::time::Duration;
305    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
306    /// timer.tick(Duration::from_secs_f32(1.5));
307    /// timer.reset();
308    /// assert!(!timer.finished());
309    /// assert!(!timer.just_finished());
310    /// assert_eq!(timer.elapsed_secs(), 0.0);
311    /// ```
312    pub fn reset(&mut self) {
313        self.stopwatch.reset();
314        self.finished = false;
315        self.times_finished_this_tick = 0;
316    }
317
318    /// Returns the percentage of the timer elapsed time (goes from 0.0 to 1.0).
319    ///
320    /// # Examples
321    /// ```no_run
322    /// # use bones_framework::prelude::*;
323    /// use std::time::Duration;
324    /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
325    /// timer.tick(Duration::from_secs_f32(0.5));
326    /// assert_eq!(timer.percent(), 0.25);
327    /// ```
328    #[inline]
329    pub fn percent(&self) -> f32 {
330        self.elapsed().as_secs_f32() / self.duration().as_secs_f32()
331    }
332
333    /// Returns the percentage of the timer remaining time (goes from 1.0 to 0.0).
334    ///
335    /// # Examples
336    /// ```no_run
337    /// # use bones_framework::prelude::*;
338    /// use std::time::Duration;
339    /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
340    /// timer.tick(Duration::from_secs_f32(0.5));
341    /// assert_eq!(timer.percent_left(), 0.75);
342    /// ```
343    #[inline]
344    pub fn percent_left(&self) -> f32 {
345        1.0 - self.percent()
346    }
347
348    /// Returns the remaining time in seconds
349    ///
350    /// # Examples
351    /// ```no_run
352    /// # use bones_framework::prelude::*;
353    /// use std::cmp::Ordering;
354    /// use std::time::Duration;
355    /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
356    /// timer.tick(Duration::from_secs_f32(0.5));
357    /// let result = timer.remaining_secs().total_cmp(&1.5);
358    /// assert_eq!(Ordering::Equal, result);
359    /// ```
360    #[inline]
361    pub fn remaining_secs(&self) -> f32 {
362        self.remaining().as_secs_f32()
363    }
364
365    /// Returns the remaining time using Duration
366    ///
367    /// # Examples
368    /// ```no_run
369    /// # use bones_framework::prelude::*;
370    /// use std::time::Duration;
371    /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once);
372    /// timer.tick(Duration::from_secs_f32(0.5));
373    /// assert_eq!(timer.remaining(), Duration::from_secs_f32(1.5));
374    /// ```
375    #[inline]
376    pub fn remaining(&self) -> Duration {
377        self.duration() - self.elapsed()
378    }
379
380    /// Returns the number of times a repeating timer
381    /// finished during the last [`tick`](Timer<T>::tick) call.
382    ///
383    /// For non repeating-timers, this method will only ever
384    /// return 0 or 1.
385    ///
386    /// # Examples
387    /// ```no_run
388    /// # use bones_framework::prelude::*;
389    /// use std::time::Duration;
390    /// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
391    /// timer.tick(Duration::from_secs_f32(6.0));
392    /// assert_eq!(timer.times_finished_this_tick(), 6);
393    /// timer.tick(Duration::from_secs_f32(2.0));
394    /// assert_eq!(timer.times_finished_this_tick(), 2);
395    /// timer.tick(Duration::from_secs_f32(0.5));
396    /// assert_eq!(timer.times_finished_this_tick(), 0);
397    /// ```
398    #[inline]
399    pub fn times_finished_this_tick(&self) -> u32 {
400        self.times_finished_this_tick
401    }
402}
403
404/// Specifies [`Timer`] behavior.
405#[derive(
406    Debug, Clone, Copy, Eq, PartialEq, Hash, Default, serde::Deserialize, serde::Serialize,
407)]
408pub enum TimerMode {
409    /// Run once and stop.
410    #[default]
411    Once,
412    /// Reset when finished.
413    Repeating,
414}
415
416// To speed up CI, only do these on miri, where they complete without waiting for time to pass.
417#[cfg(all(test, miri))]
418#[allow(clippy::float_cmp)]
419mod tests {
420    use super::*;
421
422    #[test]
423    fn non_repeating_timer() {
424        let mut t = Timer::from_seconds(10.0, TimerMode::Once);
425        // Tick once, check all attributes
426        t.tick(Duration::from_secs_f32(0.25));
427        assert_eq!(t.elapsed_secs(), 0.25);
428        assert_eq!(t.duration(), Duration::from_secs_f32(10.0));
429        assert!(!t.finished());
430        assert!(!t.just_finished());
431        assert_eq!(t.times_finished_this_tick(), 0);
432        assert_eq!(t.mode(), TimerMode::Once);
433        assert_eq!(t.percent(), 0.025);
434        assert_eq!(t.percent_left(), 0.975);
435        // Ticking while paused changes nothing
436        t.pause();
437        t.tick(Duration::from_secs_f32(500.0));
438        assert_eq!(t.elapsed_secs(), 0.25);
439        assert_eq!(t.duration(), Duration::from_secs_f32(10.0));
440        assert!(!t.finished());
441        assert!(!t.just_finished());
442        assert_eq!(t.times_finished_this_tick(), 0);
443        assert_eq!(t.mode(), TimerMode::Once);
444        assert_eq!(t.percent(), 0.025);
445        assert_eq!(t.percent_left(), 0.975);
446        // Tick past the end and make sure elapsed doesn't go past 0.0 and other things update
447        t.unpause();
448        t.tick(Duration::from_secs_f32(500.0));
449        assert_eq!(t.elapsed_secs(), 10.0);
450        assert!(t.finished());
451        assert!(t.just_finished());
452        assert_eq!(t.times_finished_this_tick(), 1);
453        assert_eq!(t.percent(), 1.0);
454        assert_eq!(t.percent_left(), 0.0);
455        // Continuing to tick when finished should only change just_finished
456        t.tick(Duration::from_secs_f32(1.0));
457        assert_eq!(t.elapsed_secs(), 10.0);
458        assert!(t.finished());
459        assert!(!t.just_finished());
460        assert_eq!(t.times_finished_this_tick(), 0);
461        assert_eq!(t.percent(), 1.0);
462        assert_eq!(t.percent_left(), 0.0);
463    }
464
465    #[test]
466    fn repeating_timer() {
467        let mut t = Timer::from_seconds(2.0, TimerMode::Repeating);
468        // Tick once, check all attributes
469        t.tick(Duration::from_secs_f32(0.75));
470        assert_eq!(t.elapsed_secs(), 0.75);
471        assert_eq!(t.duration(), Duration::from_secs_f32(2.0));
472        assert!(!t.finished());
473        assert!(!t.just_finished());
474        assert_eq!(t.times_finished_this_tick(), 0);
475        assert_eq!(t.mode(), TimerMode::Repeating);
476        assert_eq!(t.percent(), 0.375);
477        assert_eq!(t.percent_left(), 0.625);
478        // Tick past the end and make sure elapsed wraps
479        t.tick(Duration::from_secs_f32(1.5));
480        assert_eq!(t.elapsed_secs(), 0.25);
481        assert!(t.finished());
482        assert!(t.just_finished());
483        assert_eq!(t.times_finished_this_tick(), 1);
484        assert_eq!(t.percent(), 0.125);
485        assert_eq!(t.percent_left(), 0.875);
486        // Continuing to tick should turn off both finished & just_finished for repeating timers
487        t.tick(Duration::from_secs_f32(1.0));
488        assert_eq!(t.elapsed_secs(), 1.25);
489        assert!(!t.finished());
490        assert!(!t.just_finished());
491        assert_eq!(t.times_finished_this_tick(), 0);
492        assert_eq!(t.percent(), 0.625);
493        assert_eq!(t.percent_left(), 0.375);
494    }
495
496    #[test]
497    fn times_finished_repeating() {
498        let mut t = Timer::from_seconds(1.0, TimerMode::Repeating);
499        assert_eq!(t.times_finished_this_tick(), 0);
500        t.tick(Duration::from_secs_f32(3.5));
501        assert_eq!(t.times_finished_this_tick(), 3);
502        assert_eq!(t.elapsed_secs(), 0.5);
503        assert!(t.finished());
504        assert!(t.just_finished());
505        t.tick(Duration::from_secs_f32(0.2));
506        assert_eq!(t.times_finished_this_tick(), 0);
507    }
508
509    #[test]
510    fn times_finished_this_tick() {
511        let mut t = Timer::from_seconds(1.0, TimerMode::Once);
512        assert_eq!(t.times_finished_this_tick(), 0);
513        t.tick(Duration::from_secs_f32(1.5));
514        assert_eq!(t.times_finished_this_tick(), 1);
515        t.tick(Duration::from_secs_f32(0.5));
516        assert_eq!(t.times_finished_this_tick(), 0);
517    }
518
519    #[test]
520    fn times_finished_this_tick_precise() {
521        let mut t = Timer::from_seconds(0.01, TimerMode::Repeating);
522        let duration = Duration::from_secs_f64(0.333);
523
524        // total duration: 0.333 => 33 times finished
525        t.tick(duration);
526        assert_eq!(t.times_finished_this_tick(), 33);
527        // total duration: 0.666 => 33 times finished
528        t.tick(duration);
529        assert_eq!(t.times_finished_this_tick(), 33);
530        // total duration: 0.999 => 33 times finished
531        t.tick(duration);
532        assert_eq!(t.times_finished_this_tick(), 33);
533        // total duration: 1.332 => 34 times finished
534        t.tick(duration);
535        assert_eq!(t.times_finished_this_tick(), 34);
536    }
537
538    #[test]
539    fn paused() {
540        let mut t = Timer::from_seconds(10.0, TimerMode::Once);
541
542        t.tick(Duration::from_secs_f32(10.0));
543        assert!(t.just_finished());
544        assert!(t.finished());
545        // A paused timer should change just_finished to false after a tick
546        t.pause();
547        t.tick(Duration::from_secs_f32(5.0));
548        assert!(!t.just_finished());
549        assert!(t.finished());
550    }
551
552    #[test]
553    fn paused_repeating() {
554        let mut t = Timer::from_seconds(10.0, TimerMode::Repeating);
555
556        t.tick(Duration::from_secs_f32(10.0));
557        assert!(t.just_finished());
558        assert!(t.finished());
559        // A paused repeating timer should change finished and just_finished to false after a tick
560        t.pause();
561        t.tick(Duration::from_secs_f32(5.0));
562        assert!(!t.just_finished());
563        assert!(!t.finished());
564    }
565}