use std::time::{Duration, Instant};
use astrelis_core::profiling::profile_function;
#[derive(Debug, Clone)]
pub struct Time {
elapsed: Duration,
delta: Duration,
frame_count: u64,
time_scale: f32,
fixed_timestep: Duration,
fixed_accumulator: Duration,
max_delta: Duration,
start_time: Instant,
last_frame_time: Instant,
}
impl Time {
pub fn new() -> Self {
let now = Instant::now();
Self {
elapsed: Duration::ZERO,
delta: Duration::ZERO,
frame_count: 0,
time_scale: 1.0,
fixed_timestep: Duration::from_millis(20), fixed_accumulator: Duration::ZERO,
max_delta: Duration::from_millis(100), start_time: now,
last_frame_time: now,
}
}
pub fn update(&mut self) {
profile_function!();
let now = Instant::now();
let raw_delta = now.duration_since(self.last_frame_time);
self.delta = raw_delta.min(self.max_delta);
self.elapsed = now.duration_since(self.start_time);
self.last_frame_time = now;
self.frame_count += 1;
self.fixed_accumulator += self.delta;
}
#[inline]
pub fn delta(&self) -> Duration {
self.delta
}
#[inline]
pub fn delta_seconds(&self) -> f32 {
self.delta.as_secs_f32() * self.time_scale
}
#[inline]
pub fn delta_seconds_f64(&self) -> f64 {
self.delta.as_secs_f64() * self.time_scale as f64
}
#[inline]
pub fn elapsed(&self) -> Duration {
self.elapsed
}
#[inline]
pub fn elapsed_seconds(&self) -> f32 {
self.elapsed.as_secs_f32()
}
#[inline]
pub fn elapsed_seconds_f64(&self) -> f64 {
self.elapsed.as_secs_f64()
}
#[inline]
pub fn frame_count(&self) -> u64 {
self.frame_count
}
#[inline]
pub fn time_scale(&self) -> f32 {
self.time_scale
}
#[inline]
pub fn set_time_scale(&mut self, scale: f32) {
self.time_scale = scale.max(0.0);
}
#[inline]
pub fn fixed_timestep(&self) -> Duration {
self.fixed_timestep
}
#[inline]
pub fn fixed_timestep_seconds(&self) -> f32 {
self.fixed_timestep.as_secs_f32()
}
pub fn set_fixed_timestep(&mut self, timestep: Duration) {
self.fixed_timestep = timestep;
}
#[inline]
pub fn should_fixed_update(&self) -> bool {
self.fixed_accumulator >= self.fixed_timestep
}
pub fn consume_fixed_timestep(&mut self) {
if self.fixed_accumulator >= self.fixed_timestep {
self.fixed_accumulator -= self.fixed_timestep;
}
}
pub fn fixed_update_count(&self) -> usize {
let count = self.fixed_accumulator.as_secs_f32() / self.fixed_timestep.as_secs_f32();
(count as usize).min(5) }
pub fn reset_fixed_accumulator(&mut self) {
self.fixed_accumulator = Duration::ZERO;
}
#[inline]
pub fn max_delta(&self) -> Duration {
self.max_delta
}
pub fn set_max_delta(&mut self, max_delta: Duration) {
self.max_delta = max_delta;
}
#[inline]
pub fn start_time(&self) -> Instant {
self.start_time
}
#[inline]
pub fn last_frame_time(&self) -> Instant {
self.last_frame_time
}
#[inline]
pub fn pause(&mut self) {
self.time_scale = 0.0;
}
#[inline]
pub fn resume(&mut self) {
self.time_scale = 1.0;
}
#[inline]
pub fn is_paused(&self) -> bool {
self.time_scale == 0.0
}
}
impl Default for Time {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_time_creation() {
let time = Time::new();
assert_eq!(time.frame_count(), 0);
assert_eq!(time.elapsed(), Duration::ZERO);
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.time_scale(), 1.0);
assert!(!time.is_paused());
}
#[test]
fn test_time_update() {
let mut time = Time::new();
thread::sleep(Duration::from_millis(10));
time.update();
assert_eq!(time.frame_count(), 1);
assert!(time.delta() > Duration::ZERO);
assert!(time.elapsed() > Duration::ZERO);
assert!(time.delta_seconds() > 0.0);
}
#[test]
fn test_time_scale() {
let mut time = Time::new();
thread::sleep(Duration::from_millis(10));
time.update();
let normal_dt = time.delta_seconds();
time.set_time_scale(0.5);
assert_eq!(time.time_scale(), 0.5);
assert!((time.delta_seconds() - normal_dt * 0.5).abs() < 0.001);
time.set_time_scale(2.0);
assert_eq!(time.time_scale(), 2.0);
assert!((time.delta_seconds() - normal_dt * 2.0).abs() < 0.001);
time.pause();
assert!(time.is_paused());
assert_eq!(time.delta_seconds(), 0.0);
time.resume();
assert!(!time.is_paused());
assert_eq!(time.time_scale(), 1.0);
}
#[test]
fn test_fixed_timestep() {
let mut time = Time::new();
time.set_fixed_timestep(Duration::from_millis(16));
assert_eq!(time.fixed_timestep(), Duration::from_millis(16));
assert!((time.fixed_timestep_seconds() - 0.016).abs() < 0.001);
assert!(!time.should_fixed_update());
assert_eq!(time.fixed_update_count(), 0);
thread::sleep(Duration::from_millis(32));
time.update();
assert!(time.should_fixed_update());
let count = time.fixed_update_count();
assert!(count >= 1 && count <= 3);
time.consume_fixed_timestep();
time.reset_fixed_accumulator();
assert!(!time.should_fixed_update());
}
#[test]
fn test_max_delta() {
let mut time = Time::new();
time.set_max_delta(Duration::from_millis(50));
assert_eq!(time.max_delta(), Duration::from_millis(50));
thread::sleep(Duration::from_millis(100));
time.update();
assert!(time.delta() <= Duration::from_millis(50));
}
#[test]
fn test_multiple_frames() {
let mut time = Time::new();
for i in 1..=5 {
thread::sleep(Duration::from_millis(10));
time.update();
assert_eq!(time.frame_count(), i);
assert!(time.elapsed() > Duration::ZERO);
}
}
}