use alloc::vec::Vec;
use core::hash::{BuildHasher, Hash, Hasher};
use crate::prelude::*;
use bevy_ecs::prelude::*;
use bevy_math::IVec3;
use bevy_platform::{
collections::{HashMap, HashSet},
hash::{FixedHasher, PassHash},
time::Instant,
};
use bevy_reflect::Reflect;
use super::{ChangedCells, SpatialHashFilter};
use crate::portable_par::PortableParallel;
#[derive(Component, Clone, Copy, Debug, Reflect, PartialEq, Eq)]
pub struct CellHash(u64);
impl Hash for CellHash {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.0);
}
}
impl PartialEq<CellId> for CellHash {
fn eq(&self, other: &CellId) -> bool {
self.0 == other.pre_hash
}
}
impl From<CellId> for CellHash {
fn from(value: CellId) -> Self {
Self(value.pre_hash)
}
}
pub type CellHashSet = HashSet<CellId, PassHash>;
pub type CellHashMap<T> = HashMap<CellId, T, PassHash>;
#[derive(Component, Clone, Copy, Debug, Reflect)]
pub struct CellId {
coord: CellCoord,
grid: Entity,
pre_hash: u64,
}
impl PartialEq for CellId {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.pre_hash == other.pre_hash && self.coord == other.coord && self.grid == other.grid
}
}
impl Eq for CellId {}
impl Hash for CellId {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
state.write_u64(self.pre_hash);
}
}
impl CellId {
#[inline]
pub(super) fn new(parent: &ChildOf, cell: &CellCoord) -> Self {
Self::from_parent(parent.parent(), cell)
}
#[inline]
pub(super) fn from_parent(parent: Entity, cell: &CellCoord) -> Self {
let mut hasher = FixedHasher.build_hasher();
hasher.write_u64(parent.to_bits());
cell.hash(&mut hasher);
CellId {
coord: *cell,
grid: parent,
pre_hash: hasher.finish(),
}
}
#[doc(hidden)]
pub fn __new_manual(parent: Entity, cell: &CellCoord) -> Self {
Self::from_parent(parent, cell)
}
#[inline]
pub fn fast_eq(&self, other: &Self) -> bool {
self.pre_hash == other.pre_hash
}
pub fn adjacent(&self, cell_radius: u8) -> impl Iterator<Item = CellId> + '_ {
let radius = cell_radius as i32;
let search_width = 1 + 2 * radius;
let search_volume = search_width.pow(3);
let center = -radius;
let stride = IVec3::new(1, search_width, search_width.pow(2));
(0..search_volume)
.map(move |i| center + i / stride % search_width)
.filter(|offset| *offset != IVec3::ZERO) .map(move |offset| {
let neighbor_cell = self.coord + offset;
CellId::from_parent(self.grid, &neighbor_cell)
})
}
#[allow(clippy::too_many_arguments)]
pub fn update<F: SpatialHashFilter>(
mut commands: Commands,
mut changed_cells: ResMut<ChangedCells<F>>,
mut spatial_entities: Query<
(Entity, &ChildOf, &CellCoord, &mut CellId, &mut CellHash),
(F, Or<(Changed<ChildOf>, Changed<CellCoord>)>),
>,
added_entities: Query<(Entity, &ChildOf, &CellCoord), (F, Without<CellId>)>,
mut removed_cells: RemovedComponents<CellCoord>,
mut stats: Option<ResMut<crate::timing::GridHashStats>>,
mut thread_updated_hashes: Local<PortableParallel<Vec<Entity>>>,
mut thread_commands: Local<PortableParallel<Vec<(Entity, CellId, CellHash)>>>,
) {
let start = Instant::now();
changed_cells.updated.clear();
added_entities
.par_iter()
.for_each(|(entity, parent, cell)| {
let cell_guid = CellId::new(parent, cell);
let fast_hash = cell_guid.into();
thread_commands.scope(|tl| tl.push((entity, cell_guid, fast_hash)));
thread_updated_hashes.scope(|tl| tl.push(entity));
});
for (entity, cell_guid, fast_hash) in thread_commands.drain() {
commands.entity(entity).insert((cell_guid, fast_hash));
}
spatial_entities.par_iter_mut().for_each(
|(entity, parent, cell, mut cell_guid, mut fast_hash)| {
let new_cell_guid = CellId::new(parent, cell);
let new_fast_hash = new_cell_guid.pre_hash;
if cell_guid.replace_if_neq(new_cell_guid).is_some() {
thread_updated_hashes.scope(|tl| tl.push(entity));
}
fast_hash.0 = new_fast_hash;
},
);
changed_cells
.updated
.extend(thread_updated_hashes.drain().chain(removed_cells.read()));
if let Some(ref mut stats) = stats {
stats.hash_update_duration += start.elapsed();
}
}
pub fn coord(&self) -> CellCoord {
self.coord
}
pub fn grid(&self) -> Entity {
self.grid
}
}