main_game_loop 0.6.1

A tool collection for building a winit game loop
Documentation
use instant::Instant;
use std::{
    hash::{Hash, Hasher},
    ops::Deref,
    time::Duration,
};

//

#[derive(Debug, Clone, Copy)]
pub struct Reporter {
    count: u32,
    elapsed: Duration,
    report_timer: Instant,
    report_interval: Duration,

    last_interval: Option<Duration>,
    last_per_second: Option<f64>,
}

#[derive(Debug, PartialEq, Eq, Hash)]
pub struct Timer {
    begin: Instant,
}

//

impl Reporter {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn new_with_interval(report_interval: Duration) -> Self {
        Self {
            count: 0_u32,
            elapsed: Duration::default(),
            report_timer: Instant::now(),
            report_interval,

            last_interval: None,
            last_per_second: None,
        }
    }

    pub fn begin(&self) -> Timer {
        Timer {
            begin: Instant::now(),
        }
    }

    pub fn end(&mut self, timer: Timer) {
        self.elapsed += timer.begin.elapsed();
        self.count += 1;
    }

    pub fn time<T, F>(&mut self, f: F) -> T
    where
        F: FnOnce() -> T,
    {
        let timer = self.begin();
        let result = f();
        self.end(timer);
        result
    }

    pub fn should_report(&mut self) -> bool {
        self.report_timer.elapsed() >= self.report_interval
    }

    pub fn report_interval(&self) -> Duration {
        self.report_interval
    }

    pub fn report_all<'a, I>(label: &str, reporters: I) -> String
    where
        I: IntoIterator<Item = (&'static str, &'a mut Self)>,
    {
        #[cfg(debug_assertions)]
        const DEBUG: &str = "debug build";
        #[cfg(not(debug_assertions))]
        const DEBUG: &str = "release build";

        let reporters: Vec<_> = reporters
            .into_iter()
            .map(|(label, reporter)| {
                reporter.reset();
                (label, reporter.last_string())
            })
            .collect();

        let max_label_width = reporters
            .iter()
            .map(|(label, (_, time_per))| label.len() + time_per.len())
            .max()
            .unwrap_or(0)
            .max(7);
        let padding = " ".repeat(max_label_width - 7);
        let first = format!("Report {label} ({DEBUG})\n{padding}per second @ time per\n");

        Some(first)
            .into_iter()
            .chain(reporters.iter().map(|(label, (int, per_sec))| {
                let padding = " ".repeat(max_label_width - label.len() - per_sec.len() + 1);
                format!("{label}: {padding}{per_sec} @ {int}\n")
            }))
            .collect()
    }

    pub fn reset(&mut self) {
        let avg = self.elapsed.checked_div(self.count);
        let fps = self.count as f64 / self.report_interval.as_secs_f64();

        self.count = 0;
        self.elapsed = Duration::default();
        self.report_timer = Instant::now();
        self.last_interval = avg;
        self.last_per_second = Some(fps);
    }

    pub fn last(&self) -> Option<(Duration, f64)> {
        Some((self.last_interval?, self.last_per_second?))
    }

    pub fn last_string(&self) -> (String, String) {
        self.last_string_prec(4, 1)
    }

    pub fn last_string_prec(&self, int_prec: usize, per_sec_prec: usize) -> (String, String) {
        (
            self.last_interval
                .map(|ft| format!("{ft:.int_prec$?}"))
                .unwrap_or_else(|| "...".into()),
            self.last_per_second
                .map(|ps| format!("{ps:.per_sec_prec$}"))
                .unwrap_or_else(|| "...".into()),
        )
    }
}

impl Default for Reporter {
    fn default() -> Self {
        Self::new_with_interval(Duration::from_secs(3))
    }
}

impl PartialEq for Reporter {
    fn eq(&self, other: &Self) -> bool {
        self.count == other.count
            && self.elapsed == other.elapsed
            && self.report_timer == other.report_timer
            && self.report_interval == other.report_interval
    }
}

impl Eq for Reporter {}

impl Hash for Reporter {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.count.hash(state);
        self.elapsed.hash(state);
        self.report_timer.hash(state);
        self.report_interval.hash(state);
    }
}

impl Deref for Timer {
    type Target = Instant;

    fn deref(&self) -> &Self::Target {
        &self.begin
    }
}