1use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
15pub enum TimerMode {
16 #[default]
18 Once,
19 Repeating,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
27pub struct Timer {
28 duration: f32,
29 elapsed: f32,
30 mode: TimerMode,
31 paused: bool,
32 finished: bool,
33 times_finished_this_tick: u32,
34}
35
36impl Timer {
37 pub fn new(duration: f32, mode: TimerMode) -> Self {
39 Self {
40 duration: duration.max(0.0),
41 mode,
42 ..Default::default()
43 }
44 }
45
46 pub fn once(duration: f32) -> Self {
48 Self::new(duration, TimerMode::Once)
49 }
50
51 pub fn repeating(duration: f32) -> Self {
53 Self::new(duration, TimerMode::Repeating)
54 }
55
56 pub fn tick(&mut self, delta: f32) -> &mut Self {
61 if self.paused {
62 self.times_finished_this_tick = 0;
63 return self;
64 }
65
66 if self.mode == TimerMode::Once && self.finished {
67 self.times_finished_this_tick = 0;
68 return self;
69 }
70
71 self.elapsed += delta;
72
73 if self.duration <= 0.0 {
74 self.finished = true;
75 self.times_finished_this_tick = 1;
76 self.elapsed = 0.0;
77 return self;
78 }
79
80 if self.elapsed >= self.duration {
81 self.finished = true;
82 match self.mode {
83 TimerMode::Repeating => {
84 self.times_finished_this_tick = (self.elapsed / self.duration) as u32;
85 self.elapsed %= self.duration;
86 }
87 TimerMode::Once => {
88 self.times_finished_this_tick = 1;
89 self.elapsed = self.duration;
90 }
91 }
92 } else {
93 self.finished = false;
94 self.times_finished_this_tick = 0;
95 }
96
97 self
98 }
99
100 pub fn finished(&self) -> bool {
103 match self.mode {
104 TimerMode::Once => self.finished,
105 TimerMode::Repeating => self.times_finished_this_tick > 0,
106 }
107 }
108
109 pub fn just_finished(&self) -> bool {
111 self.times_finished_this_tick > 0
112 }
113
114 pub fn times_finished_this_tick(&self) -> u32 {
117 self.times_finished_this_tick
118 }
119
120 pub fn elapsed(&self) -> f32 {
122 self.elapsed
123 }
124
125 pub fn remaining(&self) -> f32 {
127 (self.duration - self.elapsed).max(0.0)
128 }
129
130 pub fn duration(&self) -> f32 {
132 self.duration
133 }
134
135 pub fn fraction(&self) -> f32 {
137 if self.duration <= 0.0 {
138 1.0
139 } else {
140 (self.elapsed / self.duration).clamp(0.0, 1.0)
141 }
142 }
143
144 pub fn fraction_remaining(&self) -> f32 {
147 1.0 - self.fraction()
148 }
149
150 pub fn mode(&self) -> TimerMode {
152 self.mode
153 }
154
155 pub fn paused(&self) -> bool {
157 self.paused
158 }
159
160 pub fn set_duration(&mut self, duration: f32) {
162 self.duration = duration.max(0.0);
163 }
164
165 pub fn set_elapsed(&mut self, elapsed: f32) {
167 self.elapsed = elapsed.max(0.0);
168 }
169
170 pub fn set_mode(&mut self, mode: TimerMode) {
172 self.mode = mode;
173 }
174
175 pub fn set_paused(&mut self, paused: bool) {
177 self.paused = paused;
178 }
179
180 pub fn pause(&mut self) {
182 self.paused = true;
183 }
184
185 pub fn unpause(&mut self) {
187 self.paused = false;
188 }
189
190 pub fn reset(&mut self) {
192 self.elapsed = 0.0;
193 self.finished = false;
194 self.times_finished_this_tick = 0;
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
202pub struct Stopwatch {
203 elapsed: f32,
204 paused: bool,
205}
206
207impl Stopwatch {
208 pub fn new() -> Self {
210 Self::default()
211 }
212
213 pub fn tick(&mut self, delta: f32) -> &mut Self {
216 if !self.paused {
217 self.elapsed += delta;
218 }
219 self
220 }
221
222 pub fn elapsed(&self) -> f32 {
224 self.elapsed
225 }
226
227 pub fn paused(&self) -> bool {
229 self.paused
230 }
231
232 pub fn set_elapsed(&mut self, elapsed: f32) {
234 self.elapsed = elapsed.max(0.0);
235 }
236
237 pub fn set_paused(&mut self, paused: bool) {
239 self.paused = paused;
240 }
241
242 pub fn pause(&mut self) {
244 self.paused = true;
245 }
246
247 pub fn unpause(&mut self) {
249 self.paused = false;
250 }
251
252 pub fn reset(&mut self) {
254 self.elapsed = 0.0;
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 #[test]
263 fn once_latches_finished_and_clamps_elapsed() {
264 let mut timer = Timer::once(1.0);
265 timer.tick(0.6);
266 assert!(!timer.finished());
267 timer.tick(0.6);
268 assert!(timer.finished());
269 assert!(timer.just_finished());
270 assert_eq!(timer.elapsed(), 1.0);
271 timer.tick(5.0);
272 assert!(timer.finished());
273 assert!(!timer.just_finished());
274 assert_eq!(timer.elapsed(), 1.0);
275 }
276
277 #[test]
278 fn repeating_carries_remainder() {
279 let mut timer = Timer::repeating(1.0);
280 timer.tick(1.5);
281 assert!(timer.just_finished());
282 assert_eq!(timer.times_finished_this_tick(), 1);
283 assert!((timer.elapsed() - 0.5).abs() < f32::EPSILON);
284 }
285
286 #[test]
287 fn repeating_counts_multiple_cycles_in_one_tick() {
288 let mut timer = Timer::repeating(0.5);
289 timer.tick(1.6);
290 assert_eq!(timer.times_finished_this_tick(), 3);
291 assert!((timer.elapsed() - 0.1).abs() < 1e-6);
292 }
293
294 #[test]
295 fn paused_timer_ignores_ticks() {
296 let mut timer = Timer::once(1.0);
297 timer.pause();
298 timer.tick(2.0);
299 assert!(!timer.finished());
300 assert_eq!(timer.elapsed(), 0.0);
301 timer.unpause();
302 timer.tick(2.0);
303 assert!(timer.finished());
304 }
305
306 #[test]
307 fn fraction_reports_progress() {
308 let mut timer = Timer::once(4.0);
309 timer.tick(1.0);
310 assert!((timer.fraction() - 0.25).abs() < f32::EPSILON);
311 assert!((timer.fraction_remaining() - 0.75).abs() < f32::EPSILON);
312 assert!((timer.remaining() - 3.0).abs() < f32::EPSILON);
313 }
314
315 #[test]
316 fn reset_clears_progress() {
317 let mut timer = Timer::once(1.0);
318 timer.tick(1.0);
319 timer.reset();
320 assert!(!timer.finished());
321 assert_eq!(timer.elapsed(), 0.0);
322 }
323
324 #[test]
325 fn stopwatch_accumulates_and_pauses() {
326 let mut stopwatch = Stopwatch::new();
327 stopwatch.tick(0.5);
328 stopwatch.tick(0.5);
329 assert!((stopwatch.elapsed() - 1.0).abs() < f32::EPSILON);
330 stopwatch.pause();
331 stopwatch.tick(1.0);
332 assert!((stopwatch.elapsed() - 1.0).abs() < f32::EPSILON);
333 stopwatch.reset();
334 assert_eq!(stopwatch.elapsed(), 0.0);
335 }
336}