use alloc::{boxed::Box, vec::Vec};
use core::{
mem::MaybeUninit,
ops::{Index, IndexMut},
};
use fixedbitset::FixedBitSet;
const DEFAULT_CHUNK_SIZE: usize = 16;
pub struct ChunkedVec<T> {
chunks: Vec<Box<[MaybeUninit<T>]>>,
occupied: FixedBitSet,
free_list: Vec<usize>,
len: usize,
chunk_size: usize,
}
impl<T> Default for ChunkedVec<T> {
fn default() -> Self {
Self {
chunks: Vec::new(),
occupied: FixedBitSet::new(),
free_list: Vec::new(),
len: 0,
chunk_size: DEFAULT_CHUNK_SIZE,
}
}
}
impl<T> ChunkedVec<T> {
pub fn new() -> Self {
Self::default()
}
pub fn with_chunk_size(chunk_size: usize) -> Self {
assert!(chunk_size > 0, "chunk size must be greater than 0");
Self {
chunks: Vec::new(),
occupied: FixedBitSet::new(),
free_list: Vec::new(),
len: 0,
chunk_size,
}
}
pub fn with_capacity(capacity: usize) -> Self {
let mut vec = Self::new();
vec.reserve(capacity);
vec
}
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[inline]
pub fn capacity(&self) -> usize {
self.chunks.len() * self.chunk_size
}
pub fn reserve(&mut self, additional: usize) {
let required = self.len.saturating_add(additional);
while self.capacity() < required {
self.grow();
}
}
pub fn insert(&mut self, value: T) -> usize {
let index = match self.free_list.pop() {
Some(idx) => idx,
None => {
let idx = self.len;
if idx >= self.capacity() {
self.grow();
}
idx
}
};
let (chunk, offset) = self.index_to_chunk_offset(index);
self.chunks[chunk][offset] = MaybeUninit::new(value);
self.occupied.grow(index + 1);
self.occupied.set(index, true);
self.len += 1;
index
}
pub fn remove(&mut self, index: usize) -> T {
assert!(self.occupied.contains(index), "slot is not occupied");
let (chunk, offset) = self.index_to_chunk_offset(index);
let value = unsafe {
core::mem::replace(&mut self.chunks[chunk][offset], MaybeUninit::uninit()).assume_init()
};
self.occupied.set(index, false);
self.free_list.push(index);
self.len -= 1;
value
}
pub fn get(&self, index: usize) -> Option<&T> {
if !self.occupied.contains(index) {
return None;
}
let (chunk, offset) = self.index_to_chunk_offset(index);
Some(unsafe { self.chunks[chunk][offset].assume_init_ref() })
}
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
if !self.occupied.contains(index) {
return None;
}
let (chunk, offset) = self.index_to_chunk_offset(index);
Some(unsafe { self.chunks[chunk][offset].assume_init_mut() })
}
#[inline]
pub fn contains(&self, index: usize) -> bool {
self.occupied.contains(index)
}
#[inline]
fn index_to_chunk_offset(&self, index: usize) -> (usize, usize) {
(index / self.chunk_size, index % self.chunk_size)
}
fn grow(&mut self) {
let chunk: Box<[MaybeUninit<T>]> = (0..self.chunk_size)
.map(|_| MaybeUninit::uninit())
.collect();
self.chunks.push(chunk);
}
}
impl<T> Drop for ChunkedVec<T> {
fn drop(&mut self) {
for index in self.occupied.ones() {
let (chunk, offset) = self.index_to_chunk_offset(index);
unsafe {
self.chunks[chunk][offset].assume_init_drop();
}
}
}
}
impl<T> Index<usize> for ChunkedVec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
self.get(index).expect("index out of bounds or slot empty")
}
}
impl<T> IndexMut<usize> for ChunkedVec<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
self.get_mut(index)
.expect("index out of bounds or slot empty")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn index_mapping() {
let vec = ChunkedVec::<()>::with_chunk_size(4);
assert_eq!(vec.index_to_chunk_offset(0), (0, 0));
assert_eq!(vec.index_to_chunk_offset(1), (0, 1));
assert_eq!(vec.index_to_chunk_offset(3), (0, 3));
assert_eq!(vec.index_to_chunk_offset(4), (1, 0));
assert_eq!(vec.index_to_chunk_offset(7), (1, 3));
assert_eq!(vec.index_to_chunk_offset(8), (2, 0));
}
#[test]
fn remove_and_reuse() {
let mut vec = ChunkedVec::new();
let idx0 = vec.insert(10);
let idx1 = vec.insert(20);
let val = vec.remove(idx1);
assert_eq!(val, 20);
assert_eq!(vec.len(), 1);
assert!(!vec.contains(idx1));
assert!(vec.contains(idx0));
let idx3 = vec.insert(40);
assert_eq!(idx3, idx1);
assert_eq!(vec[idx3], 40);
}
#[test]
fn capacity_growth() {
let mut vec = ChunkedVec::<i32>::new();
assert_eq!(vec.capacity(), 0);
vec.reserve(1);
assert!(vec.capacity() >= 1);
vec.reserve(20);
assert!(vec.capacity() >= 20);
}
#[test]
fn many_inserts() {
let mut vec = ChunkedVec::new();
for i in 0..50 {
let idx = vec.insert(i);
assert_eq!(vec[idx], i);
}
assert_eq!(vec.len(), 50);
}
#[test]
fn get_none_for_empty_slot() {
let mut vec = ChunkedVec::new();
vec.insert(10);
vec.insert(20);
vec.remove(0);
assert!(vec.get(0).is_none());
assert_eq!(vec.get(1), Some(&20));
}
}