use std::cmp;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
use std::mem::{self, MaybeUninit};
use std::num::NonZeroU64;
use std::sync::atomic::{AtomicU64, Ordering::Relaxed};
use typenum::Unsigned;
pub mod error;
pub mod id;
pub mod iter;
pub mod map;
pub mod state;
#[cfg(test)]
mod test;
use id::IdTrait;
use state::State;
fn static_assert_index_bits<IndexBits: Unsigned>() {
assert!(IndexBits::U32 >= 1);
assert!(IndexBits::U32 <= 32);
}
fn static_assert_generation_bits<GenerationBits: Unsigned>() {
assert!(GenerationBits::U32 <= 31);
}
fn static_assert_u32_fits_in_usize() {
assert!(mem::size_of::<usize>() >= mem::size_of::<u32>());
}
struct Slots<T, GenerationBits: Unsigned> {
values: Vec<MaybeUninit<T>>,
state_words: Vec<u32>,
_phantom: PhantomData<GenerationBits>,
}
impl<T, GenerationBits: Unsigned> Slots<T, GenerationBits> {
fn with_capacity(capacity: usize) -> Self {
let state_words_capacity = state::word_count_from_state_count::<GenerationBits>(capacity);
Self {
values: Vec::with_capacity(capacity),
state_words: Vec::with_capacity(state_words_capacity),
_phantom: PhantomData,
}
}
fn len(&self) -> usize {
self.values.len()
}
fn debug_assert_len_invariant(&self) {
debug_assert_eq!(
state::word_count_from_state_count::<GenerationBits>(self.values.len()),
self.state_words.len(),
);
}
unsafe fn state_unchecked(&self, index: usize) -> State<GenerationBits> {
state::read_state::<GenerationBits>(self.state_words.as_ptr(), index)
}
fn state(&self, index: usize) -> Option<State<GenerationBits>> {
if index < self.len() {
Some(unsafe { self.state_unchecked(index) })
} else {
None
}
}
unsafe fn set_state_unchecked(&mut self, index: usize, state: State<GenerationBits>) {
state::write_state::<GenerationBits>(self.state_words.as_mut_ptr(), index, state)
}
unsafe fn value_unchecked(&self, index: usize) -> &MaybeUninit<T> {
self.values.get_unchecked(index)
}
unsafe fn value_unchecked_mut(&mut self, index: usize) -> &mut MaybeUninit<T> {
self.values.get_unchecked_mut(index)
}
fn insert(&mut self, value: T) {
self.debug_assert_len_invariant();
self.values.reserve(1);
if self.values.len() % state::states_per_word::<GenerationBits>() == 0 {
self.state_words.reserve(1);
self.state_words.push(0);
}
unsafe {
self.set_state_unchecked(
self.values.len(),
State::new(1), );
}
self.values.push(MaybeUninit::new(value));
self.debug_assert_len_invariant();
}
fn allocate_empty_slots(&mut self, additional: usize) {
self.debug_assert_len_invariant();
self.values.reserve(additional);
let unused_initialized_states =
state::unused_states_in_last_word::<GenerationBits>(self.values.len());
let new_states = additional.saturating_sub(unused_initialized_states);
let new_state_words = state::word_count_from_state_count::<GenerationBits>(new_states);
self.state_words.reserve(new_state_words);
unsafe {
debug_assert!(self.values.len() <= self.values.capacity() - additional);
self.values.set_len(self.values.len() + additional);
}
self.state_words.resize(
state::word_count_from_state_count::<GenerationBits>(self.values.len()),
0,
);
self.debug_assert_len_invariant();
}
}
impl<T, GenerationBits: Unsigned> Drop for Slots<T, GenerationBits> {
fn drop(&mut self) {
if mem::needs_drop::<T>() {
for i in 0..self.values.len() {
unsafe {
if self.state_unchecked(i).is_occupied() {
self.value_unchecked_mut(i).assume_init_drop();
}
}
}
}
}
}
impl<T, GenerationBits: Unsigned> Clone for Slots<T, GenerationBits>
where
T: Clone,
{
fn clone(&self) -> Self {
let mut cloned_slots = Self {
values: Vec::with_capacity(self.values.len()),
state_words: self.state_words.clone(),
_phantom: PhantomData,
};
for i in 0..self.values.len() {
unsafe {
if self.state_unchecked(i).is_occupied() {
let cloned_element = self.value_unchecked(i).assume_init_ref().clone();
cloned_slots.values.set_len(i + 1);
cloned_slots.value_unchecked_mut(i).write(cloned_element);
}
}
}
unsafe {
cloned_slots.values.set_len(self.values.len());
}
cloned_slots
}
}
impl<T, GenerationBits: Unsigned> fmt::Debug for Slots<T, GenerationBits>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
struct EmptySlot;
impl fmt::Debug for EmptySlot {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
write!(f, "<empty>")
}
}
let mut list = f.debug_list();
for i in 0..self.values.len() {
unsafe {
if self.state_unchecked(i).is_occupied() {
list.entry(self.value_unchecked(i).assume_init_ref());
} else {
list.entry(&EmptySlot);
}
}
}
list.finish()
}
}
#[repr(transparent)]
pub struct Id<T>(
NonZeroU64,
PhantomData<fn() -> T>,
);
unsafe impl<T> IdTrait for Id<T> {
type IndexBits = typenum::U32;
type GenerationBits = typenum::U31;
fn new(index: usize, generation: u32) -> Self {
debug_assert_eq!(index as u64 >> 32, 0, "high bits should not be set");
debug_assert_eq!(generation >> 31, 0, "the high bit should not be set");
Self(
unsafe {
NonZeroU64::new_unchecked(
((((generation << 1) + 1) as u64) << 32) | index as u32 as u64,
)
},
PhantomData,
)
}
fn index(&self) -> usize {
self.0.get() as u32 as usize
}
fn generation(&self) -> u32 {
(self.0.get() >> 33) as u32
}
fn matching_state(&self) -> State<Self::GenerationBits> {
debug_assert_eq!(1, (self.0.get() >> 32) & 1);
State::new((self.0.get() >> 32) as u32)
}
fn null() -> Self {
Self(NonZeroU64::new(u64::MAX).unwrap(), PhantomData)
}
fn is_null(&self) -> bool {
self.0.get() == u64::MAX
}
}
impl<T> Copy for Id<T> {}
impl<T> Clone for Id<T> {
fn clone(&self) -> Self {
Self(self.0, PhantomData)
}
}
impl<T> fmt::Debug for Id<T>
where
Self: IdTrait,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
write!(
f,
"Id {{ index: {}, generation {} }}",
self.index(),
self.generation(),
)
}
}
impl<T> Hash for Id<T> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.0.hash(state);
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<T> Eq for Id<T> {}
pub struct Registry<T, ID: IdTrait = Id<T>> {
slots: Slots<T, ID::GenerationBits>,
free_indexes: Vec<usize>,
retired_indexes: Vec<usize>,
reservation_cursor: AtomicU64,
}
impl<T> Registry<T, Id<T>> {
pub fn new() -> Self {
Self::with_id_type()
}
pub fn with_capacity(capacity: usize) -> Self {
Self::with_id_type_and_capacity(capacity)
}
}
impl<T, ID: IdTrait> Registry<T, ID> {
pub fn with_id_type() -> Self {
Self::with_id_type_and_capacity(0)
}
pub fn with_id_type_and_capacity(capacity: usize) -> Self {
static_assert_index_bits::<ID::IndexBits>();
static_assert_generation_bits::<ID::GenerationBits>();
static_assert_u32_fits_in_usize();
Self {
slots: Slots::with_capacity(capacity),
free_indexes: Vec::new(),
retired_indexes: Vec::new(),
reservation_cursor: AtomicU64::new(0),
}
}
pub fn len(&self) -> usize {
self.slots.len() - self.free_indexes.len() - self.retired_indexes.len()
}
fn debug_best_effort_checks_for_contract_violations(&self, id: ID) {
if !cfg!(debug_assertions) {
return;
}
if id.is_null() {
return;
}
let new_slots_reserved = self
.reservation_cursor
.load(Relaxed)
.saturating_sub(self.free_indexes.len() as u64);
let valid_slots = self.slots.len() as u64 + new_slots_reserved;
debug_assert!((id.index() as u64) < valid_slots);
let Some(state) = self.slots.state(id.index()) else {
debug_assert_eq!(id.generation(), 0);
return;
};
if state.is_retired() {
return;
}
debug_assert!(
id.generation() <= state.generation(),
"ID generation ({}) is newer than its slot ({}); did it dangle across a recycle_retired()?",
id.generation(),
state.generation(),
);
}
pub fn contains_id(&self, id: ID) -> bool {
self.debug_best_effort_checks_for_contract_violations(id);
if let Some(state) = self.slots.state(id.index()) {
state == id.matching_state()
} else {
false
}
}
pub fn get(&self, id: ID) -> Option<&T> {
if self.contains_id(id) {
Some(unsafe { self.get_unchecked(id) })
} else {
None
}
}
pub fn get_mut(&mut self, id: ID) -> Option<&mut T> {
if self.contains_id(id) {
Some(unsafe { self.get_unchecked_mut(id) })
} else {
None
}
}
pub unsafe fn get_unchecked(&self, id: ID) -> &T {
self.slots.value_unchecked(id.index()).assume_init_ref()
}
pub unsafe fn get_unchecked_mut(&mut self, id: ID) -> &mut T {
self.slots.value_unchecked_mut(id.index()).assume_init_mut()
}
#[must_use]
pub fn insert(&mut self, value: T) -> ID {
self.allocate_reservations();
if let Some(free_index) = self.free_indexes.pop() {
unsafe {
self.slots.value_unchecked_mut(free_index).write(value);
let empty_state = self.slots.state_unchecked(free_index);
debug_assert!(empty_state.is_empty());
let occupied_state = empty_state.next_occupied_state();
self.slots.set_state_unchecked(free_index, occupied_state);
ID::new(free_index, occupied_state.generation())
}
} else {
assert!(self.slots.len() < ID::max_len(), "all slots occupied");
let new_index = self.slots.len();
self.slots.insert(value);
ID::new(new_index, 0)
}
}
pub fn remove(&mut self, id: ID) -> Option<T> {
self.debug_best_effort_checks_for_contract_violations(id);
self.allocate_reservations();
let Some(state) = self.slots.state(id.index()) else {
return None;
};
if state != id.matching_state() {
return None;
}
let empty_state = state.next_empty_state();
if empty_state.is_retired() {
self.retired_indexes.push(id.index());
} else {
self.free_indexes.push(id.index());
}
unsafe {
self.slots.set_state_unchecked(id.index(), empty_state);
Some(self.slots.value_unchecked(id.index()).assume_init_read())
}
}
#[must_use]
pub fn reserve_id(&self) -> ID {
self.reserve_ids(1).next().unwrap()
}
#[must_use]
pub fn reserve_ids(&self, count: usize) -> iter::ReservationIter<'_, T, ID> {
assert!(count <= u32::MAX as usize, "reservation exceeds u32::MAX");
let old_cursor = self.reservation_cursor.fetch_add(count as u64, Relaxed);
let new_cursor = old_cursor + count as u64;
let new_total_slots =
new_cursor.saturating_sub(self.free_indexes.len() as u64) + self.slots.len() as u64;
if new_total_slots > ID::max_len() as u64 {
panic!("new length exceeds max for the ID type ({})", ID::max_len());
}
return iter::ReservationIter {
registry: self,
start: old_cursor as usize,
end: new_cursor as usize,
};
}
pub fn allocate_reservations(&mut self) {
let cursor: &mut u64 = self.reservation_cursor.get_mut();
if *cursor == 0 {
return; }
let cursor_usize = (*cursor).try_into().expect("reservations overflow usize");
let reused_slots = cmp::min(cursor_usize, self.free_indexes.len());
let reused_slots_start = self.free_indexes.len() - reused_slots;
let new_slots = cursor_usize - reused_slots;
if new_slots > 0 {
self.slots.allocate_empty_slots(new_slots);
}
self.free_indexes.truncate(reused_slots_start);
*cursor = 0;
}
pub fn insert_reserved(
&mut self,
id: ID,
value: T,
) -> Result<(), error::InsertReservedError<T>> {
self.debug_best_effort_checks_for_contract_violations(id);
self.allocate_reservations();
let error_kind;
if let Some(state) = self.slots.state(id.index()) {
if state == id.reserved_state() {
unsafe {
self.slots
.set_state_unchecked(id.index(), id.matching_state());
self.slots.value_unchecked_mut(id.index()).write(value);
}
return Ok(());
}
if state == id.matching_state() {
error_kind = error::InsertReservedErrorKind::Exists;
} else if state.generation() >= id.generation() {
error_kind = error::InsertReservedErrorKind::Dangling;
} else {
error_kind = error::InsertReservedErrorKind::GenerationTooNew;
}
} else {
error_kind = error::InsertReservedErrorKind::IndexOutOfBounds;
}
Err(error::InsertReservedError {
inner: value,
kind: error_kind,
})
}
pub fn recycle_retired(&mut self) {
self.allocate_reservations();
self.free_indexes.append(&mut self.retired_indexes);
}
pub fn iter(&self) -> iter::Iter<'_, T, ID> {
iter::Iter {
registry: self,
index: 0,
}
}
pub fn iter_mut(&mut self) -> iter::IterMut<'_, T, ID> {
iter::IterMut {
registry: self,
index: 0,
}
}
pub fn into_iter(self) -> iter::IntoIter<T, ID> {
iter::IntoIter {
registry: self,
index: 0,
}
}
pub fn ids(&self) -> iter::Ids<'_, T, ID> {
iter::Ids { inner: self.iter() }
}
pub fn values(&self) -> iter::Values<'_, T, ID> {
iter::Values { inner: self.iter() }
}
pub fn values_mut(&mut self) -> iter::ValuesMut<'_, T, ID> {
iter::ValuesMut {
inner: self.iter_mut(),
}
}
pub fn into_values(self) -> iter::IntoValues<T, ID> {
iter::IntoValues {
inner: self.into_iter(),
}
}
}
impl<T, ID: IdTrait> Clone for Registry<T, ID>
where
T: Clone,
{
fn clone(&self) -> Self {
Self {
slots: self.slots.clone(),
free_indexes: self.free_indexes.clone(),
retired_indexes: self.retired_indexes.clone(),
reservation_cursor: AtomicU64::new(self.reservation_cursor.load(Relaxed)),
}
}
}
impl<T, ID: IdTrait> std::ops::Index<ID> for Registry<T, ID> {
type Output = T;
fn index(&self, id: ID) -> &T {
self.get(id).unwrap()
}
}
impl<T, ID: IdTrait> std::ops::IndexMut<ID> for Registry<T, ID> {
fn index_mut(&mut self, id: ID) -> &mut T {
self.get_mut(id).unwrap()
}
}
impl<T, ID: IdTrait> fmt::Debug for Registry<T, ID>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
f.debug_struct("Registry")
.field("slots", &self.slots)
.field("free_indexes", &self.free_indexes)
.field("retired_indexes", &self.retired_indexes)
.field("reservation_cursor", &self.reservation_cursor)
.finish()
}
}