tiny_game_loop/
lib.rs

1#![no_std]
2#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
3#![warn(missing_docs)]
4
5use core::time::Duration;
6
7/// Implemented based on <https://gafferongames.com/post/fix_your_timestep>.
8#[derive(Clone, Debug)]
9pub struct GameLoop {
10    target_frame_time: Duration,
11    max_frame_time: Duration,
12    accumulated_time: Duration,
13
14    total_num_updates: u64,
15    total_time_passed: Duration,
16}
17
18impl GameLoop {
19    /// Create a new `GameLoop` instance.
20    #[inline]
21    pub fn new(target_frame_time: Duration, max_frame_time: Duration) -> Self {
22        Self {
23            target_frame_time,
24            max_frame_time,
25            accumulated_time: Duration::ZERO,
26
27            total_num_updates: 0,
28            total_time_passed: Duration::ZERO,
29        }
30    }
31
32    /// Create a new `GameLoop` instance specifying FPS instead of frame time for convenience.
33    #[inline]
34    pub fn new_with_fps(fps: u32, max_frame_time: Duration) -> Self {
35        Self::new(Duration::from_secs_f64(1. / fps as f64), max_frame_time)
36    }
37
38    /// Set the desired (minimum) time between application updates.
39    #[inline]
40    pub fn set_target_frame_time(&mut self, time: Duration) {
41        self.target_frame_time = time;
42    }
43
44    /// Set the desired (maximum) FPS. Overrides `target_frame_time` since they are inversions of each other.
45    #[inline]
46    pub fn set_fps(&mut self, fps: u32) {
47        self.target_frame_time = Duration::from_secs_f64(1. / fps as f64);
48    }
49
50    /// Set the maximum time between application updates.
51    /// The real time can still be longer.
52    #[inline]
53    pub fn set_max_frame_time(&mut self, time: Duration) {
54        self.max_frame_time = time;
55    }
56
57    /// Perform all calculations for an update.
58    /// 
59    /// You can do something like:
60    /// 
61    /// ```
62    /// loop {
63    ///     // handling events, input or whatever
64    /// 
65    ///     let elapsed = instance.elapsed(); // using `std::time::Instance` to measure time between updates
66    ///     instance = Instance::now();
67    /// 
68    ///     let update_result = game_loop.update(elapsed).run(|update_result| {
69    ///         // your actual update logic
70    ///     });
71    /// 
72    ///     // rendering logic
73    /// }
74    /// ```
75    pub fn update(&mut self, elapsed: Duration) -> UpdateResult {
76        self.total_time_passed += elapsed;
77
78        self.accumulated_time += if elapsed > self.max_frame_time {
79            self.max_frame_time
80        } else {
81            elapsed
82        };
83
84        let mut num_updates = 0;
85
86        while self.accumulated_time >= self.target_frame_time {
87            self.accumulated_time -= self.target_frame_time;
88            num_updates += 1;
89        }
90
91        self.total_num_updates += num_updates;
92
93        let blending_factor =
94            self.accumulated_time.as_secs_f64() / self.target_frame_time.as_secs_f64();
95
96        UpdateResult {
97            num_updates,
98            total_num_updates: self.total_num_updates,
99
100            frame_time: self.target_frame_time,
101            blending_factor,
102
103            total_time_passed: self.total_time_passed,
104
105            exit: false,
106        }
107    }
108}
109
110/// The result of calling [`GameLoop::update`].
111#[derive(Clone, Debug)]
112pub struct UpdateResult {
113    /// The number of updates.
114    pub num_updates: u64,
115    /// Total number of updates since [`GameLoop`]'s creation.
116    pub total_num_updates: u64,
117
118    /// Time between previous and current update.
119    pub frame_time: Duration,
120    /// Blending between current and next frames. Primarily useful for rendering.
121    pub blending_factor: f64,
122
123    /// Total time passed since [`GameLoop`]'s creation.
124    /// This is a sum of the provided `elapsed` arguments.
125    pub total_time_passed: Duration,
126
127    /// Whether to exit next iteration.
128    /// This is only useful in [`UpdateResult::run()`] or [`UpdateResult::run_result()`].
129    pub exit: bool,
130}
131
132impl UpdateResult {
133    /// Run the provided function [`UpdateResult::num_updates`] times.
134    /// Aborts early if [`UpdateResult::exit`] is true.
135    /// 
136    /// Returns `self` for convenience.
137    #[inline]
138    pub fn run<F>(mut self, mut func: F) -> Self
139    where
140        F: FnMut(&mut Self),
141    {
142        for _i in 0..self.num_updates {
143            (func)(&mut self);
144
145            if self.exit {
146                break;
147            }
148        }
149
150        self
151    }
152
153    /// Run the provided function [`UpdateResult::num_updates`] times.
154    /// Aborts early if `func` returns `Err` or [`UpdateResult::exit`] is true.
155    /// 
156    /// Returns `self` for convenience.
157    #[inline]
158    pub fn run_result<F, E>(mut self, mut func: F) -> Result<Self, E>
159    where
160        F: FnMut(&mut Self) -> Result<(), E>,
161    {
162        for _i in 0..self.num_updates {
163            (func)(&mut self)?;
164
165            if self.exit {
166                break;
167            }
168        }
169
170        Ok(self)
171    }
172}