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}