Skip to main content

audio_code/
time.rs

1//! Everything about time: frame timing, game-time scheduling and simple timers.
2//!
3//! There are two kinds of time in the engine:
4//!
5//! * **Game time** ([`Time`], reachable as `ctx.time`). It advances once per frame, can be
6//!   slowed down or paused with [`Time::set_scale`], and drives the scheduler
7//!   ([`Time::after`], [`Time::every`], [`Time::fired`]).
8//! * **Real time** ([`Timer`], [`Cooldown`], [`Interval`]). These are standalone objects you
9//!   keep in your own structs. They measure wall clock time and work without a context.
10
11use std::time::{Duration, Instant};
12
13/// Frame timing and a simple named-event scheduler. Available as `ctx.time`.
14///
15/// ```no_run
16/// # use audio_code::prelude::*;
17/// # fn f(ctx: &mut Context) {
18/// // Fire an event called "spawn" every 2.5 seconds of game time.
19/// ctx.time.every("spawn", 2.5);
20/// // ...later, in update():
21/// if ctx.time.fired("spawn") {
22///     ctx.speech.speak("A new enemy appears");
23/// }
24/// # }
25/// ```
26#[derive(Debug)]
27pub struct Time {
28    start: Instant,
29    last_frame: Option<Instant>,
30    delta: f32,
31    raw_delta: f32,
32    elapsed: f64,
33    frame: u64,
34    scale: f32,
35    max_delta: f32,
36    fps: f32,
37    schedule: Vec<Scheduled>,
38    fired: Vec<String>,
39}
40
41#[derive(Debug)]
42struct Scheduled {
43    name: String,
44    at: f64,
45    repeat: Option<f64>,
46}
47
48impl Default for Time {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl Time {
55    pub(crate) fn new() -> Self {
56        Self {
57            start: Instant::now(),
58            last_frame: None,
59            delta: 0.0,
60            raw_delta: 0.0,
61            elapsed: 0.0,
62            frame: 0,
63            scale: 1.0,
64            max_delta: 0.25,
65            fps: 0.0,
66            schedule: Vec::new(),
67            fired: Vec::new(),
68        }
69    }
70
71    /// Advances the clock by one frame. Called by the engine.
72    pub(crate) fn advance(&mut self, now: Instant) {
73        let raw = match self.last_frame {
74            Some(last) => now.duration_since(last).as_secs_f32(),
75            None => 0.0,
76        };
77        self.last_frame = Some(now);
78        // Clamp huge hitches (e.g. a breakpoint or a dragged window) so physics does not explode.
79        self.raw_delta = raw.min(self.max_delta);
80        self.delta = self.raw_delta * self.scale;
81        self.elapsed += self.delta as f64;
82        self.frame += 1;
83        if raw > 0.0 {
84            // Exponential moving average keeps the number readable.
85            let current = 1.0 / raw;
86            self.fps = if self.fps == 0.0 { current } else { self.fps * 0.9 + current * 0.1 };
87        }
88
89        self.fired.clear();
90        let now = self.elapsed;
91        let mut i = 0;
92        while i < self.schedule.len() {
93            if self.schedule[i].at <= now {
94                self.fired.push(self.schedule[i].name.clone());
95                match self.schedule[i].repeat {
96                    Some(every) => self.schedule[i].at += every,
97                    None => {
98                        self.schedule.swap_remove(i);
99                        continue;
100                    }
101                }
102            }
103            i += 1;
104        }
105    }
106
107    /// Seconds of game time since the previous frame (affected by [`Time::set_scale`]).
108    ///
109    /// Multiply speeds by this to make movement frame rate independent:
110    /// `position.x += speed * ctx.time.delta();`
111    pub fn delta(&self) -> f32 {
112        self.delta
113    }
114
115    /// Like [`Time::delta`] but ignores the time scale. Useful for menus while the game is paused.
116    pub fn real_delta(&self) -> f32 {
117        self.raw_delta
118    }
119
120    /// Seconds of game time since the game started.
121    pub fn elapsed(&self) -> f64 {
122        self.elapsed
123    }
124
125    /// Wall clock time since the engine started, ignoring the time scale.
126    pub fn real_elapsed(&self) -> Duration {
127        self.start.elapsed()
128    }
129
130    /// Number of frames since the game started.
131    pub fn frame(&self) -> u64 {
132        self.frame
133    }
134
135    /// Current frames per second (smoothed).
136    pub fn fps(&self) -> f32 {
137        self.fps
138    }
139
140    /// Current time scale. 1.0 is normal speed.
141    pub fn scale(&self) -> f32 {
142        self.scale
143    }
144
145    /// Speeds up (> 1.0), slows down (< 1.0) or pauses (0.0) game time.
146    /// Physics, the scheduler and [`Time::delta`] all follow the scale.
147    pub fn set_scale(&mut self, scale: f32) {
148        self.scale = scale.max(0.0);
149    }
150
151    /// Pauses game time. Same as `set_scale(0.0)`.
152    pub fn pause(&mut self) {
153        self.scale = 0.0;
154    }
155
156    /// Resumes game time at normal speed.
157    pub fn resume(&mut self) {
158        self.scale = 1.0;
159    }
160
161    /// Whether game time is paused.
162    pub fn is_paused(&self) -> bool {
163        self.scale == 0.0
164    }
165
166    /// Schedules a one-shot event named `name` that fires after `seconds` of game time.
167    /// Scheduling a name that is already pending replaces it.
168    pub fn after(&mut self, name: impl Into<String>, seconds: f32) {
169        let name = name.into();
170        self.cancel(&name);
171        self.schedule.push(Scheduled { at: self.elapsed + seconds as f64, name, repeat: None });
172    }
173
174    /// Schedules a repeating event named `name` that fires every `seconds` of game time.
175    /// Scheduling a name that is already pending replaces it.
176    pub fn every(&mut self, name: impl Into<String>, seconds: f32) {
177        let name = name.into();
178        let seconds = (seconds as f64).max(0.001);
179        self.cancel(&name);
180        self.schedule.push(Scheduled { at: self.elapsed + seconds, name, repeat: Some(seconds) });
181    }
182
183    /// Cancels a scheduled event. Does nothing if it does not exist.
184    pub fn cancel(&mut self, name: &str) {
185        self.schedule.retain(|s| s.name != name);
186    }
187
188    /// Whether an event with this name is waiting to fire.
189    pub fn is_scheduled(&self, name: &str) -> bool {
190        self.schedule.iter().any(|s| s.name == name)
191    }
192
193    /// Seconds until the named event fires, or `None` if it is not scheduled.
194    pub fn remaining(&self, name: &str) -> Option<f32> {
195        self.schedule.iter().find(|s| s.name == name).map(|s| (s.at - self.elapsed).max(0.0) as f32)
196    }
197
198    /// Returns `true` during the frame in which the named event fired.
199    pub fn fired(&self, name: &str) -> bool {
200        self.fired.iter().any(|n| n == name)
201    }
202
203    /// All events that fired this frame.
204    pub fn fired_events(&self) -> impl Iterator<Item = &str> {
205        self.fired.iter().map(String::as_str)
206    }
207}
208
209/// A stopwatch measuring real time. Can be paused, resumed and restarted.
210///
211/// ```
212/// use audio_code::time::Timer;
213/// let mut timer = Timer::new();
214/// // ...
215/// if timer.elapsed_secs() > 3.0 {
216///     timer.restart();
217/// }
218/// ```
219#[derive(Debug, Clone)]
220pub struct Timer {
221    started: Instant,
222    offset: Duration,
223    paused_at: Option<Instant>,
224}
225
226impl Default for Timer {
227    fn default() -> Self {
228        Self::new()
229    }
230}
231
232impl Timer {
233    /// Creates a running timer starting at zero.
234    pub fn new() -> Self {
235        Self { started: Instant::now(), offset: Duration::ZERO, paused_at: None }
236    }
237
238    /// Time measured so far.
239    pub fn elapsed(&self) -> Duration {
240        let end = self.paused_at.unwrap_or_else(Instant::now);
241        end.duration_since(self.started) + self.offset
242    }
243
244    /// Time measured so far in seconds.
245    pub fn elapsed_secs(&self) -> f32 {
246        self.elapsed().as_secs_f32()
247    }
248
249    /// Time measured so far in milliseconds.
250    pub fn elapsed_ms(&self) -> u64 {
251        self.elapsed().as_millis() as u64
252    }
253
254    /// Whether at least `seconds` have passed.
255    pub fn has_elapsed(&self, seconds: f32) -> bool {
256        self.elapsed_secs() >= seconds
257    }
258
259    /// Resets to zero and keeps running (also unpauses).
260    pub fn restart(&mut self) {
261        *self = Self::new();
262    }
263
264    /// Sets the measured time to `seconds` without changing the paused state.
265    pub fn set(&mut self, seconds: f32) {
266        let now = Instant::now();
267        self.started = now;
268        self.offset = Duration::from_secs_f32(seconds.max(0.0));
269        if self.paused_at.is_some() {
270            self.paused_at = Some(now);
271        }
272    }
273
274    /// Stops counting until [`Timer::resume`] is called.
275    pub fn pause(&mut self) {
276        if self.paused_at.is_none() {
277            self.paused_at = Some(Instant::now());
278        }
279    }
280
281    /// Continues counting after a pause.
282    pub fn resume(&mut self) {
283        if let Some(paused) = self.paused_at.take() {
284            self.started += Instant::now().duration_since(paused);
285        }
286    }
287
288    /// Whether the timer is paused.
289    pub fn is_paused(&self) -> bool {
290        self.paused_at.is_some()
291    }
292}
293
294/// Limits how often something can happen, e.g. firing a weapon.
295///
296/// ```
297/// use audio_code::time::Cooldown;
298/// let mut gun = Cooldown::new(0.5);
299/// if gun.try_use() {
300///     // fire! The next try_use() returns false for half a second.
301/// }
302/// ```
303#[derive(Debug, Clone)]
304pub struct Cooldown {
305    length: f32,
306    timer: Timer,
307    used: bool,
308}
309
310impl Cooldown {
311    /// Creates a cooldown of `seconds` that is ready immediately.
312    pub fn new(seconds: f32) -> Self {
313        Self { length: seconds, timer: Timer::new(), used: false }
314    }
315
316    /// Whether the cooldown has run out.
317    pub fn ready(&self) -> bool {
318        !self.used || self.timer.has_elapsed(self.length)
319    }
320
321    /// If ready, starts the cooldown and returns `true`. Otherwise returns `false`.
322    pub fn try_use(&mut self) -> bool {
323        if self.ready() {
324            self.trigger();
325            true
326        } else {
327            false
328        }
329    }
330
331    /// Starts the cooldown regardless of whether it was ready.
332    pub fn trigger(&mut self) {
333        self.used = true;
334        self.timer.restart();
335    }
336
337    /// Makes the cooldown ready immediately.
338    pub fn reset(&mut self) {
339        self.used = false;
340    }
341
342    /// Seconds until ready (0.0 if ready).
343    pub fn remaining(&self) -> f32 {
344        if self.ready() { 0.0 } else { self.length - self.timer.elapsed_secs() }
345    }
346
347    /// Changes the cooldown length.
348    pub fn set_length(&mut self, seconds: f32) {
349        self.length = seconds;
350    }
351}
352
353/// Fires at a fixed real-time rate. Great for footsteps, radar beeps or clocks.
354///
355/// ```
356/// use audio_code::time::Interval;
357/// let mut steps = Interval::new(0.4);
358/// // in update():
359/// if steps.tick() {
360///     // play a footstep
361/// }
362/// ```
363#[derive(Debug, Clone)]
364pub struct Interval {
365    every: f32,
366    timer: Timer,
367}
368
369impl Interval {
370    /// Creates an interval that fires every `seconds`. The first tick happens after `seconds`.
371    pub fn new(seconds: f32) -> Self {
372        Self { every: seconds.max(0.001), timer: Timer::new() }
373    }
374
375    /// Returns `true` once each time the interval elapses. Call it every frame.
376    /// If several intervals were missed only one tick is reported.
377    pub fn tick(&mut self) -> bool {
378        let elapsed = self.timer.elapsed_secs();
379        if elapsed >= self.every {
380            // Keep the remainder so the rhythm stays steady.
381            self.timer.set((elapsed - self.every).min(self.every));
382            true
383        } else {
384            false
385        }
386    }
387
388    /// Restarts counting from zero.
389    pub fn reset(&mut self) {
390        self.timer.restart();
391    }
392
393    /// Changes how often the interval fires.
394    pub fn set_interval(&mut self, seconds: f32) {
395        self.every = seconds.max(0.001);
396    }
397
398    /// Current interval length in seconds.
399    pub fn interval(&self) -> f32 {
400        self.every
401    }
402}
403
404#[cfg(test)]
405mod tests {
406    use super::*;
407
408    #[test]
409    fn scheduler_fires_once_and_repeats() {
410        let mut t = Time::new();
411        let base = Instant::now();
412        t.advance(base);
413        t.after("once", 0.1);
414        t.every("rep", 0.05);
415        t.advance(base + Duration::from_millis(60));
416        assert!(!t.fired("once"));
417        assert!(t.fired("rep"));
418        t.advance(base + Duration::from_millis(120));
419        assert!(t.fired("once"));
420        assert!(t.fired("rep"));
421        assert!(!t.is_scheduled("once"));
422        assert!(t.is_scheduled("rep"));
423    }
424
425    #[test]
426    fn scale_pauses_game_time() {
427        let mut t = Time::new();
428        let base = Instant::now();
429        t.advance(base);
430        t.pause();
431        t.advance(base + Duration::from_millis(100));
432        assert_eq!(t.delta(), 0.0);
433        assert!(t.real_delta() > 0.0);
434    }
435
436    #[test]
437    fn cooldown_blocks() {
438        let mut c = Cooldown::new(10.0);
439        assert!(c.try_use());
440        assert!(!c.try_use());
441        c.reset();
442        assert!(c.ready());
443    }
444}