use crate::Stopwatch; use bevy_reflect::prelude::*; use bevy_utils::Duration; /// Tracks elapsed time. Enters the finished state once `duration` is reached. /// /// Non repeating timers will stop tracking and stay in the finished state until reset. /// Repeating timers will only be in the finished state on each tick `duration` is reached or /// exceeded, and can still be reset at any given point. /// /// Paused timers will not have elapsed time increased. #[derive(Clone, Debug, Default, Reflect, FromReflect)] #[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] #[reflect(Default)] pub struct Timer { stopwatch: Stopwatch, duration: Duration, mode: TimerMode, finished: bool, times_finished_this_tick: u32, } impl Timer { /// Creates a new timer with a given duration. /// /// See also [`Timer::from_seconds`](Timer::from_seconds). pub fn new(duration: Duration, mode: TimerMode) -> Self { Self { duration, mode, ..Default::default() } } /// Creates a new timer with a given duration in seconds. /// /// # Example /// ``` /// # use bevy_time::*; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// ``` pub fn from_seconds(duration: f32, mode: TimerMode) -> Self { Self { duration: Duration::from_secs_f32(duration), mode, ..Default::default() } } /// Returns `true` if the timer has reached its duration at least once. /// See also [`Timer::just_finished`](Timer::just_finished). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(1.5)); /// assert!(timer.finished()); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert!(timer.finished()); /// ``` #[inline] pub fn finished(&self) -> bool { self.finished } /// Returns `true` only on the tick the timer reached its duration. /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(1.5)); /// assert!(timer.just_finished()); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert!(!timer.just_finished()); /// ``` #[inline] pub fn just_finished(&self) -> bool { self.times_finished_this_tick > 0 } /// Returns the time elapsed on the timer. Guaranteed to be between 0.0 and `duration`. /// Will only equal `duration` when the timer is finished and non repeating. /// /// See also [`Stopwatch::elapsed`](Stopwatch::elapsed). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.elapsed(), Duration::from_secs_f32(0.5)); /// ``` #[inline] pub fn elapsed(&self) -> Duration { self.stopwatch.elapsed() } /// Returns the time elapsed on the timer as an `f32`. /// See also [`Timer::elapsed`](Timer::elapsed). #[inline] pub fn elapsed_secs(&self) -> f32 { self.stopwatch.elapsed_secs() } /// Sets the elapsed time of the timer without any other considerations. /// /// See also [`Stopwatch::set`](Stopwatch::set). /// /// # /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.set_elapsed(Duration::from_secs(2)); /// assert_eq!(timer.elapsed(), Duration::from_secs(2)); /// // the timer is not finished even if the elapsed time is greater than the duration. /// assert!(!timer.finished()); /// ``` #[inline] pub fn set_elapsed(&mut self, time: Duration) { self.stopwatch.set_elapsed(time); } /// Returns the duration of the timer. /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let timer = Timer::new(Duration::from_secs(1), TimerMode::Once); /// assert_eq!(timer.duration(), Duration::from_secs(1)); /// ``` #[inline] pub fn duration(&self) -> Duration { self.duration } /// Sets the duration of the timer. /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.5, TimerMode::Once); /// timer.set_duration(Duration::from_secs(1)); /// assert_eq!(timer.duration(), Duration::from_secs(1)); /// ``` #[inline] pub fn set_duration(&mut self, duration: Duration) { self.duration = duration; } /// Returns the mode of the timer. /// /// # Examples /// ``` /// # use bevy_time::*; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating); /// assert_eq!(timer.mode(), TimerMode::Repeating); /// ``` #[inline] pub fn mode(&self) -> TimerMode { self.mode } /// Sets the mode of the timer. /// /// # Examples /// ``` /// # use bevy_time::*; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating); /// timer.set_mode(TimerMode::Once); /// assert_eq!(timer.mode(), TimerMode::Once); /// ``` #[doc(alias = "repeating")] #[inline] pub fn set_mode(&mut self, mode: TimerMode) { if self.mode != TimerMode::Repeating && mode == TimerMode::Repeating && self.finished { self.stopwatch.reset(); self.finished = self.just_finished(); } self.mode = mode; } /// Advance the timer by `delta` seconds. /// Non repeating timer will clamp at duration. /// Repeating timer will wrap around. /// Will not affect paused timers. /// /// See also [`Stopwatch::tick`](Stopwatch::tick). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// let mut repeating = Timer::from_seconds(1.0, TimerMode::Repeating); /// timer.tick(Duration::from_secs_f32(1.5)); /// repeating.tick(Duration::from_secs_f32(1.5)); /// assert_eq!(timer.elapsed_secs(), 1.0); /// assert_eq!(repeating.elapsed_secs(), 0.5); /// ``` pub fn tick(&mut self, delta: Duration) -> &Self { if self.paused() { self.times_finished_this_tick = 0; if self.mode == TimerMode::Repeating { self.finished = false; } return self; } if self.mode != TimerMode::Repeating && self.finished() { self.times_finished_this_tick = 0; return self; } self.stopwatch.tick(delta); self.finished = self.elapsed() >= self.duration(); if self.finished() { if self.mode == TimerMode::Repeating { self.times_finished_this_tick = self .elapsed() .as_nanos() .checked_div(self.duration().as_nanos()) .map_or(u32::MAX, |x| x as u32); self.set_elapsed( self.elapsed() .as_nanos() .checked_rem(self.duration().as_nanos()) .map_or(Duration::ZERO, |x| Duration::from_nanos(x as u64)), ); } else { self.times_finished_this_tick = 1; self.set_elapsed(self.duration()); } } else { self.times_finished_this_tick = 0; } self } /// Pauses the Timer. Disables the ticking of the timer. /// /// See also [`Stopwatch::pause`](Stopwatch::pause). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.pause(); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.elapsed_secs(), 0.0); /// ``` #[inline] pub fn pause(&mut self) { self.stopwatch.pause(); } /// Unpauses the Timer. Resumes the ticking of the timer. /// /// See also [`Stopwatch::unpause()`](Stopwatch::unpause). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.pause(); /// timer.tick(Duration::from_secs_f32(0.5)); /// timer.unpause(); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.elapsed_secs(), 0.5); /// ``` #[inline] pub fn unpause(&mut self) { self.stopwatch.unpause(); } /// Returns `true` if the timer is paused. /// /// See also [`Stopwatch::paused`](Stopwatch::paused). /// /// # Examples /// ``` /// # use bevy_time::*; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// assert!(!timer.paused()); /// timer.pause(); /// assert!(timer.paused()); /// timer.unpause(); /// assert!(!timer.paused()); /// ``` #[inline] pub fn paused(&self) -> bool { self.stopwatch.paused() } /// Resets the timer. The reset doesn't affect the `paused` state of the timer. /// /// See also [`Stopwatch::reset`](Stopwatch::reset). /// /// Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(1.5)); /// timer.reset(); /// assert!(!timer.finished()); /// assert!(!timer.just_finished()); /// assert_eq!(timer.elapsed_secs(), 0.0); /// ``` pub fn reset(&mut self) { self.stopwatch.reset(); self.finished = false; self.times_finished_this_tick = 0; } /// Returns the percentage of the timer elapsed time (goes from 0.0 to 1.0). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.percent(), 0.25); /// ``` #[inline] pub fn percent(&self) -> f32 { if self.duration == Duration::ZERO { 1.0 } else { self.elapsed().as_secs_f32() / self.duration().as_secs_f32() } } /// Returns the percentage of the timer remaining time (goes from 1.0 to 0.0). /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.percent_left(), 0.75); /// ``` #[inline] pub fn percent_left(&self) -> f32 { 1.0 - self.percent() } /// Returns the remaining time in seconds /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::cmp::Ordering; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); /// let result = timer.remaining_secs().total_cmp(&1.5); /// assert_eq!(Ordering::Equal, result); /// ``` #[inline] pub fn remaining_secs(&self) -> f32 { self.remaining().as_secs_f32() } /// Returns the remaining time using Duration /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(2.0, TimerMode::Once); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.remaining(), Duration::from_secs_f32(1.5)); /// ``` #[inline] pub fn remaining(&self) -> Duration { self.duration() - self.elapsed() } /// Returns the number of times a repeating timer /// finished during the last [`tick`](Timer::tick) call. /// /// For non repeating-timers, this method will only ever /// return 0 or 1. /// /// # Examples /// ``` /// # use bevy_time::*; /// use std::time::Duration; /// let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating); /// timer.tick(Duration::from_secs_f32(6.0)); /// assert_eq!(timer.times_finished_this_tick(), 6); /// timer.tick(Duration::from_secs_f32(2.0)); /// assert_eq!(timer.times_finished_this_tick(), 2); /// timer.tick(Duration::from_secs_f32(0.5)); /// assert_eq!(timer.times_finished_this_tick(), 0); /// ``` #[inline] pub fn times_finished_this_tick(&self) -> u32 { self.times_finished_this_tick } } /// Specifies [`Timer`] behavior. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, Reflect, FromReflect)] #[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] #[reflect(Default)] pub enum TimerMode { /// Run once and stop. #[default] Once, /// Reset when finished. Repeating, } #[cfg(test)] #[allow(clippy::float_cmp)] mod tests { use super::*; #[test] fn non_repeating_timer() { let mut t = Timer::from_seconds(10.0, TimerMode::Once); // Tick once, check all attributes t.tick(Duration::from_secs_f32(0.25)); assert_eq!(t.elapsed_secs(), 0.25); assert_eq!(t.duration(), Duration::from_secs_f32(10.0)); assert!(!t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.mode(), TimerMode::Once); assert_eq!(t.percent(), 0.025); assert_eq!(t.percent_left(), 0.975); // Ticking while paused changes nothing t.pause(); t.tick(Duration::from_secs_f32(500.0)); assert_eq!(t.elapsed_secs(), 0.25); assert_eq!(t.duration(), Duration::from_secs_f32(10.0)); assert!(!t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.mode(), TimerMode::Once); assert_eq!(t.percent(), 0.025); assert_eq!(t.percent_left(), 0.975); // Tick past the end and make sure elapsed doesn't go past 0.0 and other things update t.unpause(); t.tick(Duration::from_secs_f32(500.0)); assert_eq!(t.elapsed_secs(), 10.0); assert!(t.finished()); assert!(t.just_finished()); assert_eq!(t.times_finished_this_tick(), 1); assert_eq!(t.percent(), 1.0); assert_eq!(t.percent_left(), 0.0); // Continuing to tick when finished should only change just_finished t.tick(Duration::from_secs_f32(1.0)); assert_eq!(t.elapsed_secs(), 10.0); assert!(t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.percent(), 1.0); assert_eq!(t.percent_left(), 0.0); } #[test] fn repeating_timer() { let mut t = Timer::from_seconds(2.0, TimerMode::Repeating); // Tick once, check all attributes t.tick(Duration::from_secs_f32(0.75)); assert_eq!(t.elapsed_secs(), 0.75); assert_eq!(t.duration(), Duration::from_secs_f32(2.0)); assert!(!t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.mode(), TimerMode::Repeating); assert_eq!(t.percent(), 0.375); assert_eq!(t.percent_left(), 0.625); // Tick past the end and make sure elapsed wraps t.tick(Duration::from_secs_f32(1.5)); assert_eq!(t.elapsed_secs(), 0.25); assert!(t.finished()); assert!(t.just_finished()); assert_eq!(t.times_finished_this_tick(), 1); assert_eq!(t.percent(), 0.125); assert_eq!(t.percent_left(), 0.875); // Continuing to tick should turn off both finished & just_finished for repeating timers t.tick(Duration::from_secs_f32(1.0)); assert_eq!(t.elapsed_secs(), 1.25); assert!(!t.finished()); assert!(!t.just_finished()); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.percent(), 0.625); assert_eq!(t.percent_left(), 0.375); } #[test] fn times_finished_repeating() { let mut t = Timer::from_seconds(1.0, TimerMode::Repeating); assert_eq!(t.times_finished_this_tick(), 0); t.tick(Duration::from_secs_f32(3.5)); assert_eq!(t.times_finished_this_tick(), 3); assert_eq!(t.elapsed_secs(), 0.5); assert!(t.finished()); assert!(t.just_finished()); t.tick(Duration::from_secs_f32(0.2)); assert_eq!(t.times_finished_this_tick(), 0); } #[test] fn times_finished_this_tick() { let mut t = Timer::from_seconds(1.0, TimerMode::Once); assert_eq!(t.times_finished_this_tick(), 0); t.tick(Duration::from_secs_f32(1.5)); assert_eq!(t.times_finished_this_tick(), 1); t.tick(Duration::from_secs_f32(0.5)); assert_eq!(t.times_finished_this_tick(), 0); } #[test] fn times_finished_this_tick_repeating_zero_duration() { let mut t = Timer::from_seconds(0.0, TimerMode::Repeating); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.elapsed(), Duration::ZERO); assert_eq!(t.percent(), 1.0); t.tick(Duration::from_secs(1)); assert_eq!(t.times_finished_this_tick(), u32::MAX); assert_eq!(t.elapsed(), Duration::ZERO); assert_eq!(t.percent(), 1.0); t.tick(Duration::from_secs(2)); assert_eq!(t.times_finished_this_tick(), u32::MAX); assert_eq!(t.elapsed(), Duration::ZERO); assert_eq!(t.percent(), 1.0); t.reset(); assert_eq!(t.times_finished_this_tick(), 0); assert_eq!(t.elapsed(), Duration::ZERO); assert_eq!(t.percent(), 1.0); } #[test] fn times_finished_this_tick_precise() { let mut t = Timer::from_seconds(0.01, TimerMode::Repeating); let duration = Duration::from_secs_f64(0.333); // total duration: 0.333 => 33 times finished t.tick(duration); assert_eq!(t.times_finished_this_tick(), 33); // total duration: 0.666 => 33 times finished t.tick(duration); assert_eq!(t.times_finished_this_tick(), 33); // total duration: 0.999 => 33 times finished t.tick(duration); assert_eq!(t.times_finished_this_tick(), 33); // total duration: 1.332 => 34 times finished t.tick(duration); assert_eq!(t.times_finished_this_tick(), 34); } #[test] fn paused() { let mut t = Timer::from_seconds(10.0, TimerMode::Once); t.tick(Duration::from_secs_f32(10.0)); assert!(t.just_finished()); assert!(t.finished()); // A paused timer should change just_finished to false after a tick t.pause(); t.tick(Duration::from_secs_f32(5.0)); assert!(!t.just_finished()); assert!(t.finished()); } #[test] fn paused_repeating() { let mut t = Timer::from_seconds(10.0, TimerMode::Repeating); t.tick(Duration::from_secs_f32(10.0)); assert!(t.just_finished()); assert!(t.finished()); // A paused repeating timer should change finished and just_finished to false after a tick t.pause(); t.tick(Duration::from_secs_f32(5.0)); assert!(!t.just_finished()); assert!(!t.finished()); } }