Skip to content

Commit

Permalink
Get handle to spawned entity through commands
Browse files Browse the repository at this point in the history
  • Loading branch information
foodelevator committed May 9, 2022
1 parent 46494c1 commit 8c5d283
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 93 deletions.
22 changes: 4 additions & 18 deletions ecs/src/commands/command.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
use std::{alloc::Layout, any::TypeId, borrow::Cow};

use crate::World;

use super::{EntityRef, ExecutionContext};
use crate::{Entity, World};

#[derive(Debug)]
pub enum Command {
Spawn,
Despawn(EntityRef),
Despawn(Entity),
AddComponent {
entity: EntityRef,
entity: Entity,
type_id: TypeId,
component: *mut u8, // TODO: watch out for memory leak?
name: Cow<'static, str>,
Expand All @@ -19,16 +16,9 @@ pub enum Command {
}

impl Command {
pub(super) fn execute(self, world: &mut World, ctx: &mut ExecutionContext) {
pub(super) fn execute(self, world: &mut World) {
match self {
Command::Spawn => {
ctx.entities_created.push(world.spawn());
}
Command::Despawn(entity) => {
let entity = match entity {
EntityRef::Entity(entity) => entity,
EntityRef::New(index) => ctx.entities_created[index],
};
world.despawn(entity);
}
Command::AddComponent {
Expand All @@ -39,10 +29,6 @@ impl Command {
layout,
drop,
} => {
let entity = match entity {
EntityRef::Entity(entity) => entity,
EntityRef::New(index) => ctx.entities_created[index],
};
let comp_id = match world
.component_registry_mut()
.component_id_from_type_id(type_id)
Expand Down
27 changes: 27 additions & 0 deletions ecs/src/commands/command_buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use crate::World;

use super::Command;

#[derive(Debug, Default)]
pub struct CommandBuffer {
commands: Vec<Command>,
}

impl CommandBuffer {
pub fn new() -> Self {
CommandBuffer {
commands: Vec::new(),
}
}

pub fn add(&mut self, command: Command) {
self.commands.push(command);
}

/// Executes all commands on the `World` and clears the command buffer.
pub fn apply(&mut self, world: &mut World) {
for command in self.commands.drain(..) {
command.execute(world);
}
}
}
64 changes: 16 additions & 48 deletions ecs/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,76 +5,44 @@ use std::{
};

mod command;
mod command_buffer;

pub(crate) use command::Command;
pub(crate) use command_buffer::CommandBuffer;

use crate::{Entity, World};

#[derive(Debug, Clone, Copy)]
pub enum EntityRef {
Entity(Entity),
New(usize),
}

impl From<Entity> for EntityRef {
fn from(entity: Entity) -> Self {
EntityRef::Entity(entity)
}
}

#[derive(Default)]
struct ExecutionContext {
entities_created: Vec<Entity>,
}
use crate::{Entities, Entity};

/// Allows commands to be issued without exclusive access the world by deferring them to be run at
/// a later state, when `World::maintain` is called.
/// Issuing commands on a command buffer after calling `World::maintain` since creation will result
/// in a panic.
pub struct Commands {
commands: Vec<Command>,
entity_counter: usize,
pub struct Commands<'b, 'e> {
buffer: &'b mut CommandBuffer,
entities: &'e Entities,
}

impl Commands {
/// Retrieves a `Commands` which can be used to issue commands to be run on this `World` when
/// possible, which is when `maintain` is called.
/// After `maintain` is called, issuing more commands to the `Commands` will result in a panic.
pub fn new() -> Self {
Self {
commands: vec![],
entity_counter: 0,
}
}

/// Executes all commands on the `World` and clears the command buffer.
pub fn apply(&mut self, world: &mut World) {
let mut ctx = ExecutionContext::default();
for command in self.commands.drain(..) {
command.execute(world, &mut ctx);
}
self.entity_counter = 0;
impl<'b, 'e> Commands<'b, 'e> {
pub fn new(buffer: &'b mut CommandBuffer, entities: &'e Entities) -> Self {
Self { buffer, entities }
}

pub fn spawn(&mut self) -> EntityRef {
let id = self.entity_counter;
self.entity_counter += 1;
self.commands.push(Command::Spawn);
EntityRef::New(id)
pub fn spawn(&mut self) -> Entity {
let entity = self.entities.spawn();
entity
}

pub fn despawn<E: Into<EntityRef>>(&mut self, entity: E) {
self.commands.push(Command::Despawn(entity.into()));
pub fn despawn(&mut self, entity: Entity) {
self.buffer.add(Command::Despawn(entity.into()));
}

pub fn add<T: 'static, E: Into<EntityRef>>(&mut self, entity: E, component: T) {
pub fn add<T: 'static>(&mut self, entity: Entity, component: T) {
unsafe fn drop<T: 'static>(ptr: *mut u8) {
ptr.cast::<T>().drop_in_place();
}
let component = Box::new(component);
let component = Box::into_raw(component);
let component = component as *mut u8;
self.commands.push(Command::AddComponent {
self.buffer.add(Command::AddComponent {
entity: entity.into(),
type_id: TypeId::of::<T>(),
name: Cow::Borrowed(any::type_name::<T>()),
Expand Down
39 changes: 21 additions & 18 deletions ecs/src/entity.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use std::collections::HashSet;
use std::{
cell::{Cell, RefCell},
collections::HashSet,
};

type EntityId = u32;
type Generation = u32;
Expand All @@ -22,8 +25,8 @@ impl Entity {
/// these are exceeded there will be a panic.
#[derive(Debug, Default)]
pub struct Entities {
generations: Vec<Generation>,
unused_ids: Vec<EntityId>,
generations: RefCell<Vec<Cell<Generation>>>,
unused_ids: RefCell<Vec<EntityId>>,
}

impl Entities {
Expand All @@ -32,9 +35,9 @@ impl Entities {
/// *O*(1) (ammortized).
/// The current implementation keeps a `Vec` of all entities' *generations* which might have to
/// grow.
pub fn spawn(&mut self) -> Entity {
if let Some(id) = self.unused_ids.pop() {
let gen = self.generations[id as usize];
pub fn spawn(&self) -> Entity {
if let Some(id) = self.unused_ids.borrow_mut().pop() {
let gen = self.generations.borrow()[id as usize].get();
Entity { id, gen }
} else {
Entity {
Expand All @@ -58,22 +61,22 @@ impl Entities {
/// Despawns the entity with id `id`. Does not check generation or if `id` is already currently
/// despawned.
pub fn despawn_unchecked(&mut self, id: EntityId) {
let gen = &mut self.generations[id as usize];
if *gen == Generation::MAX {
let gen = &self.generations.borrow()[id as usize];
if gen.get() == Generation::MAX {
// TODO: we're not doomed in this scenario. We can still mark this id as no longer
// usable somehow.
panic!("Generation counter for entity id {} has overflown.", id);
}
*gen += 1;
self.unused_ids.push(id);
gen.set(gen.get() + 1);
self.unused_ids.borrow_mut().push(id);
}

/// Indicates whether `entity` still is alive.
/// # Time complexity
/// *O*(1)
pub fn exists(&self, entity: Entity) -> bool {
let Entity { id, gen } = entity;
self.generations[id as usize] == gen
self.generations.borrow()[id as usize].get() == gen
}

/// Returns the id of `entity` if `entity` is still alive.
Expand All @@ -92,15 +95,15 @@ impl Entities {
Iter::new(self)
}

fn create_new_id(&mut self) -> EntityId {
let id = self
.generations
fn create_new_id(&self) -> EntityId {
let mut g = self.generations.borrow_mut();
let id = g
.len()
.try_into()
// The bookkeeping alone for all those entities would require more than 17 GB so this
// shouldn'n be an issue.
.expect("Max entity count (4 294 967 296) exceeded");
self.generations.push(0);
g.push(Cell::new(0));
id
}
}
Expand All @@ -116,7 +119,7 @@ impl<'e> Iter<'e> {
Self {
curr: 0,
entities,
unused_ids: entities.unused_ids.iter().copied().collect(),
unused_ids: entities.unused_ids.borrow().iter().copied().collect(),
}
}

Expand All @@ -130,13 +133,13 @@ impl<'e> Iterator for Iter<'e> {
type Item = Entity;

fn next(&mut self) -> Option<Self::Item> {
while (self.curr as usize) < self.entities.generations.len() {
while (self.curr as usize) < self.entities.generations.borrow().len() {
let curr = self.curr;
self.curr += 1;
if self.unused_ids.contains(&curr) {
continue;
} else {
let gen = self.entities.generations[curr as usize];
let gen = self.entities.generations.borrow()[curr as usize].get();
return Some(Entity { id: curr, gen });
}
}
Expand Down
22 changes: 14 additions & 8 deletions ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ mod tests {
time::{Duration, Instant},
};

use crate::component::{ComponentRegistry, Storage, StorageType};
use crate::{
commands::CommandBuffer,
component::{ComponentRegistry, Storage, StorageType},
};

use super::*;

Expand Down Expand Up @@ -116,7 +119,7 @@ mod tests {
drop_counter,
)
};
let mut entities = Entities::default();
let entities = Entities::default();
let es: Vec<_> = (0..100).map(|_| entities.spawn()).collect();
for i in (0..100).step_by(2) {
assert_eq!(i / 2, counter.get());
Expand Down Expand Up @@ -687,7 +690,8 @@ mod tests {
let d = world.spawn();
world.add(d, Health(10));

let mut commands = Commands::new();
let mut command_buffer = CommandBuffer::new();
let mut commands = Commands::new(&mut command_buffer, world.entities());
query_iter!(world, (entity: Entity, health: mut Health) => {
health.0 -= 10;
if health.0 == 0 {
Expand All @@ -698,7 +702,7 @@ mod tests {
assert!(world.entities().exists(b));
assert!(world.entities().exists(c));
assert!(world.entities().exists(d));
commands.apply(&mut world);
command_buffer.apply(&mut world);
assert!(world.entities().exists(a));
assert!(!world.entities().exists(b));
assert!(world.entities().exists(c));
Expand All @@ -711,7 +715,8 @@ mod tests {
let e1 = world.spawn();
let e2 = world.spawn();
let counter = Rc::new(Cell::new(0));
let mut commands = Commands::new();
let mut command_buffer = CommandBuffer::new();
let mut commands = Commands::new(&mut command_buffer, world.entities());

commands.add(e1, Counter::named(counter.clone(), "a"));
assert_eq!(counter.get(), 1);
Expand All @@ -721,15 +726,16 @@ mod tests {
assert_eq!(counter.get(), 3);
commands.despawn(e2);
assert_eq!(counter.get(), 3);
commands.apply(&mut world);
command_buffer.apply(&mut world);
assert_eq!(counter.get(), 1);
}

#[test]
fn add_component_to_newly_created_entity_through_commands() {
let mut world = World::default();
let counter = Rc::new(Cell::new(0));
let mut commands = Commands::new();
let mut command_buffer = CommandBuffer::new();
let mut commands = Commands::new(&mut command_buffer, world.entities());

let e1 = commands.spawn();
commands.add(e1, Counter::named(counter.clone(), "a"));
Expand All @@ -742,7 +748,7 @@ mod tests {
commands.despawn(e2);
assert_eq!(counter.get(), 3);

commands.apply(&mut world);
command_buffer.apply(&mut world);
assert_eq!(counter.get(), 1);
query_iter!(world, (c: Counter) => {
assert_eq!(c.1, "b");
Expand Down
2 changes: 1 addition & 1 deletion ecs/src/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub struct World {

impl Default for World {
fn default() -> Self {
let mut entities = Entities::default();
let entities = Entities::default();
let resource_holder = entities.spawn();
Self {
entities,
Expand Down

0 comments on commit 8c5d283

Please sign in to comment.