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}