Skip to main content

blockworx_web/
meter.rs

1//! The frame-rate readout's numbers: the frames of the last second, each with
2//! what it spent in the kernel and on the canvas.
3//!
4//! The editor draws only when something changes, so a second at rest holds no
5//! frames and the readout stands on the last one it saw. What it says is the
6//! rate *while working* — a drag, a pan — and what the last frame cost.
7
8use core::time::Duration;
9use std::collections::VecDeque;
10
11/// How far back the rate counts.
12const WINDOW: Duration = Duration::from_secs(1);
13
14/// One frame's costs, and when it ran on the page's clock.
15#[derive(Clone, Copy, Debug)]
16pub struct Frame {
17    pub at: Duration,
18    pub kernel: Duration,
19    pub paint: Duration,
20}
21
22/// What the readout shows.
23#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
24pub struct Metered {
25    /// Frames in the last second.
26    pub frames: usize,
27    /// The last frame's kernel call.
28    pub kernel: Duration,
29    /// The last frame's replay onto the canvas.
30    pub paint: Duration,
31    /// The slowest frame of the last second, kernel and canvas together.
32    pub slowest: Duration,
33}
34
35#[derive(Default)]
36pub struct FrameMeter {
37    frames: VecDeque<Frame>,
38}
39
40impl FrameMeter {
41    /// Count `frame`, forget what has left the window, and say what the readout
42    /// now shows.
43    pub fn record(&mut self, frame: Frame) -> Metered {
44        self.frames.push_back(frame);
45        while self
46            .frames
47            .front()
48            .is_some_and(|oldest| oldest.at + WINDOW <= frame.at)
49        {
50            self.frames.pop_front();
51        }
52        Metered {
53            frames: self.frames.len(),
54            kernel: frame.kernel,
55            paint: frame.paint,
56            slowest: self
57                .frames
58                .iter()
59                .map(|f| f.kernel + f.paint)
60                .max()
61                .unwrap_or_default(),
62        }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    fn frame(at_ms: u64, kernel_ms: u64, paint_ms: u64) -> Frame {
71        Frame {
72            at: Duration::from_millis(at_ms),
73            kernel: Duration::from_millis(kernel_ms),
74            paint: Duration::from_millis(paint_ms),
75        }
76    }
77
78    #[test]
79    fn the_rate_counts_the_frames_of_the_last_second() {
80        let mut meter = FrameMeter::default();
81        let mut last = Metered::default();
82        for n in 0..60 {
83            last = meter.record(frame(n * 16, 5, 3));
84        }
85        assert_eq!(last.frames, 60, "a busy second at 60 Hz");
86        let later = meter.record(frame(59 * 16 + 1_000, 5, 3));
87        assert_eq!(
88            later.frames, 1,
89            "a second later only the frame just drawn is in the window"
90        );
91    }
92
93    #[test]
94    fn the_reading_is_the_last_frames_cost_and_the_seconds_slowest() {
95        let mut meter = FrameMeter::default();
96        meter.record(frame(0, 40, 10));
97        let reading = meter.record(frame(16, 4, 2));
98        assert_eq!(reading.kernel, Duration::from_millis(4));
99        assert_eq!(reading.paint, Duration::from_millis(2));
100        assert_eq!(reading.slowest, Duration::from_millis(50));
101    }
102}