use std::{marker, mem::MaybeUninit, ops, ptr};
use o3::{
alloc::{self, array::Plan, tail},
depot::{arena::linked::chain, array::init},
};
use crate::driver::retained::flight::storage::{
Flights, Preparation, Prepared, Slots, index_limit, is_layout_valid, state,
};
impl<T: Copy, const INDEX_BITS: u32, const GENERATION_BITS: u32>
Flights<T, INDEX_BITS, GENERATION_BITS>
{
pub(super) fn directory(
&self,
) -> &Vec<ptr::NonNull<state::State<T, INDEX_BITS, GENERATION_BITS>>> {
unsafe { &*self.directory.get() }
}
pub(in crate::driver::retained::flight) unsafe fn try_batch<'a, 'owner, const GROUPS: usize>(
&'owner self,
caps: [usize; GROUPS],
) -> Result<Prepared<'a, 'owner, T, INDEX_BITS, GENERATION_BITS, GROUPS>, alloc::Error> {
const { assert!(GROUPS > 0) };
if !is_layout_valid::<INDEX_BITS, GENERATION_BITS>() {
return Err(alloc::Error::Overflow);
}
let base = self.directory().len();
let mut end = base;
let mut rest = caps
.iter()
.try_fold(0usize, |total, capacity| total.checked_add(*capacity));
let mut entries = MaybeUninit::uninit();
let mut guard = init::Guard::<_, GROUPS, false>::new(&mut entries);
let states = loop {
let vacant = match guard.next() {
ops::ControlFlow::Break(states) => break states.into_array(),
ops::ControlFlow::Continue(vacant) => vacant,
};
let capacity = *vacant.get(&caps);
rest = rest.map(|rest| rest - capacity);
let state = match state::Header::select(&self.groups, capacity) {
Some(slots) => Preparation::Reuse(slots),
None => {
let (seed, extent, next) = Self::seed(end, capacity, rest)?;
let block = state::Header::try_block(extent, seed)?;
end = next;
Preparation::New { block, capacity }
}
};
guard = vacant.insert(state);
};
let added = end - base;
let dir = self.directory();
let spare = dir.capacity() - dir.len();
let next = if added > spare {
Some(self.try_plan(end)?)
} else {
None
};
Ok(Prepared {
arena: self,
preparation: marker::PhantomData,
states,
directory: next,
})
}
fn try_plan(
&self,
required: usize,
) -> Result<Vec<ptr::NonNull<state::State<T, INDEX_BITS, GENERATION_BITS>>>, alloc::Error> {
let dir = self.directory();
let source = dir.as_slice();
let mut next: Vec<ptr::NonNull<state::State<T, INDEX_BITS, GENERATION_BITS>>> =
Plan::try_reserve(dir.capacity(), required)?.try_vec()?;
unsafe {
ptr::copy_nonoverlapping(source.as_ptr(), next.as_mut_ptr(), source.len());
next.set_len(source.len());
}
Ok(next)
}
fn publish<'owner>(
groups: &'owner chain::List<
state::Header<T, INDEX_BITS, GENERATION_BITS>,
state::State<T, INDEX_BITS, GENERATION_BITS>,
>,
dir: &mut Vec<ptr::NonNull<state::State<T, INDEX_BITS, GENERATION_BITS>>>,
block: tail::Block<
chain::Node<
state::Header<T, INDEX_BITS, GENERATION_BITS>,
state::State<T, INDEX_BITS, GENERATION_BITS>,
>,
state::State<T, INDEX_BITS, GENERATION_BITS>,
>,
capacity: usize,
) -> Slots<'owner, T, INDEX_BITS, GENERATION_BITS> {
let len = dir.len();
let slots = state::Header::publish(groups, block, capacity);
let entries = slots.group.as_slice();
let target = dir.as_mut_ptr();
for (offset, entry) in entries.iter().enumerate() {
unsafe { target.add(len + offset).write(ptr::NonNull::from(entry)) };
}
unsafe { dir.set_len(len + entries.len()) };
slots
}
fn seed(
len: usize,
capacity: usize,
rest: Option<usize>,
) -> Result<(u32, usize, usize), alloc::Overflow> {
let required = len.checked_add(capacity).ok_or(alloc::Overflow)?;
let limit = index_limit::<INDEX_BITS>();
if required > limit {
return Err(alloc::Overflow);
}
let extent = match rest {
Some(rest) if capacity != 0 => capacity
.checked_next_power_of_two()
.filter(|extent| *extent <= (limit - len).saturating_sub(rest))
.unwrap_or(capacity),
Some(_) | None => capacity,
};
Ok((len as u32, extent, len + extent))
}
}
impl<'owner, T: Copy, const INDEX_BITS: u32, const GENERATION_BITS: u32, const GROUPS: usize>
Prepared<'_, 'owner, T, INDEX_BITS, GENERATION_BITS, GROUPS>
{
pub fn groups(self) -> [Slots<'owner, T, INDEX_BITS, GENERATION_BITS>; GROUPS] {
let Self {
arena,
preparation: marker::PhantomData,
states,
directory: next,
} = self;
let dir = unsafe { &mut *arena.directory.get() };
if let Some(next) = next {
*dir = next;
}
states.map(|state| match state {
Preparation::Reuse(slots) => slots,
Preparation::New { block, capacity } => {
Flights::publish(&arena.groups, dir, block, capacity)
}
})
}
}