1use std::collections::VecDeque;
2use std::env;
3use std::time::{Duration, Instant};
4
5const LOG_INTERVAL_SECONDS: u64 = 1;
6const LOG_INTERVAL: Duration = Duration::from_secs(LOG_INTERVAL_SECONDS);
7const WINDOW_INTERVAL_SECONDS: u64 = 3;
8const WINDOW_INTERVAL: Duration = Duration::from_secs(WINDOW_INTERVAL_SECONDS);
9
10#[derive(Debug, Clone)]
11pub struct FpsTracker {
12    last_window_time: Instant,
13    last_log_time: Instant,
14    frame_times: VecDeque<Instant>,
15}
16
17impl FpsTracker {
18    pub fn new() -> Self {
19        Self {
20            last_window_time: Instant::now() - (WINDOW_INTERVAL - LOG_INTERVAL),
21            last_log_time: Instant::now(),
22            frame_times: VecDeque::with_capacity((2 * WINDOW_INTERVAL_SECONDS * 60) as usize),
23        }
24    }
25
26    pub fn record_frame(&mut self) {
27        let now = Instant::now();
28        self.frame_times.push_back(now);
29
30        let next_log_time = self.last_log_time + LOG_INTERVAL;
31        if now >= next_log_time {
32            let window_end_time = self.last_window_time + WINDOW_INTERVAL;
33            let mut frame_count = 0;
34            for &time in &self.frame_times {
35                if time >= window_end_time {
36                    break;
37                }
38                frame_count += 1;
39            }
40
41            let next_window_time = self.last_window_time + LOG_INTERVAL;
42            while self.frame_times.front().is_some_and(|&time| time < next_window_time) {
43                self.frame_times.pop_front();
44            }
45
46            self.last_window_time = next_window_time;
47            self.last_log_time = next_log_time;
48
49            // TODO expose FPS in the UI somewhere?
50            if env::var("JGENESIS_LOG_FPS").is_ok_and(|var| !var.is_empty()) {
51                let fps = f64::from(frame_count) / (WINDOW_INTERVAL_SECONDS as f64);
52                log::info!("FPS: {}", fps.round());
53            }
54        }
55    }
56}