use std::{
cmp::Eq,
hash::Hash,
marker::PhantomData,
ops::{Deref, DerefMut},
};
use super::{
coords::{calculate_cell_index, calculate_chunk_coordinate},
CellCoord, CellIndex, CellMap, CellMapLabel, Chunk, ChunkCoord, InChunk, InMap,
};
use aery::{
edges::{CheckedDespawn, Unset, Withdraw},
prelude::Set,
};
use bevy::{
ecs::system::{Command, EntityCommands},
prelude::{Bundle, Commands, Entity, With, World},
utils::{hashbrown::hash_map::Entry, HashMap},
};
mod cell_batch;
mod cell_single;
mod chunk_batch;
mod chunk_single;
mod map;
use cell_batch::*;
use cell_single::*;
use chunk_batch::*;
use chunk_single::*;
use map::*;
pub struct CellCommands<'a, 'w, 's, L, const N: usize> {
commands: &'a mut Commands<'w, 's>,
phantom: PhantomData<L>,
}
impl<'a, 'w, 's, L, const N: usize> Deref for CellCommands<'a, 'w, 's, L, N>
where
L: CellMapLabel + 'static,
{
type Target = Commands<'w, 's>;
fn deref(&self) -> &Self::Target {
self.commands
}
}
impl<'a, 'w, 's, L, const N: usize> DerefMut for CellCommands<'a, 'w, 's, L, N>
where
L: CellMapLabel + 'static,
{
fn deref_mut(&mut self) -> &mut Self::Target {
self.commands
}
}
pub trait CellCommandExt<'w, 's> {
fn cells<'a, L, const N: usize>(&'a mut self) -> CellCommands<'a, 'w, 's, L, N>
where
L: CellMapLabel + 'static;
}
impl<'w, 's> CellCommandExt<'w, 's> for Commands<'w, 's> {
fn cells<L, const N: usize>(&mut self) -> CellCommands<'_, 'w, 's, L, N>
where
L: CellMapLabel + 'static,
{
CellCommands {
commands: self,
phantom: PhantomData,
}
}
}
impl<'a, 'w, 's, L, const N: usize> CellCommands<'a, 'w, 's, L, N>
where
L: CellMapLabel + 'static,
{
pub fn spawn_cell<T>(&mut self, cell_c: [isize; N], bundle: T) -> EntityCommands<'w, 's, '_>
where
T: Bundle + 'static,
{
let cell_id = self.spawn(bundle).id();
self.add(SpawnCell::<L, N> {
cell_c,
cell_id,
label: std::marker::PhantomData,
});
self.entity(cell_id)
}
pub fn spawn_cell_batch<F, B, IC>(&mut self, cell_cs: IC, bundle_f: F)
where
F: Fn([isize; N]) -> B + Send + 'static,
B: Bundle + Send + 'static,
IC: IntoIterator<Item = [isize; N]> + Send + 'static,
{
self.add(SpawnCellBatch::<L, F, B, IC, N> {
cell_cs,
bundle_f,
label: std::marker::PhantomData,
});
}
pub fn despawn_cell(&mut self, cell_c: [isize; N]) -> &mut Self {
self.add(DespawnCell::<L, N> {
cell_c,
label: PhantomData,
});
self
}
pub fn despawn_cell_batch<IC>(&mut self, cell_cs: IC)
where
IC: IntoIterator<Item = [isize; N]> + Send + 'static,
{
self.add(DespawnCellBatch::<L, IC, N> {
cell_cs,
label: std::marker::PhantomData,
});
}
pub fn move_cell(&mut self, old_c: [isize; N], new_c: [isize; N]) -> &mut Self {
self.add(MoveCell::<L, N> {
old_c,
new_c,
label: PhantomData,
});
self
}
pub fn move_cell_batch<IC>(&mut self, cell_cs: IC)
where
IC: IntoIterator<Item = ([isize; N], [isize; N])> + Send + 'static,
{
self.add(MoveCellBatch::<L, IC, N> {
cell_cs,
label: std::marker::PhantomData,
});
}
pub fn swap_cells(&mut self, cell_c_1: [isize; N], cell_c_2: [isize; N]) -> &mut Self {
self.add(SwapCell::<L, N> {
cell_c_1,
cell_c_2,
label: PhantomData,
});
self
}
pub fn swap_cell_batch<IC>(&mut self, cell_cs: IC)
where
IC: IntoIterator<Item = ([isize; N], [isize; N])> + Send + 'static,
{
self.add(SwapCellBatch::<L, IC, N> {
cell_cs,
label: std::marker::PhantomData,
});
}
pub fn spawn_chunk<T>(&mut self, chunk_c: [isize; N], bundle: T) -> EntityCommands<'w, 's, '_>
where
T: Bundle + 'static,
{
let chunk_id = self.spawn(bundle).id();
self.add(SpawnChunk::<L, N> {
chunk_c,
chunk_id,
label: std::marker::PhantomData,
});
self.entity(chunk_id)
}
pub fn spawn_chunk_batch_with<F, B, IC>(&mut self, chunk_cs: IC, bundle_f: F)
where
F: Fn([isize; N]) -> B + Send + 'static,
B: Bundle + Send + 'static,
IC: IntoIterator<Item = [isize; N]> + Send + 'static,
{
self.add(SpawnChunkBatch::<L, F, B, IC, N> {
chunk_cs,
bundle_f,
label: std::marker::PhantomData,
});
}
pub fn despawn_chunk(&mut self, chunk_c: [isize; N]) -> &mut Self {
self.add(DespawnChunk::<L, N> {
chunk_c,
label: std::marker::PhantomData,
});
self
}
pub fn despawn_chunk_batch<IC>(&mut self, chunk_cs: IC)
where
IC: IntoIterator<Item = [isize; N]> + Send + 'static,
{
self.add(DespawnChunkBatch::<L, IC, N> {
chunk_cs,
label: std::marker::PhantomData,
});
}
pub fn despawn_map(&mut self) -> &mut Self {
self.add(DespawnMap::<L, N> { label: PhantomData });
self
}
}
#[inline]
fn spawn_or_remove_chunk<L, const N: usize>(
world: &mut World,
map: &mut CellMap<L, N>,
map_id: Entity,
chunk_c: [isize; N],
) -> (Entity, Chunk)
where
L: CellMapLabel + Send + 'static,
{
if let Some(chunk_info) = remove_chunk::<L, N>(world, map, chunk_c) {
chunk_info
} else {
let chunk_id = world.spawn(ChunkCoord::from(chunk_c)).id();
map.chunks.insert(chunk_c.into(), chunk_id);
Set::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
(chunk_id, Chunk::new(L::CHUNK_SIZE.pow(N as u32)))
}
}
#[inline]
fn remove_chunk<L, const N: usize>(
world: &mut World,
map: &mut CellMap<L, N>,
chunk_c: [isize; N],
) -> Option<(Entity, Chunk)>
where
L: CellMapLabel + Send + 'static,
{
map.chunks
.get(&chunk_c.into())
.cloned()
.and_then(|chunk_id| world.get_entity_mut(chunk_id))
.map(|mut chunk_e| (chunk_e.id(), chunk_e.take::<Chunk>().unwrap()))
}
#[inline]
fn spawn_or_remove_map<L, const N: usize>(world: &mut World) -> (Entity, CellMap<L, N>)
where
L: CellMapLabel + Send + 'static,
{
let map_info = remove_map::<L, N>(world);
if let Some(map_info) = map_info {
map_info
} else {
(world.spawn_empty().id(), CellMap::<L, N>::default())
}
}
#[inline]
fn remove_map<L, const N: usize>(world: &mut World) -> Option<(Entity, CellMap<L, N>)>
where
L: CellMapLabel + Send + 'static,
{
world
.query_filtered::<Entity, With<CellMap<L, N>>>()
.get_single_mut(world)
.ok()
.map(|map_id| {
(
map_id,
world
.get_entity_mut(map_id)
.unwrap()
.take::<CellMap<L, N>>()
.unwrap(),
)
})
}
pub fn insert_cell<L, const N: usize>(world: &mut World, cell_c: [isize; N], cell_id: Entity)
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = spawn_or_remove_map::<L, N>(world);
let chunk_c = calculate_chunk_coordinate(cell_c, L::CHUNK_SIZE);
let (chunk_id, mut chunk) = spawn_or_remove_chunk::<L, N>(world, &mut map, map_id, chunk_c);
let cell_i = calculate_cell_index(cell_c, L::CHUNK_SIZE);
if let Some(cell) = chunk.cells.get_mut(cell_i) {
if let Some(old_cell_id) = cell.replace(cell_id) {
world.despawn(old_cell_id);
}
}
Set::<InChunk<L, N>>::new(cell_id, chunk_id).apply(world);
world
.get_entity_mut(cell_id)
.unwrap()
.insert((CellIndex::from(cell_i), CellCoord::<N>::new(cell_c)));
world.get_entity_mut(chunk_id).unwrap().insert(chunk);
world.get_entity_mut(map_id).unwrap().insert(map);
}
pub fn take_cell<L, const N: usize>(world: &mut World, cell_c: [isize; N]) -> Option<Entity>
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = remove_map::<L, N>(world)?;
let chunk_c = calculate_chunk_coordinate(cell_c, L::CHUNK_SIZE);
let (chunk_id, mut chunk) =
if let Some(chunk_info) = remove_chunk::<L, N>(world, &mut map, chunk_c) {
chunk_info
} else {
world.get_entity_mut(map_id).unwrap().insert(map);
return None;
};
let cell_i = calculate_cell_index(cell_c, L::CHUNK_SIZE);
let cell = if let Some(mut cell_e) = chunk
.cells
.get_mut(cell_i)
.and_then(|cell| cell.take())
.and_then(|cell_id| world.get_entity_mut(cell_id))
{
cell_e.remove::<(CellIndex, CellCoord)>();
let cell_id = cell_e.id();
Unset::<InChunk<L, N>>::new(cell_id, chunk_id).apply(world);
Some(cell_id)
} else {
None
};
world.get_entity_mut(chunk_id).unwrap().insert(chunk);
world.get_entity_mut(map_id).unwrap().insert(map);
cell
}
pub fn insert_cell_batch<L, const N: usize>(
world: &mut World,
cells: impl IntoIterator<Item = ([isize; N], Entity)>,
) where
L: CellMapLabel + Send + 'static,
{
let chunked_cells = cells
.into_iter()
.group_by(|(cell_c, _)| calculate_chunk_coordinate(*cell_c, L::CHUNK_SIZE));
let (map_id, mut map) = spawn_or_remove_map::<L, N>(world);
let cells_with_chunk = Vec::from_iter(chunked_cells.into_iter().map(|(chunk_c, cells)| {
let (chunk_id, chunk) = spawn_or_remove_chunk::<L, N>(world, &mut map, map_id, chunk_c);
(chunk_id, chunk, cells)
}));
for (chunk_id, mut chunk, cells) in cells_with_chunk {
for (cell_c, cell_id) in cells {
let cell_i = calculate_cell_index(cell_c, L::CHUNK_SIZE);
if let Some(cell) = chunk.cells.get_mut(cell_i) {
if let Some(old_cell_id) = cell.replace(cell_id) {
world.despawn(old_cell_id);
}
}
Set::<InChunk<L, N>>::new(cell_id, chunk_id).apply(world);
world
.get_entity_mut(cell_id)
.unwrap()
.insert((CellIndex::from(cell_i), CellCoord::<N>::new(cell_c)));
}
world.get_entity_mut(chunk_id).unwrap().insert(chunk);
}
world.get_entity_mut(map_id).unwrap().insert(map);
}
pub fn take_cell_batch<L, const N: usize>(
world: &mut World,
cells: impl IntoIterator<Item = [isize; N]>,
) -> Vec<([isize; N], Entity)>
where
L: CellMapLabel + Send + 'static,
{
let chunked_cells = cells
.into_iter()
.group_by(|cell_c| calculate_chunk_coordinate(*cell_c, L::CHUNK_SIZE));
let (map_id, mut map) = if let Some(map_info) = remove_map::<L, N>(world) {
map_info
} else {
return Vec::new();
};
let cells_with_chunk = chunked_cells
.into_iter()
.filter_map(|(chunk_c, cells)| {
remove_chunk(world, &mut map, chunk_c)
.map(|chunk_info| (chunk_info.0, chunk_info.1, cells))
})
.map(|(chunk_id, chunk, cells)| {
(
chunk_id,
chunk,
cells.into_iter().collect::<Vec<[isize; N]>>(),
)
})
.collect::<Vec<(Entity, Chunk, Vec<[isize; N]>)>>();
let mut cell_ids = Vec::new();
for (chunk_id, mut chunk, cells) in cells_with_chunk {
for cell_c in cells {
let cell_i = calculate_cell_index(cell_c, L::CHUNK_SIZE);
if let Some(mut cell_e) = chunk
.cells
.get_mut(cell_i)
.and_then(|cell| cell.take())
.and_then(|cell_id| world.get_entity_mut(cell_id))
{
cell_e.remove::<(CellIndex, CellCoord)>();
let cell_id = cell_e.id();
Unset::<InChunk<L, N>>::new(cell_id, chunk_id).apply(world);
cell_ids.push((cell_c, cell_id));
}
}
world.get_entity_mut(chunk_id).unwrap().insert(chunk);
}
world.get_entity_mut(map_id).unwrap().insert(map);
cell_ids
}
pub fn insert_chunk<L, const N: usize>(world: &mut World, chunk_c: [isize; N], chunk_id: Entity)
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = spawn_or_remove_map::<L, N>(world);
if let Some(chunk_id) = map.chunks.insert(chunk_c.into(), chunk_id) {
CheckedDespawn(chunk_id).apply(world);
}
world.get_entity_mut(chunk_id).unwrap().insert((
Chunk::new(L::CHUNK_SIZE.pow(N as u32)),
ChunkCoord::from(chunk_c),
));
Set::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
map.chunks.insert(chunk_c.into(), chunk_id);
world.entity_mut(map_id).insert(map);
}
pub fn take_chunk<L, const N: usize>(world: &mut World, chunk_c: [isize; N]) -> Option<Entity>
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = remove_map::<L, N>(world)?;
let chunk_id = if let Some(mut chunk_e) = map
.chunks
.remove(&chunk_c.into())
.and_then(|chunk_id| world.get_entity_mut(chunk_id))
{
chunk_e.remove::<(Chunk, ChunkCoord)>();
let chunk_id = chunk_e.id();
Unset::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
Withdraw::<InChunk<L, N>>::new(chunk_id).apply(world);
Some(chunk_id)
} else {
None
};
world.entity_mut(map_id).insert(map);
chunk_id
}
pub fn take_chunk_despawn_cells<L, const N: usize>(
world: &mut World,
chunk_c: [isize; N],
) -> Option<Entity>
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = remove_map::<L, N>(world)?;
let chunk_id = if let Some(mut chunk_e) = map
.chunks
.remove(&chunk_c.into())
.and_then(|chunk_id| world.get_entity_mut(chunk_id))
{
let (chunk, _) = chunk_e.take::<(Chunk, ChunkCoord)>().unwrap();
let chunk_id = chunk_e.id();
for cell_id in chunk.cells.into_iter().flatten() {
world.despawn(cell_id);
}
Unset::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
Withdraw::<InChunk<L, N>>::new(chunk_id).apply(world);
Some(chunk_id)
} else {
None
};
world.entity_mut(map_id).insert(map);
chunk_id
}
pub fn insert_chunk_batch<L, const N: usize>(
world: &mut World,
chunks: impl IntoIterator<Item = ([isize; N], Entity)>,
) where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = spawn_or_remove_map::<L, N>(world);
for (chunk_c, chunk_id) in chunks.into_iter() {
if let Some(chunk_id) = map.chunks.insert(chunk_c.into(), chunk_id) {
CheckedDespawn(chunk_id).apply(world);
}
world.get_entity_mut(chunk_id).unwrap().insert((
Chunk::new(L::CHUNK_SIZE.pow(N as u32)),
ChunkCoord::from(chunk_c),
));
Set::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
map.chunks.insert(chunk_c.into(), chunk_id);
}
world.get_entity_mut(map_id).unwrap().insert(map);
}
pub fn take_chunk_batch<L, const N: usize>(
world: &mut World,
chunks: impl IntoIterator<Item = [isize; N]>,
) -> Vec<([isize; N], Entity)>
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = if let Some(map_info) = remove_map::<L, N>(world) {
map_info
} else {
return Vec::new();
};
let mut chunk_ids = Vec::new();
for chunk_c in chunks.into_iter() {
if let Some(mut chunk_e) = map
.chunks
.remove(&chunk_c.into())
.and_then(|chunk_id| world.get_entity_mut(chunk_id))
{
chunk_e.remove::<(Chunk, ChunkCoord)>();
let chunk_id = chunk_e.id();
Unset::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
Withdraw::<InChunk<L, N>>::new(chunk_id).apply(world);
chunk_ids.push((chunk_c, chunk_id));
};
}
world.get_entity_mut(map_id).unwrap().insert(map);
chunk_ids
}
pub fn take_chunk_batch_despawn_cells<L, const N: usize>(
world: &mut World,
chunks: impl IntoIterator<Item = [isize; N]>,
) -> Vec<([isize; N], Entity)>
where
L: CellMapLabel + Send + 'static,
{
let (map_id, mut map) = if let Some(map_info) = remove_map::<L, N>(world) {
map_info
} else {
return Vec::new();
};
let mut chunk_ids = Vec::new();
for chunk_c in chunks.into_iter() {
if let Some(mut chunk_e) = map
.chunks
.remove(&chunk_c.into())
.and_then(|chunk_id| world.get_entity_mut(chunk_id))
{
let (chunk, _) = chunk_e.take::<(Chunk, ChunkCoord)>().unwrap();
let chunk_id = chunk_e.id();
for cell_id in chunk.cells.into_iter().flatten() {
world.despawn(cell_id);
}
Unset::<InMap<L, N>>::new(chunk_id, map_id).apply(world);
Withdraw::<InChunk<L, N>>::new(chunk_id).apply(world);
chunk_ids.push((chunk_c, chunk_id));
};
}
world.get_entity_mut(map_id).unwrap().insert(map);
chunk_ids
}
trait GroupBy: Iterator {
fn group_by<F, K>(
self,
f: F,
) -> bevy::utils::hashbrown::hash_map::IntoIter<
K,
std::vec::Vec<<Self as std::iter::Iterator>::Item>,
>
where
F: Fn(&Self::Item) -> K,
K: Eq + Hash;
}
impl<T> GroupBy for T
where
T: Iterator,
{
fn group_by<F, K>(
self,
f: F,
) -> bevy::utils::hashbrown::hash_map::IntoIter<
K,
std::vec::Vec<<T as std::iter::Iterator>::Item>,
>
where
F: Fn(&Self::Item) -> K,
K: Eq + Hash,
{
let mut map = HashMap::new();
for item in self {
let key = f(&item);
match map.entry(key) {
Entry::Vacant(v) => {
v.insert(vec![item]);
}
Entry::Occupied(mut o) => o.get_mut().push(item),
}
}
map.into_iter()
}
}