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}