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

Fix Linear Map for Cargo 1.71 #376

Closed
wants to merge 16 commits into from
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}"
}
]
}
10 changes: 10 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,7 +37,14 @@ 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

### Removed

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);
}
}
Loading
Loading