From 314fd856afd63d0150e50ac0dd86c54eb2d9e509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sosth=C3=A8ne=20Gu=C3=A9don?= Date: Mon, 1 Jul 2024 14:29:36 +0200 Subject: [PATCH] Implement HistoryBufferView on top of #486 --- CHANGELOG.md | 1 + src/histbuf.rs | 265 ++++++++++++++++++++++++++++++++++--------------- src/lib.rs | 2 +- src/ser.rs | 10 +- 4 files changed, 194 insertions(+), 84 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e7f6aad4..af764d1c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Added `StringView`, the `!Sized` version of `String`. - Added `MpMcQueueView`, the `!Sized` version of `MpMcQueue`. - Added `LinearMapView`, the `!Sized` version of `LinearMap`. +- Added `HistoryBufferView`, the `!Sized` version of `HistoryBuffer`. ### Changed diff --git a/src/histbuf.rs b/src/histbuf.rs index 5b7084b4a1..93e79fbe33 100644 --- a/src/histbuf.rs +++ b/src/histbuf.rs @@ -1,9 +1,59 @@ +//! 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 +//! is overwritten. Thus, the buffer is useful to keep a history of values with +//! some desired depth, and for example calculate a rolling average. +//! +//! # Examples +//! ``` +//! use heapless::HistoryBuffer; +//! +//! // Initialize a new buffer with 8 elements. +//! let mut buf = HistoryBuffer::<_, 8>::new(); +//! +//! // Starts with no data +//! assert_eq!(buf.recent(), None); +//! +//! buf.write(3); +//! buf.write(5); +//! buf.extend(&[4, 4]); +//! +//! // The most recent written element is a four. +//! assert_eq!(buf.recent(), Some(&4)); +//! +//! // To access all elements in an unspecified order, use `as_slice()`. +//! for el in buf.as_slice() { +//! println!("{:?}", el); +//! } +//! +//! // Now we can prepare an average of all values, which comes out to 4. +//! let avg = buf.as_slice().iter().sum::() / buf.len(); +//! assert_eq!(avg, 4); +//! ``` + +use core::borrow::Borrow; +use core::borrow::BorrowMut; use core::fmt; +use core::marker::PhantomData; use core::mem::MaybeUninit; use core::ops::Deref; use core::ptr; use core::slice; +use crate::storage::OwnedStorage; +use crate::storage::Storage; +use crate::storage::ViewStorage; + +/// Base struct for [`HistoryBuffer`] and [`HistoryBufferView`], generic over the [`Storage`]. +/// +/// In most cases you should use [`HistoryBuffer`] or [`HistoryBufferView`] directly. Only use this +/// struct if you want to write code that's generic over both. +pub struct HistoryBufferInner { + write_at: usize, + filled: bool, + data: S::Buffer>, +} + /// 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 @@ -36,11 +86,40 @@ use core::slice; /// let avg = buf.as_slice().iter().sum::() / buf.len(); /// assert_eq!(avg, 4); /// ``` -pub struct HistoryBuffer { - data: [MaybeUninit; N], - write_at: usize, - filled: bool, -} +pub type HistoryBuffer = HistoryBufferInner>; + +/// A "view" into a [`HistoryBuffer`] +/// +/// Unlike [`HistoryBuffer`], it doesn't have the `const N: usize` in its type signature. +/// +/// # Examples +/// ``` +/// use heapless::histbuf::{HistoryBuffer, HistoryBufferView}; +/// +/// // Initialize a new buffer with 8 elements. +/// let mut owned_buf = HistoryBuffer::<_, 8>::new(); +/// let buf: &mut HistoryBufferView<_> = &mut owned_buf; +/// +/// // Starts with no data +/// assert_eq!(buf.recent(), None); +/// +/// buf.write(3); +/// buf.write(5); +/// buf.extend(&[4, 4]); +/// +/// // The most recent written element is a four. +/// assert_eq!(buf.recent(), Some(&4)); +/// +/// // To access all elements in an unspecified order, use `as_slice()`. +/// for el in buf.as_slice() { +/// println!("{:?}", el); +/// } +/// +/// // Now we can prepare an average of all values, which comes out to 4. +/// let avg = buf.as_slice().iter().sum::() / buf.len(); +/// assert_eq!(avg, 4); +/// ``` +pub type HistoryBufferView = HistoryBufferInner; impl HistoryBuffer { const INIT: MaybeUninit = MaybeUninit::uninit(); @@ -69,12 +148,6 @@ impl HistoryBuffer { filled: false, } } - - /// Clears the buffer, replacing every element with the default value of - /// type `T`. - pub fn clear(&mut self) { - *self = Self::new(); - } } impl HistoryBuffer @@ -101,19 +174,46 @@ where filled: true, } } - +} +impl HistoryBufferInner { /// Clears the buffer, replacing every element with the given value. pub fn clear_with(&mut self, t: T) { - *self = Self::new_with(t); + // SAFETY: we reset the values just after + unsafe { self.drop_contents() }; + self.write_at = 0; + self.filled = true; + + for d in self.data.borrow_mut() { + *d = MaybeUninit::new(t); + } } } -impl HistoryBuffer { +impl HistoryBufferInner { + /// Clears the buffer + pub fn clear(&mut self) { + // SAFETY: we reset the values just after + unsafe { self.drop_contents() }; + self.write_at = 0; + self.filled = false; + } +} + +impl HistoryBufferInner { + unsafe fn drop_contents(&mut self) { + unsafe { + ptr::drop_in_place(ptr::slice_from_raw_parts_mut( + self.data.borrow_mut().as_mut_ptr() as *mut T, + self.len(), + )) + } + } + /// Returns the current fill level of the buffer. #[inline] pub fn len(&self) -> usize { if self.filled { - N + self.capacity() } else { self.write_at } @@ -138,7 +238,7 @@ impl HistoryBuffer { /// underlying backing array. #[inline] pub fn capacity(&self) -> usize { - N + self.data.borrow().len() } /// Returns whether the buffer is full @@ -151,9 +251,9 @@ impl HistoryBuffer { pub fn write(&mut self, t: T) { if self.filled { // Drop the old before we overwrite it. - unsafe { ptr::drop_in_place(self.data[self.write_at].as_mut_ptr()) } + unsafe { ptr::drop_in_place(self.data.borrow_mut()[self.write_at].as_mut_ptr()) } } - self.data[self.write_at] = MaybeUninit::new(t); + self.data.borrow_mut()[self.write_at] = MaybeUninit::new(t); self.write_at += 1; if self.write_at == self.capacity() { @@ -189,7 +289,7 @@ impl HistoryBuffer { /// ``` pub fn recent(&self) -> Option<&T> { self.recent_index() - .map(|i| unsafe { &*self.data[i].as_ptr() }) + .map(|i| unsafe { &*self.data.borrow()[i].as_ptr() }) } /// Returns index of the most recently written value in the underlying slice. @@ -230,7 +330,7 @@ impl HistoryBuffer { /// ``` pub fn oldest(&self) -> Option<&T> { self.oldest_index() - .map(|i| unsafe { &*self.data[i].as_ptr() }) + .map(|i| unsafe { &*self.data.borrow()[i].as_ptr() }) } /// Returns index of the oldest value in the underlying slice. @@ -258,7 +358,7 @@ impl HistoryBuffer { /// Returns the array slice backing the buffer, without keeping track /// of the write position. Therefore, the element order is unspecified. pub fn as_slice(&self) -> &[T] { - unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.len()) } + unsafe { slice::from_raw_parts(self.data.borrow().as_ptr() as *const _, self.len()) } } /// Returns a pair of slices which contain, in order, the contents of the buffer. @@ -298,25 +398,16 @@ impl HistoryBuffer { /// assert_eq!(x, y) /// } /// ``` - pub fn oldest_ordered(&self) -> OldestOrdered<'_, T, N> { - match (self.oldest_index(), self.recent_index()) { - (Some(oldest_index), Some(recent_index)) => OldestOrdered { - buf: self, - next: oldest_index, - back: recent_index, - done: false, - }, - _ => OldestOrdered { - buf: self, - next: 0, - back: 0, - done: true, - }, + pub fn oldest_ordered(&self) -> OldestOrderedInner<'_, T, S> { + let (old, new) = self.as_slices(); + OldestOrderedInner { + phantom: PhantomData, + inner: old.iter().chain(new), } } } -impl Extend for HistoryBuffer { +impl Extend for HistoryBufferInner { fn extend(&mut self, iter: I) where I: IntoIterator, @@ -327,7 +418,7 @@ impl Extend for HistoryBuffer { } } -impl<'a, T, const N: usize> Extend<&'a T> for HistoryBuffer +impl<'a, T, S: Storage> Extend<&'a T> for HistoryBufferInner where T: 'a + Clone, { @@ -354,18 +445,13 @@ where } } -impl Drop for HistoryBuffer { +impl Drop for HistoryBufferInner { 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(), - )) - } + unsafe { self.drop_contents() } } } -impl Deref for HistoryBuffer { +impl Deref for HistoryBufferInner { type Target = [T]; fn deref(&self) -> &[T] { @@ -373,14 +459,14 @@ impl Deref for HistoryBuffer { } } -impl AsRef<[T]> for HistoryBuffer { +impl AsRef<[T]> for HistoryBufferInner { #[inline] fn as_ref(&self) -> &[T] { self } } -impl fmt::Debug for HistoryBuffer +impl fmt::Debug for HistoryBufferInner where T: fmt::Debug, { @@ -395,7 +481,7 @@ impl Default for HistoryBuffer { } } -impl PartialEq for HistoryBuffer +impl PartialEq for HistoryBufferInner where T: PartialEq, { @@ -404,51 +490,74 @@ where } } +/// Base struct for [`OldestOrdered`] and [`OldestOrderedView`], generic over the [`Storage`]. +/// +/// In most cases you should use [`OldestOrdered`] or [`OldestOrderedView`] directly. Only use this +/// struct if you want to write code that's generic over both. +pub struct OldestOrderedInner<'a, T, S: Storage> { + phantom: PhantomData, + inner: core::iter::Chain, core::slice::Iter<'a, T>>, +} + /// Double ended iterator on the underlying buffer ordered from the oldest data /// to the newest -#[derive(Clone)] -pub struct OldestOrdered<'a, T, const N: usize> { - buf: &'a HistoryBuffer, - next: usize, - back: usize, - done: bool, -} +/// This type exists for backwards compatibility. It is always better to convert it to an [`OldestOrderedView`] with [`into_view`](OldestOrdered::into_view) +pub type OldestOrdered<'a, T, const N: usize> = OldestOrderedInner<'a, T, OwnedStorage>; -impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> { - type Item = &'a T; +/// Double ended iterator on the underlying buffer ordered from the oldest data +/// to the newest +pub type OldestOrderedView<'a, T> = OldestOrderedInner<'a, T, ViewStorage>; - fn next(&mut self) -> Option<&'a T> { - if self.done { - return None; +impl<'a, T, const N: usize> OldestOrdered<'a, T, N> { + /// Remove the `N` const-generic parameter from the iterator + /// + /// For the opposite operation, see [`into_legacy_iter`](OldestOrderedView::into_legacy_iter) + pub fn into_view(self) -> OldestOrderedView<'a, T> { + OldestOrderedView { + phantom: PhantomData, + inner: self.inner, } + } +} - if self.next == self.back { - self.done = true; +impl<'a, T> OldestOrderedView<'a, T> { + /// Add back the `N` const-generic parameter to use it with APIs expecting the legacy type + /// + /// You probably do not need this + /// + /// For the opposite operation, see [`into_view`](OldestOrdered::into_view) + pub fn into_legacy_iter(self) -> OldestOrdered<'a, T, N> { + OldestOrdered { + phantom: PhantomData, + inner: self.inner, } - - let item = &self.buf[self.next]; - - self.next = if self.next == N - 1 { 0 } else { self.next + 1 }; - - Some(item) } } -impl<'a, T, const N: usize> DoubleEndedIterator for OldestOrdered<'a, T, N> { - fn next_back(&mut self) -> Option { - if self.done { - return None; +impl<'a, T, S: Storage> Clone for OldestOrderedInner<'a, T, S> { + fn clone(&self) -> Self { + Self { + phantom: PhantomData, + inner: self.inner.clone(), } + } +} - if self.next == self.back { - self.done = true; - } +impl<'a, T, S: Storage> Iterator for OldestOrderedInner<'a, T, S> { + type Item = &'a T; - let item = &self.buf[self.back]; + fn next(&mut self) -> Option<&'a T> { + self.inner.next() + } - self.back = if self.back == 0 { N - 1 } else { self.back - 1 }; + fn size_hint(&self) -> (usize, Option) { + self.inner.size_hint() + } +} - Some(item) +impl<'a, T, const N: usize> DoubleEndedIterator for OldestOrdered<'a, T, N> { + fn next_back(&mut self) -> Option { + self.inner.next_back() } } diff --git a/src/lib.rs b/src/lib.rs index 46ff4c5db7..86fa687467 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,7 +104,7 @@ pub use vec::{Vec, VecView}; mod test_helpers; mod deque; -mod histbuf; +pub mod histbuf; mod indexmap; mod indexset; pub mod linear_map; diff --git a/src/ser.rs b/src/ser.rs index 52064a1c78..20ce4882b1 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -1,8 +1,8 @@ use core::hash::{BuildHasher, Hash}; use crate::{ - binary_heap::Kind as BinaryHeapKind, linear_map::LinearMapInner, storage::Storage, - string::StringInner, vec::VecInner, BinaryHeap, Deque, HistoryBuffer, IndexMap, IndexSet, + binary_heap::Kind as BinaryHeapKind, histbuf::HistoryBufferInner, linear_map::LinearMapInner, + storage::Storage, string::StringInner, vec::VecInner, BinaryHeap, Deque, IndexMap, IndexSet, }; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; @@ -74,13 +74,13 @@ where } } -impl Serialize for HistoryBuffer +impl Serialize for HistoryBufferInner where T: Serialize, { - fn serialize(&self, serializer: S) -> Result + fn serialize(&self, serializer: SER) -> Result where - S: Serializer, + SER: Serializer, { let mut seq = serializer.serialize_seq(Some(self.len()))?; for element in self.oldest_ordered() {