Skip to content

Commit 2ae7e8b

Browse files
Implement HistoryBufferView on top of #486
1 parent 4002864 commit 2ae7e8b

File tree

4 files changed

+188
-78
lines changed

4 files changed

+188
-78
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
2828
- Added `StringView`, the `!Sized` version of `String`.
2929
- Added `MpMcQueueView`, the `!Sized` version of `MpMcQueue`.
3030
- Added `LinearMapView`, the `!Sized` version of `LinearMap`.
31+
- Added `HistoryBufferView`, the `!Sized` version of `HistoryBuffer`.
3132

3233
### Changed
3334

src/histbuf.rs

Lines changed: 181 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,59 @@
1+
//! A "history buffer", similar to a write-only ring buffer of fixed length.
2+
//!
3+
//! This buffer keeps a fixed number of elements. On write, the oldest element
4+
//! is overwritten. Thus, the buffer is useful to keep a history of values with
5+
//! some desired depth, and for example calculate a rolling average.
6+
//!
7+
//! # Examples
8+
//! ```
9+
//! use heapless::HistoryBuffer;
10+
//!
11+
//! // Initialize a new buffer with 8 elements.
12+
//! let mut buf = HistoryBuffer::<_, 8>::new();
13+
//!
14+
//! // Starts with no data
15+
//! assert_eq!(buf.recent(), None);
16+
//!
17+
//! buf.write(3);
18+
//! buf.write(5);
19+
//! buf.extend(&[4, 4]);
20+
//!
21+
//! // The most recent written element is a four.
22+
//! assert_eq!(buf.recent(), Some(&4));
23+
//!
24+
//! // To access all elements in an unspecified order, use `as_slice()`.
25+
//! for el in buf.as_slice() {
26+
//! println!("{:?}", el);
27+
//! }
28+
//!
29+
//! // Now we can prepare an average of all values, which comes out to 4.
30+
//! let avg = buf.as_slice().iter().sum::<usize>() / buf.len();
31+
//! assert_eq!(avg, 4);
32+
//! ```
33+
34+
use core::borrow::Borrow;
35+
use core::borrow::BorrowMut;
136
use core::fmt;
37+
use core::marker::PhantomData;
238
use core::mem::MaybeUninit;
339
use core::ops::Deref;
440
use core::ptr;
541
use core::slice;
642

43+
use crate::storage::OwnedStorage;
44+
use crate::storage::Storage;
45+
use crate::storage::ViewStorage;
46+
47+
/// Base struct for [`HistoryBuffer`] and [`HistoryBufferView`], generic over the [`Storage`].
48+
///
49+
/// In most cases you should use [`HistoryBuffer`] or [`HistoryBufferView`] directly. Only use this
50+
/// struct if you want to write code that's generic over both.
51+
pub struct HistoryBufferInner<T, S: Storage> {
52+
write_at: usize,
53+
filled: bool,
54+
data: S::Buffer<MaybeUninit<T>>,
55+
}
56+
757
/// A "history buffer", similar to a write-only ring buffer of fixed length.
858
///
959
/// This buffer keeps a fixed number of elements. On write, the oldest element
@@ -36,11 +86,40 @@ use core::slice;
3686
/// let avg = buf.as_slice().iter().sum::<usize>() / buf.len();
3787
/// assert_eq!(avg, 4);
3888
/// ```
39-
pub struct HistoryBuffer<T, const N: usize> {
40-
data: [MaybeUninit<T>; N],
41-
write_at: usize,
42-
filled: bool,
43-
}
89+
pub type HistoryBuffer<T, const N: usize> = HistoryBufferInner<T, OwnedStorage<N>>;
90+
91+
/// A "view" into a [`HistoryBuffer`]
92+
///
93+
/// Unlike [`HistoryBuffer`], it doesn't have the `const N: usize` in its type signature.
94+
///
95+
/// # Examples
96+
/// ```
97+
/// use heapless::HistoryBuffer;
98+
///
99+
/// // Initialize a new buffer with 8 elements.
100+
/// let mut owned_buf = HistoryBuffer::<_, 8>::new();
101+
/// let buf: &mut HistoryBufferView<_> = &mut owned_buf;
102+
///
103+
/// // Starts with no data
104+
/// assert_eq!(buf.recent(), None);
105+
///
106+
/// buf.write(3);
107+
/// buf.write(5);
108+
/// buf.extend(&[4, 4]);
109+
///
110+
/// // The most recent written element is a four.
111+
/// assert_eq!(buf.recent(), Some(&4));
112+
///
113+
/// // To access all elements in an unspecified order, use `as_slice()`.
114+
/// for el in buf.as_slice() {
115+
/// println!("{:?}", el);
116+
/// }
117+
///
118+
/// // Now we can prepare an average of all values, which comes out to 4.
119+
/// let avg = buf.as_slice().iter().sum::<usize>() / buf.len();
120+
/// assert_eq!(avg, 4);
121+
/// ```
122+
pub type HistoryBufferView<T> = HistoryBufferInner<T, ViewStorage>;
44123

