Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

HistoryBuffer Add pop_oldest #377

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
113 changes: 91 additions & 22 deletions src/histbuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use core::slice;
pub struct HistoryBuffer<T, const N: usize> {
data: [MaybeUninit<T>; N],
write_at: usize,
filled: bool,
len: usize, // filled: bool,
}

impl<T, const N: usize> HistoryBuffer<T, N> {
Expand All @@ -64,7 +64,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
Self {
data: [Self::INIT; N],
write_at: 0,
filled: false,
len: 0,
}
}

Expand Down Expand Up @@ -96,7 +96,7 @@ where
Self {
data: [MaybeUninit::new(t); N],
write_at: 0,
filled: true,
len: N,
}
}

Expand All @@ -110,11 +110,7 @@ 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
}
self.len
}

/// Returns the capacity of the buffer, which is the length of the
Expand All @@ -124,21 +120,46 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
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<T> {
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()`
Expand Down Expand Up @@ -166,7 +187,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
/// ```
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
Expand All @@ -179,7 +200,11 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
/// 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.
Expand All @@ -197,7 +222,7 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
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])
Expand All @@ -220,18 +245,25 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
///
/// ```
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,
}
}
}
}
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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;
Expand All @@ -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)
}
Expand Down Expand Up @@ -575,4 +607,41 @@ mod tests {
);
}
}

#[test]
fn pop_oldest() {
let mut x: HistoryBuffer<u8, 5> = 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);
}
}