use core::time::Duration;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TimerMode {
#[default]
Once,
Repeating,
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Stopwatch {
elapsed: f32,
paused: bool,
}
impl Stopwatch {
pub const fn new() -> Self {
Self {
elapsed: 0.0,
paused: false,
}
}
#[inline]
pub fn tick(&mut self, dt: f32) -> &mut Self {
if !self.paused {
self.elapsed += dt;
}
self
}
#[inline]
pub fn tick_duration(&mut self, duration: Duration) -> &mut Self {
self.tick(duration.as_secs_f32())
}
#[inline]
pub fn elapsed_secs(&self) -> f32 {
self.elapsed
}
#[inline]
pub fn elapsed(&self) -> Duration {
Duration::from_secs_f32(self.elapsed.max(0.0))
}
#[inline]
pub fn set_elapsed(&mut self, time: f32) {
self.elapsed = time;
}
#[inline]
pub fn reset(&mut self) {
self.elapsed = 0.0;
}
#[inline]
pub fn pause(&mut self) {
self.paused = true;
}
#[inline]
pub fn unpause(&mut self) {
self.paused = false;
}
#[inline]
pub fn is_paused(&self) -> bool {
self.paused
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Timer {
stopwatch: Stopwatch,
duration: f32,
mode: TimerMode,
just_finished: bool,
}
impl Default for Timer {
fn default() -> Self {
Self::from_seconds(1.0, TimerMode::Once)
}
}
impl Timer {
pub const fn from_seconds(duration: f32, mode: TimerMode) -> Self {
Self {
stopwatch: Stopwatch::new(),
duration: if duration > 0.0 { duration } else { 0.0 },
mode,
just_finished: false,
}
}
pub fn new(duration: Duration, mode: TimerMode) -> Self {
Self::from_seconds(duration.as_secs_f32(), mode)
}
pub fn tick(&mut self, dt: f32) -> &mut Self {
if self.stopwatch.is_paused() {
self.just_finished = false;
return self;
}
if self.mode == TimerMode::Once && self.is_finished() {
self.just_finished = false;
return self;
}
self.stopwatch.tick(dt);
if self.stopwatch.elapsed_secs() >= self.duration {
self.just_finished = true;
match self.mode {
TimerMode::Once => {
self.stopwatch.set_elapsed(self.duration);
}
TimerMode::Repeating => {
if self.duration > 0.0 {
let rem = self.stopwatch.elapsed_secs() % self.duration;
self.stopwatch.set_elapsed(rem);
} else {
self.stopwatch.set_elapsed(0.0);
}
}
}
} else {
self.just_finished = false;
}
self
}
#[inline]
pub fn tick_duration(&mut self, duration: Duration) -> &mut Self {
self.tick(duration.as_secs_f32())
}
#[inline]
pub fn is_finished(&self) -> bool {
self.stopwatch.elapsed_secs() >= self.duration
}
#[inline]
pub fn just_finished(&self) -> bool {
self.just_finished
}
#[inline]
pub fn fraction(&self) -> f32 {
if self.duration <= 0.0 {
1.0
} else {
(self.stopwatch.elapsed_secs() / self.duration).clamp(0.0, 1.0)
}
}
#[inline]
pub fn remaining_secs(&self) -> f32 {
(self.duration - self.stopwatch.elapsed_secs()).max(0.0)
}
#[inline]
pub fn reset(&mut self) {
self.stopwatch.reset();
self.just_finished = false;
}
#[inline]
pub fn pause(&mut self) {
self.stopwatch.pause();
}
#[inline]
pub fn unpause(&mut self) {
self.stopwatch.unpause();
}
#[inline]
pub fn is_paused(&self) -> bool {
self.stopwatch.is_paused()
}
#[inline]
pub fn elapsed_secs(&self) -> f32 {
self.stopwatch.elapsed_secs()
}
#[inline]
pub fn duration_secs(&self) -> f32 {
self.duration
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FixedTimestep {
step: f32,
accumulator: f32,
max_substeps: u32,
}
impl FixedTimestep {
pub const fn from_hz(hz: f32, max_substeps: u32) -> Self {
let step = if hz > 0.0 { 1.0 / hz } else { 1.0 / 60.0 };
Self {
step,
accumulator: 0.0,
max_substeps,
}
}
pub const fn from_step(step: f32, max_substeps: u32) -> Self {
Self {
step: if step > 0.0 { step } else { 0.01666667 },
accumulator: 0.0,
max_substeps,
}
}
pub fn update(&mut self, frame_dt: f32) -> FixedStepIter {
self.accumulator += frame_dt;
let mut steps = 0;
while self.accumulator >= self.step && steps < self.max_substeps {
self.accumulator -= self.step;
steps += 1;
}
if self.accumulator >= self.step {
self.accumulator = 0.0;
}
FixedStepIter {
step: self.step,
remaining: steps,
}
}
#[inline]
pub fn alpha(&self) -> f32 {
(self.accumulator / self.step).clamp(0.0, 1.0)
}
#[inline]
pub fn step_secs(&self) -> f32 {
self.step
}
}
pub struct FixedStepIter {
step: f32,
remaining: u32,
}
impl Iterator for FixedStepIter {
type Item = f32;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.remaining > 0 {
self.remaining -= 1;
Some(self.step)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stopwatch() {
let mut sw = Stopwatch::new();
assert_eq!(sw.elapsed_secs(), 0.0);
sw.tick(0.5);
assert_eq!(sw.elapsed_secs(), 0.5);
sw.pause();
sw.tick(0.5);
assert_eq!(sw.elapsed_secs(), 0.5);
sw.unpause();
sw.tick(0.5);
assert_eq!(sw.elapsed_secs(), 1.0);
sw.reset();
assert_eq!(sw.elapsed_secs(), 0.0);
}
#[test]
fn test_timer_once() {
let mut timer = Timer::from_seconds(1.0, TimerMode::Once);
assert!(!timer.is_finished());
assert!(!timer.just_finished());
assert_eq!(timer.fraction(), 0.0);
timer.tick(0.5);
assert!(!timer.is_finished());
assert_eq!(timer.fraction(), 0.5);
assert_eq!(timer.remaining_secs(), 0.5);
timer.tick(0.5);
assert!(timer.is_finished());
assert!(timer.just_finished());
assert_eq!(timer.fraction(), 1.0);
timer.tick(0.5);
assert!(timer.is_finished());
assert!(!timer.just_finished());
}
#[test]
fn test_timer_repeating() {
let mut timer = Timer::from_seconds(1.0, TimerMode::Repeating);
timer.tick(0.7);
assert!(!timer.just_finished());
timer.tick(0.5); assert!(timer.just_finished());
assert!((timer.elapsed_secs() - 0.2).abs() < 1e-5);
}
#[test]
fn test_fixed_timestep() {
let mut fixed = FixedTimestep::from_hz(60.0, 4); let step = fixed.step_secs();
let count = fixed.update(step * 1.5).count();
assert_eq!(count, 1);
assert!(fixed.alpha() > 0.4 && fixed.alpha() < 0.6);
let count2 = fixed.update(step * 0.5).count();
assert_eq!(count2, 1);
}
#[test]
fn test_stopwatch_duration_controls_and_pause() {
let mut sw = Stopwatch::new();
sw.tick_duration(Duration::from_millis(250));
assert!((sw.elapsed_secs() - 0.25).abs() < 1e-4);
assert_eq!(sw.elapsed(), Duration::from_secs_f32(0.25));
sw.set_elapsed(3.0);
assert_eq!(sw.elapsed_secs(), 3.0);
sw.pause();
assert!(sw.is_paused());
sw.tick(1.0);
assert_eq!(sw.elapsed_secs(), 3.0);
sw.unpause();
assert!(!sw.is_paused());
sw.reset();
assert_eq!(sw.elapsed_secs(), 0.0);
}
#[test]
fn test_timer_pause_duration_and_reset_paths() {
let mut timer = Timer::new(Duration::from_millis(500), TimerMode::Repeating);
assert!((timer.duration_secs() - 0.5).abs() < 1e-4);
timer.tick(0.1);
assert!((timer.elapsed_secs() - 0.1).abs() < 1e-4);
timer.pause();
assert!(timer.is_paused());
timer.tick(0.2);
assert!((timer.elapsed_secs() - 0.1).abs() < 1e-4);
timer.unpause();
timer.tick_duration(Duration::from_millis(500));
assert!(timer.just_finished());
timer.reset();
assert!(!timer.is_finished());
assert_eq!(timer.elapsed_secs(), 0.0);
let _ = Timer::default();
}
}