use std::{
collections::VecDeque,
time::{Duration, Instant},
};
use smallvec::{smallvec, SmallVec};
use crate::{utils::Timing, Handle, Module};
use super::{Schedule, Scheduler};
const CACHED_DELTA_TIMES_COUNT: usize = 20;
#[derive(Debug)]
pub struct Time {
frame_count: usize,
last_frame: Instant,
delta_time: Duration,
total_time: Duration,
start_time: Instant,
delta_times: VecDeque<Duration>,
stats: TimeStats,
scheduler: Handle<Scheduler>,
}
impl Module for Time {
type Config = ();
type Dependencies = Handle<Scheduler>;
fn new(_config: Self::Config, deps: Self::Dependencies) -> anyhow::Result<Self> {
let mut delta_times = VecDeque::new();
delta_times.push_back(Duration::from_millis(10));
Ok(Time {
start_time: Instant::now(),
total_time: Duration::ZERO,
frame_count: 0,
last_frame: Instant::now() - Duration::from_millis(10),
delta_time: Duration::from_millis(10),
delta_times,
stats: TimeStats::default(),
scheduler: deps,
})
}
fn intialize(mut self_handle: crate::Handle<Self>) -> anyhow::Result<()> {
self_handle.start_time = Instant::now();
self_handle.last_frame = Instant::now();
let scheduler = self_handle.scheduler.get_mut();
scheduler.register(
self_handle,
Schedule::Update,
Timing::VERY_EARLY - 10,
Self::update,
);
Ok(())
}
}
#[derive(Debug, Default)]
pub struct TimeStats {
fps: Stats,
delta_ms: Stats,
}
#[derive(Debug, Default)]
pub struct Stats {
pub max: f64,
pub min: f64,
pub avg: f64,
pub std: f64,
}
impl Time {
pub fn fps(&self) -> f64 {
self.stats.fps.avg
}
pub fn delta(&self) -> &Duration {
&self.delta_time
}
pub fn total(&self) -> &Duration {
&self.total_time
}
pub fn frame_count(&self) -> usize {
self.frame_count
}
pub fn update(&mut self) {
self.total_time = Instant::now() - self.start_time;
let this_frame = Instant::now();
if self.delta_times.len() >= CACHED_DELTA_TIMES_COUNT {
self.delta_times.pop_back();
}
self.delta_time = this_frame.duration_since(self.last_frame);
self.delta_times.push_front(self.delta_time);
self.last_frame = this_frame;
self.frame_count += 1;
self.stats.recalculate(&self.delta_times);
}
pub fn egui_time_stats(&mut self, mut egui_ctx: egui::Context) {
egui::Window::new("Time Stats").show(&mut egui_ctx, |ui| {
ui.label(format!(
"{} fps / {:.1} ms",
self.stats.fps.avg as i64, self.stats.delta_ms.avg,
));
if ui.button("Log Time Stats").clicked() {
dbg!(&self);
}
});
}
}
impl TimeStats {
fn recalculate(&mut self, delta_times: &VecDeque<Duration>) {
assert!(!delta_times.is_empty());
assert!(delta_times.len() <= CACHED_DELTA_TIMES_COUNT);
let mut delta_ms: SmallVec<[f64; CACHED_DELTA_TIMES_COUNT]> = smallvec![];
let mut fps: SmallVec<[f64; CACHED_DELTA_TIMES_COUNT]> = smallvec![];
for d in delta_times {
let secs = d.as_secs_f64();
delta_ms.push(secs * 1000.0);
fps.push(1.0 / secs);
}
self.delta_ms = Stats::new(&delta_ms);
self.fps = Stats::new(&fps);
}
}
impl Stats {
fn new(nums: &[f64]) -> Self {
let mut max: f64 = f64::NAN;
let mut min: f64 = f64::NAN;
let mut sum: f64 = 0.0;
let mut sqsum: f64 = 0.0;
for e in nums {
sum += *e;
sqsum += *e * *e;
if !(*e < max) {
max = *e;
}
if !(*e > min) {
min = *e;
}
}
let len = nums.len() as f64;
let avg = sum / len;
let var = (sqsum / len) - ((sum / len) * (sum / len));
let std = var.sqrt();
Stats { max, min, avg, std }
}
}