use crate::{
borrow::AtomicRefCell,
cons::{ConsAppend, ConsFlatten},
entity::{Entity, EntityAllocator},
filter::{ChunksetFilterData, Filter},
storage::{Component, ComponentTypeId, Tag, TagTypeId},
world::{
ComponentSource, ComponentTupleSet, IntoComponentSource, PreallocComponentSource,
TagLayout, TagSet, World, WorldId,
},
};
use derivative::Derivative;
use smallvec::SmallVec;
use std::ops::Range;
use std::{collections::VecDeque, iter::FromIterator, marker::PhantomData, sync::Arc};
use tracing::{span, Level};
pub trait WorldWritable {
fn write(self: Arc<Self>, world: &mut World, cmd: &CommandBuffer);
fn write_components(&self) -> Vec<ComponentTypeId>;
fn write_tags(&self) -> Vec<TagTypeId>;
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct InsertBufferedCommand<T, C> {
write_components: Vec<ComponentTypeId>,
write_tags: Vec<TagTypeId>,
#[derivative(Debug = "ignore")]
tags: T,
#[derivative(Debug = "ignore")]
components: C,
entities: Range<usize>,
}
impl<T, C> WorldWritable for InsertBufferedCommand<T, C>
where
T: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: ComponentSource,
{
fn write(self: Arc<Self>, world: &mut World, cmd: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap();
world.insert(
consumed.tags,
PreallocComponentSource::new(
cmd.pending_insertion[consumed.entities].iter().copied(),
consumed.components,
),
);
}
fn write_components(&self) -> Vec<ComponentTypeId> { self.write_components.clone() }
fn write_tags(&self) -> Vec<TagTypeId> { self.write_tags.clone() }
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct InsertCommand<T, C> {
write_components: Vec<ComponentTypeId>,
write_tags: Vec<TagTypeId>,
#[derivative(Debug = "ignore")]
tags: T,
#[derivative(Debug = "ignore")]
components: C,
}
impl<T, C> WorldWritable for InsertCommand<T, C>
where
T: TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: IntoComponentSource,
{
fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap();
world.insert(consumed.tags, consumed.components);
}
fn write_components(&self) -> Vec<ComponentTypeId> { self.write_components.clone() }
fn write_tags(&self) -> Vec<TagTypeId> { self.write_tags.clone() }
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct DeleteEntityCommand(Entity);
impl WorldWritable for DeleteEntityCommand {
fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) { world.delete(self.0); }
fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) }
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct AddTagCommand<T> {
entity: Entity,
#[derivative(Debug = "ignore")]
tag: T,
}
impl<T> WorldWritable for AddTagCommand<T>
where
T: Tag,
{
fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap();
if let Err(err) = world.add_tag(consumed.entity, consumed.tag) {
tracing::error!(error = %err, "error adding tag");
}
}
fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn write_tags(&self) -> Vec<TagTypeId> { vec![TagTypeId::of::<T>()] }
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct RemoveTagCommand<T> {
entity: Entity,
_marker: PhantomData<T>,
}
impl<T> WorldWritable for RemoveTagCommand<T>
where
T: Tag,
{
fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
if let Err(err) = world.remove_tag::<T>(self.entity) {
tracing::error!(error = %err, "error removing tag");
}
}
fn write_components(&self) -> Vec<ComponentTypeId> { Vec::with_capacity(0) }
fn write_tags(&self) -> Vec<TagTypeId> { vec![TagTypeId::of::<T>()] }
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct AddComponentCommand<C> {
#[derivative(Debug = "ignore")]
entity: Entity,
#[derivative(Debug = "ignore")]
component: C,
}
impl<C> WorldWritable for AddComponentCommand<C>
where
C: Component,
{
fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
let consumed = Arc::try_unwrap(self).unwrap();
if let Err(err) = world.add_component::<C>(consumed.entity, consumed.component) {
tracing::error!(error = %err, "error adding component");
}
}
fn write_components(&self) -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<C>()] }
fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) }
}
#[derive(Derivative)]
#[derivative(Debug(bound = ""))]
struct RemoveComponentCommand<C> {
entity: Entity,
_marker: PhantomData<C>,
}
impl<C> WorldWritable for RemoveComponentCommand<C>
where
C: Component,
{
fn write(self: Arc<Self>, world: &mut World, _: &CommandBuffer) {
if let Err(err) = world.remove_component::<C>(self.entity) {
tracing::error!(error = %err, "error removing component");
}
}
fn write_components(&self) -> Vec<ComponentTypeId> { vec![ComponentTypeId::of::<C>()] }
fn write_tags(&self) -> Vec<TagTypeId> { Vec::with_capacity(0) }
}
#[allow(clippy::enum_variant_names)]
enum EntityCommand {
WriteWorld(Arc<dyn WorldWritable>),
ExecWorld(Arc<dyn Fn(&World)>),
ExecMutWorld(Arc<dyn Fn(&mut World)>),
}
pub struct EntityBuilder<'a, TS = (), CS = ()> {
cmd: &'a mut CommandBuffer,
tags: TS,
components: CS,
}
impl<'a, TS, CS> EntityBuilder<'a, TS, CS>
where
TS: 'static + Send + ConsFlatten,
CS: 'static + Send + ConsFlatten,
{
pub fn with_component<C: Component>(
self,
component: C,
) -> EntityBuilder<'a, TS, <CS as ConsAppend<C>>::Output>
where
CS: ConsAppend<C>,
<CS as ConsAppend<C>>::Output: ConsFlatten,
{
EntityBuilder {
cmd: self.cmd,
components: ConsAppend::append(self.components, component),
tags: self.tags,
}
}
pub fn with_tag<T: Tag>(self, tag: T) -> EntityBuilder<'a, <TS as ConsAppend<T>>::Output, CS>
where
TS: ConsAppend<T>,
<TS as ConsAppend<T>>::Output: ConsFlatten,
{
EntityBuilder {
cmd: self.cmd,
tags: ConsAppend::append(self.tags, tag),
components: self.components,
}
}
pub fn build(self) -> Entity
where
<TS as ConsFlatten>::Output: TagSet + TagLayout + for<'b> Filter<ChunksetFilterData<'b>>,
ComponentTupleSet<
<CS as ConsFlatten>::Output,
std::iter::Once<<CS as ConsFlatten>::Output>,
>: ComponentSource,
{
self.cmd.insert(
self.tags.flatten(),
std::iter::once(self.components.flatten()),
)[0]
}
}
pub struct CommandBuffer {
world_id: WorldId,
commands: AtomicRefCell<VecDeque<EntityCommand>>,
entity_allocator: Arc<EntityAllocator>,
preallocated_capacity: usize,
free_list: SmallVec<[Entity; 64]>,
pending_insertion: SmallVec<[Entity; 64]>,
}
unsafe impl Send for CommandBuffer {}
unsafe impl Sync for CommandBuffer {}
impl CommandBuffer {
pub fn new_with_capacity(world: &World, capacity: usize) -> Self {
let free_list =
SmallVec::from_iter((0..capacity).map(|_| world.entity_allocator.create_entity()));
Self {
world_id: world.id(),
free_list,
preallocated_capacity: capacity,
commands: Default::default(),
pending_insertion: SmallVec::new(),
entity_allocator: world.entity_allocator.clone(),
}
}
pub fn new(world: &World) -> Self {
let free_list = SmallVec::from_iter(
(0..world.command_buffer_size()).map(|_| world.entity_allocator.create_entity()),
);
Self {
world_id: world.id(),
free_list,
preallocated_capacity: world.command_buffer_size(),
commands: Default::default(),
pending_insertion: SmallVec::new(),
entity_allocator: world.entity_allocator.clone(),
}
}
pub fn world(&self) -> WorldId { self.world_id }
#[allow(clippy::comparison_chain)]
fn resize(&mut self) {
let allocator = &self.entity_allocator;
let free_list = &mut self.free_list;
let capacity = self.preallocated_capacity;
if free_list.len() < capacity {
for entity in allocator.create_entities().take(capacity - free_list.len()) {
free_list.push(entity);
}
} else if free_list.len() > capacity {
(free_list.len() - capacity..capacity).for_each(|_| {
allocator.delete_entity(free_list.pop().unwrap());
});
}
}
pub fn write(&mut self, world: &mut World) {
let span = span!(Level::TRACE, "Draining command buffer");
let _guard = span.enter();
if self.world_id != world.id() {
panic!("command buffers may only write into their parent world");
}
while let Some(command) = self.commands.get_mut().pop_back() {
match command {
EntityCommand::WriteWorld(ptr) => ptr.write(world, self),
EntityCommand::ExecMutWorld(closure) => closure(world),
EntityCommand::ExecWorld(closure) => closure(world),
}
}
self.pending_insertion.clear();
self.resize();
}
pub fn start_entity(&mut self) -> EntityBuilder<(), ()> {
EntityBuilder {
cmd: self,
tags: (),
components: (),
}
}
fn allocate_entity(&mut self) -> Entity {
if self.free_list.is_empty() {
self.resize();
}
let entity = self
.free_list
.pop()
.unwrap_or_else(|| self.entity_allocator.create_entity());
self.pending_insertion.push(entity);
entity
}
pub fn exec_mut<F>(&self, f: F)
where
F: 'static + Fn(&mut World),
{
self.commands
.get_mut()
.push_front(EntityCommand::ExecMutWorld(Arc::new(f)));
}
fn insert_writer<W>(&self, writer: W)
where
W: 'static + WorldWritable,
{
self.commands
.get_mut()
.push_front(EntityCommand::WriteWorld(Arc::new(writer)));
}
pub fn insert<T, C>(&mut self, tags: T, components: C) -> &[Entity]
where
T: 'static + TagSet + TagLayout + for<'a> Filter<ChunksetFilterData<'a>>,
C: 'static + IntoComponentSource,
{
let components = components.into();
let start = self.pending_insertion.len();
let count = components.len();
self.pending_insertion.reserve(count);
for _ in 0..count {
self.allocate_entity();
}
let range = start..self.pending_insertion.len();
self.commands
.get_mut()
.push_front(EntityCommand::WriteWorld(Arc::new(InsertBufferedCommand {
write_components: Vec::default(),
write_tags: Vec::default(),
tags,
components,
entities: range.clone(),
})));
&self.pending_insertion[range]
}
pub fn delete(&self, entity: Entity) { self.insert_writer(DeleteEntityCommand(entity)); }
pub fn add_component<C: Component>(&self, entity: Entity, component: C) {
self.insert_writer(AddComponentCommand { entity, component });
}
pub fn remove_component<C: Component>(&self, entity: Entity) {
self.insert_writer(RemoveComponentCommand {
entity,
_marker: PhantomData::<C>::default(),
});
}
pub fn add_tag<T: Tag>(&self, entity: Entity, tag: T) {
self.insert_writer(AddTagCommand { entity, tag });
}
pub fn remove_tag<T: Tag>(&self, entity: Entity) {
self.insert_writer(RemoveTagCommand {
entity,
_marker: PhantomData::<T>::default(),
});
}
#[inline]
pub fn len(&self) -> usize { self.commands.get().len() }
#[inline]
pub fn is_empty(&self) -> bool { self.commands.get().len() == 0 }
}
impl Drop for CommandBuffer {
fn drop(&mut self) {
while let Some(entity) = self.free_list.pop() {
self.entity_allocator.delete_entity(entity);
}
while let Some(entity) = self.pending_insertion.pop() {
self.entity_allocator.delete_entity(entity);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq)]
struct Pos(f32, f32, f32);
#[derive(Clone, Copy, Debug, PartialEq)]
struct Vel(f32, f32, f32);
#[derive(Default)]
struct TestResource(pub i32);
#[test]
fn create_entity_test() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
let mut world = universe.create_world();
let components = vec![
(Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)),
(Pos(4., 5., 6.), Vel(0.4, 0.5, 0.6)),
];
let components_len = components.len();
let mut command = CommandBuffer::new(&world);
let entity1 = command.start_entity().build();
let entity2 = command.start_entity().build();
command.add_component(entity1, Pos(1., 2., 3.));
command.add_component(entity2, Pos(4., 5., 6.));
command.write(&mut world);
let query = Read::<Pos>::query();
let mut count = 0;
for _ in query.iter_entities(&world) {
count += 1;
}
assert_eq!(components_len, count);
}
#[test]
fn simple_write_test() {
let _ = tracing_subscriber::fmt::try_init();
let universe = Universe::new();
let mut world = universe.create_world();
let components = vec![
(Pos(1., 2., 3.), Vel(0.1, 0.2, 0.3)),
(Pos(4., 5., 6.), Vel(0.4, 0.5, 0.6)),
];
let components_len = components.len();
let mut command = CommandBuffer::new(&world);
let _ = command.insert((), components);
command.write(&mut world);
let query = Read::<Pos>::query();
let mut count = 0;
for _ in query.iter_entities_mut(&mut world) {
count += 1;
}
assert_eq!(components_len, count);
}
}