Skip to content

Commit

Permalink
might have fixed this circular buffer misery
Browse files Browse the repository at this point in the history
  • Loading branch information
gbin committed May 22, 2024
1 parent 23c45dc commit 025a80f
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 111 deletions.
167 changes: 59 additions & 108 deletions copper/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
//! A circular buffer-like queue.
//! (Adapted by gbin for fixed size and inplace)
//!
//! The `CircularQueue<T>` is created with a set capacity, then items are pushed in. When the queue
//! runs out of capacity, newer items start overwriting the old ones, starting from the oldest.
//!
//! There are built-in iterators that go from the newest items to the oldest ones and from the
//! oldest items to the newest ones.
//!
//! Two queues are considered equal if iterating over them with `iter()` would yield the same
//! sequence of elements.
//!
//!
extern crate alloc;

use std::iter::{Chain, Rev};
use std::mem::{replace, MaybeUninit};
use std::mem::replace;
use std::slice::{Iter as SliceIter, IterMut as SliceIterMut};

pub use generic_array::typenum::consts::*;
const MAX_TASKS: usize = 255;
const MAX_CONCURRENT_TASKS: usize = 8;

#[derive(Debug)]
struct CopperLiskMask {
mask: [u128; MAX_TASKS / 128 + 1],
}

#[derive(Debug)]
enum CopperListState {
Empty,
PrecessingTasks(CopperLiskMask),
BeingSerialized,
}

