use std::{mem, num::NonZero};
use crate::{
alloc::{self, BoxUninitExt as _},
base::marker::ThreadBound,
depot::{
arena::{self, linked::slots::store::Free},
array::copy,
capacity,
},
};
pub struct Fixed<N: arena::raw::LinkedNode> {
nodes: Box<[mem::MaybeUninit<N>]>,
free: Free<Option<NonZero<u32>>>,
initialized: u16,
available: u16,
_thread: ThreadBound,
}
impl<N: arena::raw::LinkedNode> Fixed<N> {
pub fn try_new(capacity: capacity::Count) -> Result<Self, alloc::Error> {
const {
assert!(!mem::needs_drop::<N>());
assert!(mem::size_of::<Self>() == 2 * mem::size_of::<usize>() + 8);
}
if capacity.raw() > u16::MAX as u32 {
return Err(alloc::Overflow.into());
}
Ok(Self {
nodes: Box::<[mem::MaybeUninit<N>]>::try_uninit(capacity.get())?,
free: Free::new(None),
initialized: 0,
available: capacity.raw() as u16,
_thread: ThreadBound::NEW,
})
}
pub fn reserve<const C: usize>(
&mut self,
inputs: ©::Vec<N::Input, C>,
) -> Option<Batch<'_, N, C>> {
if inputs.len() > self.available as usize {
return None;
}
let len = inputs.len() as u16;
self.available = self.available.wrapping_sub(len);
let mut nodes = [const { mem::MaybeUninit::uninit() }; C];
for (slot, &input) in nodes.iter_mut().zip(inputs) {
slot.write(unsafe { self.claim(input) });
}
Some(Batch {
owner: self,
nodes,
len,
next: 0,
})
}
pub fn get(&self, index: NonZero<u32>) -> Option<&N> {
let index = self.live(index)?;
Some(unsafe { self.slot(index) })
}
pub fn get_mut(&mut self, index: NonZero<u32>) -> Option<&mut N> {
let index = self.live(index)?;
Some(unsafe { self.slot_mut(index) })
}
pub fn entry(&mut self, index: NonZero<u32>) -> Option<Occupied<'_, N>> {
let index = self.live(index)?;
Some(Occupied { owner: self, index })
}
fn live(&self, index: NonZero<u32>) -> Option<NonZero<u32>> {
if index.get() > u32::from(self.initialized) {
return None;
}
let node = unsafe { self.slot(index) };
node.next().is_none().then_some(index)
}
unsafe fn claim(&mut self, input: N::Input) -> NonZero<u32> {
if let Some(index) = self.free.head {
let node = unsafe { self.slot_mut(index) };
let next = node.next();
node.reset(input);
unsafe { node.set_next(None) };
self.free.pop(Some(index), next, None);
return index;
}
let offset = self.initialized as usize;
unsafe { self.nodes.get_unchecked_mut(offset) }.write(N::new(input));
self.initialized = self.initialized.wrapping_add(1);
NonZero::<u32>::MIN.saturating_add(offset as u32)
}
unsafe fn recycle(&mut self, index: NonZero<u32>) {
let mut free = self.free;
let next = free.push(Some(index), None);
unsafe { self.slot_mut(index).set_next(next) };
self.free = free;
}
fn refund(&mut self, count: u16) {
self.available = self.available.wrapping_add(count);
}
unsafe fn slot(&self, index: NonZero<u32>) -> &N {
unsafe {
self.nodes
.get_unchecked(index.get() as usize - 1)
.assume_init_ref()
}
}
unsafe fn slot_mut(&mut self, index: NonZero<u32>) -> &mut N {
unsafe {
self.nodes
.get_unchecked_mut(index.get() as usize - 1)
.assume_init_mut()
}
}
}
#[must_use]
pub struct Batch<'a, N: arena::raw::LinkedNode, const C: usize> {
owner: &'a mut Fixed<N>,
nodes: [mem::MaybeUninit<NonZero<u32>>; C],
len: u16,
next: u16,
}
impl<N: arena::raw::LinkedNode, const C: usize> Batch<'_, N, C> {
pub fn take(&mut self) -> Option<NonZero<u32>> {
if self.next == self.len {
return None;
}
let index = unsafe {
self.nodes
.get_unchecked(self.next as usize)
.assume_init_read()
};
self.next = self.next.wrapping_add(1);
Some(index)
}
pub fn get(&self, index: NonZero<u32>) -> Option<&N> {
self.owner.get(index)
}
pub fn get_mut(&mut self, index: NonZero<u32>) -> Option<&mut N> {
self.owner.get_mut(index)
}
}
impl<N: arena::raw::LinkedNode, const C: usize> Drop for Batch<'_, N, C> {
fn drop(&mut self) {
let count = self.len.wrapping_sub(self.next);
while let Some(index) = self.take() {
unsafe { self.owner.recycle(index) };
}
self.owner.refund(count);
}
}
#[must_use]
pub struct Occupied<'a, N: arena::raw::LinkedNode> {
owner: &'a mut Fixed<N>,
index: NonZero<u32>,
}
impl<N: arena::raw::LinkedNode> Occupied<'_, N> {
pub fn remove(self) {
unsafe { self.owner.recycle(self.index) };
self.owner.refund(1);
}
}