use alloc::collections::VecDeque;
use core::marker::PhantomData;
use super::SpatialHashFilter;
use crate::hash::component::{CellHashMap, CellHashSet};
use crate::prelude::*;
use bevy_ecs::entity::{EntityHashMap, EntityHashSet};
use bevy_ecs::prelude::*;
use bevy_platform::{collections::HashSet, prelude::*, time::Instant};
#[derive(Clone, Debug)]
pub struct CellLookupEntry {
pub entities: EntityHashSet,
pub occupied_neighbors: Vec<CellId>,
}
impl CellLookupEntry {
fn neighbor_index(&self, hash: &CellId) -> Option<usize> {
self.occupied_neighbors
.iter()
.enumerate()
.rev() .find_map(|(i, h)| (h == hash).then_some(i))
}
pub fn nearby<'a, F: SpatialHashFilter>(
&'a self,
map: &'a CellLookup<F>,
) -> impl Iterator<Item = &'a CellLookupEntry> + 'a {
map.nearby(self)
}
}
pub trait SpatialEntryToEntities<'a> {
fn entities(self) -> impl Iterator<Item = Entity> + 'a;
}
impl<'a, T, I> SpatialEntryToEntities<'a> for T
where
T: Iterator<Item = I> + 'a,
I: SpatialEntryToEntities<'a> + 'a,
{
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
self.flat_map(SpatialEntryToEntities::entities)
}
}
impl<'a> SpatialEntryToEntities<'a> for &'a CellLookupEntry {
#[inline]
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
self.entities.iter().copied()
}
}
impl<'a> SpatialEntryToEntities<'a> for Neighbor<'a> {
#[inline]
fn entities(self) -> impl Iterator<Item = Entity> + 'a {
self.1.entities.iter().copied()
}
}
#[derive(Resource, Clone)]
pub struct CellLookup<F = ()>
where
F: SpatialHashFilter,
{
map: InnerCellLookup,
reverse_map: EntityHashMap<CellId>,
spooky: PhantomData<F>,
}
impl<F> core::fmt::Debug for CellLookup<F>
where
F: SpatialHashFilter,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("GridHashMap")
.field("map", &self.map)
.field("reverse_map", &self.reverse_map)
.finish()
}
}
impl<F> Default for CellLookup<F>
where
F: SpatialHashFilter,
{
fn default() -> Self {
Self {
map: Default::default(),
reverse_map: Default::default(),
spooky: PhantomData,
}
}
}
impl<F> CellLookup<F>
where
F: SpatialHashFilter,
{
#[inline]
pub fn get(&self, hash: &CellId) -> Option<&CellLookupEntry> {
self.map.inner.get(hash)
}
#[inline]
pub fn contains(&self, hash: &CellId) -> bool {
self.map.inner.contains_key(hash)
}
#[inline]
pub fn all_entries(&self) -> impl Iterator<Item = (&CellId, &CellLookupEntry)> {
self.map.inner.iter()
}
#[inline]
pub fn nearby<'a>(
&'a self,
entry: &'a CellLookupEntry,
) -> impl Iterator<Item = &'a CellLookupEntry> + 'a {
Iterator::chain(
core::iter::once(entry),
entry.occupied_neighbors.iter().map(|neighbor_hash| {
self.get(neighbor_hash)
.expect("occupied_neighbors should be occupied")
}),
)
}
#[inline]
pub fn within_cube<'a>(
&'a self,
center: &'a CellId,
radius: u8,
) -> impl Iterator<Item = &'a CellLookupEntry> + 'a {
Iterator::chain(core::iter::once(*center), center.adjacent(radius))
.filter_map(|hash| self.get(&hash))
}
#[doc(alias = "bfs")]
pub fn flood(
&self,
seed: &CellId,
max_depth: Option<GridPrecision>,
) -> impl Iterator<Item = Neighbor<'_>> {
let starting_cell_cell = seed.coord();
ContiguousNeighborsIter {
initial_hash: Some(*seed),
spatial_map: self,
stack: Default::default(),
visited_cells: Default::default(),
}
.take_while(move |Neighbor(hash, _)| {
let Some(max_depth) = max_depth else {
return true;
};
let dist = hash.coord() - starting_cell_cell;
dist.x <= max_depth && dist.y <= max_depth && dist.z <= max_depth
})
}
pub fn newly_occupied(&self) -> &CellHashSet {
&self.map.just_inserted
}
pub fn newly_emptied(&self) -> &CellHashSet {
&self.map.just_removed
}
}
impl<F> CellLookup<F>
where
F: SpatialHashFilter,
{
pub(super) fn update(
mut spatial_map: ResMut<Self>,
changed_hashes: Res<super::ChangedCells<F>>,
all_hashes: Query<(Entity, &CellId), F>,
mut removed: RemovedComponents<CellId>,
mut stats: Option<ResMut<crate::timing::GridHashStats>>,
) {
let start = Instant::now();
spatial_map.map.just_inserted.clear();
spatial_map.map.just_removed.clear();
for entity in removed.read() {
spatial_map.remove(entity);
}
if let Some(ref mut stats) = stats {
stats.moved_entities = changed_hashes.updated.len();
}
for (entity, spatial_hash) in changed_hashes
.updated
.iter()
.filter_map(|entity| all_hashes.get(*entity).ok())
{
spatial_map.insert(entity, *spatial_hash);
}
if let Some(ref mut stats) = stats {
stats.map_update_duration += start.elapsed();
}
}
}
impl<F> CellLookup<F>
where
F: SpatialHashFilter,
{
#[inline]
fn insert(&mut self, entity: Entity, hash: CellId) {
if let Some(old_hash) = self.reverse_map.get_mut(&entity) {
if hash.eq(old_hash) {
return; }
self.map.remove(entity, *old_hash);
*old_hash = hash;
} else {
self.reverse_map.insert(entity, hash);
}
self.map.insert(entity, hash);
}
#[inline]
fn remove(&mut self, entity: Entity) {
if let Some(old_hash) = self.reverse_map.remove(&entity) {
self.map.remove(entity, old_hash);
}
}
}
#[derive(Debug, Clone, Default)]
struct InnerCellLookup {
inner: CellHashMap<CellLookupEntry>,
hash_set_pool: Vec<EntityHashSet>,
neighbor_pool: Vec<Vec<CellId>>,
just_inserted: CellHashSet,
just_removed: CellHashSet,
}
impl InnerCellLookup {
#[inline]
fn insert(&mut self, entity: Entity, hash: CellId) {
if let Some(entry) = self.inner.get_mut(&hash) {
entry.entities.insert(entity);
} else {
let mut entities = self.hash_set_pool.pop().unwrap_or_default();
entities.insert(entity);
self.insert_entry(hash, entities);
}
}
#[inline]
fn insert_entry(&mut self, hash: CellId, entities: EntityHashSet) {
let mut occupied_neighbors = self.neighbor_pool.pop().unwrap_or_default();
occupied_neighbors.extend(hash.adjacent(1).filter(|neighbor| {
self.inner
.get_mut(neighbor)
.map(|entry| {
entry.occupied_neighbors.push(hash);
true
})
.unwrap_or_default()
}));
self.inner.insert(
hash,
CellLookupEntry {
entities,
occupied_neighbors,
},
);
if !self.just_removed.remove(&hash) {
self.just_inserted.insert(hash);
}
}
#[inline]
fn remove(&mut self, entity: Entity, old_hash: CellId) {
if let Some(entry) = self.inner.get_mut(&old_hash) {
entry.entities.remove(&entity);
if !entry.entities.is_empty() {
return; }
}
if let Some(mut removed_entry) = self.inner.remove(&old_hash) {
removed_entry
.occupied_neighbors
.drain(..)
.for_each(|neighbor_hash| {
let neighbor = self
.inner
.get_mut(&neighbor_hash)
.expect("occupied neighbors is guaranteed to be up to date");
let index = neighbor.neighbor_index(&old_hash).unwrap();
neighbor.occupied_neighbors.remove(index);
});
self.hash_set_pool.push(removed_entry.entities);
self.neighbor_pool.push(removed_entry.occupied_neighbors);
if !self.just_inserted.remove(&old_hash) {
self.just_removed.insert(old_hash);
}
}
}
}
pub struct ContiguousNeighborsIter<'a, F>
where
F: SpatialHashFilter,
{
initial_hash: Option<CellId>,
spatial_map: &'a CellLookup<F>,
stack: VecDeque<Neighbor<'a>>,
visited_cells: HashSet<CellId>,
}
pub struct Neighbor<'a>(pub CellId, pub &'a CellLookupEntry);
impl<'a, F> Iterator for ContiguousNeighborsIter<'a, F>
where
F: SpatialHashFilter,
{
type Item = Neighbor<'a>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(hash) = self.initial_hash.take() {
self.stack
.push_front(Neighbor(hash, self.spatial_map.get(&hash)?));
self.visited_cells.insert(hash);
}
let Neighbor(hash, entry) = self.stack.pop_back()?;
for (neighbor_hash, neighbor_entry) in entry
.occupied_neighbors
.iter()
.filter(|neighbor_hash| self.visited_cells.insert(**neighbor_hash))
.map(|neighbor_hash| {
let entry = self
.spatial_map
.get(neighbor_hash)
.expect("Neighbor hashes in GridHashEntry are guaranteed to exist.");
(neighbor_hash, entry)
})
{
self.stack
.push_front(Neighbor(*neighbor_hash, neighbor_entry));
}
Some(Neighbor(hash, entry))
}
}