use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TimerMode {
Once,
Repeating,
}
#[derive(Component, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct StepTimer {
elapsed: f32,
dur: f32,
mode: TimerMode,
just_finished: bool,
}
impl Default for StepTimer {
fn default() -> Self {
Self::disarmed()
}
}
impl StepTimer {
pub fn from_seconds(dur: f32, mode: TimerMode) -> Self {
Self {
elapsed: 0.0,
dur: dur.max(0.0),
mode,
just_finished: false,
}
}
pub fn disarmed() -> Self {
Self {
elapsed: 0.0,
dur: 0.0,
mode: TimerMode::Once,
just_finished: false,
}
}
pub fn tick(&mut self, dt: f32) {
self.just_finished = false;
if self.dur <= 0.0 {
return;
}
match self.mode {
TimerMode::Once => {
let was = self.finished();
self.elapsed = (self.elapsed + dt).min(self.dur);
self.just_finished = !was && self.finished();
}
TimerMode::Repeating => {
self.elapsed += dt;
if self.elapsed >= self.dur {
self.elapsed %= self.dur;
self.just_finished = true;
}
}
}
}
pub fn just_finished(&self) -> bool {
self.just_finished
}
pub fn finished(&self) -> bool {
self.elapsed >= self.dur
}
pub fn is_finished(&self) -> bool {
self.finished()
}
pub fn duration(&self) -> f32 {
self.dur
}
pub fn elapsed_secs(&self) -> f32 {
self.elapsed
}
pub fn remaining_secs(&self) -> f32 {
(self.dur - self.elapsed).max(0.0)
}
pub fn fraction(&self) -> f32 {
if self.dur <= 0.0 {
return 1.0;
}
(self.elapsed / self.dur).clamp(0.0, 1.0)
}
pub fn remaining(&self) -> f32 {
self.remaining_secs()
}
pub fn reset(&mut self) {
self.elapsed = 0.0;
self.just_finished = false;
}
pub fn set_duration(&mut self, dur: f32) {
self.dur = dur.max(0.0);
}
}
#[cfg(test)]
mod tests {
use super::*;
const DT: f32 = 1.0 / 30.0;
#[test]
fn once_fires_exactly_once_and_stays_finished() {
let mut t = StepTimer::from_seconds(3.0 * DT, TimerMode::Once);
let mut fires = 0;
for _ in 0..2 {
t.tick(DT);
assert!(!t.just_finished());
}
for _ in 0..7 {
t.tick(DT);
fires += t.just_finished() as u32;
}
assert_eq!(fires, 1);
assert!(t.finished());
}
#[test]
fn disarmed_and_zero_duration_never_fire() {
let mut d = StepTimer::disarmed();
d.tick(DT);
assert!(!d.just_finished());
assert!(d.finished());
assert_eq!(d.fraction(), 1.0);
let mut z = StepTimer::from_seconds(0.0, TimerMode::Once);
for _ in 0..10 {
z.tick(DT);
assert!(!z.just_finished());
}
}
#[test]
fn repeating_waits_the_period_then_fires() {
let mut t = StepTimer::from_seconds(4.0 * DT, TimerMode::Repeating);
let mut fires = 0;
for i in 0..6 {
t.tick(DT);
if i < 3 {
assert!(!t.just_finished());
}
fires += t.just_finished() as u32;
}
assert_eq!(fires, 1);
}
#[test]
fn fraction_and_remaining_track_progress() {
let mut t = StepTimer::from_seconds(4.0 * DT, TimerMode::Once);
t.tick(DT);
assert!((t.fraction() - 0.25).abs() < 1e-6);
assert!((t.remaining_secs() - 3.0 * DT).abs() < 1e-6);
t.tick(10.0 * DT);
assert_eq!(t.fraction(), 1.0);
assert_eq!(t.remaining_secs(), 0.0);
}
#[test]
fn reset_rearms() {
let mut t = StepTimer::from_seconds(2.0 * DT, TimerMode::Once);
for _ in 0..4 {
t.tick(DT);
}
assert!(t.finished());
t.reset();
assert!(!t.finished());
let mut fires = 0;
for _ in 0..4 {
t.tick(DT);
fires += t.just_finished() as u32;
}
assert_eq!(fires, 1);
}
}