embedded_fps/
fps.rs

1use embedded_time::{duration::Seconds, Clock, Instant};
2use heapless::Deque;
3
4
5
6/// Measures Frames Per Second (FPS).
7///
8/// `MAX_FPS` - Defines the maximum FPS that you expect to measure.
9#[derive(Debug, Clone)]
10pub struct FPS<const MAX_FPS: usize, C: Clock> {
11    /// The last registered frames.
12    last_second_frames: Deque<Instant<C>, MAX_FPS>,
13    /// The embedded [`Clock`] that will be used to track the passed second.
14    clock: C,
15}
16
17impl<const MAX_FPS: usize, C: Clock> FPS<MAX_FPS, C> {
18    /// Creates a new Frames Per Second counter.
19    pub fn new(clock: C) -> FPS<MAX_FPS, C> {
20        FPS {
21            last_second_frames: Deque::<_, MAX_FPS>::new(),
22            clock,
23        }
24    }
25
26    /// Adds another frame tick and returns the current Frames Pre Second.
27    ///
28    /// # Panics
29    ///
30    /// When [`Clock::try_now()`] returns an error or if the `MAX_FPS` is reached.
31    pub fn tick(&mut self) -> usize {
32        self.try_tick().unwrap()
33    }
34
35    /// Adds another frame tick and returns the current Frames Pre Second.
36    ///
37    /// This method will not panic if the `MAX_FPS` is reached,
38    /// instead it will just return the `MAX_FPS` value (capping it in a nutshell).
39    ///
40    /// # Panics
41    ///
42    /// If [`Clock::try_now()`] returns an error.
43    pub fn tick_max(&mut self) -> usize {
44        self.try_tick_max().unwrap()
45    }
46
47    /// Adds another frame tick and returns the current Frames Pre Second.
48    ///
49    /// This method will not return an error if the `MAX_FPS` is reached,
50    /// instead it will just return the `MAX_FPS` value (capping it in a nutshell).
51    pub fn try_tick_max(&mut self) -> Result<usize, Error> {
52        match self.try_tick() {
53            Ok(fps) => Ok(fps),
54            Err(Error::MaxFPS(_)) => Ok(MAX_FPS),
55            Err(err) => Err(err),
56        }
57    }
58
59    /// Adds another frame tick and returns the current Frames Pre Second.
60    ///
61    /// # Panics
62    ///
63    /// When [`Clock::try_now()`] returns an error or if the `MAX_FPS` is reached.
64    pub fn try_tick(&mut self) -> Result<usize, Error> {
65        let now = self.clock.try_now().map_err(Error::Clock)?;
66        let a_second_ago = now - Seconds(1);
67
68        while self
69            .last_second_frames
70            .front()
71            .copied()
72            .map_or(false, |tick| tick < a_second_ago)
73        {
74            self.last_second_frames.pop_front();
75        }
76
77        self.last_second_frames
78            .push_back(now)
79            .map_err(|_cap_err| Error::MaxFPS(MAX_FPS))?;
80
81        // return the frames per second
82        Ok(self.last_second_frames.len())
83    }
84}
85
86impl<const MAX_FPS: usize, C> Default for FPS<MAX_FPS, C>
87where
88    C: Clock + Default,
89{
90    fn default() -> Self {
91        Self::new(C::default())
92    }
93}
94
95/// The errors that [`FPS`] can return.
96///
97/// Keep in mind that [`Error::MaxFPS`] will trigger panic on [`FPS::tick`]
98/// or be returned as an error on [`FPS::try_tick`].
99#[derive(Debug)]
100pub enum Error {
101    /// The clock returned an error when calling [`Clock::try_now`].
102    Clock(embedded_time::clock::Error),
103    /// The maximum reading of Frames per second was reached.
104    /// The internal deque reached it's capacity.
105    ///
106    /// Increase the `MAX_FPS` to avoid this problem.
107    MaxFPS(usize),
108}