/// A circular buffer-like queue.
#[derive(Debug)]
pub struct CircularQueue<T: Default + Sized + PartialEq, const N: usize> {
data: [MaybeUninit<T>; N],
pub struct CopperListsManager<T: Sized + PartialEq, const N: usize> {
// copper_list_masks: CopperListMask,
data: Box<[T; N]>,
length: usize,
insertion_index: usize,
}
Expand All @@ -43,26 +43,24 @@ pub type AscIterMut<'a, T> = Chain<SliceIterMut<'a, T>, SliceIterMut<'a, T>>;
/// A value popped from `CircularQueue<T>` as the result of a push operation.
pub type Popped<T> = Option<T>;

impl<T: Default + Copy + Sized + PartialEq, const N: usize> PartialEq for CircularQueue<T, N> {
impl<T: Sized + PartialEq, const N: usize> PartialEq for CopperListsManager<T, N> {
fn eq(&self, other: &Self) -> bool {
if self.len() != other.len() {
return false;
}
other.iter().zip(self.iter()).all(|(a, b)| a == b)
}

fn ne(&self, other: &Self) -> bool {
if self.len() != other.len() {
return true;
}
!self.eq(other)
}
}

impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N> {
impl<T: Sized + PartialEq, const N: usize> CopperListsManager<T, N> {
pub fn new() -> Self {
CircularQueue {
data: unsafe { MaybeUninit::uninit().assume_init() },
let data = unsafe {
let layout = std::alloc::Layout::new::<[T; N]>();
let ptr = std::alloc::alloc_zeroed(layout) as *mut [T; N];
Box::from_raw(ptr)
};
CopperListsManager {
data,
length: 0,
insertion_index: 0,
}
Expand Down Expand Up @@ -104,14 +102,6 @@ impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N>
self.length = 0;
}

unsafe fn initialized_slice(&self) -> &[T] {
&*(self.data.get_unchecked(0..self.length) as *const _ as *const [T])
}

unsafe fn initialized_slice_mut(&mut self) -> &mut [T] {
&mut *(self.data.get_unchecked_mut(0..self.length) as *mut _ as *mut [T])
}

/// Pushes a new element into the queue.
///
/// Once the capacity is reached, pushing new items will overwrite old ones.
Expand All @@ -126,39 +116,30 @@ impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N>
return old;
}

if self.is_full() {
// Replace the element and take ownership of the old value
unsafe {
old = Some(self.data[self.insertion_index].as_ptr().read());
}
} else {
if !self.is_full() {
self.data[self.insertion_index] = x;
self.length += 1;
} else {
old = Some(replace(&mut self.data[self.insertion_index], x));
}

// Write the new element in place
unsafe {
self.data[self.insertion_index].as_mut_ptr().write(x);
}
self.insertion_index = (self.insertion_index + 1) % self.capacity();

old
}

#[inline]
pub fn pop(&mut self) -> Option<T> {
pub fn pop(&mut self) -> Popped<&T> {
if self.capacity() == 0 || self.length == 0 {
return None;
}

if self.insertion_index == 0 {
self.insertion_index = self.capacity() - 1;
} else {
self.insertion_index -= 1;
}
self.length -= 1;

// Take ownership of the element
unsafe { Some(self.data[self.insertion_index].as_ptr().read()) }
Some(&self.data[self.insertion_index])
}

/// Returns an iterator over the queue's contents.
Expand All @@ -167,8 +148,7 @@ impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N>
///
#[inline]
pub fn iter(&self) -> Iter<T> {
let initialized_part = unsafe { self.initialized_slice() };
let (a, b) = initialized_part.split_at(self.insertion_index);
let (a, b) = self.data[0..self.length].split_at(self.insertion_index);
a.iter().rev().chain(b.iter().rev())
}

Expand All @@ -178,9 +158,7 @@ impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N>
///
#[inline]
pub fn iter_mut(&mut self) -> IterMut<T> {
let insertion_index = self.insertion_index;
let initialized_part = unsafe { self.initialized_slice_mut() };
let (a, b) = initialized_part.split_at_mut(insertion_index);
let (a, b) = self.data.split_at_mut(self.insertion_index);
a.iter_mut().rev().chain(b.iter_mut().rev())
}

Expand All @@ -190,8 +168,7 @@ impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N>
///
#[inline]
pub fn asc_iter(&self) -> AscIter<T> {
let initialized_part = unsafe { self.initialized_slice() };
let (a, b) = initialized_part.split_at(self.insertion_index);
let (a, b) = self.data.split_at(self.insertion_index);
b.iter().chain(a.iter())
}

Expand All @@ -201,9 +178,7 @@ impl<T: Default + Copy + Sized + PartialEq, const N: usize> CircularQueue<T, N>
///
#[inline]
pub fn asc_iter_mut(&mut self) -> AscIterMut<T> {
let insertion_index = self.insertion_index;
let initialized_part = unsafe { self.initialized_slice_mut() };
let (a, b) = initialized_part.split_at_mut(insertion_index);
let (a, b) = self.data.split_at_mut(self.insertion_index);
b.iter_mut().chain(a.iter_mut())
}
}
Expand All @@ -214,7 +189,7 @@ mod tests {

#[test]
fn zero_capacity() {
let mut q = CircularQueue::<i32, 0>::new();
let mut q = CopperListsManager::<i32, 0>::new();
assert_eq!(q.len(), 0);
assert_eq!(q.capacity(), 0);
assert!(q.is_empty());
Expand All @@ -235,15 +210,15 @@ mod tests {

#[test]
fn empty_queue() {
let q = CircularQueue::<i32, 5>::new();
let q = CopperListsManager::<i32, 5>::new();

assert!(q.is_empty());
assert_eq!(q.iter().next(), None);
}

#[test]
fn partially_full_queue() {
let mut q = CircularQueue::<_, 5>::new();
let mut q = CopperListsManager::<_, 5>::new();
q.push(1);
q.push(2);
q.push(3);
Expand All @@ -257,7 +232,7 @@ mod tests {

#[test]
fn full_queue() {
let mut q = CircularQueue::<_, 5>::new();
let mut q = CopperListsManager::<_, 5>::new();
q.push(1);
q.push(2);
q.push(3);
Expand All @@ -272,7 +247,7 @@ mod tests {

#[test]
fn over_full_queue() {
let mut q = CircularQueue::<_, 5>::new();
let mut q = CopperListsManager::<_, 5>::new();
q.push(1);
q.push(2);
q.push(3);
Expand All @@ -289,7 +264,7 @@ mod tests {

#[test]
fn clear() {
let mut q = CircularQueue::<_, 5>::new();
let mut q = CopperListsManager::<_, 5>::new();
q.push(1);
q.push(2);
q.push(3);
Expand All @@ -315,7 +290,7 @@ mod tests {

#[test]
fn mutable_iterator() {
let mut q = CircularQueue::<_, 5>::new();
let mut q = CopperListsManager::<_, 5>::new();
q.push(1);
q.push(2);
q.push(3);
Expand All @@ -334,7 +309,7 @@ mod tests {

#[test]
fn zero_sized() {
let mut q = CircularQueue::<_, 5>::new();
let mut q = CopperListsManager::<_, 5>::new();
assert_eq!(q.capacity(), 5);

q.push(());
Expand All @@ -352,8 +327,8 @@ mod tests {

#[test]
fn empty_queue_eq() {
let q1 = CircularQueue::<i32, 5>::new();
let q2 = CircularQueue::<i32, 5>::new();
let q1 = CopperListsManager::<i32, 5>::new();
let q2 = CopperListsManager::<i32, 5>::new();
assert_eq!(q1, q2);

// I am not sure here
Expand All @@ -363,12 +338,12 @@ mod tests {

#[test]
fn partially_full_queue_eq() {
let mut q1 = CircularQueue::<i32, 5>::new();
let mut q1 = CopperListsManager::<i32, 5>::new();
q1.push(1);
q1.push(2);
q1.push(3);

let mut q2 = CircularQueue::<i32, 5>::new();
let mut q2 = CopperListsManager::<i32, 5>::new();
q2.push(1);
q2.push(2);
assert_ne!(q1, q2);
Expand All @@ -382,14 +357,14 @@ mod tests {

#[test]
fn full_queue_eq() {
let mut q1 = CircularQueue::<i32, 5>::new();
let mut q1 = CopperListsManager::<i32, 5>::new();
q1.push(1);
q1.push(2);
q1.push(3);
q1.push(4);
q1.push(5);

let mut q2 = CircularQueue::<i32, 5>::new();
let mut q2 = CopperListsManager::<i32, 5>::new();
q2.push(1);
q2.push(2);
q2.push(3);
Expand All @@ -401,7 +376,7 @@ mod tests {

#[test]
fn over_full_queue_eq() {
let mut q1 = CircularQueue::<i32, 5>::new();
let mut q1 = CopperListsManager::<i32, 5>::new();
q1.push(1);
q1.push(2);
q1.push(3);
Expand All @@ -410,7 +385,7 @@ mod tests {
q1.push(6);
q1.push(7);

let mut q2 = CircularQueue::<i32, 5>::new();
let mut q2 = CopperListsManager::<i32, 5>::new();
q2.push(1);
q2.push(2);
q2.push(3);
Expand All @@ -435,7 +410,7 @@ mod tests {

#[test]
fn clear_eq() {
let mut q1 = CircularQueue::<i32, 5>::new();
let mut q1 = CopperListsManager::<i32, 5>::new();
q1.push(1);
q1.push(2);
q1.push(3);
Expand All @@ -445,7 +420,7 @@ mod tests {
q1.push(7);
q1.clear();

let mut q2 = CircularQueue::<i32, 5>::new();
let mut q2 = CopperListsManager::<i32, 5>::new();
assert_eq!(q1, q2);

q2.push(1);
Expand All @@ -455,13 +430,13 @@ mod tests {

#[test]
fn zero_sized_eq() {
let mut q1 = CircularQueue::<_, 3>::new();
let mut q1 = CopperListsManager::<_, 3>::new();
q1.push(());
q1.push(());
q1.push(());
q1.push(());

let mut q2 = CircularQueue::<_, 3>::new();
let mut q2 = CopperListsManager::<_, 3>::new();
q2.push(());
q2.push(());
assert_ne!(q1, q2);
Expand All @@ -476,32 +451,8 @@ mod tests {
assert_eq!(q1, q2);
}

struct Big {
_data: [u8; 10000000],
}
impl Default for Big {
fn default() -> Self {
Big {
_data: [0; 10000000],
}
}
}

impl Clone for Big {
fn clone(&self) -> Self {
Big { _data: self._data }
}
}

impl Copy for Big {}
impl PartialEq for Big {
fn eq(&self, _: &Self) -> bool {
true
}
}

#[test]
fn no_stack_allocation() {
let _ = CircularQueue::<Big, 5>::new(); // this should not stack overflow
fn be_sure_we_wont_stackoverflow_at_init() {
let _ = CopperListsManager::<[u8; 10_000_000], 3>::new();
}
}
Loading

0 comments on commit 025a80f

Please sign in to comment.