ticktock/clock.rs
1//! Frame clock module
2//!
3//! Contains a clocking that ticks in a fixed interval as precisely as
4//! possible.
5
6// FIXME: clock should start immediately, not waiting the initial interval
7
8use std::{iter, thread, time};
9
10/// Clock structure.
11pub struct Clock {
12 /// Start time of the clock, in ns since epoch
13 started_at: time::Instant,
14 /// Tick length
15 tick_len: time::Duration,
16}
17
18/// A clock iterator
19///
20/// Used to iterate over the clock:
21///
22/// ```
23/// use std::time;
24/// use ticktock::Clock;
25///
26/// let start = time::Instant::now();
27/// // ticks once per second
28/// let mut clock = Clock::new(time::Duration::from_secs(1));
29///
30/// // as soon as the clock starts, it will wait for the next tick.
31/// // in this case, we'll start at t = 1 second
32/// for tick in clock.iter() {
33/// // ...
34///
35/// // a simple break will exit
36/// break;
37/// }
38///
39/// let end = time::Instant::now();
40///
41/// assert!(time::Duration::from_secs(1) < end - start);
42/// ```
43pub struct ClockIter<'a>(&'a Clock);
44
45impl Clock {
46 /// Creates a new clock.
47 ///
48 /// Create a clock with a tick size of `tick_len_ms`, in ms.
49 #[inline]
50 pub fn new(tick_len: time::Duration) -> Clock {
51 Clock::new_with_start_time(tick_len, time::Instant::now())
52 }
53
54 /// Creates a new clock with a specified start time
55 #[inline]
56 pub fn new_with_start_time(tick_len: time::Duration, start: time::Instant) -> Clock {
57 Clock {
58 started_at: start,
59 tick_len,
60 }
61 }
62
63 /// Creates a new fixed-framerate clock
64 #[inline]
65 pub fn framerate(fps: f64) -> Clock {
66 Clock::framerate_with_start_time(fps, time::Instant::now())
67 }
68
69 /// Creates a new fixed-framerate clock with a specified sart time
70 #[inline]
71 pub fn framerate_with_start_time(fps: f64, start: time::Instant) -> Clock {
72 let frame_time_s = 1.0 / fps;
73
74 Clock::new_with_start_time(time::Duration::from_secs_f64(frame_time_s), start)
75 }
76
77 /// Creates a new clock with a different tick length that is synced to
78 /// the original clock
79 #[inline]
80 pub fn synced(&self, tick_len: time::Duration) -> Clock {
81 Clock {
82 started_at: self.started_at,
83 tick_len,
84 }
85 }
86
87 /// Get start time
88 #[inline]
89 pub fn started_at(&self) -> time::Instant {
90 self.started_at
91 }
92
93 /// Returns the tick number preceding an specific instant in time
94 #[inline]
95 pub fn tick_num_at(&self, now: time::Instant) -> u128 {
96 (now - self.started_at).as_nanos() / self.tick_len.as_nanos()
97 }
98
99 /// Waits for the next clock tick.
100 ///
101 /// Will wait until the next tick and return the current tick count.
102 #[inline]
103 pub fn wait_until_tick(&self) -> (u128, time::Instant) {
104 // uses signed math because ntp might put us in the past
105 let now = time::Instant::now();
106
107 let current_tick_num = self.tick_num_at(now);
108 let next_tick_num = current_tick_num + 1;
109
110 let next_tick = self.started_at + self.tick_len * next_tick_num as u32;
111 let until_next: time::Duration = next_tick - now;
112
113 thread::sleep(until_next);
114 (next_tick_num, next_tick)
115 }
116
117 /// Creates a clock iterator.
118 ///
119 /// The iterator will iterate forever, calling `wait_until_tick` on each
120 /// iteration. It will panic after about 293 years.
121 ///
122 /// Returns (current tick number, absolute time) on each iteration, where
123 /// absolute time is relative to a fixed offset that depends on the machine
124 /// (see `Instant`).
125 #[inline]
126 pub fn iter(&self) -> ClockIter {
127 ClockIter(self)
128 }
129
130 /// Create a relative clock iterator.
131 ///
132 /// Similar to `iter()`, but the resulting iterator will return a tuple of
133 /// (current tick number, relative time), with relative time being a
134 /// `time::Duration` from the start of the clock.
135 #[inline]
136 pub fn rel_iter(&self) -> ClockIterRelative {
137 ClockIterRelative(self)
138 }
139}
140
141impl<'a> iter::Iterator for ClockIter<'a> {
142 type Item = (u128, time::Instant);
143
144 #[inline]
145 fn next(&mut self) -> Option<Self::Item> {
146 Some(self.0.wait_until_tick())
147 }
148}
149
150/// Similar to `ClockIter`, but returns a relative time instead.
151///
152/// The resulting returned tuple will be of the form `(tick_number,
153/// duration_since_clock_start)`
154pub struct ClockIterRelative<'a>(&'a Clock);
155
156impl<'a> iter::Iterator for ClockIterRelative<'a> {
157 type Item = (u128, time::Duration);
158
159 #[inline]
160 fn next(&mut self) -> Option<Self::Item> {
161 let (n, t) = self.0.wait_until_tick();
162 Some((n, t - self.0.started_at))
163 }
164}