use std::{convert::Infallible, marker::PhantomData, mem, num::NonZero};
use crate::{
alloc::{self, BoxSliceExt as _, VecExt as _},
base::{
marker::ThreadBound,
num::{Generation, bounded},
},
depot::{map, queue::links},
};
mod raw;
pub mod scoped;
pub type Id = bounded::U64<0, { u64::MAX >> 2 }>;
#[derive(Clone, Copy, Debug)]
pub struct Plan {
records: u32,
buckets: NonZero<u32>,
}
impl Plan {
pub const fn counts<const N: usize, const LIMIT: u32>(
counts: [bounded::U32<0, LIMIT>; N],
) -> Self {
const { assert!((N as u128) * (LIMIT as u128) <= 1 << 29, "capacity") };
let mut records = 0_u32;
let mut index = 0;
while index < N {
records = records.wrapping_add(counts[index].get());
index += 1;
}
let shift = records.wrapping_mul(2).wrapping_sub(1).leading_zeros();
let buckets = 1_u32.wrapping_shl(shift.wrapping_neg());
Self {
records,
buckets: NonZero::<u32>::MIN.saturating_add(buckets - 1),
}
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Key<const MAX: u32>(NonZero<u64>);
impl<const MAX: u32> Key<MAX> {
pub const fn from_raw(raw: NonZero<u64>) -> Option<Self> {
if raw.get() as u32 != 0 && (raw.get() >> 32) as u32 <= MAX {
Some(Self(raw))
} else {
None
}
}
pub const fn raw(self) -> NonZero<u64> {
self.0
}
pub const fn index(self) -> u32 {
(self.0.get() as u32) - 1
}
pub const fn generation(self) -> u32 {
(self.0.get() >> 32) as u32
}
fn new(index: links::Index, epoch: Generation<MAX>) -> Self {
let slot = NonZero::<u64>::MIN.saturating_add(u64::from(index.raw()));
Self(slot | (u64::from(epoch.get()) << 32))
}
fn slot(self) -> links::Index {
unsafe { links::Index::new_raw(self.index()) }
}
}
pub struct Table<T, H, const MAX: u32> {
records: Vec<raw::Record<T, MAX>>,
buckets: Box<[u32]>,
free: Option<links::Index>,
available: u32,
hash: H,
bound: PhantomData<ThreadBound>,
}
impl<T, H: map::Hasher<Id>, const MAX: u32> Table<T, H, MAX> {
pub fn try_new(plan: Plan, hash: H) -> Result<Self, alloc::Error> {
let records = Vec::try_new(plan.records as usize)?;
let buckets = Box::try_repeat(plan.buckets.get() as usize, links::Index::END)?;
Ok(Self {
records,
buckets,
free: None,
available: plan.records,
hash,
bound: PhantomData,
})
}
pub const fn available(&self) -> usize {
self.available as usize
}
pub fn get(&self, key: Key<MAX>) -> Option<&T> {
let record = self.record(key)?;
Some(unsafe { record.value.assume_init_ref() })
}
pub fn lookup(&self, key: Key<MAX>, id: Id) -> Option<&T> {
let record = self.record(key)?;
if record.id != id.get() {
return None;
}
Some(unsafe { record.value.assume_init_ref() })
}
pub fn id(&self, key: Key<MAX>) -> Option<Id> {
Some(self.record(key)?.id())
}
pub fn find(&self, id: Id) -> Option<(Key<MAX>, &T)> {
let map::raw::State::Occupied(bucket) = self.scan(id) else {
return None;
};
let index = unsafe { self.index(&bucket) };
let record = unsafe { self.at(index) };
Some((Key::new(index, record.generation), unsafe {
record.value.assume_init_ref()
}))
}
pub fn key(&self, id: Id) -> Option<Key<MAX>> {
self.find(id).map(|(key, _)| key)
}
pub fn entry(
&mut self,
id: Id,
) -> map::Entry<Occupied<'_, T, H, MAX>, Option<Vacant<'_, T, H, MAX>>> {
match self.scan(id) {
map::raw::State::Occupied(bucket) => {
let index = unsafe { self.index(&bucket) };
map::Entry::Occupied(Occupied {
table: self,
bucket: Some(bucket),
index,
})
}
map::raw::State::Vacant(bucket) if self.available != 0 => {
map::Entry::Vacant(Some(Vacant {
table: self,
bucket,
id,
}))
}
map::raw::State::Vacant(_) | map::raw::State::Full => map::Entry::Vacant(None),
}
}
pub fn exact(&mut self, key: Key<MAX>, id: Id) -> Result<Occupied<'_, T, H, MAX>, &mut Self> {
if !self.record(key).is_some_and(|record| record.id == id.get()) {
return Err(self);
}
Ok(Occupied {
table: self,
bucket: None,
index: key.slot(),
})
}
pub fn scope<R>(
&mut self,
run: impl for<'id> FnOnce(scoped::Scope<'_, 'id, T, H, MAX>) -> R,
) -> R {
run(scoped::Scope::new(self))
}
fn record(&self, key: Key<MAX>) -> Option<&raw::Record<T, MAX>> {
let record = self.records.get(key.index() as usize)?;
record.matches(key).then_some(record)
}
fn scan(&self, id: Id) -> map::raw::State {
match map::raw::scan::<_, Infallible, true>(self, self.hash.hash(&id), |bucket| {
let index = unsafe { self.index(&bucket) };
Ok(unsafe { self.at(index) }.id == id.get())
}) {
Ok(state) => state,
Err(error) => match error {},
}
}
unsafe fn index(&self, bucket: &map::raw::Occupied) -> links::Index {
let raw = unsafe { *self.buckets.get_unchecked(bucket.index()) };
unsafe { links::Index::new_raw(raw) }
}
unsafe fn at(&self, index: links::Index) -> &raw::Record<T, MAX> {
unsafe { self.records.get_unchecked(index.get()) }
}
unsafe fn member(&self, index: links::Index) -> map::raw::Occupied {
let id = unsafe { self.at(index) }.id();
unsafe {
map::raw::member(self, self.hash.hash(&id), |bucket| {
self.index(&bucket) == index
})
}
}
}
impl<T, H, const MAX: u32> Drop for Table<T, H, MAX> {
fn drop(&mut self) {
if !mem::needs_drop::<T>() {
return;
}
for record in &mut self.records {
if record.is_live() {
unsafe { record.value.assume_init_drop() };
}
}
}
}
unsafe impl<T, H: map::Hasher<Id>, const MAX: u32> map::raw::Directory for Table<T, H, MAX> {
fn buckets(&self) -> usize {
self.buckets.len()
}
unsafe fn hash(&self, index: usize) -> Option<u64> {
let raw = unsafe { *self.buckets.get_unchecked(index) };
let index = links::Index::decode(raw)?;
Some(self.hash.hash(&unsafe { self.at(index) }.id()))
}
unsafe fn candidate(&self, index: usize, _: u64) -> map::raw::Candidate {
if unsafe { *self.buckets.get_unchecked(index) } == links::Index::END {
map::raw::Candidate::Vacant
} else {
map::raw::Candidate::Occupied
}
}
}
unsafe impl<T, H: map::Hasher<Id>, const MAX: u32> map::raw::Shift for Table<T, H, MAX> {
unsafe fn shift(&mut self, hole: &map::raw::Vacant, next: &map::raw::Occupied, _: u64) {
let raw = unsafe { *self.buckets.get_unchecked(next.index()) };
unsafe {
*self.buckets.get_unchecked_mut(hole.index()) = raw;
*self.buckets.get_unchecked_mut(next.index()) = links::Index::END;
}
}
}
pub struct Occupied<'a, T, H, const MAX: u32> {
table: &'a mut Table<T, H, MAX>,
bucket: Option<map::raw::Occupied>,
index: links::Index,
}
impl<'a, T, H: map::Hasher<Id>, const MAX: u32> Occupied<'a, T, H, MAX> {
pub fn key(&self) -> Key<MAX> {
Key::new(self.index, unsafe { self.table.at(self.index) }.generation)
}
pub fn id(&self) -> Id {
unsafe { self.table.at(self.index) }.id()
}
pub fn get(&self) -> &T {
unsafe { self.table.at(self.index).value.assume_init_ref() }
}
pub fn get_mut(&mut self) -> &mut T {
unsafe {
self.table
.records
.get_unchecked_mut(self.index.get())
.value
.assume_init_mut()
}
}
pub fn remove(self) -> T {
let bucket = match self.bucket {
Some(bucket) => bucket,
None => unsafe { self.table.member(self.index) },
};
unsafe { raw::remove(self.table, bucket, self.index) }
}
}
pub struct Vacant<'a, T, H, const MAX: u32> {
table: &'a mut Table<T, H, MAX>,
bucket: map::raw::Vacant,
id: Id,
}
impl<'a, T, H: map::Hasher<Id>, const MAX: u32> Vacant<'a, T, H, MAX> {
pub fn insert(self, value: T) -> Occupied<'a, T, H, MAX> {
let Self { table, bucket, id } = self;
let offset = bucket.index();
let index = unsafe { raw::insert(table, bucket, id, value) };
let bucket = unsafe { map::raw::Occupied::new(offset) };
Occupied {
table,
bucket: Some(bucket),
index,
}
}
}
const _: () = {
assert!(mem::size_of::<raw::Record<[u32; 3], { u32::MAX }>>() == 24);
assert!(mem::size_of::<Key<{ u32::MAX }>>() == 8);
assert!(mem::size_of::<Option<Key<{ u32::MAX }>>>() == 8);
};