1use bevy_ecs::prelude::*;
11use serde::{Deserialize, Serialize};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
15pub enum TimerMode {
16 Once,
17 Repeating,
18}
19
20#[derive(Component, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
22pub struct StepTimer {
23 elapsed: f32,
24 dur: f32,
25 mode: TimerMode,
26 just_finished: bool,
27}
28
29impl Default for StepTimer {
30 fn default() -> Self {
32 Self::disarmed()
33 }
34}
35
36impl StepTimer {
37 pub fn from_seconds(dur: f32, mode: TimerMode) -> Self {
38 Self {
39 elapsed: 0.0,
40 dur: dur.max(0.0),
41 mode,
42 just_finished: false,
43 }
44 }
45
46 pub fn disarmed() -> Self {
48 Self {
49 elapsed: 0.0,
50 dur: 0.0,
51 mode: TimerMode::Once,
52 just_finished: false,
53 }
54 }
55
56 pub fn tick(&mut self, dt: f32) {
59 self.just_finished = false;
60 if self.dur <= 0.0 {
61 return;
62 }
63 match self.mode {
64 TimerMode::Once => {
65 let was = self.finished();
66 self.elapsed = (self.elapsed + dt).min(self.dur);
67 self.just_finished = !was && self.finished();
68 }
69 TimerMode::Repeating => {
70 self.elapsed += dt;
71 if self.elapsed >= self.dur {
72 self.elapsed %= self.dur;
73 self.just_finished = true;
74 }
75 }
76 }
77 }
78
79 pub fn just_finished(&self) -> bool {
81 self.just_finished
82 }
83
84 pub fn finished(&self) -> bool {
86 self.elapsed >= self.dur
87 }
88
89 pub fn is_finished(&self) -> bool {
91 self.finished()
92 }
93
94 pub fn duration(&self) -> f32 {
95 self.dur
96 }
97
98 pub fn elapsed_secs(&self) -> f32 {
99 self.elapsed
100 }
101
102 pub fn remaining_secs(&self) -> f32 {
103 (self.dur - self.elapsed).max(0.0)
104 }
105
106 pub fn fraction(&self) -> f32 {
108 if self.dur <= 0.0 {
109 return 1.0;
110 }
111 (self.elapsed / self.dur).clamp(0.0, 1.0)
112 }
113
114 pub fn remaining(&self) -> f32 {
116 self.remaining_secs()
117 }
118
119 pub fn reset(&mut self) {
121 self.elapsed = 0.0;
122 self.just_finished = false;
123 }
124
125 pub fn set_duration(&mut self, dur: f32) {
127 self.dur = dur.max(0.0);
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134
135 const DT: f32 = 1.0 / 30.0;
136
137 #[test]
138 fn once_fires_exactly_once_and_stays_finished() {
139 let mut t = StepTimer::from_seconds(3.0 * DT, TimerMode::Once);
140 let mut fires = 0;
141 for _ in 0..2 {
142 t.tick(DT);
143 assert!(!t.just_finished());
144 }
145 for _ in 0..7 {
146 t.tick(DT);
147 fires += t.just_finished() as u32;
148 }
149 assert_eq!(fires, 1);
150 assert!(t.finished());
151 }
152
153 #[test]
154 fn disarmed_and_zero_duration_never_fire() {
155 let mut d = StepTimer::disarmed();
156 d.tick(DT);
157 assert!(!d.just_finished());
158 assert!(d.finished());
159 assert_eq!(d.fraction(), 1.0);
160
161 let mut z = StepTimer::from_seconds(0.0, TimerMode::Once);
162 for _ in 0..10 {
163 z.tick(DT);
164 assert!(!z.just_finished());
165 }
166 }
167
168 #[test]
169 fn repeating_waits_the_period_then_fires() {
170 let mut t = StepTimer::from_seconds(4.0 * DT, TimerMode::Repeating);
171 let mut fires = 0;
172 for i in 0..6 {
173 t.tick(DT);
174 if i < 3 {
175 assert!(!t.just_finished());
176 }
177 fires += t.just_finished() as u32;
178 }
179 assert_eq!(fires, 1);
180 }
181
182 #[test]
183 fn fraction_and_remaining_track_progress() {
184 let mut t = StepTimer::from_seconds(4.0 * DT, TimerMode::Once);
185 t.tick(DT);
186 assert!((t.fraction() - 0.25).abs() < 1e-6);
187 assert!((t.remaining_secs() - 3.0 * DT).abs() < 1e-6);
188 t.tick(10.0 * DT);
189 assert_eq!(t.fraction(), 1.0);
190 assert_eq!(t.remaining_secs(), 0.0);
191 }
192
193 #[test]
194 fn reset_rearms() {
195 let mut t = StepTimer::from_seconds(2.0 * DT, TimerMode::Once);
196 for _ in 0..4 {
197 t.tick(DT);
198 }
199 assert!(t.finished());
200 t.reset();
201 assert!(!t.finished());
202 let mut fires = 0;
203 for _ in 0..4 {
204 t.tick(DT);
205 fires += t.just_finished() as u32;
206 }
207 assert_eq!(fires, 1);
208 }
209}