Cleanup and HistoryBuffer is now const and using MaybeUninit

This commit is contained in:
Emil Fresk 2021-04-01 19:14:40 +02:00
parent aee0817491
commit 1444990e52
5 changed files with 81 additions and 45 deletions

View File

@ -2,6 +2,7 @@
authors = [ authors = [
"Jorge Aparicio <jorge@japaric.io>", "Jorge Aparicio <jorge@japaric.io>",
"Per Lindgren <per.lindgren@ltu.se>", "Per Lindgren <per.lindgren@ltu.se>",
"Emil Fresk <emil.fresk@gmail.com>",
] ]
categories = [ categories = [
"data-structures", "data-structures",

View File

@ -1,14 +1,13 @@
use core::mem::MaybeUninit;
use core::ptr;
use core::slice;
/// A "history buffer", similar to a write-only ring buffer of fixed length. /// A "history buffer", similar to a write-only ring buffer of fixed length.
/// ///
/// This buffer keeps a fixed number of elements. On write, the oldest element /// This buffer keeps a fixed number of elements. On write, the oldest element
/// is overwritten. Thus, the buffer is useful to keep a history of values with /// is overwritten. Thus, the buffer is useful to keep a history of values with
/// some desired depth, and for example calculate a rolling average. /// some desired depth, and for example calculate a rolling average.
/// ///
/// The buffer is always fully initialized; depending on the constructor, the
/// initial value is either the default value for the element type or a supplied
/// initial value. This simplifies the API and is mostly irrelevant for the
/// intended use case.
///
/// # Examples /// # Examples
/// ``` /// ```
/// use heapless::HistoryBuffer; /// use heapless::HistoryBuffer;
@ -16,34 +15,35 @@
/// // Initialize a new buffer with 8 elements, all initially zero. /// // Initialize a new buffer with 8 elements, all initially zero.
/// let mut buf = HistoryBuffer::<_, 8>::new(); /// let mut buf = HistoryBuffer::<_, 8>::new();
/// ///
/// // Starts with no data
/// assert_eq!(buf.recent(), None);
///
/// buf.write(3); /// buf.write(3);
/// buf.write(5); /// buf.write(5);
/// buf.extend(&[4, 4]); /// buf.extend(&[4, 4]);
/// ///
/// // The most recent written element is a four. /// // The most recent written element is a four.
/// assert_eq!(buf.recent(), &4); /// assert_eq!(buf.recent(), Some(&4));
/// ///
/// // To access all elements in an unspecified order, use `as_slice()`. /// // To access all elements in an unspecified order, use `as_slice()`.
/// for el in buf.as_slice() { println!("{:?}", el); } /// for el in buf.as_slice() { println!("{:?}", el); }
/// ///
/// // Now we can prepare an average of all values, which comes out to 2. /// // Now we can prepare an average of all values, which comes out to 4.
/// let avg = buf.as_slice().iter().sum::<usize>() / buf.len(); /// let avg = buf.as_slice().iter().sum::<usize>() / buf.len();
/// assert_eq!(avg, 2); /// assert_eq!(avg, 4);
/// ``` /// ```
#[derive(Clone)]
pub struct HistoryBuffer<T, const N: usize> { pub struct HistoryBuffer<T, const N: usize> {
data: [T; N], data: [MaybeUninit<T>; N],
write_at: usize, write_at: usize,
filled: bool,
} }
impl<T, const N: usize> HistoryBuffer<T, N> impl<T, const N: usize> HistoryBuffer<T, N> {
where const INIT: MaybeUninit<T> = MaybeUninit::uninit();
T: Default + Copy,
{ /// Constructs a new history buffer.
/// Constructs a new history buffer, where every element is filled with the
/// default value of the type `T`.
/// ///
/// `HistoryBuffer` currently cannot be constructed in `const` context. /// The construction of a `HistoryBuffer` works in `const` contexts.
/// ///
/// # Examples /// # Examples
/// ///
@ -51,16 +51,15 @@ where
/// use heapless::HistoryBuffer; /// use heapless::HistoryBuffer;
/// ///
/// // Allocate a 16-element buffer on the stack /// // Allocate a 16-element buffer on the stack
/// let mut x: HistoryBuffer<u8, 16> = HistoryBuffer::new(); /// let x: HistoryBuffer<u8, 16> = HistoryBuffer::new();
/// // All elements are zero /// assert_eq!(x.len(), 0);
/// assert_eq!(x.as_slice(), [0; 16]);
/// ``` /// ```
pub fn new() -> Self { #[inline]
pub const fn new() -> Self {
Self { Self {
// seems not yet implemented data: [Self::INIT; N],
// data: Default::default(),
data: [T::default(); N],
write_at: 0, write_at: 0,
filled: false,
} }
} }
@ -87,10 +86,12 @@ where
/// // All elements are four /// // All elements are four
/// assert_eq!(x.as_slice(), [4; 16]); /// assert_eq!(x.as_slice(), [4; 16]);
/// ``` /// ```
#[inline]
pub fn new_with(t: T) -> Self { pub fn new_with(t: T) -> Self {
Self { Self {
data: [t; N], data: [MaybeUninit::new(t); N],
write_at: 0, write_at: 0,
filled: true,
} }
} }
@ -101,18 +102,35 @@ where
} }
impl<T, const N: usize> HistoryBuffer<T, N> { impl<T, const N: usize> HistoryBuffer<T, N> {
/// Returns the current fill level of the buffer.
#[inline]
pub fn len(&self) -> usize {
if self.filled {
N
} else {
self.write_at
}
}
/// Returns the capacity of the buffer, which is the length of the /// Returns the capacity of the buffer, which is the length of the
/// underlying backing array. /// underlying backing array.
pub fn len(&self) -> usize { #[inline]
self.data.len() pub fn capacity(&self) -> usize {
N
} }
/// Writes an element to the buffer, overwriting the oldest value. /// Writes an element to the buffer, overwriting the oldest value.
pub fn write(&mut self, t: T) { pub fn write(&mut self, t: T) {
self.data[self.write_at] = t; if self.filled {
// Drop the old before we overwrite it.
unsafe { ptr::drop_in_place(self.data[self.write_at].as_mut_ptr()) }
}
self.data[self.write_at] = MaybeUninit::new(t);
self.write_at += 1; self.write_at += 1;
if self.write_at == self.len() { if self.write_at == self.capacity() {
self.write_at = 0; self.write_at = 0;
self.filled = true;
} }
} }
@ -139,20 +157,28 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
/// let mut x: HistoryBuffer<u8, 16> = HistoryBuffer::new(); /// let mut x: HistoryBuffer<u8, 16> = HistoryBuffer::new();
/// x.write(4); /// x.write(4);
/// x.write(10); /// x.write(10);
/// assert_eq!(x.recent(), &10); /// assert_eq!(x.recent(), Some(&10));
/// ``` /// ```
pub fn recent(&self) -> &T { pub fn recent(&self) -> Option<&T> {
if self.write_at == 0 { if self.write_at == 0 {
&self.data[self.len() - 1] if self.filled {
Some(unsafe { &*self.data[self.capacity() - 1].as_ptr() })
} else {
None
}
} else { } else {
&self.data[self.write_at - 1] Some(unsafe { &*self.data[self.write_at - 1].as_ptr() })
} }
} }
/// Returns the array slice backing the buffer, without keeping track /// Returns the array slice backing the buffer, without keeping track
/// of the write position. Therefore, the element order is unspecified. /// of the write position. Therefore, the element order is unspecified.
pub fn as_slice(&self) -> &[T] { pub fn as_slice(&self) -> &[T] {
&self.data if self.filled {
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.capacity()) }
} else {
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.write_at) }
}
} }
} }
@ -179,6 +205,17 @@ where
} }
} }
impl<T, const N: usize> Drop for HistoryBuffer<T, N> {
fn drop(&mut self) {
unsafe {
ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
self.data.as_mut_ptr() as *mut T,
self.len(),
))
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::HistoryBuffer; use crate::HistoryBuffer;
@ -190,7 +227,7 @@ mod tests {
assert_eq!(x.as_slice(), [1; 4]); assert_eq!(x.as_slice(), [1; 4]);
let x: HistoryBuffer<u8, 4> = HistoryBuffer::new(); let x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
assert_eq!(x.as_slice(), [0; 4]); assert_eq!(x.as_slice(), []);
} }
#[test] #[test]
@ -198,7 +235,7 @@ mod tests {
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new(); let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
x.write(1); x.write(1);
x.write(4); x.write(4);
assert_eq!(x.as_slice(), [1, 4, 0, 0]); assert_eq!(x.as_slice(), [1, 4]);
x.write(5); x.write(5);
x.write(6); x.write(6);
@ -213,7 +250,7 @@ mod tests {
fn clear() { fn clear() {
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new_with(1); let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new_with(1);
x.clear(); x.clear();
assert_eq!(x.as_slice(), [0; 4]); assert_eq!(x.as_slice(), []);
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new(); let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
x.clear_with(1); x.clear_with(1);
@ -223,16 +260,16 @@ mod tests {
#[test] #[test]
fn recent() { fn recent() {
let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new(); let mut x: HistoryBuffer<u8, 4> = HistoryBuffer::new();
assert_eq!(x.recent(), &0); assert_eq!(x.recent(), None);
x.write(1); x.write(1);
x.write(4); x.write(4);
assert_eq!(x.recent(), &4); assert_eq!(x.recent(), Some(&4));
x.write(5); x.write(5);
x.write(6); x.write(6);
x.write(10); x.write(10);
assert_eq!(x.recent(), &10); assert_eq!(x.recent(), Some(&10));
} }
#[test] #[test]

View File

@ -375,7 +375,6 @@ where
K: Eq + Hash, K: Eq + Hash,
S: Default + Hasher, S: Default + Hasher,
{ {
// TODO turn into a `const fn`; needs `mem::zeroed` to be a `const fn`
/// Creates an empty `IndexMap`. /// Creates an empty `IndexMap`.
/// ///
/// **NOTE** This constructor will become a `const fn` in the future /// **NOTE** This constructor will become a `const fn` in the future

View File

@ -63,15 +63,14 @@
//! //!
//! # Minimum Supported Rust Version (MSRV) //! # Minimum Supported Rust Version (MSRV)
//! //!
//! This crate is guaranteed to compile on stable Rust 1.36 and up with its default set of features. //! This crate is guaranteed to compile on stable Rust 1.51 and up with its default set of features.
//! It *might* compile on older versions but that may change in any new patch release. //! It *might* compile on older versions but that may change in any new patch release.
// experimental usage of const generics, requires nightly 2020-08-18 (or newer)
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(rust_2018_compatibility)] #![deny(rust_2018_compatibility)]
#![deny(rust_2018_idioms)] #![deny(rust_2018_idioms)]
// #![deny(warnings)] #![deny(warnings)]
pub use binary_heap::BinaryHeap; pub use binary_heap::BinaryHeap;
pub use histbuf::HistoryBuffer; pub use histbuf::HistoryBuffer;

View File

@ -142,7 +142,7 @@ where
/// A statically allocated single producer single consumer queue with a capacity of `N` elements /// A statically allocated single producer single consumer queue with a capacity of `N` elements
/// ///
/// *IMPORTANT*: To get better performance use a capacity that is a power of 2 (e.g. `U16`, `U32`, /// *IMPORTANT*: To get better performance use a capacity that is a power of 2 (e.g. `16`, `32`,
/// etc.). /// etc.).
/// ///
/// By default `spsc::Queue` will use `usize` integers to hold the indices to its head and tail. For /// By default `spsc::Queue` will use `usize` integers to hold the indices to its head and tail. For