Skip to content

Commit cd19915

Browse files
committed
Prepare for release:
- Cargo - use `heapless` for `Deque` - split to modules `fps` & `std_clock` - lib - add more attributes
1 parent 5656688 commit cd19915

File tree

4 files changed

+203
-186
lines changed

4 files changed

+203
-186
lines changed

Cargo.toml

+12-5
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@
22
name = "embedded-fps"
33
version = "0.1.0"
44
authors = ["Lechev.space <[email protected]>", "Lachezar Lechev"]
5-
description = "Frames per second counter for embedded devices"
5+
description = "Frames Per Second counter for embedded devices"
66
categories = ["embedded", "no-std", "graphics"]
77

8-
keywords = ["graphics", "embedded", "no_std", "frames per second", "fps"]
8+
keywords = ["graphics", "embedded", "no_std", "frames-per-second", "fps"]
99
license = "MIT OR Apache-2.0"
1010
repository = "https://github.com/LechevSpace/embedded-fps"
1111
documentation = "https://docs.rs/embedded-fps"
12+
homepage = "https://github.com/LechevSpace/embedded-fps"
1213

1314
edition = "2021"
14-
# Because of `holodeque` and the `array_map` rust feature that was introduced in 1.55
15-
# and edition 2021 requires at least 1.56
15+
16+
# Edition 2021 requires at least 1.56
1617
rust-version = "1.56"
1718

19+
[package.metadata.docs.rs]
20+
all-features = true
21+
rustdoc-args = ["--cfg", "docsrs"]
22+
1823
[features]
1924

2025
default = []
@@ -30,7 +35,9 @@ required-features = ["std"]
3035

3136
[dependencies]
3237
embedded-time = "0.12"
33-
holodeque = {version = "0.2", default-features = false, git = "https://github.com/elpiel/holodeque", branch = "derive-copy-for-array-deque"}
38+
heapless = {version = "0.7", default-features = false }
39+
40+
[dev-dependencies]
3441
# used for the `embedded-graphics` example
3542
embedded-graphics-simulator = "0.3"
3643
embedded-graphics = "0.7"

src/fps.rs

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
use embedded_time::{duration::Seconds, Clock, Instant};
2+
use 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)]
10+
pub 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+
17+
impl<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+
86+
impl<const MAX_FPS: usize, C> Default for FPS<MAX_FPS, C>
87+
where
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)]
100+
pub 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+
}

src/lib.rs

+12-181
Original file line numberDiff line numberDiff line change
@@ -110,193 +110,24 @@
110110
// Rustdoc lints
111111
#![deny(rustdoc::broken_intra_doc_links)]
112112
// Rustc lints
113-
#![deny(missing_docs, unused_imports)]
113+
#![deny(
114+
missing_debug_implementations,
115+
missing_docs,
116+
rust_2018_idioms,
117+
unreachable_pub,
118+
unused_imports
119+
)]
114120
// adds `#[no_std]` attribute if the `std` feature is not enabled
115121
#![cfg_attr(not(feature = "std"), no_std)]
122+
#![cfg_attr(docsrs, feature(doc_cfg))]
116123

117-
use embedded_time::{duration::Seconds, Clock, Instant};
118-
use holodeque::ArrayDeque;
124+
pub use fps::{FPS, Error};
125+
126+
mod fps;
119127

120128
#[cfg(feature = "std")]
121129
#[doc(inline)]
122130
pub use std_clock::StdClock;
123131

