changelog shortlog graph tags branches changeset files revisions annotate raw help

Mercurial > core / rust/lib/util/src/time.rs

changeset 698: 96958d3eb5b0
parent: 4f49127c9048
author: Richard Westhaver <ellis@rwest.io>
date: Fri, 04 Oct 2024 22:04:59 -0400
permissions: -rw-r--r--
description: fixes
1 //! Utilities for working with time
2 pub use chrono::{DateTime, TimeZone, Utc};
3 use std::time::{Duration, Instant};
4 
5 /// Returns the number of non-leap milliseconds since January 1, 1970 0:00:00
6 /// UTC (UNIX timestamp).
7 pub fn unix_epoch_ms() -> u64 {
8  let now: DateTime<Utc> = Utc::now();
9 
10  now.timestamp_millis() as u64
11 }
12 
13 /// Frame timing values.
14 #[derive(Clone, Copy, Debug, PartialEq)]
15 pub struct Time {
16  /// Time elapsed since the last frame in seconds.
17  delta_seconds: f32,
18  /// Time elapsed since the last frame.
19  delta_time: Duration,
20  /// Time elapsed since the last frame in seconds ignoring the time speed
21  /// multiplier.
22  delta_real_seconds: f32,
23  /// Time elapsed since the last frame ignoring the time speed multiplier.
24  delta_real_time: Duration,
25  /// Rate at which `State::fixed_update` is called in seconds.
26  fixed_seconds: f32,
27  /// Rate at which `State::fixed_update` is called.
28  fixed_time: Duration,
29  /// The total number of frames that have been played in this session.
30  frame_number: u64,
31  ///Time elapsed since game start, ignoring the speed multipler.
32  absolute_real_time: Duration,
33  ///Time elapsed since game start, taking the speed multiplier into account.
34  absolute_time: Duration,
35  ///Time multiplier. Affects returned delta_seconds, delta_time and
36  /// absolute_time.
37  time_scale: f32,
38  /// Fixed timestep accumulator.
39  fixed_time_accumulator: f32,
40  /// Fixed update interpolation alpha
41  interpolation_alpha: f32,
42 }
43 
44 impl Time {
45  /// Gets the time difference between frames in seconds.
46  ///
47  /// This function should not be used during `fixed_update`s, use
48  /// `fixed_seconds` instead.
49  pub fn delta_seconds(&self) -> f32 {
50  self.delta_seconds
51  }
52 
53  /// Gets the time difference between frames.
54  ///
55  /// This function should not be used during `fixed_update`s, use
56  /// `fixed_time` instead.
57  pub fn delta_time(&self) -> Duration {
58  self.delta_time
59  }
60 
61  /// Gets the time difference between frames in seconds ignoring the time
62  /// speed multiplier.
63  ///
64  /// This function should not be used during `fixed_update`s.
65  pub fn delta_real_seconds(&self) -> f32 {
66  self.delta_real_seconds
67  }
68 
69  /// Gets the time difference between frames ignoring the time speed
70  /// multiplier.
71  pub fn delta_real_time(&self) -> Duration {
72  self.delta_real_time
73  }
74 
75  /// Gets the fixed time step in seconds.
76  pub fn fixed_seconds(&self) -> f32 {
77  self.fixed_seconds
78  }
79 
80  /// Gets the fixed time step.
81  pub fn fixed_time(&self) -> Duration {
82  self.fixed_time
83  }
84 
85  /// Gets the current frame number. This increments by 1 every frame. There
86  /// is no frame 0.
87  pub fn frame_number(&self) -> u64 {
88  self.frame_number
89  }
90 
91  /// Gets the time since the start of the game, taking into account the speed
92  /// multiplier.
93  pub fn absolute_time(&self) -> Duration {
94  self.absolute_time
95  }
96 
97  /// Gets the time since the start of the game as seconds, taking into
98  /// account the speed multiplier.
99  pub fn absolute_time_seconds(&self) -> f64 {
100  duration_to_secs_f64(self.absolute_time)
101  }
102 
103  /// Gets the time since the start of the game, ignoring the speed
104  /// multiplier.
105  pub fn absolute_real_time(&self) -> Duration {
106  self.absolute_real_time
107  }
108 
109  /// Gets the time since the start of the game as seconds, ignoring the speed
110  /// multiplier.
111  pub fn absolute_real_time_seconds(&self) -> f64 {
112  duration_to_secs_f64(self.absolute_real_time)
113  }
114 
115  /// Gets the current time speed multiplier.
116  pub fn time_scale(&self) -> f32 {
117  self.time_scale
118  }
119 
120  /// Gets the current interpolation alpha factor.
121  pub fn interpolation_alpha(&self) -> f32 {
122  self.interpolation_alpha
123  }
124 
125  /// Sets both `delta_seconds` and `delta_time` based on the seconds given.
126  ///
127  /// This should only be called by the engine. Bad things might happen if
128  /// you call this in your game.
129  pub fn set_delta_seconds(&mut self, secs: f32) {
130  self.delta_seconds = secs * self.time_scale;
131  self.delta_time = secs_to_duration(secs * self.time_scale);
132  self.delta_real_seconds = secs;
133  self.delta_real_time = secs_to_duration(secs);
134 
135  self.absolute_time += self.delta_time;
136  self.absolute_real_time += self.delta_real_time;
137  }
138 
139  /// Sets both `delta_time` and `delta_seconds` based on the duration given.
140  ///
141  /// This should only be called by the engine. Bad things might happen if
142  /// you call this in your game.
143  pub fn set_delta_time(&mut self, time: Duration) {
144  self.delta_seconds = duration_to_secs(time) * self.time_scale;
145  self.delta_time =
146  secs_to_duration(duration_to_secs(time) * self.time_scale);
147  self.delta_real_seconds = duration_to_secs(time);
148  self.delta_real_time = time;
149 
150  self.absolute_time += self.delta_time;
151  self.absolute_real_time += self.delta_real_time;
152  }
153 
154  /// Sets both `fixed_seconds` and `fixed_time` based on the seconds given.
155  pub fn set_fixed_seconds(&mut self, secs: f32) {
156  self.fixed_seconds = secs;
157  self.fixed_time = secs_to_duration(secs);
158  }
159 
160  /// Sets both `fixed_time` and `fixed_seconds` based on the duration given.
161  pub fn set_fixed_time(&mut self, time: Duration) {
162  self.fixed_seconds = duration_to_secs(time);
163  self.fixed_time = time;
164  }
165 
166  /// Increments the current frame number by 1.
167  ///
168  /// This should only be called by the engine. Bad things might happen if
169  /// you call this in your game.
170  pub fn increment_frame_number(&mut self) {
171  self.frame_number += 1;
172  }
173 
174  /// Sets the time multiplier that affects how time values are computed,
175  /// effectively slowing or speeding up your game.
176  ///
177  /// ## Panics
178  /// This will panic if multiplier is NaN, Infinity, or less than 0.
179  pub fn set_time_scale(&mut self, multiplier: f32) {
180  use std::f32::INFINITY;
181  assert!(multiplier >= 0.0);
182  assert!(multiplier != INFINITY);
183  self.time_scale = multiplier;
184  }
185 
186  /// Restarts the internal fixed update accumulator to the desired fixed
187  /// update delta time.
188  ///
189  /// This should only be called by the engine. Bad things might happen if
190  /// you call this in your game.
191  pub fn start_fixed_update(&mut self) {
192  self.fixed_time_accumulator += self.delta_real_seconds;
193  }
194 
195  /// Checks to see if we should perform another fixed update iteration, and
196  /// if so, returns true and reduces the accumulator.
197  ///
198  /// This should only be called by the engine. Bad things might happen if
199  /// you call this in your game.
200  pub fn step_fixed_update(&mut self) -> bool {
201  if self.fixed_time_accumulator >= self.fixed_seconds {
202  self.fixed_time_accumulator -= self.fixed_seconds;
203  true
204  } else {
205  false
206  }
207  }
208 
209  /// Updates the interpolation alpha factor given the current fixed update
210  /// rate and accumulator.
211  ///
212  /// This should only be called by the engine. Bad things might happen if
213  /// you call this in your game.
214  pub fn finish_fixed_update(&mut self) {
215  self.interpolation_alpha = self.fixed_time_accumulator / self.fixed_seconds;
216  }
217 }
218 
219 impl Default for Time {
220  fn default() -> Time {
221  Time {
222  delta_seconds: 0.0,
223  delta_time: Duration::from_secs(0),
224  delta_real_seconds: 0.0,
225  delta_real_time: Duration::from_secs(0),
226  fixed_seconds: duration_to_secs(Duration::new(0, 16_666_666)),
227  fixed_time: Duration::new(0, 16_666_666),
228  fixed_time_accumulator: 0.0,
229  frame_number: 0,
230  interpolation_alpha: 0.0,
231  absolute_real_time: Duration::default(),
232  absolute_time: Duration::default(),
233  time_scale: 1.0,
234  }
235  }
236 }
237 
238 /// A stopwatch which accurately measures elapsed time.
239 #[derive(Clone, Debug, Eq, PartialEq, Default)]
240 pub enum Stopwatch {
241  /// Initial state with an elapsed time value of 0 seconds.
242  #[default]
243  Waiting,
244  /// Stopwatch has started counting the elapsed time since this `Instant`
245  /// and accumuluated time from previous start/stop cycles `Duration`.
246  Started(Duration, Instant),
247  /// Stopwatch has been stopped and reports the elapsed time `Duration`.
248  Ended(Duration),
249 }
250 
251 impl Stopwatch {
252  /// Creates a new stopwatch.
253  pub fn new() -> Stopwatch {
254  Default::default()
255  }
256 
257  /// Retrieves the elapsed time.
258  pub fn elapsed(&self) -> Duration {
259  match *self {
260  Stopwatch::Waiting => Duration::new(0, 0),
261  Stopwatch::Started(dur, start) => dur + start.elapsed(),
262  Stopwatch::Ended(dur) => dur,
263  }
264  }
265 
266  /// Stops, resets, and starts the stopwatch again.
267  pub fn restart(&mut self) {
268  *self = Stopwatch::Started(Duration::new(0, 0), Instant::now());
269  }
270 
271  /// Starts, or resumes, measuring elapsed time. If the stopwatch has been
272  /// started and stopped before, the new results are compounded onto the
273  /// existing elapsed time value.
274  ///
275  /// Note: Starting an already running stopwatch will do nothing.
276  pub fn start(&mut self) {
277  match *self {
278  Stopwatch::Waiting => self.restart(),
279  Stopwatch::Ended(dur) => {
280  *self = Stopwatch::Started(dur, Instant::now());
281  }
282  _ => {}
283  }
284  }
285 
286  /// Stops measuring elapsed time.
287  ///
288  /// Note: Stopping a stopwatch that isn't running will do nothing.
289  pub fn stop(&mut self) {
290  if let Stopwatch::Started(dur, start) = *self {
291  *self = Stopwatch::Ended(dur + start.elapsed());
292  }
293  }
294 
295  /// Clears the current elapsed time value.
296  pub fn reset(&mut self) {
297  *self = Stopwatch::Waiting;
298  }
299 }
300 
301 /// Converts a Duration to the time in seconds.
302 pub fn duration_to_secs(duration: Duration) -> f32 {
303  duration.as_secs() as f32 + (duration.subsec_nanos() as f32 / 1.0e9)
304 }
305 
306 /// Converts a Duration to the time in seconds in an f64.
307 pub fn duration_to_secs_f64(duration: Duration) -> f64 {
308  duration.as_secs() as f64 + (f64::from(duration.subsec_nanos()) / 1.0e9)
309 }
310 
311 /// Converts a time in seconds to a duration
312 pub fn secs_to_duration(secs: f32) -> Duration {
313  Duration::new(secs as u64, ((secs % 1.0) * 1.0e9) as u32)
314 }
315 
316 /// Converts a Duration to nanoseconds
317 pub fn duration_to_nanos(duration: Duration) -> u64 {
318  (duration.as_secs() * 1_000_000_000) + u64::from(duration.subsec_nanos())
319 }
320 
321 /// Converts nanoseconds to a Duration
322 pub fn nanos_to_duration(nanos: u64) -> Duration {
323  Duration::new(nanos / 1_000_000_000, (nanos % 1_000_000_000) as u32)
324 }
325 
326 // Unit tests
327 #[cfg(test)]
328 mod tests {
329  use std::{thread, time::Duration};
330 
331  use super::Stopwatch;
332 
333  // Timing varies more on macOS CI
334  fn get_uncertainty() -> u32 {
335  let is_macos = !std::env::var("MACOS").unwrap_or_default().is_empty();
336  let is_ci = std::env::var("CI").is_ok();
337  if is_macos && is_ci {
338  20
339  } else {
340  10
341  }
342  }
343 
344  #[test]
345  fn elapsed() {
346  const DURATION: u64 = 1; // in seconds.
347  let mut watch = Stopwatch::new();
348  let uncertainty = get_uncertainty();
349 
350  watch.start();
351  thread::sleep(Duration::from_secs(DURATION));
352  watch.stop();
353 
354  // check that elapsed time was DURATION sec +/- UNCERTAINTY%
355  let elapsed = watch.elapsed();
356  let duration = Duration::new(DURATION, 0);
357  let lower = duration / 100 * (100 - uncertainty);
358  let upper = duration / 100 * (100 + uncertainty);
359  assert!(
360  elapsed < upper && elapsed > lower,
361  "expected {} +- {}% seconds, got {:?}",
362  DURATION,
363  uncertainty,
364  elapsed
365  );
366  }
367 
368  #[test]
369  fn reset() {
370  const DURATION: u64 = 2; // in seconds.
371  let mut watch = Stopwatch::new();
372 
373  watch.start();
374  thread::sleep(Duration::from_secs(DURATION));
375  watch.stop();
376  watch.reset();
377 
378  assert_eq!(0, watch.elapsed().subsec_nanos());
379  }
380 
381  #[test]
382  fn restart() {
383  const DURATION0: u64 = 2; // in seconds.
384  const DURATION: u64 = 1; // in seconds.
385  let uncertainty = get_uncertainty(); // in percents.
386  let mut watch = Stopwatch::new();
387 
388  watch.start();
389  thread::sleep(Duration::from_secs(DURATION0));
390  watch.stop();
391 
392  watch.restart();
393  thread::sleep(Duration::from_secs(DURATION));
394  watch.stop();
395 
396  // check that elapsed time was DURATION sec +/- UNCERTAINTY%
397  let elapsed = watch.elapsed();
398  let duration = Duration::new(DURATION, 0);
399  let lower = duration / 100 * (100 - uncertainty);
400  let upper = duration / 100 * (100 + uncertainty);
401  assert!(
402  elapsed < upper && elapsed > lower,
403  "expected {} +- {}% seconds, got {:?}",
404  DURATION,
405  uncertainty,
406  elapsed
407  );
408  }
409 
410  // test that multiple start-stop cycles are cumulative
411  #[test]
412  fn stop_start() {
413  const DURATION: u64 = 3; // in seconds.
414  let uncertainty = get_uncertainty(); // in percents.
415  let mut watch = Stopwatch::new();
416 
417  for _ in 0..DURATION {
418  watch.start();
419  thread::sleep(Duration::from_secs(1));
420  watch.stop();
421  }
422 
423  // check that elapsed time was DURATION sec +/- UNCERTAINTY%
424  let elapsed = watch.elapsed();
425  let duration = Duration::new(DURATION, 0);
426  let lower = duration / 100 * (100 - uncertainty);
427  let upper = duration / 100 * (100 + uncertainty);
428  assert!(
429  elapsed < upper && elapsed > lower,
430  "expected {} +- {}% seconds, got {:?}",
431  DURATION,
432  uncertainty,
433  elapsed
434  );
435  }
436 
437  // Test that fixed_update methods accumulate and return correctly
438  // Test confirms that with a fixed update of 120fps, we run fixed update twice
439  // with the timer Runs at 10 times game speed, which shouldn't affect fixed
440  // updates
441  #[test]
442  fn fixed_update_120fps() {
443  use super::Time;
444 
445  let mut time = Time::default();
446  time.set_fixed_seconds(1.0 / 120.0);
447  time.set_time_scale(10.0);
448 
449  let step = 1.0 / 60.0;
450  let mut fixed_count = 0;
451  for _ in 0..60 {
452  time.set_delta_seconds(step);
453  time.start_fixed_update();
454 
455  while time.step_fixed_update() {
456  fixed_count += 1;
457  }
458 
459  time.finish_fixed_update();
460  }
461 
462  assert_eq!(fixed_count, 120);
463  }
464 
465  // Test that fixed_update methods accumulate and return correctly
466  // Test confirms that with a fixed update every 1 second, it runs every 1
467  // second only
468  #[test]
469  fn fixed_update_1sec() {
470  use super::Time;
471 
472  let mut time = Time::default();
473  time.set_fixed_seconds(1.0);
474 
475  let step = 1.0 / 60.0;
476  let mut fixed_count = 0;
477  for _ in 0..130 {
478  // Run two seconds
479  time.set_delta_seconds(step);
480  time.start_fixed_update();
481 
482  while time.step_fixed_update() {
483  fixed_count += 1;
484  }
485 
486  time.finish_fixed_update();
487  }
488  assert_eq!(fixed_count, 2);
489  }
490 }