use alloc::collections::TryReserveError;
use bevy::platform::hash::RandomState;
use core::mem::MaybeUninit;
use std::collections::hash_map::{self, HashMap};
use std::hash;
use bevy::ecs::entity::{Entity, EntityGeneration};
#[derive(Debug, Clone)]
struct Slot<T> {
generation: EntityGeneration,
value: T,
}
#[derive(Debug, Clone)]
pub struct SparseSecondaryEntityMap<V, S: hash::BuildHasher = RandomState> {
slots: HashMap<u32, Slot<V>, S>,
}
impl<V> SparseSecondaryEntityMap<V, hash_map::RandomState> {
#[inline]
pub fn new() -> Self {
Self::with_capacity(0)
}
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self {
slots: HashMap::with_capacity(capacity),
}
}
}
fn is_older_generation(a: u32, b: u32) -> bool {
let diff = a.wrapping_sub(b);
diff >= (1 << 31)
}
impl<V, S: hash::BuildHasher> SparseSecondaryEntityMap<V, S> {
#[inline]
pub fn with_hasher(hash_builder: S) -> Self {
Self {
slots: HashMap::with_hasher(hash_builder),
}
}
#[inline]
pub fn with_capacity_and_hasher(capacity: usize, hash_builder: S) -> Self {
Self {
slots: HashMap::with_capacity_and_hasher(capacity, hash_builder),
}
}
#[inline]
pub fn len(&self) -> usize {
self.slots.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
#[inline]
pub fn capacity(&self) -> usize {
self.slots.capacity()
}
#[inline]
pub fn reserve(&mut self, additional: usize) {
self.slots.reserve(additional);
}
#[inline]
pub fn try_reserve(&mut self, additional: usize) -> Result<(), TryReserveError> {
self.slots.try_reserve(additional)
}
#[inline]
pub fn contains(&self, entity: Entity) -> bool {
self.slots
.get(&entity.index_u32())
.is_some_and(|slot| slot.generation == entity.generation())
}
#[inline]
pub fn insert(&mut self, entity: Entity, value: V) -> Option<V> {
if entity == Entity::PLACEHOLDER {
return None;
}
let (index, generation) = (entity.index_u32(), entity.generation());
if let Some(slot) = self.slots.get_mut(&index) {
if slot.generation == generation {
return Some(core::mem::replace(&mut slot.value, value));
}
if unsafe {
is_older_generation(
core::mem::transmute::<EntityGeneration, u32>(generation),
core::mem::transmute::<EntityGeneration, u32>(slot.generation),
)
} {
return None;
}
*slot = Slot { generation, value };
return None;
}
self.slots.insert(index, Slot { generation, value });
None
}
#[inline]
pub fn remove(&mut self, entity: Entity) -> Option<V> {
if let hash_map::Entry::Occupied(entry) = self.slots.entry(entity.index_u32())
&& entry.get().generation == entity.generation()
{
return Some(entry.remove_entry().1.value);
}
None
}
#[inline]
pub fn clear(&mut self) {
self.slots.clear();
}
#[inline]
pub fn get(&self, entity: Entity) -> Option<&V> {
self.slots
.get(&entity.index_u32())
.filter(|slot| slot.generation == entity.generation())
.map(|slot| &slot.value)
}
#[inline]
pub unsafe fn get_unchecked(&self, entity: Entity) -> &V {
debug_assert!(self.contains(entity));
unsafe { self.get(entity).unwrap_unchecked() }
}
#[inline]
pub fn get_mut(&mut self, entity: Entity) -> Option<&mut V> {
self.slots
.get_mut(&entity.index_u32())
.filter(|slot| slot.generation == entity.generation())
.map(|slot| &mut slot.value)
}
#[inline]
pub unsafe fn get_unchecked_mut(&mut self, entity: Entity) -> &mut V {
debug_assert!(self.contains(entity));
unsafe { self.get_mut(entity).unwrap_unchecked() }
}
#[inline]
pub fn get_or_insert_with<F>(&mut self, entity: Entity, f: F) -> V
where
F: FnOnce() -> V,
V: Clone + Copy,
{
if let Some(slot) = self
.slots
.get(&entity.index_u32())
.filter(|s| s.generation == entity.generation())
{
slot.value
} else {
let value = f();
self.insert(entity, value);
value
}
}
#[inline]
pub fn get_disjoint_mut<const N: usize>(
&mut self,
entities: [Entity; N],
) -> Option<[&mut V; N]> {
let mut ptrs: [MaybeUninit<*mut V>; N] = unsafe { MaybeUninit::uninit().assume_init() };
let mut i = 0;
while i < N {
let entity = entities[i];
match self.slots.get_mut(&entity.index_u32()) {
Some(Slot { generation, value }) if *generation == entity.generation() => {
ptrs[i] = MaybeUninit::new(&mut *value);
unsafe {
*core::mem::transmute::<&mut EntityGeneration, &mut u32>(generation) ^= 1;
}
}
_ => break,
}
i += 1;
}
for entity in &entities[0..i] {
match self.slots.get_mut(&entity.index_u32()) {
Some(Slot { generation, .. }) => unsafe {
*core::mem::transmute::<&mut EntityGeneration, &mut u32>(generation) ^= 1;
},
_ => unsafe { core::hint::unreachable_unchecked() },
}
}
if i == N {
Some(unsafe { core::mem::transmute_copy::<_, [&mut V; N]>(&ptrs) })
} else {
None
}
}
#[inline]
pub unsafe fn get_disjoint_unchecked_mut<const N: usize>(
&mut self,
entities: [Entity; N],
) -> [&mut V; N] {
unsafe {
let mut ptrs: [MaybeUninit<*mut V>; N] = MaybeUninit::uninit().assume_init();
for i in 0..N {
ptrs[i] = MaybeUninit::new(self.get_unchecked_mut(entities[i]));
}
core::mem::transmute_copy::<_, [&mut V; N]>(&ptrs)
}
}
}
impl<V, S> Default for SparseSecondaryEntityMap<V, S>
where
S: hash::BuildHasher + Default,
{
fn default() -> Self {
Self::with_hasher(Default::default())
}
}