Thanks to visit codestin.com
Credit goes to docs.rs

Skip to main content

timer_data/
lib.rs

1//! Serializer-independent data types for Bevy's Timer.
2//!
3//! Bevy's `Timer` doesn't implement common serialization traits.
4//! This crate provides [`TimerData`] and [`TimerModeData`] as intermediate
5//! representations with optional `From`/`Into` conversions (behind the `bevy`
6//! feature), plus optional serde and rkyv derives behind feature flags.
7//!
8//! # Features
9//!
10//! - **`bevy`** (default): Enables `bevy_time` dependency and `From`/`Into` conversions
11//!   between `Timer`/`TimerMode` and `TimerData`/`TimerModeData`.
12//! - **`serde`** (default): Enables `Serialize`/`Deserialize` derives.
13//!   With `bevy`, also provides `#[serde(with = "timer_data")]` helpers.
14//! - **`rkyv`**: Enables `Archive`/`Serialize`/`Deserialize` derives (rkyv 0.8).
15//!
16//! # Serde usage
17//!
18//! With the `bevy` + `serde` features, use `#[serde(with = "timer_data")]`:
19//!
20//! ```ignore
21//! use serde::{Serialize, Deserialize};
22//! use bevy_time::Timer;
23//!
24//! #[derive(Serialize, Deserialize)]
25//! pub struct MyComponent {
26//!     #[serde(with = "timer_data")]
27//!     pub timer: Timer,
28//! }
29//! ```
30
31#![no_std]
32
33use core::fmt;
34use core::hash::{Hash, Hasher};
35use core::time::Duration;
36
37/// Intermediate data representation of a Bevy `Timer`.
38#[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    /// Duration in nanoseconds
46    pub duration: u64,
47    /// Elapsed time in nanoseconds
48    pub elapsed: u64,
49    /// Whether the timer has finished
50    pub finished: bool,
51    /// Timer mode (once or repeating)
52    pub mode: TimerModeData,
53}
54
55/// Intermediate data representation of Bevy's `TimerMode`.
56#[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/// Error returned when [`TimerData`] contains invalid state.
69#[derive(Debug, Clone, PartialEq, Eq, Hash)]
70pub enum TimerDataError {
71    /// Elapsed time exceeds duration on a non-repeating timer.
72    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    /// Create a new `TimerData`.
93    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    /// Create a `TimerData` from seconds (like Bevy's `Timer::from_seconds`).
103    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    /// Create a `TimerData` from a [`Duration`].
113    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    /// Validate that this `TimerData` is in a consistent state.
123    ///
124    /// Returns an error if elapsed exceeds duration on a `Once` timer.
125    /// Repeating timers may have elapsed > duration (they wrap around).
126    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    /// Duration as a [`Duration`].
137    pub fn duration(&self) -> Duration {
138        Duration::from_nanos(self.duration)
139    }
140
141    /// Elapsed time as a [`Duration`].
142    pub fn elapsed(&self) -> Duration {
143        Duration::from_nanos(self.elapsed)
144    }
145
146    /// Remaining time as a [`Duration`]. Saturates at zero.
147    pub fn remaining(&self) -> Duration {
148        Duration::from_nanos(self.duration.saturating_sub(self.elapsed))
149    }
150
151    /// Whether the timer has finished.
152    pub fn is_finished(&self) -> bool {
153        self.finished
154    }
155
156    /// Fraction of the timer elapsed, from 0.0 to 1.0.
157    ///
158    /// Returns 0.0 if duration is zero.
159    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    /// Fraction of the timer remaining, from 1.0 to 0.0.
167    ///
168    /// Returns 0.0 if duration is zero.
169    pub fn fraction_remaining(&self) -> f32 {
170        if self.duration == 0 {
171            return 0.0;
172        }
173        1.0 - self.fraction()
174    }
175
176    /// Compute a 64-bit hash and return it as a hex string (16 chars).
177    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/// A 64-bit hash displayed as lowercase hex. Returned by [`TimerData::hash_hex`].
185#[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/// FNV-1a 64-bit hasher (no_std compatible).
209#[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// ============================================================================
241// Bevy conversions (requires `bevy` feature)
242// ============================================================================
243
244#[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// ============================================================================
294// Serde helper (for #[serde(with = "...")]) — requires bevy + serde
295// ============================================================================
296
297/// Serialize a `Timer` via serde.
298///
299/// Use with `#[serde(with = "timer_data")]` on a `Timer` field.
300#[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/// Deserialize a `Timer` via serde.
309#[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        // Display includes a 16-char hex hash in brackets
435        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}