124-
/// Measures Frames Per Second (FPS).
125-
///
126-
/// `MAX_FPS` - Defines the maximum FPS that you expect to measure.
127-
#[derive(Debug, Clone)]
128-
pub struct FPS<const MAX_FPS: usize, C: Clock> {
129-
/// The last registered frames.
130-
last_second_frames: ArrayDeque<Option<Instant<C>>, MAX_FPS>,
131-
/// The embedded [`Clock`] that will be used to track the passed second.
132-
clock: C,
133-
}
134-
135-
impl<const MAX_FPS: usize, C: Clock> FPS<MAX_FPS, C> {
136-
/// Creates a new Frames Per Second counter.
137-
pub fn new(clock: C) -> FPS<MAX_FPS, C> {
138-
FPS {
139-
last_second_frames: ArrayDeque::<_, MAX_FPS>::new(),
140-
clock,
141-
}
142-
}
143-
144-
/// Adds another frame tick and returns the current Frames Pre Second.
145-
///
146-
/// # Panics
147-
///
148-
/// When [`Clock::try_now()`] returns an error or if the `MAX_FPS` is reached.
149-
pub fn tick(&mut self) -> usize {
150-
self.try_tick().unwrap()
151-
}
152-
153-
/// Adds another frame tick and returns the current Frames Pre Second.
154-
///
155-
/// This method will not panic if the `MAX_FPS` is reached,
156-
/// instead it will just return the `MAX_FPS` value (capping it in a nutshell).
157-
///
158-
/// # Panics
159-
///
160-
/// If [`Clock::try_now()`] returns an error.
161-
pub fn tick_max(&mut self) -> usize {
162-
self.try_tick_max().unwrap()
163-
}
164-
165-
/// Adds another frame tick and returns the current Frames Pre Second.
166-
///
167-
/// This method will not return an error if the `MAX_FPS` is reached,
168-
/// instead it will just return the `MAX_FPS` value (capping it in a nutshell).
169-
pub fn try_tick_max(&mut self) -> Result<usize, Error> {
170-
match self.try_tick() {
171-
Ok(fps) => Ok(fps),
172-
Err(Error::MaxFPS(_)) => Ok(MAX_FPS),
173-
Err(err) => Err(err),
174-
}
175-
}
176-
177-
/// Adds another frame tick and returns the current Frames Pre Second.
178-
///
179-
/// # Panics
180-
///
181-
/// When [`Clock::try_now()`] returns an error or if the `MAX_FPS` is reached.
182-
pub fn try_tick(&mut self) -> Result<usize, Error> {
183-
let now = self.clock.try_now().map_err(Error::Clock)?;
184-
let a_second_ago = now - Seconds(1);
185-
186-
while self
187-
.last_second_frames
188-
.front()
189-
.copied()
190-
.flatten()
191-
.map_or(false, |tick| tick < a_second_ago)
192-
{
193-
self.last_second_frames.pop_front();
194-
}
195-
196-
self.last_second_frames
197-
.push_back(Some(now))
198-
.map_err(|_cap_err| Error::MaxFPS(MAX_FPS))?;
199-
200-
// return the frames per second
201-
Ok(self.last_second_frames.len())
202-
}
203-
}
204-
205-
impl<const MAX_FPS: usize, C> Default for FPS<MAX_FPS, C>
206-
where
207-
C: Clock + Default,
208-
{
209-
fn default() -> Self {
210-
Self::new(C::default())
211-
}
212-
}
213-
214-
#[derive(Debug)]
215-
/// The errors that [`FPS`] can return.
216-
///
217-
/// Keep in mind that [`Error::MaxFPS`] will trigger panic on [`FPS::tick`]
218-
/// or be returned as an error on [`FPS::try_tick`].
219-
pub enum Error {
220-
/// The clock returned an error when calling [`Clock::try_now`].
221-
Clock(embedded_time::clock::Error),
222-
/// The maximum reading of Frames per second was reached
223-
/// The internal deque reached it's capacity.
224-
///
225-
/// Increase the `MAX_FPS` to avoid this problem.
226-
MaxFPS(usize),
227-
}
228-
229132
#[cfg(feature = "std")]
230-
mod std_clock {
231-
use std::time::Instant as StdInstant;
232-
233-
use embedded_time::{clock::Error, rate::Fraction, Clock, Instant as EmbeddedInstant};
234-
235-
/// A Standard clock based on [`std`].
236-
///
237-
/// It takes the [`Instant::elapsed()`] time and uses nanoseconds converted to [`u64`].
238-
/// This still leaves us with ~594 years of representable time
239-
///
240-
/// [`Instant::elapsed()`]: std::time::Instant::elapsed()
241-
#[derive(Debug, Clone, Copy)]
242-
pub struct StdClock(StdInstant);
243-
244-
impl Default for StdClock {
245-
fn default() -> Self {
246-
Self::new()
247-
}
248-
}
249-
250-
impl StdClock {
251-
/// Creates a new [`StdClock`].
252-
/// Internally it calls [`Instant::now()`].
253-
///
254-
/// [`Instant::now()`]: std::time::Instant::now()
255-
pub fn new() -> Self {
256-
Self(StdInstant::now())
257-
}
258-
}
259-
260-
impl Clock for StdClock {
261-
type T = u64;
262-
263-
const SCALING_FACTOR: Fraction = Fraction::new(1, 1_000_000_000);
264-
265-
fn try_now(&self) -> Result<EmbeddedInstant<Self>, Error> {
266-
// discarding the upper u64 still leaves us with ~594 years of representable time
267-
Ok(EmbeddedInstant::new(self.0.elapsed().as_nanos() as u64))
268-
}
269-
}
270-
271-
#[cfg(test)]
272-
mod tests {
273-
use std::thread::sleep;
274-
275-
use embedded_time::{
276-
duration::{Extensions, Milliseconds},
277-
Clock,
278-
};
279-
280-
use super::StdClock;
281-
282-
#[test]
283-
fn it_creates_std_instant_from_milliseconds_clock() {
284-
let clock = StdClock::new();
285-
286-
sleep(std::time::Duration::from_millis(400));
287-
288-
let start = clock.try_now().unwrap();
289-
// wait 1.5 seconds
290-
sleep(std::time::Duration::from_millis(1_600));
291-
let end = clock.try_now().unwrap();
292-
293-
let elapsed = Milliseconds::<u64>::try_from(end - start).unwrap();
294-
295-
let lower_bound = Milliseconds::<u64>::try_from(1_599_u32.milliseconds()).unwrap();
296-
assert!(elapsed > lower_bound);
297-
298-
let upper_bound = Milliseconds::<u64>::try_from(2_000_u32.milliseconds()).unwrap();
299-
assert!(elapsed < upper_bound);
300-
}
301-
}
302-
}
133+
mod std_clock;

0 commit comments

Comments
 (0)