Skip to main content

animato_driver/
clock.rs

1//! Time sources for driving animations.
2
3/// Abstracts any time source that can produce a `dt` (delta-time) value in seconds.
4///
5/// Implement this for custom timers, game-engine time sources, or scroll-based drivers.
6///
7/// # Example
8///
9/// ```rust
10/// use animato_driver::{Clock, MockClock};
11///
12/// let mut clk = MockClock::new(1.0 / 60.0);
13/// assert!((clk.delta() - 1.0 / 60.0).abs() < 1e-6);
14/// ```
15pub trait Clock {
16    /// Returns seconds elapsed since the last call.
17    ///
18    /// The first call returns `0.0` for `WallClock`, or the configured step
19    /// for `MockClock` / `ManualClock`.
20    fn delta(&mut self) -> f32;
21}
22
23// ──────────────────────────────────────────────────────────────────────────────
24// WallClock
25// ──────────────────────────────────────────────────────────────────────────────
26
27/// A real-time clock backed by [`std::time::Instant`].
28///
29/// The first call to [`delta`](Clock::delta) returns `0.0` (no elapsed time since creation).
30///
31/// # Example
32///
33/// ```rust
34/// use animato_driver::{Clock, WallClock};
35///
36/// let mut clk = WallClock::new();
37/// let dt = clk.delta(); // first call: ~0.0
38/// assert!(dt >= 0.0);
39/// ```
40#[derive(Debug)]
41pub struct WallClock {
42    last: std::time::Instant,
43}
44
45impl WallClock {
46    /// Create a new `WallClock` set to the current instant.
47    pub fn new() -> Self {
48        Self {
49            last: std::time::Instant::now(),
50        }
51    }
52}
53
54impl Default for WallClock {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl Clock for WallClock {
61    fn delta(&mut self) -> f32 {
62        let now = std::time::Instant::now();
63        let dt = now.duration_since(self.last).as_secs_f32();
64        self.last = now;
65        dt
66    }
67}
68
69// ──────────────────────────────────────────────────────────────────────────────
70// ManualClock
71// ──────────────────────────────────────────────────────────────────────────────
72
73/// A clock where the caller explicitly sets the dt each frame.
74///
75/// Call [`advance`](ManualClock::advance) to set the pending dt,
76/// then [`delta`](Clock::delta) to consume it (resets to `0.0`).
77///
78/// Useful for custom game loops that already compute their own dt.
79///
80/// # Example
81///
82/// ```rust
83/// use animato_driver::{Clock, ManualClock};
84///
85/// let mut clk = ManualClock::new();
86/// clk.advance(0.016);
87/// assert!((clk.delta() - 0.016).abs() < 1e-6);
88/// assert_eq!(clk.delta(), 0.0); // consumed
89/// ```
90#[derive(Debug, Default)]
91pub struct ManualClock {
92    pending: f32,
93}
94
95impl ManualClock {
96    /// Create a new `ManualClock` with zero pending time.
97    pub fn new() -> Self {
98        Self { pending: 0.0 }
99    }
100
101    /// Set the pending dt that [`delta`](Clock::delta) will return on next call.
102    pub fn advance(&mut self, dt: f32) {
103        self.pending = dt.max(0.0);
104    }
105}
106
107impl Clock for ManualClock {
108    fn delta(&mut self) -> f32 {
109        let dt = self.pending;
110        self.pending = 0.0;
111        dt
112    }
113}
114
115// ──────────────────────────────────────────────────────────────────────────────
116// MockClock
117// ──────────────────────────────────────────────────────────────────────────────
118
119/// A fixed-step clock for deterministic tests.
120///
121/// Always returns the same `step` value from [`delta`](Clock::delta),
122/// making tests independent of wall-clock speed.
123///
124/// # Example
125///
126/// ```rust
127/// use animato_driver::{Clock, MockClock};
128///
129/// let mut clk = MockClock::new(1.0 / 60.0);
130/// for _ in 0..5 {
131///     let dt = clk.delta();
132///     assert!((dt - 1.0 / 60.0).abs() < 1e-6);
133/// }
134/// ```
135#[derive(Debug, Clone)]
136pub struct MockClock {
137    step: f32,
138}
139
140impl MockClock {
141    /// Create a `MockClock` that always returns `step_seconds` from [`delta`](Clock::delta).
142    pub fn new(step_seconds: f32) -> Self {
143        Self {
144            step: step_seconds.max(0.0),
145        }
146    }
147}
148
149impl Clock for MockClock {
150    fn delta(&mut self) -> f32 {
151        self.step
152    }
153}
154
155// ──────────────────────────────────────────────────────────────────────────────
156// Tests
157// ──────────────────────────────────────────────────────────────────────────────
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn mock_clock_always_returns_step() {
165        let mut clk = MockClock::new(1.0 / 60.0);
166        for _ in 0..10 {
167            let dt = clk.delta();
168            assert!((dt - 1.0 / 60.0).abs() < 1e-6);
169        }
170    }
171
172    #[test]
173    fn manual_clock_advance_and_consume() {
174        let mut clk = ManualClock::new();
175        clk.advance(0.016);
176        assert!((clk.delta() - 0.016).abs() < 1e-6);
177        // After consumption, returns 0.0
178        assert_eq!(clk.delta(), 0.0);
179    }
180
181    #[test]
182    fn manual_clock_negative_clamped() {
183        let mut clk = ManualClock::new();
184        clk.advance(-5.0);
185        assert_eq!(clk.delta(), 0.0);
186    }
187
188    #[test]
189    fn wall_clock_non_negative() {
190        let mut clk = WallClock::new();
191        let dt = clk.delta();
192        assert!(dt >= 0.0);
193    }
194
195    #[test]
196    fn mock_clock_zero_step() {
197        let mut clk = MockClock::new(0.0);
198        assert_eq!(clk.delta(), 0.0);
199    }
200}