diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b34e84bad..945188b189 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - Implemented `retain` for `IndexMap` and `IndexSet`. - Recover `StableDeref` trait for `pool::object::Object` and `pool::boxed::Box`. - Add polyfills for ESP32S2 +- `HistoryBuffer.pop_oldest()` +- `HistoryBuffer.filled()` getter to replace filled member variable +- `HistoryBuffer` unit tests for `HistoryBuffer.pop_oldest`. ### Changed @@ -34,6 +37,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [breaking-change] this crate now depends on `atomic-polyfill` v1.0.1, meaning that targets that require a polyfill need a `critical-section` **v1.x.x** implementation. +- `HistoryBuffer.len()` to be a getter rather than computed on call. +- `HistoryBuffer.write()` +- `HistoryBuffer.recent()` +- `HistoryBuffer.oldest_ordered()` +- `OldestOrdered.next()` + ### Fixed ### Removed diff --git a/src/histbuf.rs b/src/histbuf.rs index 4ccd83b774..ffa5ed6efc 100644 --- a/src/histbuf.rs +++ b/src/histbuf.rs @@ -37,7 +37,7 @@ use core::slice; pub struct HistoryBuffer { data: [MaybeUninit; N], write_at: usize, - filled: bool, + len: usize, // filled: bool, } impl HistoryBuffer { @@ -64,7 +64,7 @@ impl HistoryBuffer { Self { data: [Self::INIT; N], write_at: 0, - filled: false, + len: 0, } } @@ -96,7 +96,7 @@ where Self { data: [MaybeUninit::new(t); N], write_at: 0, - filled: true, + len: N, } } @@ -110,11 +110,7 @@ impl HistoryBuffer { /// Returns the current fill level of the buffer. #[inline] pub fn len(&self) -> usize { - if self.filled { - N - } else { - self.write_at - } + self.len } /// Returns the capacity of the buffer, which is the length of the @@ -124,21 +120,46 @@ impl HistoryBuffer { N } + #[inline] + fn filled(&self) -> bool { + self.len() == self.capacity() + } + /// Writes an element to the buffer, overwriting the oldest value. pub fn write(&mut self, t: T) { - if self.filled { + if self.filled() { // Drop the old before we overwrite it. unsafe { ptr::drop_in_place(self.data[self.write_at].as_mut_ptr()) } + } else { + self.len += 1; } + self.data[self.write_at] = MaybeUninit::new(t); self.write_at += 1; if self.write_at == self.capacity() { self.write_at = 0; - self.filled = true; } } + /// Gets the oldest element from the buffer and removes it. + pub fn pop_oldest(&mut self) -> Option { + if self.len == 0 { + return None; + } + + let popped = unsafe { + Some( + self.data[(self.write_at + self.capacity() - self.len()) % self.capacity()] + .assume_init_read(), + ) + }; + + self.len -= 1; + + popped + } + /// Clones and writes all elements in a slice to the buffer. /// /// If the slice is longer than the buffer, only the last `self.len()` @@ -166,7 +187,7 @@ impl HistoryBuffer { /// ``` pub fn recent(&self) -> Option<&T> { if self.write_at == 0 { - if self.filled { + if self.filled() { Some(unsafe { &*self.data[self.capacity() - 1].as_ptr() }) } else { None @@ -179,7 +200,11 @@ 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.as_ptr() as *const _, self.len(), + ) + } } /// Returns a pair of slices which contain, in order, the contents of the buffer. @@ -197,7 +222,7 @@ impl HistoryBuffer { pub fn as_slices(&self) -> (&[T], &[T]) { let buffer = self.as_slice(); - if !self.filled { + if !self.filled() { (buffer, &[]) } else { (&buffer[self.write_at..], &buffer[..self.write_at]) @@ -220,18 +245,25 @@ impl HistoryBuffer { /// /// ``` pub fn oldest_ordered<'a>(&'a self) -> OldestOrdered<'a, T, N> { - if self.filled { + if self.filled() { OldestOrdered { buf: self, cur: self.write_at, wrapped: false, } } else { - // special case: act like we wrapped already to handle empty buffer. - OldestOrdered { - buf: self, - cur: 0, - wrapped: true, + if self.write_at >= self.len() { + OldestOrdered { + buf: self, + cur: self.write_at - self.len(), + wrapped: true, + } + } else { + OldestOrdered { + buf: self, + cur: self.write_at + self.capacity() - self.len(), + wrapped: true, + } } } } @@ -269,8 +301,8 @@ where for (new, old) in ret.data.iter_mut().zip(self.as_slice()) { new.write(old.clone()); } - ret.filled = self.filled; ret.write_at = self.write_at; + ret.len = self.len; ret } } @@ -337,7 +369,7 @@ impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> { type Item = &'a T; fn next(&mut self) -> Option<&'a T> { - if self.cur == self.buf.len() && self.buf.filled { + if self.cur == self.buf.capacity() { // roll-over self.cur = 0; self.wrapped = true; @@ -347,7 +379,7 @@ impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> { return None; } - let item = &self.buf[self.cur]; + let item = unsafe { self.buf.data[self.cur].assume_init_ref() }; self.cur += 1; Some(item) } @@ -575,4 +607,41 @@ mod tests { ); } } + + #[test] + fn pop_oldest() { + let mut x: HistoryBuffer = HistoryBuffer::new(); + + // simple pop + x.extend(&[1, 2, 3]); + + assert_eq!(x.pop_oldest(), Some(1)); + + // pop after wrap-around + x.extend(&[4, 5, 6]); + + assert_eq!(x.pop_oldest(), Some(2)); + + // recent + assert_eq!(x.recent(), Some(&6)); + + // ordered iterator + assert_eq_iter(x.oldest_ordered(), &[3, 4, 5, 6]); + + // clear via pop + for i in 3..=6 { + assert_eq!(x.pop_oldest(), Some(i)); + } + + assert_eq!(x.pop_oldest(), None); + + // clear again from empty + x.extend(&[1, 2, 3, 4, 5, 6, 7, 8]); + + for i in 4..=8 { + assert_eq!(x.pop_oldest(), Some(i)); + } + + assert_eq!(x.pop_oldest(), None); + } }