Skip to content

Commit

Permalink
Merge branch 'main' into fix-linearmap-drop
Browse files Browse the repository at this point in the history
  • Loading branch information
AdinAck authored Sep 23, 2023
2 parents 8452e92 + 886c129 commit 60b8ed0
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 25 deletions.
65 changes: 65 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug unit tests in library 'heapless'",
"cargo": {
"args": [
"test",
"--no-run",
"--lib",
"--package=heapless"
],
"filter": {
"name": "heapless",
"kind": "lib"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug integration test 'tsan'",
"cargo": {
"args": [
"test",
"--no-run",
"--test=tsan",
"--package=heapless"
],
"filter": {
"name": "tsan",
"kind": "test"
}
},
"args": [],
"cwd": "${workspaceFolder}"
},
{
"type": "lldb",
"request": "launch",
"name": "Debug integration test 'cpass'",
"cargo": {
"args": [
"test",
"--no-run",
"--test=cpass",
"--package=heapless"
],
"filter": {
"name": "cpass",
"kind": "test"
}
},
"args": [],
"cwd": "${workspaceFolder}"
}
]
}
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
- Linear Map `Drop` impl

Expand Down
10 changes: 7 additions & 3 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,13 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("cargo:rustc-cfg=unstable_channel");
}

match compile_probe(ARM_LLSC_PROBE) {
Some(status) if status.success() => println!("cargo:rustc-cfg=arm_llsc"),
_ => {}
// AArch64 instruction set contains `clrex` but not `ldrex` or `strex`; the
// probe will succeed when we already know to deny this target from LLSC.
if !target.starts_with("aarch64") {
match compile_probe(ARM_LLSC_PROBE) {
Some(status) if status.success() => println!("cargo:rustc-cfg=arm_llsc"),
_ => {}
}
}

Ok(())
Expand Down
162 changes: 140 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,21 +245,33 @@ 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,
}
}
}
}

/// Returns an iterator from oldest to youngest, popping values as it iterates.
pub fn oldest_ordered_mut<'a>(&'a mut self) -> OldestOrderedMut<'a, T, N> {
OldestOrderedMut { buf: self }
}
}

impl<T, const N: usize> Extend<T> for HistoryBuffer<T, N> {
Expand Down Expand Up @@ -269,8 +306,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 +374,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,12 +384,24 @@ 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)
}
}

pub struct OldestOrderedMut<'a, T, const N: usize> {
buf: &'a mut HistoryBuffer<T, N>,
}

impl<'a, T, const N: usize> Iterator for OldestOrderedMut<'a, T, N> {
type Item = T;

fn next(&mut self) -> Option<T> {
self.buf.pop_oldest()
}
}

#[cfg(test)]
mod tests {
use crate::HistoryBuffer;
Expand Down Expand Up @@ -575,4 +624,73 @@ 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);
}

#[test]
fn ordered_mut() {
let mut x: HistoryBuffer<u8, 5> = HistoryBuffer::new();

// simple
x.extend(&[1, 2, 3]);

assert_eq_iter(x.oldest_ordered_mut(), [1, 2, 3]);
assert_eq!(x.len(), 0);

// after wrap-around
x.extend(&[1, 2, 3, 4, 5, 6]);

assert_eq_iter(x.oldest_ordered_mut(), [2, 3, 4, 5, 6]);
assert_eq!(x.len(), 0);

// from empty
{
let mut ordered = x.oldest_ordered_mut();
assert_eq!(ordered.next(), None);
}

// partial iteration
x.extend(&[1, 2, 3, 4, 5, 6]);
{
let mut ordered = x.oldest_ordered_mut();
assert_eq!(ordered.next(), Some(2));
}

assert_eq!(x.len(), 4 as usize);
}
}

0 comments on commit 60b8ed0

Please sign in to comment.