chron/clock.rs
1//! Provides the game loop iterator and related types.
2
3use std::num;
4use std::thread;
5use std::time;
6
7/// The game loop iterator, whichs emits [`Tick`] events.
8///
9/// # Examples
10///
11/// See the [crate-level documentation](crate#examples).
12#[must_use = "iterators are lazy and do nothing unless consumed"]
13#[derive(Debug)]
14pub struct Clock {
15 time_per_update: time::Duration,
16 time_per_render: Option<time::Duration>,
17 max_updates_per_frame: Option<usize>,
18 updates: usize,
19
20 previous_update_time: time::Instant,
21 previous_render_time: time::Instant,
22}
23
24impl Clock {
25 /// Creates a new `Clock`.
26 ///
27 /// # Examples
28 ///
29 /// ```
30 /// use chron::Clock;
31 /// use std::num::NonZeroU32;
32 ///
33 /// # fn main() { test().unwrap(); }
34 /// # fn test() -> Option<()> {
35 /// let updates_per_second = NonZeroU32::new(50)?;
36 /// let clock = Clock::new(updates_per_second);
37 /// # Some(())
38 /// # }
39 /// ```
40 #[inline]
41 pub fn new(updates_per_second: num::NonZeroU32) -> Self {
42 let time_per_update = time::Duration::from_secs(1) / updates_per_second.get();
43
44 let now = time::Instant::now();
45
46 Clock {
47 time_per_update,
48 time_per_render: None,
49 max_updates_per_frame: None,
50
51 previous_update_time: now - time_per_update,
52 previous_render_time: now,
53 updates: 0,
54 }
55 }
56
57 /// Sets the maximum frame rate.
58 ///
59 /// # Examples
60 ///
61 /// ```
62 /// use chron::Clock;
63 /// use std::num::NonZeroU32;
64 ///
65 /// # fn main() { test().unwrap(); }
66 /// # fn test() -> Option<()> {
67 /// let updates_per_second = NonZeroU32::new(50)?;
68 /// let frames_per_second = NonZeroU32::new(60)?;
69 ///
70 /// let clock = Clock::new(updates_per_second)
71 /// .max_frame_rate(frames_per_second);
72 /// # Some(())
73 /// # }
74 /// ```
75 #[inline]
76 pub fn max_frame_rate(mut self, frames_per_second: num::NonZeroU32) -> Self {
77 let time_per_render = time::Duration::from_secs(1) / frames_per_second.get();
78 self.time_per_render = Some(time_per_render);
79 self.previous_render_time -= time_per_render;
80
81 self
82 }
83
84 /// See [`Clock::max_frame_rate`].
85 #[deprecated(note = "use `Clock::max_frame_rate` instead")]
86 #[inline]
87 pub fn with_frame_limit(self, frames_per_second: num::NonZeroU32) -> Self {
88 self.max_frame_rate(frames_per_second)
89 }
90
91 /// Sets the maximum number of updates to be emitted in between frames.
92 ///
93 /// When the loop cannot maintain the specified *updates per second* (e.g.
94 /// because updates are taking too long) it has to play catch-up by only
95 /// emitting updates and **no** renders. This setting limits how many
96 /// updates are emitted before a render is enforced. It prevents the game
97 /// from freezing up, at the cost of slowing down even more.
98 ///
99 /// # Examples
100 ///
101 /// ```
102 /// use chron::Clock;
103 /// use std::num::NonZeroU32;
104 ///
105 /// # fn main() { test().unwrap(); }
106 /// # fn test() -> Option<()> {
107 /// let updates_per_second = NonZeroU32::new(50)?;
108 ///
109 /// let clock = Clock::new(updates_per_second)
110 /// .max_updates_per_frame(3);
111 /// # Some(())
112 /// # }
113 /// ```
114 #[inline]
115 pub fn max_updates_per_frame(mut self, max_updates_per_frame: usize) -> Self {
116 self.max_updates_per_frame = Some(max_updates_per_frame);
117
118 self
119 }
120
121 /// See [`Clock::max_updates_per_frame`].
122 #[deprecated(note = "use `Clock::max_updates_per_frame` instead")]
123 #[inline]
124 pub fn with_frame_skip(self, frame_skip: usize) -> Self {
125 self.max_updates_per_frame(frame_skip)
126 }
127}
128
129impl Iterator for Clock {
130 type Item = Tick;
131
132 #[inline]
133 fn next(&mut self) -> Option<Self::Item> {
134 let mut now = time::Instant::now();
135
136 if let Some(time_per_render) = self.time_per_render {
137 if now - self.previous_render_time < time_per_render
138 && now - self.previous_update_time < self.time_per_update
139 {
140 let sleep_time = std::cmp::min(
141 self.previous_render_time + time_per_render - now,
142 self.previous_update_time + self.time_per_update - now,
143 );
144
145 thread::sleep(sleep_time);
146
147 now += sleep_time;
148 }
149 }
150
151 if now - self.previous_update_time >= self.time_per_update
152 && self.updates < self.max_updates_per_frame.unwrap_or(usize::MAX)
153 {
154 self.previous_update_time += self.time_per_update;
155 self.updates += 1;
156
157 Some(Tick::Update)
158 } else {
159 self.previous_render_time = now;
160 self.updates = 0;
161
162 let interpolation = (now - self.previous_update_time).as_secs_f32()
163 / self.time_per_update.as_secs_f32();
164
165 Some(Tick::Render { interpolation })
166 }
167 }
168}
169
170/// A game loop event.
171#[derive(Clone, Copy, Debug)]
172pub enum Tick {
173 /// Indicates that it is time for an update to the game logic.
174 Update,
175
176 /// Indicates that it is time to render the game.
177 Render {
178 /// Indicates how much time elapsed between the previous update and the
179 /// next one.
180 interpolation: f32,
181 },
182}