45124
impl<T, const N: usize> HistoryBuffer<T, N> {
46125
const INIT: MaybeUninit<T> = MaybeUninit::uninit();
@@ -69,12 +148,6 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
69148
filled: false,
70149
}
71150
}
72-
73-
/// Clears the buffer, replacing every element with the default value of
74-
/// type `T`.
75-
pub fn clear(&mut self) {
76-
*self = Self::new();
77-
}
78151
}
79152

80153
impl<T, const N: usize> HistoryBuffer<T, N>
@@ -101,19 +174,46 @@ where
101174
filled: true,
102175
}
103176
}
104-
177+
}
178+
impl<T: Copy, S: Storage> HistoryBufferInner<T, S> {
105179
/// Clears the buffer, replacing every element with the given value.
106180
pub fn clear_with(&mut self, t: T) {
107-
*self = Self::new_with(t);
181+
// SAFETY: we reset the values just after
182+
unsafe { self.drop_contents() };
183+
self.write_at = 0;
184+
self.filled = true;
185+
186+
for d in self.data.borrow_mut() {
187+
*d = MaybeUninit::new(t);
188+
}
108189
}
109190
}
110191

111-
impl<T, const N: usize> HistoryBuffer<T, N> {
192+
impl<T, S: Storage> HistoryBufferInner<T, S> {
193+
/// Clears the buffer
194+
pub fn clear(&mut self) {
195+
// SAFETY: we reset the values just after
196+
unsafe { self.drop_contents() };
197+
self.write_at = 0;
198+
self.filled = false;
199+
}
200+
}
201+
202+
impl<T, S: Storage> HistoryBufferInner<T, S> {
203+
unsafe fn drop_contents(&mut self) {
204+
unsafe {
205+
ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
206+
self.data.borrow_mut().as_mut_ptr() as *mut T,
207+
self.len(),
208+
))
209+
}
210+
}
211+
112212
/// Returns the current fill level of the buffer.
113213
#[inline]
114214
pub fn len(&self) -> usize {
115215
if self.filled {
116-
N
216+
self.capacity()
117217
} else {
118218
self.write_at
119219
}
@@ -138,7 +238,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
138238
/// underlying backing array.
139239
#[inline]
140240
pub fn capacity(&self) -> usize {
141-
N
241+
self.data.borrow().len()
142242
}
143243

144244
/// Returns whether the buffer is full
@@ -151,9 +251,9 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
151251
pub fn write(&mut self, t: T) {
152252
if self.filled {
153253
// Drop the old before we overwrite it.
154-
unsafe { ptr::drop_in_place(self.data[self.write_at].as_mut_ptr()) }
254+
unsafe { ptr::drop_in_place(self.data.borrow_mut()[self.write_at].as_mut_ptr()) }
155255
}
156-
self.data[self.write_at] = MaybeUninit::new(t);
256+
self.data.borrow_mut()[self.write_at] = MaybeUninit::new(t);
157257

158258
self.write_at += 1;
159259
if self.write_at == self.capacity() {
@@ -189,7 +289,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
189289
/// ```
190290
pub fn recent(&self) -> Option<&T> {
191291
self.recent_index()
192-
.map(|i| unsafe { &*self.data[i].as_ptr() })
292+
.map(|i| unsafe { &*self.data.borrow()[i].as_ptr() })
193293
}
194294

195295
/// Returns index of the most recently written value in the underlying slice.
@@ -230,7 +330,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
230330
/// ```
231331
pub fn oldest(&self) -> Option<&T> {
232332
self.oldest_index()
233-
.map(|i| unsafe { &*self.data[i].as_ptr() })
333+
.map(|i| unsafe { &*self.data.borrow()[i].as_ptr() })
234334
}
235335

236336
/// Returns index of the oldest value in the underlying slice.
@@ -258,7 +358,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
258358
/// Returns the array slice backing the buffer, without keeping track
259359
/// of the write position. Therefore, the element order is unspecified.
260360
pub fn as_slice(&self) -> &[T] {
261-
unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.len()) }
361+
unsafe { slice::from_raw_parts(self.data.borrow().as_ptr() as *const _, self.len()) }
262362
}
263363

264364
/// Returns a pair of slices which contain, in order, the contents of the buffer.
@@ -298,20 +398,11 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
298398
/// assert_eq!(x, y)
299399
/// }
300400
/// ```
301-
pub fn oldest_ordered(&self) -> OldestOrdered<'_, T, N> {
302-
match (self.oldest_index(), self.recent_index()) {
303-
(Some(oldest_index), Some(recent_index)) => OldestOrdered {
304-
buf: self,
305-
next: oldest_index,
306-
back: recent_index,
307-
done: false,
308-
},
309-
_ => OldestOrdered {
310-
buf: self,
311-
next: 0,
312-
back: 0,
313-
done: true,
314-
},
401+
pub fn oldest_ordered(&self) -> OldestOrderedInner<'_, T, S> {
402+
let (old, new) = self.as_slices();
403+
OldestOrderedInner {
404+
phantom: PhantomData,
405+
inner: old.iter().chain(new),
315406
}
316407
}
317408
}
@@ -354,14 +445,9 @@ where
354445
}
355446
}
356447

