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}