time_tick/lib.rs
1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/piot/time-tick
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5
6//! # Time Tick
7//!
8//! `time-tick` is a Rust crate designed to manage and calculate time intervals for game loops.
9//! It provides a simple interface to track elapsed time and determine how many ticks should
10//! be processed in a single update cycle. This is particularly useful in games or simulations
11//! that require consistent timing for updates and actions.
12//!
13//! ## Features
14//! - Track time intervals for game updates.
15//! - Specify tick durations and maximum ticks per update.
16//! - Reset and adjust tick timing dynamically.
17//! ## Usage
18//!
19//! To use this crate, add the following to your `Cargo.toml`:
20//!
21//! ```toml
22//! [dependencies]
23//! time-tick = "0.1" // Replace with the latest version
24//! ```
25
26use monotonic_time_rs::{Millis, MillisDuration};
27
28/// A struct to manage and calculate the number of ticks for a game loop based on elapsed time.
29///
30/// The `TimeTick` struct is designed to track time intervals for game updates, allowing you to
31/// specify a duration for each tick and the maximum number of ticks that can occur in a single
32/// update. This is particularly useful for games that require consistent timing for game logic
33/// updates.
34#[derive(Debug)]
35pub struct TimeTick {
36 tick_duration: MillisDuration,
37 consumed_absolute_time: Millis,
38 max_tick_per_update: u16,
39}
40
41impl TimeTick {
42 /// Creates a new `TimeTick` instance.
43 ///
44 /// # Arguments
45 ///
46 /// * `now` - The current absolute time in milliseconds.
47 /// * `tick_duration` - The duration of each tick in milliseconds.
48 /// * `max_tick_per_update` - The maximum number of ticks that can be processed in a single update.
49 ///
50 /// # Returns
51 ///
52 /// A new instance of `TimeTick` initialized with the provided values.
53 pub const fn new(now: Millis, tick_duration: MillisDuration, max_tick_per_update: u16) -> Self {
54 TimeTick {
55 tick_duration,
56 consumed_absolute_time: now,
57 max_tick_per_update,
58 }
59 }
60
61 /// Sets a new tick duration.
62 ///
63 /// This method allows you to change the duration of each tick dynamically.
64 ///
65 /// # Arguments
66 ///
67 /// * `tick_duration` - The new duration for each tick in milliseconds.
68 pub fn set_tick_duration(&mut self, tick_duration: MillisDuration) {
69 self.tick_duration = tick_duration;
70 }
71
72 /// Resets the consumed absolute time to the current time.
73 ///
74 /// This method is useful when you want to restart the timing calculations, for example,
75 /// when restarting a game or resetting the state.
76 ///
77 /// # Arguments
78 ///
79 /// * `now` - The current absolute time in milliseconds to reset to.
80 pub fn reset(&mut self, now: Millis) {
81 self.consumed_absolute_time = now;
82 }
83
84 /// Calculates the number of ticks that should be performed based on the elapsed time since the last update.
85 ///
86 /// This method should be called each frame in the game loop. It computes how many ticks can
87 /// be processed based on the time that has passed since the last update, clamping the result
88 /// to the maximum ticks allowed per update.
89 ///
90 /// # Arguments
91 ///
92 /// * `now` - The current absolute time in milliseconds.
93 ///
94 /// # Returns
95 ///
96 /// The number of ticks that should be performed this frame.
97 #[inline]
98 pub fn calculate_ticks(&mut self, now: Millis) -> u16 {
99 let time_ahead = now - self.consumed_absolute_time;
100 let tick_count = (time_ahead.as_millis() / self.tick_duration.as_millis()) as u16;
101 // trace!("time ahead is: {time_ahead} tick_count:{tick_count}");
102 if tick_count >= self.max_tick_per_update {
103 self.max_tick_per_update
104 } else {
105 tick_count
106 }
107 }
108
109 /// Updates the consumed absolute time based on the number of ticks performed.
110 ///
111 /// This method should be called after processing the ticks to adjust the internal timer
112 /// accordingly. It calculates the new consumed absolute time based on the ticks that have
113 /// been processed in this update.
114 ///
115 /// In most cases, the value passed to this method should be the same as the number of ticks
116 /// returned by `calculate_ticks`. However, it can be a lower value if fewer ticks were actually
117 /// processed (e.g., due to frame rate limitations or logic constraints). It is important to note
118 /// that this value must not exceed the number of ticks returned by `calculate_ticks`.
119 ///
120 /// # Arguments
121 ///
122 /// * `tick_count` - The number of ticks that were performed during this update. This value should
123 /// typically match the value returned by `calculate_ticks`, but can be lower in certain cases.
124 #[inline]
125 pub fn performed_ticks(&mut self, tick_count: u16) {
126 self.consumed_absolute_time +=
127 MillisDuration::from_millis(tick_count as u64 * self.tick_duration.as_millis())
128 }
129}