Skip to main content

pito_tui/
pace.rs

1use std::time::{Duration, Instant};
2
3pub const FRAME: Duration = Duration::from_nanos(8_333_333);
4
5#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
6pub struct Pace {
7    last: Option<Instant>,
8}
9
10impl Pace {
11    pub fn ready(&self, now: Instant) -> bool {
12        self.last.is_none_or(|last| now >= last + FRAME)
13    }
14
15    pub fn next(&self, now: Instant) -> Instant {
16        self.last.map_or(now, |last| (last + FRAME).max(now))
17    }
18
19    pub fn drew(&mut self, at: Instant) {
20        self.last = Some(at);
21    }
22}
23
24pub fn wait_until(
25    now: Instant,
26    dirty: bool,
27    pace: &Pace,
28    deadlines: &[Option<Instant>],
29) -> Option<Instant> {
30    if dirty {
31        return Some(pace.next(now));
32    }
33    deadlines.iter().flatten().min().copied()
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    #[test]
41    fn frames_are_paced_by_time_at_120_per_second_at_most() {
42        let start = Instant::now();
43        let mut pace = Pace::default();
44        assert!(pace.ready(start));
45        assert_eq!(pace.next(start), start);
46        pace.drew(start);
47        assert!(!pace.ready(start + Duration::from_millis(5)));
48        assert_eq!(
49            pace.next(start + Duration::from_millis(5)),
50            start + FRAME,
51            "a frame that took 5 ms waits only the rest of its 8.3 ms"
52        );
53        assert!(pace.ready(start + FRAME));
54        let late = start + Duration::from_millis(20);
55        assert_eq!(pace.next(late), late, "a slow frame is followed at once");
56        assert_eq!(FRAME.as_secs_f64().recip().round(), 120.0);
57    }
58
59    #[test]
60    fn idle_waits_for_a_wake_or_the_next_deadline_alone() {
61        let start = Instant::now();
62        let mut pace = Pace::default();
63        pace.drew(start);
64        assert_eq!(wait_until(start, false, &pace, &[None, None]), None);
65        let soon = start + Duration::from_secs(3);
66        let later = start + Duration::from_secs(9);
67        assert_eq!(
68            wait_until(start, false, &pace, &[Some(later), Some(soon)]),
69            Some(soon)
70        );
71        assert_eq!(
72            wait_until(start, true, &pace, &[Some(later)]),
73            Some(start + FRAME),
74            "an animation waits one frame"
75        );
76    }
77}