use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "bevy_ecs", derive(bevy_ecs::system::Resource))]
pub struct Time {
startup_time: Instant,
last_update: Instant,
current_time: Instant,
delta_time: Duration,
elapsed_time: Duration,
frame_count: u64,
first_update: bool,
}
impl Default for Time {
fn default() -> Self {
Self::new()
}
}
impl Time {
pub fn new() -> Self {
let now = Instant::now();
Self {
startup_time: now,
last_update: now,
current_time: now,
delta_time: Duration::ZERO,
elapsed_time: Duration::ZERO,
frame_count: 0,
first_update: true,
}
}
pub fn update(&mut self) {
let now = Instant::now();
if self.first_update {
self.first_update = false;
self.delta_time = Duration::ZERO;
} else {
self.delta_time = now.duration_since(self.current_time);
}
self.last_update = self.current_time;
self.current_time = now;
self.elapsed_time = now.duration_since(self.startup_time);
self.frame_count += 1;
}
pub fn delta(&self) -> Duration {
self.delta_time
}
pub fn delta_seconds(&self) -> f32 {
self.delta_time.as_secs_f32()
}
pub fn delta_seconds_f64(&self) -> f64 {
self.delta_time.as_secs_f64()
}
pub fn delta_millis(&self) -> u128 {
self.delta_time.as_millis()
}
pub fn elapsed(&self) -> Duration {
self.elapsed_time
}
pub fn elapsed_seconds(&self) -> f32 {
self.elapsed_time.as_secs_f32()
}
pub fn elapsed_seconds_f64(&self) -> f64 {
self.elapsed_time.as_secs_f64()
}
pub fn frame_count(&self) -> u64 {
self.frame_count
}
pub fn fps(&self) -> f64 {
if self.elapsed_time.is_zero() || self.frame_count == 0 {
0.0
} else {
self.frame_count as f64 / self.elapsed_seconds_f64()
}
}
pub fn instant_fps(&self) -> f64 {
if self.delta_time.is_zero() {
0.0
} else {
1.0 / self.delta_seconds_f64()
}
}
pub fn startup_time(&self) -> Instant {
self.startup_time
}
pub fn current_time(&self) -> Instant {
self.current_time
}
pub fn is_first_frame(&self) -> bool {
self.frame_count == 0
}
pub fn reset(&mut self) {
let now = Instant::now();
self.startup_time = now;
self.last_update = now;
self.current_time = now;
self.delta_time = Duration::ZERO;
self.elapsed_time = Duration::ZERO;
self.frame_count = 0;
self.first_update = true;
}
pub fn with_scale(&self, scale: f32) -> ScaledTime {
ScaledTime::new(self.clone(), scale)
}
}
#[derive(Debug, Clone)]
pub struct ScaledTime {
inner: Time,
scale: f32,
}
impl ScaledTime {
pub fn new(time: Time, scale: f32) -> Self {
Self {
inner: time,
scale,
}
}
pub fn scale(&self) -> f32 {
self.scale
}
pub fn set_scale(&mut self, scale: f32) {
self.scale = scale;
}
pub fn delta(&self) -> Duration {
if self.scale >= 0.0 {
Duration::from_secs_f32(self.inner.delta_seconds() * self.scale)
} else {
Duration::ZERO
}
}
pub fn delta_seconds(&self) -> f32 {
self.inner.delta_seconds() * self.scale
}
pub fn inner(&self) -> &Time {
&self.inner
}
pub fn inner_mut(&mut self) -> &mut Time {
&mut self.inner
}
pub fn update(&mut self) {
self.inner.update();
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use approx::assert_relative_eq;
#[test]
fn test_time_creation() {
let time = Time::new();
assert_eq!(time.frame_count(), 0);
assert_eq!(time.delta_seconds(), 0.0);
assert!(time.is_first_frame());
}
#[test]
fn test_time_update() {
let mut time = Time::new();
time.update();
assert_eq!(time.frame_count(), 1);
assert_eq!(time.delta_seconds(), 0.0); assert!(!time.is_first_frame());
std::thread::sleep(Duration::from_millis(10));
time.update();
assert_eq!(time.frame_count(), 2);
assert!(time.delta_seconds() > 0.0);
assert!(time.elapsed_seconds() > 0.0);
}
#[test]
fn test_fps_calculation() {
let mut time = Time::new();
for _ in 0..10 {
std::thread::sleep(Duration::from_millis(16)); time.update();
}
let fps = time.fps();
assert!(fps > 30.0 && fps < 120.0, "FPS {:.1} out of expected range", fps);
let instant_fps = time.instant_fps();
assert!(instant_fps > 0.0);
}
#[test]
fn test_time_reset() {
let mut time = Time::new();
time.update();
time.update();
assert_eq!(time.frame_count(), 2);
time.reset();
assert_eq!(time.frame_count(), 0);
assert!(time.is_first_frame());
}
#[test]
fn test_scaled_time() {
let mut time = Time::new();
std::thread::sleep(Duration::from_millis(10));
time.update();
let original_delta = time.delta_seconds();
let scaled_time = time.with_scale(0.5);
assert_eq!(scaled_time.scale(), 0.5);
assert_relative_eq!(scaled_time.delta_seconds(), original_delta * 0.5, epsilon = 1e-6);
}
#[test]
fn test_time_precision() {
let mut time = Time::new();
time.update();
std::thread::sleep(Duration::from_millis(50));
time.update();
let delta_f32 = time.delta_seconds();
let delta_f64 = time.delta_seconds_f64();
let delta_millis = time.delta_millis();
assert!(delta_f32 > 0.0, "delta_f32 should be positive, got: {}", delta_f32);
assert!(delta_f64 > 0.0, "delta_f64 should be positive, got: {}", delta_f64);
assert!(delta_millis > 0, "delta_millis should be positive, got: {}", delta_millis);
assert!(delta_f32 >= 0.01 && delta_f32 <= 0.2, "delta_f32 out of expected range: {}", delta_f32);
assert!(delta_f64 >= 0.01 && delta_f64 <= 0.2, "delta_f64 out of expected range: {}", delta_f64);
}
#[test]
fn test_time_consistency() {
let mut time = Time::new();
let start_time = time.startup_time();
std::thread::sleep(Duration::from_millis(50));
time.update();
assert_eq!(time.startup_time(), start_time);
assert!(time.current_time() > start_time);
assert!(time.elapsed() > Duration::ZERO);
let manual_elapsed = time.current_time().duration_since(start_time);
let reported_elapsed = time.elapsed();
let diff = if manual_elapsed > reported_elapsed {
manual_elapsed - reported_elapsed
} else {
reported_elapsed - manual_elapsed
};
assert!(diff < Duration::from_millis(1));
}
#[test]
fn test_time_multiple_resets() {
let mut time = Time::new();
for _ in 0..5 {
time.update();
time.reset();
assert_eq!(time.frame_count(), 0);
assert!(time.is_first_frame());
}
}
#[test]
fn test_time_first_update_zero_delta() {
let mut time = Time::new();
time.update();
assert_eq!(time.delta(), Duration::ZERO);
assert_eq!(time.delta_seconds(), 0.0);
}
#[test]
fn test_time_elapsed_monotonic() {
let mut time = Time::new();
let mut prev_elapsed = Duration::ZERO;
for _ in 0..10 {
time.update();
assert!(time.elapsed() >= prev_elapsed);
prev_elapsed = time.elapsed();
}
}
#[test]
fn test_scaled_time_zero_scale() {
let mut time = Time::new();
std::thread::sleep(Duration::from_millis(10));
time.update();
let scaled = time.with_scale(0.0);
assert_eq!(scaled.delta_seconds(), 0.0);
assert_eq!(scaled.delta(), Duration::ZERO);
}
#[test]
fn test_scaled_time_negative_scale() {
let mut time = Time::new();
time.update();
std::thread::sleep(Duration::from_millis(10));
time.update();
let scaled = time.with_scale(-1.0);
assert_eq!(scaled.delta(), Duration::ZERO);
assert!(scaled.delta_seconds() < 0.0);
}
#[test]
fn test_scaled_time_double_speed() {
let mut time = Time::new();
std::thread::sleep(Duration::from_millis(10));
time.update();
let original_delta = time.delta_seconds();
let scaled = time.with_scale(2.0);
assert_relative_eq!(scaled.delta_seconds(), original_delta * 2.0, epsilon = 1e-6);
}
#[test]
fn test_scaled_time_set_scale() {
let time = Time::new();
let mut scaled = time.with_scale(1.0);
assert_eq!(scaled.scale(), 1.0);
scaled.set_scale(0.5);
assert_eq!(scaled.scale(), 0.5);
}
#[test]
fn test_scaled_time_update() {
let time = Time::new();
let mut scaled = time.with_scale(1.0);
assert_eq!(scaled.inner().frame_count(), 0);
scaled.update();
assert_eq!(scaled.inner().frame_count(), 1);
}
#[test]
fn test_time_default() {
let time = Time::default();
assert_eq!(time.frame_count(), 0);
assert!(time.is_first_frame());
}
#[test]
fn test_time_fps_zero_when_no_frames() {
let time = Time::new();
assert_eq!(time.fps(), 0.0);
assert_eq!(time.instant_fps(), 0.0);
}
}