Skip to content

Commit

Permalink
histbuf: Implement DoubleEndedIterator for OldestOrdered
Browse files Browse the repository at this point in the history
Introduce new helper methods next_index() and prev_index() for
cleaner iteration over underlying slice of HistoryBuffer.
Use them to rewrite OldestOrdered Iterator implementation
with a bit more abstraction and extend the struct with
DoubleEndedIterator.

Signed-off-by: Witold Lipieta <[email protected]>
  • Loading branch information
witek103 committed Jan 25, 2024
1 parent 6d62cb2 commit 2dbe72d
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 24 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `is_full`, `recent_index`, `oldest`, and `oldest_index` to `HistoryBuffer`
- Added infallible conversions from arrays to `Vec`.
- Added `Vec::spare_capacity_mut`.
- Added `DoubleEndedIterator` implementation for `OldestOrdered`.

### Changed

Expand Down
113 changes: 89 additions & 24 deletions src/histbuf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
}
}

/// Returns an iterator for iterating over the buffer from oldest to newest.
/// Returns double ended iterator for iterating over the buffer from
/// the oldest to the newest and back.
///
/// # Examples
///
Expand All @@ -298,19 +299,49 @@ impl<T, const N: usize> HistoryBuffer<T, N> {
/// }
/// ```
pub fn oldest_ordered(&self) -> OldestOrdered<'_, T, N> {
if self.filled {
OldestOrdered {
buf: self,
cur: self.write_at,
wrapped: false,
}
OldestOrdered {
buf: self,
next: self.oldest_index(),
back: self.recent_index(),
crossed: false,
}
}

/// Returns index of the value written after given index in the underlying
/// slice. If the index of the most recent value is given this returns
/// index of the oldest value in the buffer.
fn next_index(&self, current: usize) -> Option<usize> {
let upper_bound = if self.filled {
self.capacity()
} else {
// special case: act like we wrapped already to handle empty buffer.
OldestOrdered {
buf: self,
cur: 0,
wrapped: true,
}
self.write_at
};

if current >= upper_bound {
None
} else if current == upper_bound - 1 {
Some(0)
} else {
Some(current + 1)
}
}

/// Returns index of the value written before given index in the underlying
/// slice. If the index of the oldest value is given this returns index of
/// the most recent value in the buffer.
fn prev_index(&self, current: usize) -> Option<usize> {
let upper_bound = if self.filled {
self.capacity()
} else {
self.write_at
};

if current >= upper_bound {
None
} else if current == 0 {
Some(upper_bound - 1)
} else {
Some(current - 1)
}
}
}
Expand Down Expand Up @@ -403,31 +434,51 @@ where
}
}

/// An iterator on the underlying buffer ordered from oldest data to newest
/// 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<T, N>,
cur: usize,
wrapped: bool,
next: Option<usize>,
back: Option<usize>,
crossed: bool,
}

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 {
// roll-over
self.cur = 0;
self.wrapped = true;
let cur = self.next?;

if self.crossed {
return None;
}

if self.cur == self.buf.write_at && self.wrapped {
if self.next == self.back {
self.crossed = true;
}

self.next = self.buf.next_index(cur);

Some(&self.buf[cur])
}
}

impl<'a, T, const N: usize> DoubleEndedIterator for OldestOrdered<'a, T, N> {
fn next_back(&mut self) -> Option<Self::Item> {
let cur = self.back?;

if self.crossed {
return None;
}

let item = &self.buf[self.cur];
self.cur += 1;
Some(item)
if self.next == self.back {
self.crossed = true;
}

self.back = self.buf.prev_index(cur);

Some(&self.buf[cur])
}
}

Expand Down Expand Up @@ -603,18 +654,28 @@ mod tests {
let mut iter = buffer.oldest_ordered();
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
assert_eq!(iter.next_back(), None);
assert_eq!(iter.next_back(), None);

// test on a un-filled buffer
let mut buffer: HistoryBuffer<u8, 6> = HistoryBuffer::new();
buffer.extend([1, 2, 3]);
assert_eq!(buffer.len(), 3);
assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3]);
assert_eq_iter(buffer.oldest_ordered().rev(), &[3, 2, 1]);
let mut iter = buffer.oldest_ordered();
assert_eq!(iter.next(), Some(&1));
assert_eq!(iter.next_back(), Some(&3));
assert_eq!(iter.next_back(), Some(&2));
assert_eq!(iter.next_back(), None);
assert_eq!(iter.next(), None);

// test on a filled buffer
let mut buffer: HistoryBuffer<u8, 6> = HistoryBuffer::new();
buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]);
assert_eq!(buffer.len(), 6);
assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3, 4, 5, 6]);
assert_eq_iter(buffer.oldest_ordered().rev(), &[6, 5, 4, 3, 2, 1]);

// comprehensive test all cases
for n in 0..50 {
Expand All @@ -625,6 +686,10 @@ mod tests {
buffer.oldest_ordered().copied(),
n.saturating_sub(N as u8)..n,
);
assert_eq_iter(
buffer.oldest_ordered().rev().copied(),
(n.saturating_sub(N as u8)..n).rev(),
);
}
}

Expand Down

0 comments on commit 2dbe72d

Please sign in to comment.