use std::mem::MaybeUninit;
use std::ptr::NonNull;
const DEFAULT_BLOCK_CAP: usize = 4096;
pub struct BlockList<T> {
blocks: Vec<Box<[MaybeUninit<T>]>>,
block_cap: usize,
len: usize,
}
impl<T> BlockList<T> {
#[must_use]
pub fn new(block_cap: usize) -> Self {
assert!(block_cap > 0, "block_cap must be > 0");
Self {
blocks: Vec::new(),
block_cap,
len: 0,
}
}
#[must_use]
pub fn with_default_cap() -> Self {
Self::new(DEFAULT_BLOCK_CAP)
}
#[inline]
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[inline]
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn alloc(&mut self, val: T) -> NonNull<T> {
let block_idx = self.len / self.block_cap;
let slot_idx = self.len % self.block_cap;
if block_idx >= self.blocks.len() {
let block: Box<[MaybeUninit<T>]> = (0..self.block_cap)
.map(|_| MaybeUninit::uninit())
.collect::<Vec<_>>()
.into_boxed_slice();
self.blocks.push(block);
}
let slot = &mut self.blocks[block_idx][slot_idx];
slot.write(val);
self.len += 1;
unsafe { NonNull::new_unchecked(slot.as_mut_ptr()) }
}
#[inline]
#[must_use]
pub fn get(&self, index: usize) -> &T {
assert!(
index < self.len,
"BlockList index {index} out of bounds (len {})",
self.len
);
let block_idx = index / self.block_cap;
let slot_idx = index % self.block_cap;
unsafe { self.blocks[block_idx][slot_idx].assume_init_ref() }
}
#[inline]
pub fn get_mut(&mut self, index: usize) -> &mut T {
assert!(
index < self.len,
"BlockList index {index} out of bounds (len {})",
self.len
);
let block_idx = index / self.block_cap;
let slot_idx = index % self.block_cap;
unsafe { self.blocks[block_idx][slot_idx].assume_init_mut() }
}
pub fn rewind_to(&mut self, mark: usize) {
assert!(mark <= self.len, "mark {mark} exceeds len {}", self.len);
for i in (mark..self.len).rev() {
let block_idx = i / self.block_cap;
let slot_idx = i % self.block_cap;
unsafe {
self.blocks[block_idx][slot_idx].assume_init_drop();
}
}
self.len = mark;
}
pub fn reset(&mut self) {
self.rewind_to(0);
}
}
impl<T> Drop for BlockList<T> {
fn drop(&mut self) {
self.reset();
}
}
impl<T> Default for BlockList<T> {
fn default() -> Self {
Self::new(DEFAULT_BLOCK_CAP)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn basic_alloc_and_get() {
let mut bl: BlockList<u64> = BlockList::new(4);
for i in 0..10u64 {
bl.alloc(i);
}
assert_eq!(bl.len(), 10);
for i in 0..10 {
assert_eq!(*bl.get(i), i as u64);
}
}
#[test]
fn pointer_stability() {
let mut bl: BlockList<u64> = BlockList::new(2);
let p0 = bl.alloc(42);
let p1 = bl.alloc(43);
let p2 = bl.alloc(44);
let p3 = bl.alloc(45);
let _p4 = bl.alloc(46);
assert_eq!(unsafe { *p0.as_ptr() }, 42);
assert_eq!(unsafe { *p1.as_ptr() }, 43);
assert_eq!(unsafe { *p2.as_ptr() }, 44);
assert_eq!(unsafe { *p3.as_ptr() }, 45);
}
#[test]
fn rewind_drops_elements() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct Counted(u32);
impl Drop for Counted {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
let mut bl = BlockList::new(4);
for i in 0..6u32 {
bl.alloc(Counted(i));
}
assert_eq!(bl.len(), 6);
bl.rewind_to(2);
assert_eq!(bl.len(), 2);
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 4);
assert_eq!(bl.get(0).0, 0);
assert_eq!(bl.get(1).0, 1);
bl.alloc(Counted(99));
assert_eq!(bl.len(), 3);
assert_eq!(bl.get(2).0, 99);
}
#[test]
fn reset_drops_all() {
static DROP_COUNT: AtomicUsize = AtomicUsize::new(0);
struct Counted;
impl Drop for Counted {
fn drop(&mut self) {
DROP_COUNT.fetch_add(1, Ordering::SeqCst);
}
}
DROP_COUNT.store(0, Ordering::SeqCst);
let mut bl = BlockList::new(4);
for _ in 0..10 {
bl.alloc(Counted);
}
bl.reset();
assert_eq!(bl.len(), 0);
assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 10);
assert!(!bl.blocks.is_empty()); }
#[test]
fn rewind_reuse_blocks() {
let mut bl: BlockList<u64> = BlockList::new(4);
for i in 0..8 {
bl.alloc(i);
}
assert_eq!(bl.blocks.len(), 2);
bl.rewind_to(0);
assert_eq!(bl.blocks.len(), 2);
for i in 0..8 {
bl.alloc(i + 100);
}
assert_eq!(bl.blocks.len(), 2);
assert_eq!(*bl.get(0), 100);
}
}