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