1#![no_std]
32
33use core::fmt;
34use core::hash::{Hash, Hasher};
35use core::time::Duration;
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40#[cfg_attr(
41 feature = "rkyv",
42 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
43)]
44pub struct TimerData {
45 pub duration: u64,
47 pub elapsed: u64,
49 pub finished: bool,
51 pub mode: TimerModeData,
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
57#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58#[cfg_attr(
59 feature = "rkyv",
60 derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
61)]
62pub enum TimerModeData {
63 #[default]
64 Once,
65 Repeating,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Hash)]
70pub enum TimerDataError {
71 ElapsedExceedsDuration {
73 elapsed: u64,
74 duration: u64,
75 },
76}
77
78impl fmt::Display for TimerDataError {
79 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80 match self {
81 Self::ElapsedExceedsDuration { elapsed, duration } => {
82 write!(
83 f,
84 "elapsed ({elapsed} ns) exceeds duration ({duration} ns) on a Once timer"
85 )
86 }
87 }
88 }
89}
90
91impl TimerData {
92 pub fn new(duration_nanos: u64, elapsed_nanos: u64, finished: bool, mode: TimerModeData) -> Self {
94 Self {
95 duration: duration_nanos,
96 elapsed: elapsed_nanos,
97 finished,
98 mode,
99 }
100 }
101
102 pub fn from_secs(secs: f32, mode: TimerModeData) -> Self {
104 Self {
105 duration: (secs as f64 * 1_000_000_000.0) as u64,
106 elapsed: 0,
107 finished: false,
108 mode,
109 }
110 }
111
112 pub fn from_duration(duration: Duration, mode: TimerModeData) -> Self {
114 Self {
115 duration: duration.as_nanos() as u64,
116 elapsed: 0,
117 finished: false,
118 mode,
119 }
120 }
121
122 pub fn validate(&self) -> Result<(), TimerDataError> {
127 if self.mode == TimerModeData::Once && self.elapsed > self.duration {
128 return Err(TimerDataError::ElapsedExceedsDuration {
129 elapsed: self.elapsed,
130 duration: self.duration,
131 });
132 }
133 Ok(())
134 }
135
136 pub fn duration(&self) -> Duration {
138 Duration::from_nanos(self.duration)
139 }
140
141 pub fn elapsed(&self) -> Duration {
143 Duration::from_nanos(self.elapsed)
144 }
145
146 pub fn remaining(&self) -> Duration {
148 Duration::from_nanos(self.duration.saturating_sub(self.elapsed))
149 }
150
151 pub fn is_finished(&self) -> bool {
153 self.finished
154 }
155
156 pub fn fraction(&self) -> f32 {
160 if self.duration == 0 {
161 return 0.0;
162 }
163 (self.elapsed as f64 / self.duration as f64) as f32
164 }
165
166 pub fn fraction_remaining(&self) -> f32 {
170 if self.duration == 0 {
171 return 0.0;
172 }
173 1.0 - self.fraction()
174 }
175
176 pub fn hash_hex(&self) -> HashHex {
178 let mut hasher = FnvHasher::default();
179 self.hash(&mut hasher);
180 HashHex(hasher.finish())
181 }
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub struct HashHex(pub u64);
187
188impl fmt::Display for HashHex {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 write!(f, "{:016x}", self.0)
191 }
192}
193
194impl fmt::Display for TimerData {
195 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196 let dur_ms = self.duration as f64 / 1_000_000.0;
197 let elapsed_ms = self.elapsed as f64 / 1_000_000.0;
198 write!(
199 f,
200 "{elapsed_ms:.1}/{dur_ms:.1}ms ({}, {}) [{}]",
201 self.mode,
202 if self.finished { "finished" } else { "running" },
203 self.hash_hex(),
204 )
205 }
206}
207
208#[derive(Clone)]
210struct FnvHasher(u64);
211
212impl Default for FnvHasher {
213 fn default() -> Self {
214 Self(0xcbf29ce484222325)
215 }
216}
217
218impl Hasher for FnvHasher {
219 fn write(&mut self, bytes: &[u8]) {
220 for &b in bytes {
221 self.0 ^= b as u64;
222 self.0 = self.0.wrapping_mul(0x100000001b3);
223 }
224 }
225
226 fn finish(&self) -> u64 {
227 self.0
228 }
229}
230
231impl fmt::Display for TimerModeData {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 match self {
234 Self::Once => write!(f, "Once"),
235 Self::Repeating => write!(f, "Repeating"),
236 }
237 }
238}
239
240#[cfg(feature = "bevy")]
245mod bevy_impls {
246 use super::*;
247 use bevy_time::{Timer, TimerMode};
248
249 impl From<&Timer> for TimerData {
250 fn from(timer: &Timer) -> Self {
251 Self {
252 duration: timer.duration().as_nanos() as u64,
253 elapsed: timer.elapsed().as_nanos() as u64,
254 finished: timer.is_finished(),
255 mode: timer.mode().into(),
256 }
257 }
258 }
259
260 impl From<Timer> for TimerData {
261 fn from(timer: Timer) -> Self {
262 Self::from(&timer)
263 }
264 }
265
266 impl From<TimerData> for Timer {
267 fn from(data: TimerData) -> Self {
268 let mut timer = Timer::new(Duration::from_nanos(data.duration), data.mode.into());
269 timer.tick(Duration::from_nanos(data.elapsed));
270 timer
271 }
272 }
273
274 impl From<TimerMode> for TimerModeData {
275 fn from(mode: TimerMode) -> Self {
276 match mode {
277 TimerMode::Once => Self::Once,
278 TimerMode::Repeating => Self::Repeating,
279 }
280 }
281 }
282
283 impl From<TimerModeData> for TimerMode {
284 fn from(data: TimerModeData) -> Self {
285 match data {
286 TimerModeData::Once => Self::Once,
287 TimerModeData::Repeating => Self::Repeating,
288 }
289 }
290 }
291}
292
293#[cfg(all(feature = "bevy", feature = "serde"))]
301pub fn serialize<S>(timer: &bevy_time::Timer, serializer: S) -> Result<S::Ok, S::Error>
302where
303 S: serde::Serializer,
304{
305 serde::Serialize::serialize(&TimerData::from(timer), serializer)
306}
307
308#[cfg(all(feature = "bevy", feature = "serde"))]
310pub fn deserialize<'de, D>(deserializer: D) -> Result<bevy_time::Timer, D::Error>
311where
312 D: serde::Deserializer<'de>,
313{
314 <TimerData as serde::Deserialize>::deserialize(deserializer).map(bevy_time::Timer::from)
315}
316
317#[cfg(test)]
318extern crate alloc;
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[cfg(feature = "bevy")]
325 #[test]
326 fn test_timer_data_roundtrip() {
327 use bevy_time::{Timer, TimerMode};
328
329 let timer = Timer::from_seconds(5.0, TimerMode::Once);
330 let data = TimerData::from(&timer);
331 let restored = Timer::from(data);
332 assert_eq!(timer.duration(), restored.duration());
333 assert_eq!(timer.mode(), restored.mode());
334 }
335
336 #[cfg(all(feature = "bevy", feature = "serde"))]
337 #[test]
338 fn test_serde_with_roundtrip() {
339 use bevy_time::{Timer, TimerMode};
340 use serde::{Deserialize, Serialize};
341
342 #[derive(Serialize, Deserialize)]
343 struct TestStruct {
344 #[serde(with = "crate")]
345 timer: Timer,
346 }
347
348 let original = TestStruct {
349 timer: Timer::from_seconds(5.0, TimerMode::Once),
350 };
351
352 let json = serde_json::to_string(&original).unwrap();
353 let restored: TestStruct = serde_json::from_str(&json).unwrap();
354
355 assert_eq!(original.timer.duration(), restored.timer.duration());
356 assert_eq!(original.timer.mode(), restored.timer.mode());
357 }
358
359 #[test]
360 fn test_timer_data_new() {
361 let data = TimerData::new(5_000_000_000, 2_500_000_000, false, TimerModeData::Once);
362 assert_eq!(data.duration(), Duration::from_secs(5));
363 assert_eq!(data.elapsed(), Duration::from_millis(2500));
364 assert!(!data.finished);
365 assert_eq!(data.mode, TimerModeData::Once);
366 }
367
368 #[test]
369 fn test_from_secs() {
370 let data = TimerData::from_secs(3.0, TimerModeData::Repeating);
371 assert_eq!(data.duration(), Duration::from_secs(3));
372 assert_eq!(data.elapsed(), Duration::ZERO);
373 assert!(!data.finished);
374 assert_eq!(data.mode, TimerModeData::Repeating);
375 }
376
377 #[test]
378 fn test_from_duration() {
379 let data = TimerData::from_duration(Duration::from_millis(500), TimerModeData::Once);
380 assert_eq!(data.duration(), Duration::from_millis(500));
381 assert_eq!(data.elapsed(), Duration::ZERO);
382 }
383
384 #[test]
385 fn test_fraction() {
386 let data = TimerData::new(4_000_000_000, 1_000_000_000, false, TimerModeData::Once);
387 assert!((data.fraction() - 0.25).abs() < f32::EPSILON);
388 assert!((data.fraction_remaining() - 0.75).abs() < f32::EPSILON);
389 }
390
391 #[test]
392 fn test_fraction_zero_duration() {
393 let data = TimerData::new(0, 0, false, TimerModeData::Once);
394 assert_eq!(data.fraction(), 0.0);
395 assert_eq!(data.fraction_remaining(), 0.0);
396 }
397
398 #[test]
399 fn test_remaining() {
400 let data = TimerData::new(5_000_000_000, 3_000_000_000, false, TimerModeData::Once);
401 assert_eq!(data.remaining(), Duration::from_secs(2));
402 }
403
404 #[test]
405 fn test_remaining_saturates() {
406 let data = TimerData::new(1_000_000_000, 5_000_000_000, true, TimerModeData::Repeating);
407 assert_eq!(data.remaining(), Duration::ZERO);
408 }
409
410 #[test]
411 fn test_validate_ok() {
412 let data = TimerData::new(5_000_000_000, 3_000_000_000, false, TimerModeData::Once);
413 assert!(data.validate().is_ok());
414 }
415
416 #[test]
417 fn test_validate_elapsed_exceeds_duration() {
418 let data = TimerData::new(1_000_000_000, 5_000_000_000, false, TimerModeData::Once);
419 assert!(data.validate().is_err());
420 }
421
422 #[test]
423 fn test_validate_repeating_allows_overflow() {
424 let data = TimerData::new(1_000_000_000, 5_000_000_000, false, TimerModeData::Repeating);
425 assert!(data.validate().is_ok());
426 }
427
428 #[test]
429 fn test_display_contains_hash() {
430 let data = TimerData::new(5_000_000_000, 2_500_000_000, false, TimerModeData::Once);
431 let s = alloc::format!("{data}");
432 assert!(s.contains("Once"));
433 assert!(s.contains("running"));
434 assert!(s.contains('['));
436 assert!(s.contains(']'));
437 }
438
439 #[test]
440 fn test_hash_hex_deterministic() {
441 let data = TimerData::new(5_000_000_000, 2_500_000_000, false, TimerModeData::Once);
442 assert_eq!(data.hash_hex(), data.hash_hex());
443 }
444
445 #[test]
446 fn test_hash_hex_differs() {
447 let a = TimerData::new(5_000_000_000, 0, false, TimerModeData::Once);
448 let b = TimerData::new(5_000_000_000, 1, false, TimerModeData::Once);
449 assert_ne!(a.hash_hex(), b.hash_hex());
450 }
451
452 #[test]
453 fn test_hash_hex_format() {
454 let data = TimerData::new(1, 0, false, TimerModeData::Once);
455 let hex = alloc::format!("{}", data.hash_hex());
456 assert_eq!(hex.len(), 16);
457 assert!(hex.chars().all(|c| c.is_ascii_hexdigit()));
458 }
459
460 #[test]
461 fn test_timer_mode_default() {
462 assert_eq!(TimerModeData::default(), TimerModeData::Once);
463 }
464}