Skip to main content

katui_rt/
clock.rs

1//! The frame clock: fixed-frame deadlines, where a frame that misses its
2//! slot restarts the schedule so a stall never ends in a burst.
3
4use std::thread;
5use std::time::{Duration, Instant};
6
7/// A fixed-rate frame pacer.
8#[derive(Debug)]
9pub struct FrameClock {
10    frame_time: Duration,
11    deadline: Instant,
12    frame: u64,
13}
14
15impl FrameClock {
16    /// A clock ticking `frames_per_second` times a second from now.
17    #[must_use]
18    pub fn new(frames_per_second: u32) -> Self {
19        let rate = f64::from(frames_per_second.max(1));
20        Self {
21            frame_time: Duration::from_secs_f64(1.0 / rate),
22            deadline: Instant::now(),
23            frame: 0,
24        }
25    }
26
27    /// One frame's duration.
28    #[must_use]
29    pub fn frame_time(&self) -> Duration {
30        self.frame_time
31    }
32
33    /// The frames issued so far.
34    #[must_use]
35    pub fn frame(&self) -> u64 {
36        self.frame
37    }
38
39    /// Sleep until the next frame deadline and advance. A late frame
40    /// returns immediately.
41    pub fn wait_for_next_frame(&mut self) {
42        let remaining = self.advance(Instant::now());
43        if !remaining.is_zero() {
44            thread::sleep(remaining);
45        }
46    }
47
48    /// Move to the next slot and report the sleep it needs at `now`.
49    fn advance(&mut self, now: Instant) -> Duration {
50        self.frame += 1;
51        self.deadline += self.frame_time;
52        if self.deadline < now {
53            self.deadline = now;
54        }
55        self.deadline.duration_since(now)
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use std::time::Duration;
62
63    use super::FrameClock;
64
65    #[test]
66    fn a_sixty_fps_clock_allots_sixteen_milli_frames() {
67        let clock = FrameClock::new(60);
68        let frame_time = clock.frame_time();
69        assert!(frame_time >= Duration::from_millis(16));
70        assert!(frame_time <= Duration::from_millis(17));
71    }
72
73    #[test]
74    fn a_stall_restarts_the_schedule_instead_of_bursting() {
75        let mut clock = FrameClock::new(10);
76        let stalled_until = clock.deadline + Duration::from_secs(5);
77        assert!(
78            clock.advance(stalled_until).is_zero(),
79            "the late frame runs now"
80        );
81        assert_eq!(
82            clock.advance(stalled_until),
83            Duration::from_millis(100),
84            "the next frame gets a whole slot, not a catch-up"
85        );
86    }
87
88    #[test]
89    fn waiting_counts_frames_without_panicking() {
90        let mut clock = FrameClock::new(1000);
91        clock.wait_for_next_frame();
92        clock.wait_for_next_frame();
93        assert_eq!(clock.frame(), 2);
94    }
95}