pixelpwnr_render/
fps_counter.rs

1use std::time::{Duration, Instant};
2
3/// The maximum number of frame times that is stored.
4const FRAME_BUFFER_MAX: usize = 512;
5
6/// The maximum time in milliseconds a frame time is remembered.
7const FRAME_TTL: Duration = Duration::from_millis(5000);
8
9/// The interval in milliseconds to report the FPS count at to the console.
10const REPORT_INTERVAL: Duration = Duration::from_millis(1000);
11
12/// The minimum number of frames to collect before the first report.
13const REPORT_FIRST_FRAMES_MIN: usize = 5;
14
15/// An accurate FPS counter, that remembers samples frame times in order to
16/// calculate the current FPS as accurratly as possible.
17///
18/// The frame sampler has a limited size, when it grows to big, the oldest
19/// frames are dropped.
20/// Frames that become outdated (frames that have been timed more than 5
21/// seconds ago) are also dropped.
22///
23/// To count frames, the `tick()` method must be called repeatedly, to sample
24/// a frame.
25pub struct FpsCounter {
26    /// A history of frame times, used to calculate the FPS.
27    frames: Vec<Instant>,
28
29    /// The time the FPS was last reported at, none if not reported.
30    last_report: Option<Instant>,
31}
32
33impl FpsCounter {
34    /// Create a new FPS counter.
35    pub fn new() -> FpsCounter {
36        FpsCounter {
37            frames: Vec::with_capacity(FRAME_BUFFER_MAX),
38            last_report: None,
39        }
40    }
41
42    /// Tick/count a new frame, and report the FPS.
43    pub fn tick(&mut self) {
44        // Make sure there's enough room in the vector
45        if self.frames.len() >= FRAME_BUFFER_MAX {
46            self.frames.remove(0);
47        }
48
49        // Add the current time to the list
50        self.frames.push(Instant::now());
51
52        // Periodically report the FPS
53        self.report_periodically();
54    }
55
56    /// Calculate the FPS based on the known frame times.
57    ///
58    /// If we are unable to calculate the FPS, None is returned.
59    pub fn calculate_fps(&mut self) -> Option<f64> {
60        // Clean up the frame times database
61        self.cleanup_frames();
62
63        // Make sure we have at least one frame available
64        if self.frames.len() == 0 {
65            return None;
66        }
67
68        // Find the numbers of milliseconds passed since the first frame
69
70        let passed = Instant::now().duration_since(self.frames[0]).as_micros();
71
72        // Calculate the FPS
73        Some((self.frames.len() as f64) / ((passed as f64) / 1_000_000f64))
74    }
75
76    /// Report the FPS to the console periodically.
77    /// By default this happens each second.
78    ///
79    /// If this method is invoked but the FPS has been reported too recently,
80    /// nothing happens.
81    pub fn report_periodically(&mut self) {
82        // Make sure we've collected enough frames
83        if self.last_report.is_none() && self.frames.len() < REPORT_FIRST_FRAMES_MIN {
84            return;
85        }
86
87        // Check if the report time has passed
88        if let Some(last_report) = self.last_report {
89            // Calculate the passed time
90            let passed = Instant::now().duration_since(last_report);
91
92            // Make sure enough time has passed
93            if passed < REPORT_INTERVAL {
94                return;
95            }
96        }
97
98        // Report
99        self.report();
100    }
101
102    /// Report the current FPS to the console.
103    pub fn report(&mut self) {
104        // Calculate the FPS
105        if let Some(fps) = self.calculate_fps() {
106            // Report the FPS
107            println!("FPS: {:.1}", fps);
108
109            // Set the last report time
110            self.last_report = Some(Instant::now());
111        }
112    }
113
114    /// Clean up frame times that are outdated, or excessive frames if we
115    /// have too many.
116    fn cleanup_frames(&mut self) {
117        // Drain frames if we're over the buffer maximum
118        if self.frames.len() > FRAME_BUFFER_MAX {
119            // Count the frames to remove, and drain it
120            let overhead = self.frames.len() - FRAME_BUFFER_MAX;
121            self.frames.drain(0..overhead);
122        }
123
124        // Find the number of outdated/dead frames
125        let now = Instant::now();
126        let dead = self
127            .frames
128            .iter()
129            .take_while(|frame| now.duration_since(**frame) > FRAME_TTL)
130            .count();
131
132        // Remove the dead frames
133        if dead > 0 {
134            self.frames.drain(0..dead);
135        }
136    }
137}