use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TickRate {
Hz30,
Hz60,
Hz120,
Hz240,
}
impl TickRate {
#[must_use]
pub fn interval(&self) -> Duration {
match self {
Self::Hz30 => Duration::from_secs_f64(1.0 / 30.0),
Self::Hz60 => Duration::from_secs_f64(1.0 / 60.0),
Self::Hz120 => Duration::from_secs_f64(1.0 / 120.0),
Self::Hz240 => Duration::from_secs_f64(1.0 / 240.0),
}
}
#[must_use]
pub const fn delta_seconds(&self) -> f32 {
match self {
Self::Hz30 => 1.0 / 30.0,
Self::Hz60 => 1.0 / 60.0,
Self::Hz120 => 1.0 / 120.0,
Self::Hz240 => 1.0 / 240.0,
}
}
}
pub struct GameLoop {
frame_cap: u32,
tick_rate: TickRate,
accumulator: Duration,
last_frame: Instant,
running: bool,
}
impl GameLoop {
#[must_use]
pub fn new(frame_cap: u32, tick_rate: TickRate) -> Self {
log::info!("game loop: frame_cap={frame_cap}, tick_rate={tick_rate:?}");
Self {
frame_cap,
tick_rate,
accumulator: Duration::ZERO,
last_frame: Instant::now(),
running: true,
}
}
#[must_use]
pub const fn frame_cap(&self) -> u32 {
self.frame_cap
}
pub fn set_frame_cap(&mut self, frame_cap: u32) {
self.frame_cap = frame_cap;
log::info!("game loop: frame_cap changed to {frame_cap}");
}
#[must_use]
pub const fn tick_rate(&self) -> TickRate {
self.tick_rate
}
pub fn set_tick_rate(&mut self, tick_rate: TickRate) {
self.tick_rate = tick_rate;
self.accumulator = Duration::ZERO;
log::info!("game loop: tick_rate changed to {tick_rate:?}");
}
#[must_use]
pub const fn is_running(&self) -> bool {
self.running
}
pub fn stop(&mut self) {
self.running = false;
}
pub fn tick(&mut self) -> (u32, f32) {
let now = Instant::now();
let delta = now - self.last_frame;
self.last_frame = now;
let frame_delta = delta.as_secs_f32();
self.accumulator += delta;
let tick_interval = self.tick_rate.interval();
let mut ticks = 0u32;
while self.accumulator >= tick_interval {
self.accumulator -= tick_interval;
ticks += 1;
}
(ticks.min(5), frame_delta)
}
#[must_use]
pub fn interpolation_alpha(&self) -> f32 {
let interval = self.tick_rate.interval().as_secs_f32();
(self.accumulator.as_secs_f32() / interval).clamp(0.0, 1.0)
}
pub fn apply_frame_cap(&self) {
#[cfg(target_arch = "wasm32")]
return;
#[cfg(not(target_arch = "wasm32"))]
{
if self.frame_cap == 0 {
return;
}
let frame_duration = Duration::from_secs_f64(1.0 / f64::from(self.frame_cap));
let elapsed = self.last_frame.elapsed();
if elapsed < frame_duration {
let remaining = frame_duration - elapsed;
if remaining > Duration::from_millis(1) {
std::thread::sleep(remaining - Duration::from_millis(1));
}
while self.last_frame.elapsed() < frame_duration {
std::hint::spin_loop();
}
}
}
}
}