From 102856315542dd67c3889e2ed4b6b53590c7df59 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 18 Dec 2019 07:47:24 +0100 Subject: [PATCH] add the HistoryBuffer type --- src/histbuf.rs | 325 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 + tests/cpass.rs | 3 +- 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 src/histbuf.rs diff --git a/src/histbuf.rs b/src/histbuf.rs new file mode 100644 index 00000000..b10dab60 --- /dev/null +++ b/src/histbuf.rs @@ -0,0 +1,325 @@ +use generic_array::{ArrayLength, GenericArray, sequence::GenericSequence}; +use as_slice::{AsSlice, AsMutSlice}; + +/// A "history buffer", similar to a write-only ring buffer. +/// +/// This buffer keeps a fixed number of elements. On push, the oldest element +/// is overwritten. It is useful to keep a history of values with 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 +/// ``` +/// use heapless::HistoryBuffer; +/// use heapless::consts::*; +/// +/// // Initialize a new buffer with 8 elements, all initially zero. +/// let mut buf = HistoryBuffer::<_, U8>::new(); +/// +/// buf.push(3); +/// buf.push(5); +/// buf.extend(&[4, 4]); +/// +/// // The first (oldest) element is still zero. +/// assert_eq!(buf.first(), &0); +/// // The last (newest) element is a four. +/// assert_eq!(buf.last(), &4); +/// for el in buf.iter() { println!("{:?}", el); } +/// +/// // Now we can prepare an average of all values, which comes out to 2. +/// let avg = buf.iter().sum::() / buf.len(); +/// assert_eq!(avg, 2); +/// ``` +#[derive(Clone)] +pub struct HistoryBuffer +where + N: ArrayLength, +{ + data: GenericArray, + write_at: usize, +} + + +impl HistoryBuffer +where + N: ArrayLength, + T: Default, +{ + /// 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. + /// + /// # Examples + /// + /// ``` + /// use heapless::HistoryBuffer; + /// use heapless::consts::*; + /// + /// // Allocate a 16-element buffer on the stack + /// let mut x: HistoryBuffer = HistoryBuffer::new(); + /// // All elements are zero + /// assert!(x.iter().eq([0; 16].iter())); + /// ``` + pub fn new() -> Self { + Self { + data: Default::default(), + write_at: 0, + } + } + + /// Clears the buffer, replacing every element with the default value of + /// type `T`. + pub fn clear(&mut self) { + *self = Self::new(); + } +} + +impl HistoryBuffer +where + N: ArrayLength, + T: Clone, +{ + /// Constructs a new history buffer, where every element is the given value. + /// + /// # Examples + /// + /// ``` + /// use heapless::HistoryBuffer; + /// use heapless::consts::*; + /// + /// // Allocate a 16-element buffer on the stack + /// let mut x: HistoryBuffer = HistoryBuffer::new_with(4); + /// // All elements are four + /// assert!(x.iter().eq([4; 16].iter())); + /// ``` + pub fn new_with(t: T) -> Self { + Self { + data: GenericArray::generate(|_| t.clone()), + write_at: 0, + } + } + + /// Clears the buffer, replacing every element with the given value. + pub fn clear_with(&mut self, t: T) { + *self = Self::new_with(t); + } +} + +impl HistoryBuffer +where + N: ArrayLength, +{ + /// Returns the length of the buffer, which is the length of the underlying + /// backing array. + pub fn len(&self) -> usize { + self.data.len() + } + + /// Writes an element to the buffer, overwriting the oldest value. + pub fn push(&mut self, t: T) { + self.data[self.write_at] = t; + self.write_at = (self.write_at + 1) % self.len(); + } + + /// Clones and pushes all elements in a slice to the buffer. + /// + /// If the slice is longer than the buffer, only the last `self.len()` + /// elements will actually be stored. + pub fn extend_from_slice(&mut self, other: &[T]) + where + T: Clone, + { + for item in other { + self.push(item.clone()); + } + } + + /// Returns a reference to the oldest (least recently pushed) value. + pub fn first(&self) -> &T { + &self.data[self.write_at] + } + + /// Returns a reference to the newest (most recently pushed) value. + /// + /// # Examples + /// + /// ``` + /// use heapless::HistoryBuffer; + /// use heapless::consts::*; + /// + /// let mut x: HistoryBuffer = HistoryBuffer::new(); + /// x.push(4); + /// x.push(10); + /// assert_eq!(x.last(), &10); + /// ``` + pub fn last(&self) -> &T { + &self.data[(self.write_at + self.len() - 1) % self.len()] + } + + /// Returns an iterator over the elements of the buffer. + /// + /// Note: if the order of elements is not important, use + /// `.as_slice().iter()` instead. + pub fn iter(&self) -> Iter<'_, T> { + Iter { + data: &self.data, + cur: self.write_at, + left: self.len(), + } + } + + /// Returns an iterator over mutable elements of the buffer. + /// + /// Note: if the order of elements is not important, use + /// `.as_mut_slice().iter_mut()` instead. + pub fn iter_mut(&mut self) -> impl Iterator { + let (p1, p2) = self.data.split_at_mut(self.write_at); + p2.iter_mut().chain(p1) + } +} + +pub struct Iter<'a, T> { + data: &'a [T], + cur: usize, + left: usize, +} + +impl<'a, T> Iterator for Iter<'a, T> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + if self.left == 0 { + None + } else { + let el = &self.data[self.cur]; + self.cur = (self.cur + 1) % self.data.len(); + self.left -= 1; + Some(el) + } + } +} + +impl Extend for HistoryBuffer +where + N: ArrayLength, +{ + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + for item in iter.into_iter() { + self.push(item); + } + } +} + +impl<'a, T, N> Extend<&'a T> for HistoryBuffer +where + T: 'a + Clone, + N: ArrayLength, +{ + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.extend(iter.into_iter().cloned()) + } +} + +impl AsSlice for HistoryBuffer +where + N: ArrayLength, +{ + type Element = T; + + /// Returns the array slice backing the buffer, without keeping track + /// of the write position. Therefore, the element order is unspecified. + fn as_slice(&self) -> &[T] { + &self.data + } +} + +impl AsMutSlice for HistoryBuffer +where + N: ArrayLength, +{ + /// Returns the array slice backing the buffer, without keeping track + /// of the write position. Therefore, the element order is unspecified. + fn as_mut_slice(&mut self) -> &mut [T] { + &mut self.data + } +} + +#[cfg(test)] +mod tests { + use crate::{consts::*, HistoryBuffer}; + use as_slice::{AsSlice, AsMutSlice}; + + #[test] + fn new() { + let x: HistoryBuffer = HistoryBuffer::new_with(1); + assert!(x.iter().eq([1; 4].iter())); + + let x: HistoryBuffer = HistoryBuffer::new(); + assert!(x.iter().eq([0; 4].iter())); + } + + #[test] + fn push_iter() { + let mut x: HistoryBuffer = HistoryBuffer::new(); + x.push(1); + x.push(4); + assert!(x.iter().eq([0, 0, 1, 4].iter())); + + x.push(5); + x.push(6); + x.push(10); + assert!(x.iter().eq([4, 5, 6, 10].iter())); + + x.extend([11, 12].iter()); + assert!(x.iter().eq([6, 10, 11, 12].iter())); + + assert!(x.iter_mut().eq([6, 10, 11, 12].iter())); + } + + #[test] + fn clear() { + let mut x: HistoryBuffer = HistoryBuffer::new_with(1); + x.clear(); + assert!(x.iter().eq([0; 4].iter())); + + let mut x: HistoryBuffer = HistoryBuffer::new(); + x.clear_with(1); + assert!(x.iter().eq([1; 4].iter())); + } + + #[test] + fn first_last() { + let mut x: HistoryBuffer = HistoryBuffer::new(); + x.push(1); + x.push(4); + assert_eq!(x.first(), &0); + assert_eq!(x.last(), &4); + + x.push(5); + x.push(6); + x.push(10); + assert_eq!(x.first(), &4); + assert_eq!(x.last(), &10); + } + + #[test] + fn as_slice() { + let mut x: HistoryBuffer = HistoryBuffer::new(); + + x.extend([1, 2, 3, 4, 5].iter()); + + assert_eq!(x.as_slice(), &[5, 2, 3, 4]); + assert_eq!(x.as_mut_slice(), &mut [5, 2, 3, 4]); + } +} diff --git a/src/lib.rs b/src/lib.rs index 9a81010a..c4141b87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -75,6 +75,7 @@ pub use indexset::{FnvIndexSet, IndexSet}; pub use linear_map::LinearMap; pub use string::String; pub use vec::Vec; +pub use histbuf::HistoryBuffer; // NOTE this code was last ported from v0.4.1 of the indexmap crate mod indexmap; @@ -82,6 +83,7 @@ mod indexset; mod linear_map; mod string; mod vec; +mod histbuf; #[cfg(feature = "serde")] mod de; diff --git a/tests/cpass.rs b/tests/cpass.rs index bf5e60c8..f834e9d1 100644 --- a/tests/cpass.rs +++ b/tests/cpass.rs @@ -3,7 +3,7 @@ use heapless::{ consts, spsc::{Consumer, Producer, Queue}, - Vec, + Vec, HistoryBuffer, }; #[test] @@ -22,4 +22,5 @@ fn send() { is_send::>(); is_send::>(); is_send::>(); + is_send::>(); }