1use core::time::Duration;
10
11#[derive(Clone, Copy, PartialEq, Eq, Debug)]
15pub struct Ticket(u64);
16
17#[derive(Clone, Copy, PartialEq, Eq, Debug)]
19pub enum Schedule {
20 Idle,
22 Frame,
24 Timer { ticket: Ticket, after: Duration },
26}
27
28#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
30enum Waiting {
31 #[default]
32 Nothing,
33 Frame,
35 Timer(Ticket),
37}
38
39#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
41pub struct Pacing {
42 waiting: Waiting,
43 minted: u64,
44}
45
46impl Pacing {
47 pub fn said(&mut self) -> Schedule {
50 match self.waiting {
51 Waiting::Frame => Schedule::Idle,
52 Waiting::Nothing | Waiting::Timer(_) => self.books_a_frame(),
53 }
54 }
55
56 pub fn entered(&mut self) -> Schedule {
59 self.waiting = Waiting::Nothing;
60 Schedule::Idle
61 }
62
63 pub fn ran(&mut self, repaint: Option<Duration>) -> Schedule {
67 if self.waiting != Waiting::Nothing {
68 return Schedule::Idle;
69 }
70 match repaint {
71 None => Schedule::Idle,
72 Some(Duration::ZERO) => self.books_a_frame(),
73 Some(after) => {
74 self.minted += 1;
75 let ticket = Ticket(self.minted);
76 self.waiting = Waiting::Timer(ticket);
77 Schedule::Timer { ticket, after }
78 }
79 }
80 }
81
82 pub fn woke(&mut self, ticket: Ticket) -> Schedule {
84 if self.waiting == Waiting::Timer(ticket) {
85 self.books_a_frame()
86 } else {
87 Schedule::Idle
88 }
89 }
90
91 fn books_a_frame(&mut self) -> Schedule {
92 self.waiting = Waiting::Frame;
93 Schedule::Frame
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 const SOON: Duration = Duration::from_millis(16);
102
103 #[test]
104 fn the_first_thing_said_books_a_frame_and_the_next_things_ride_it() {
105 let mut pacing = Pacing::default();
106 assert_eq!(pacing.said(), Schedule::Frame);
107 assert_eq!(pacing.said(), Schedule::Idle);
108 assert_eq!(pacing.said(), Schedule::Idle);
109 }
110
111 #[test]
112 fn a_frame_that_asks_for_nothing_leaves_the_loop_idle() {
113 let mut pacing = Pacing::default();
114 assert_eq!(pacing.said(), Schedule::Frame);
115 assert_eq!(pacing.entered(), Schedule::Idle);
116 assert_eq!(pacing.ran(None), Schedule::Idle);
117 assert_eq!(pacing.said(), Schedule::Frame);
119 }
120
121 #[test]
122 fn a_repaint_owed_at_once_books_the_next_animation_frame() {
123 let mut pacing = Pacing::default();
124 pacing.said();
125 pacing.entered();
126 assert_eq!(pacing.ran(Some(Duration::ZERO)), Schedule::Frame);
127 assert_eq!(pacing.said(), Schedule::Idle);
128 }
129
130 #[test]
131 fn a_repaint_owed_later_books_a_timer_that_runs_when_it_fires() {
132 let mut pacing = Pacing::default();
133 pacing.said();
134 pacing.entered();
135 let Schedule::Timer { ticket, after } = pacing.ran(Some(SOON)) else {
136 panic!("a later repaint books a timer");
137 };
138 assert_eq!(after, SOON);
139 assert_eq!(pacing.woke(ticket), Schedule::Frame);
140 }
141
142 #[test]
146 fn something_said_overtakes_a_pending_timer_and_leaves_it_stale() {
147 let mut pacing = Pacing::default();
148 pacing.said();
149 pacing.entered();
150 let Schedule::Timer { ticket, .. } = pacing.ran(Some(SOON)) else {
151 panic!("a later repaint books a timer");
152 };
153 assert_eq!(pacing.said(), Schedule::Frame);
154 assert_eq!(pacing.woke(ticket), Schedule::Idle);
155 }
156
157 #[test]
160 fn a_frame_booked_during_the_run_is_the_only_one_booked() {
161 let mut pacing = Pacing::default();
162 pacing.said();
163 pacing.entered();
164 assert_eq!(pacing.said(), Schedule::Frame);
165 assert_eq!(pacing.ran(Some(Duration::ZERO)), Schedule::Idle);
166 assert_eq!(pacing.ran(Some(SOON)), Schedule::Idle);
167 }
168}