use bevy::ecs::system::{Command, EntityCommands, SystemParam};
use bevy::prelude::*;
use super::VoxelQueryError;
use crate::storage::chunk_pointers::ChunkEntityPointers;
use crate::storage::{VoxelChunk, VoxelWorld};
#[derive(SystemParam)]
pub struct VoxelCommands<'w, 's> {
chunk_pointers: Query<'w, 's, &'static ChunkEntityPointers, With<VoxelWorld>>,
all_chunks: Query<'w, 's, Entity, With<VoxelChunk>>,
commands: Commands<'w, 's>,
}
impl<'w, 's, 'cmd_ref> VoxelCommands<'w, 's> {
pub fn has_world(&self, world_id: Entity) -> bool {
self.chunk_pointers.contains(world_id)
}
pub fn get_world(
&'cmd_ref mut self,
world_id: Entity,
) -> Result<VoxelWorldCommands<'w, 's, 'cmd_ref>, VoxelQueryError> {
if !self.has_world(world_id) {
return Err(VoxelQueryError::WorldNotFound(world_id));
}
Ok(VoxelWorldCommands {
voxel_commands: self,
world_id,
})
}
pub fn commands(&'cmd_ref mut self) -> &'cmd_ref mut Commands<'w, 's> {
&mut self.commands
}
pub fn spawn_world<B>(&'cmd_ref mut self, bundle: B) -> VoxelWorldCommands<'w, 's, 'cmd_ref>
where
B: Bundle,
{
let world_id = self
.commands
.spawn((VoxelWorld, ChunkEntityPointers::default(), bundle))
.id();
VoxelWorldCommands {
voxel_commands: self,
world_id,
}
}
}
pub struct VoxelWorldCommands<'w, 's, 'cmd_ref> {
voxel_commands: &'cmd_ref mut VoxelCommands<'w, 's>,
world_id: Entity,
}
impl<'w, 's, 'cmd_ref, 'chunk_ref> VoxelWorldCommands<'w, 's, 'cmd_ref> {
pub fn spawn_chunk<B>(
&'chunk_ref mut self,
chunk_coords: IVec3,
bundle: B,
) -> Result<VoxelChunkCommands<'w, 's, 'chunk_ref>, VoxelQueryError>
where
B: Bundle,
{
if self.get_chunk_id(chunk_coords).is_some() {
return Err(VoxelQueryError::ChunkAlreadyExists(
self.world_id,
chunk_coords,
));
}
let chunk_id = self
.voxel_commands
.commands
.spawn((VoxelChunk::new(self.world_id, chunk_coords), bundle))
.set_parent(self.world_id)
.id();
self.voxel_commands.commands.add(UpdateChunkPointersAction {
world_id: self.world_id,
chunk_id: Some(chunk_id),
chunk_coords,
});
Ok(VoxelChunkCommands {
voxel_commands: self.voxel_commands,
world_id: self.world_id,
chunk_id,
chunk_coords,
})
}
pub fn get_chunk_id(&self, chunk_coords: IVec3) -> Option<Entity> {
let pointers = self.voxel_commands.chunk_pointers.get(self.world_id).ok()?;
let Some(chunk_id) = pointers.get_chunk_entity(chunk_coords) else {
return None;
};
if !self.voxel_commands.all_chunks.contains(chunk_id) {
return None;
}
Some(chunk_id)
}
pub fn get_chunk(
&'chunk_ref mut self,
chunk_coords: IVec3,
) -> Result<VoxelChunkCommands<'w, 's, 'chunk_ref>, VoxelQueryError> {
let chunk_id = self
.get_chunk_id(chunk_coords)
.ok_or(VoxelQueryError::ChunkNotFound(self.world_id, chunk_coords))?;
Ok(VoxelChunkCommands {
voxel_commands: self.voxel_commands,
world_id: self.world_id,
chunk_id,
chunk_coords,
})
}
pub fn id(&self) -> Entity {
self.world_id
}
pub fn as_entity_commands(self) -> EntityCommands<'w, 's, 'cmd_ref> {
self.voxel_commands
.commands
.get_entity(self.world_id)
.unwrap()
}
}
pub struct VoxelChunkCommands<'world, 'state, 'cmd_ref> {
voxel_commands: &'cmd_ref mut VoxelCommands<'world, 'state>,
world_id: Entity,
chunk_id: Entity,
chunk_coords: IVec3,
}
impl<'world, 'state, 'cmd_ref> VoxelChunkCommands<'world, 'state, 'cmd_ref> {
pub fn despawn(self) {
self.voxel_commands
.commands
.entity(self.chunk_id)
.despawn_recursive();
self.voxel_commands.commands.add(UpdateChunkPointersAction {
world_id: self.world_id,
chunk_id: None,
chunk_coords: self.chunk_coords,
})
}
pub fn as_entity_commands(self) -> EntityCommands<'world, 'state, 'cmd_ref> {
self.voxel_commands
.commands
.get_entity(self.chunk_id)
.unwrap()
}
pub fn as_world_commands(self) -> VoxelWorldCommands<'world, 'state, 'cmd_ref> {
self.voxel_commands.get_world(self.world_id).unwrap()
}
pub fn world_id(&self) -> Entity {
self.world_id
}
pub fn id(&self) -> Entity {
self.chunk_id
}
pub fn chunk_coords(&self) -> IVec3 {
self.chunk_coords
}
}
struct UpdateChunkPointersAction {
world_id: Entity,
chunk_id: Option<Entity>,
chunk_coords: IVec3,
}
impl Command for UpdateChunkPointersAction {
fn apply(self, world: &mut World) {
let mut pointers = world.get_mut::<ChunkEntityPointers>(self.world_id).unwrap();
if pointers.get_chunk_entity(self.chunk_coords).is_some() && self.chunk_id.is_some() {
panic!(
"Tried to spawn chunk at {}, in world {:?}, but it already exists!",
self.chunk_coords, self.world_id
)
};
pointers.set_chunk_entity(self.chunk_coords, self.chunk_id);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn build_world() {
let mut app = App::new();
fn init(mut commands: VoxelCommands) {
commands
.spawn_world(())
.spawn_chunk(IVec3::new(13, 15, 17), ())
.unwrap();
}
Schedule::new().add_systems(init).run(&mut app.world);
fn validate(world_query: Query<Entity, With<VoxelWorld>>, mut commands: VoxelCommands) {
let world_id = world_query.get_single().unwrap();
commands
.get_world(world_id)
.unwrap()
.get_chunk(IVec3::new(13, 15, 17))
.unwrap();
}
Schedule::new().add_systems(validate).run(&mut app.world);
}
#[test]
#[should_panic(
expected = "Tried to spawn chunk at [0, 0, 0], in world 0v0, but it already exists!"
)]
fn spawn_two_identical_chunks_same_frame() {
let mut app = App::new();
fn init(mut commands: VoxelCommands) {
commands.spawn_world(());
}
Schedule::new().add_systems(init).run(&mut app.world);
fn a(world_query: Query<Entity, With<VoxelWorld>>, mut commands: VoxelCommands) {
let world_id = world_query.get_single().unwrap();
commands
.get_world(world_id)
.unwrap()
.spawn_chunk(IVec3::ZERO, ())
.unwrap();
}
fn b(world_query: Query<Entity, With<VoxelWorld>>, mut commands: VoxelCommands) {
let world_id = world_query.get_single().unwrap();
commands
.get_world(world_id)
.unwrap()
.spawn_chunk(IVec3::ZERO, ())
.unwrap();
}
Schedule::new()
.add_systems(a)
.add_systems(b)
.run(&mut app.world);
}
}