#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct Time {
dt: f32,
raw_dt: f32,
elapsed: f64,
frame_count: u64,
time_scale: f32,
max_dt: f32,
}
const DEFAULT_MAX_DT: f32 = 1.0 / 20.0;
impl Time {
pub fn new() -> Self {
Self {
dt: 0.0,
raw_dt: 0.0,
elapsed: 0.0,
frame_count: 0,
time_scale: 1.0,
max_dt: DEFAULT_MAX_DT,
}
}
pub fn update(&mut self, raw_dt: f32) {
self.raw_dt = raw_dt.max(0.0); self.dt = (self.raw_dt * self.time_scale).min(self.max_dt);
self.elapsed += self.dt as f64;
self.frame_count += 1;
}
#[inline]
pub fn dt(&self) -> f32 {
self.dt
}
#[inline]
pub fn raw_dt(&self) -> f32 {
self.raw_dt
}
#[inline]
pub fn elapsed(&self) -> f64 {
self.elapsed
}
#[inline]
pub fn frame(&self) -> u64 {
self.frame_count
}
#[inline]
pub fn time_scale(&self) -> f32 {
self.time_scale
}
#[inline]
pub fn fps(&self) -> f32 {
if self.raw_dt > 0.0 {
1.0 / self.raw_dt
} else {
0.0
}
}
pub fn set_time_scale(&mut self, scale: f32) {
self.time_scale = scale.max(0.0);
}
pub fn set_max_dt(&mut self, max: f32) {
self.max_dt = max.max(0.001); }
}
impl Default for Time {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct PhysicsTime {
fixed_dt: f32,
accumulator: f32,
max_accumulator: f32,
step_count: u64,
physics_elapsed: f64,
alpha: f32,
}
impl PhysicsTime {
pub fn new(hz: u32) -> Self {
let fixed_dt = 1.0 / hz as f32;
Self {
fixed_dt,
accumulator: 0.0,
max_accumulator: fixed_dt * 8.0, step_count: 0,
physics_elapsed: 0.0,
alpha: 0.0,
}
}
pub fn accumulate(&mut self, render_dt: f32) {
self.accumulator += render_dt;
if self.accumulator > self.max_accumulator {
self.accumulator = self.max_accumulator;
}
}
#[inline]
pub fn should_step(&self) -> bool {
self.accumulator >= self.fixed_dt
}
pub fn consume_step(&mut self) {
self.accumulator -= self.fixed_dt;
self.step_count += 1;
self.physics_elapsed += self.fixed_dt as f64;
}
pub fn compute_alpha(&mut self) {
self.alpha = self.accumulator / self.fixed_dt;
}
#[inline]
pub fn fixed_dt(&self) -> f32 {
self.fixed_dt
}
#[inline]
pub fn alpha(&self) -> f32 {
self.alpha
}
#[inline]
pub fn step_count(&self) -> u64 {
self.step_count
}
#[inline]
pub fn physics_elapsed(&self) -> f64 {
self.physics_elapsed
}
#[inline]
pub fn accumulator(&self) -> f32 {
self.accumulator
}
pub fn set_hz(&mut self, hz: u32) {
self.fixed_dt = 1.0 / hz.max(1) as f32;
self.max_accumulator = self.fixed_dt * 8.0;
}
}
impl Default for PhysicsTime {
fn default() -> Self {
Self::new(60) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_basic_update() {
let mut time = Time::new();
time.update(0.016);
assert!((time.dt() - 0.016).abs() < 0.0001);
assert!((time.raw_dt() - 0.016).abs() < 0.0001);
assert!((time.elapsed() - 0.016).abs() < 0.001);
assert_eq!(time.frame(), 1);
}
#[test]
fn test_dt_clamp() {
let mut time = Time::new();
time.update(1.0);
assert!(time.dt() <= DEFAULT_MAX_DT + 0.0001);
assert!((time.raw_dt() - 1.0).abs() < 0.0001);
}
#[test]
fn test_negative_dt_clamped_to_zero() {
let mut time = Time::new();
time.update(-0.5);
assert_eq!(time.dt(), 0.0);
assert_eq!(time.raw_dt(), 0.0);
}
#[test]
fn test_time_scale() {
let mut time = Time::new();
time.set_time_scale(0.5);
time.update(0.016);
assert!((time.dt() - 0.008).abs() < 0.0001); assert!((time.raw_dt() - 0.016).abs() < 0.0001);
}
#[test]
fn test_time_scale_zero_is_pause() {
let mut time = Time::new();
time.set_time_scale(0.0);
time.update(0.016);
assert_eq!(time.dt(), 0.0);
assert_eq!(time.elapsed(), 0.0);
assert_eq!(time.frame(), 1); }
#[test]
fn test_elapsed_accumulates() {
let mut time = Time::new();
for _ in 0..100 {
time.update(0.01);
}
assert!((time.elapsed() - 1.0).abs() < 0.01);
assert_eq!(time.frame(), 100);
}
#[test]
fn test_fps() {
let mut time = Time::new();
time.update(1.0 / 60.0);
assert!((time.fps() - 60.0).abs() < 1.0);
time.update(0.0);
assert_eq!(time.fps(), 0.0); }
#[test]
fn test_custom_max_dt() {
let mut time = Time::new();
time.set_max_dt(1.0 / 10.0); time.update(0.5);
assert!((time.dt() - 0.1).abs() < 0.0001); }
#[test]
fn test_serde_derive() {
let mut time = Time::new();
time.update(0.016);
time.update(0.016);
let cloned = time;
assert_eq!(cloned.frame(), time.frame());
assert!((cloned.elapsed() - time.elapsed()).abs() < 0.001);
}
#[test]
fn test_physics_time_basic_step() {
let mut pt = PhysicsTime::new(60);
assert!(!pt.should_step());
pt.accumulate(1.0 / 60.0); assert!(pt.should_step());
pt.consume_step();
assert!(!pt.should_step());
assert_eq!(pt.step_count(), 1);
}
#[test]
fn test_physics_time_multiple_steps() {
let mut pt = PhysicsTime::new(60);
let fixed_dt = pt.fixed_dt();
pt.accumulate(fixed_dt * 3.5);
let mut steps = 0;
while pt.should_step() {
pt.consume_step();
steps += 1;
}
assert_eq!(steps, 3);
assert_eq!(pt.step_count(), 3);
}
#[test]
fn test_physics_time_spiral_of_death() {
let mut pt = PhysicsTime::new(60);
pt.accumulate(1.0);
let mut steps = 0;
while pt.should_step() {
pt.consume_step();
steps += 1;
}
assert!(
steps <= 8,
"Spiral koruması: max 8 adım, bulundu: {}",
steps
);
}
#[test]
fn test_physics_time_alpha() {
let mut pt = PhysicsTime::new(60);
let fixed_dt = 1.0 / 60.0;
pt.accumulate(fixed_dt * 1.5);
pt.consume_step(); pt.compute_alpha();
assert!(
(pt.alpha() - 0.5).abs() < 0.01,
"Alpha ≈ 0.5: {}",
pt.alpha()
);
}
#[test]
fn test_physics_time_elapsed() {
let mut pt = PhysicsTime::new(60);
for _ in 0..60 {
pt.accumulate(1.0 / 60.0);
while pt.should_step() {
pt.consume_step();
}
}
assert!((pt.physics_elapsed() - 1.0).abs() < 0.001);
}
#[test]
fn test_physics_time_set_hz() {
let mut pt = PhysicsTime::new(60);
pt.set_hz(120);
assert!((pt.fixed_dt() - 1.0 / 120.0).abs() < 1e-6);
}
}