357-
impl<T, const N: usize> Drop for HistoryBuffer<T, N> {
448+
impl<T, S: Storage> Drop for HistoryBufferInner<T, S> {
358449
fn drop(&mut self) {
359-
unsafe {
360-
ptr::drop_in_place(ptr::slice_from_raw_parts_mut(
361-
self.data.as_mut_ptr() as *mut T,
362-
self.len(),
363-
))
364-
}
450+
unsafe { self.drop_contents() }
365451
}
366452
}
367453

@@ -404,51 +490,74 @@ where
404490
}
405491
}
406492

493+
/// Base struct for [`OldestOrdered`] and [`OldestOrderedView`], generic over the [`Storage`].
494+
///
495+
/// In most cases you should use [`OldestOrdered`] or [`OldestOrderedView`] directly. Only use this
496+
/// struct if you want to write code that's generic over both.
497+
pub struct OldestOrderedInner<'a, T, S: Storage> {
498+
phantom: PhantomData<S>,
499+
inner: core::iter::Chain<core::slice::Iter<'a, T>, core::slice::Iter<'a, T>>,
500+
}
501+
407502
/// Double ended iterator on the underlying buffer ordered from the oldest data
408503
/// to the newest
409-
#[derive(Clone)]
410-
pub struct OldestOrdered<'a, T, const N: usize> {
411-
buf: &'a HistoryBuffer<T, N>,
412-
next: usize,
413-
back: usize,
414-
done: bool,
415-
}
504+
/// This type exists for backwards compatibility. It is always better to convert it to an [`OldestOrderedView`] with [`into_view`](OldestOrdered::into_view)
505+
pub type OldestOrdered<'a, T, const N: usize> = OldestOrderedInner<'a, T, OwnedStorage<N>>;
416506

417-
impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> {
418-
type Item = &'a T;
507+
/// Double ended iterator on the underlying buffer ordered from the oldest data
508+
/// to the newest
509+
pub type OldestOrderedView<'a, T> = OldestOrderedInner<'a, T, ViewStorage>;
419510

420-
fn next(&mut self) -> Option<&'a T> {
421-
if self.done {
422-
return None;
511+
impl<'a, T, const N: usize> OldestOrdered<'a, T, N> {
512+
/// Remove the `N` const-generic parameter from the iterator
513+
///
514+
/// For the opposite operation, see [`into_legacy_iter`](OldestOrderedView::into_legacy_iter)
515+
pub fn into_view(self) -> OldestOrderedView<'a, T> {
516+
OldestOrderedView {
517+
phantom: PhantomData,
518+
inner: self.inner,
423519
}
520+
}
521+
}
424522

425-
if self.next == self.back {
426-
self.done = true;
523+
impl<'a, T> OldestOrderedView<'a, T> {
524+
/// Add back the `N` const-generic parameter to use it with APIs expecting the legacy type
525+
///
526+
/// You probably do not need this
527+
///
528+
/// For the opposite operation, see [`into_view`](OldestOrdered::into_view)
529+
pub fn into_legacy_iter<const N: usize>(self) -> OldestOrdered<'a, T, N> {
530+
OldestOrdered {
531+
phantom: PhantomData,
532+
inner: self.inner,
427533
}
428-
429-
let item = &self.buf[self.next];
430-
431-
self.next = if self.next == N - 1 { 0 } else { self.next + 1 };
432-
433-
Some(item)
434534
}
435535
}
436536

437-
impl<'a, T, const N: usize> DoubleEndedIterator for OldestOrdered<'a, T, N> {
438-
fn next_back(&mut self) -> Option<Self::Item> {
439-
if self.done {
440-
return None;
537+
impl<'a, T, S: Storage> Clone for OldestOrderedInner<'a, T, S> {
538+
fn clone(&self) -> Self {
539+
Self {
540+
phantom: PhantomData,
541+
inner: self.inner.clone(),
441542
}
543+
}
544+
}
442545

443-
if self.next == self.back {
444-
self.done = true;
445-
}
546+
impl<'a, T, S: Storage> Iterator for OldestOrderedInner<'a, T, S> {
547+
type Item = &'a T;
446548

447-
let item = &self.buf[self.back];
549+
fn next(&mut self) -> Option<&'a T> {
550+
self.inner.next()
551+
}
448552

449-
self.back = if self.back == 0 { N - 1 } else { self.back - 1 };
553+
fn size_hint(&self) -> (usize, Option<usize>) {
554+
self.inner.size_hint()
555+
}
556+
}
450557

451-
Some(item)
558+
impl<'a, T, const N: usize> DoubleEndedIterator for OldestOrdered<'a, T, N> {
559+
fn next_back(&mut self) -> Option<Self::Item> {
560+
self.inner.next_back()
452561
}
453562
}
454563

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ pub use vec::{Vec, VecView};
104104
mod test_helpers;
105105

106106
mod deque;
107-
mod histbuf;
107+
pub mod histbuf;
108108
mod indexmap;
109109
mod indexset;
110110
pub mod linear_map;

0 commit comments

Comments
 (0)