From 380c8df7408734d0e630cc457168cf2b94bb686c Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 23 Feb 2024 16:34:21 -0500 Subject: [PATCH] ecs: implement deferred system commands, change the way system fn arguments are implemented --- lyra-ecs/Cargo.toml | 2 + lyra-ecs/src/archetype.rs | 4 +- lyra-ecs/src/command.rs | 114 +++++++++++++++++++ lyra-ecs/src/entity.rs | 58 ++++++++++ lyra-ecs/src/lib.rs | 22 ++++ lyra-ecs/src/query/borrow.rs | 2 +- lyra-ecs/src/query/entities.rs | 2 +- lyra-ecs/src/query/view.rs | 2 +- lyra-ecs/src/system/batched.rs | 4 + lyra-ecs/src/system/fn_sys.rs | 202 ++++++++++++++++++++------------- lyra-ecs/src/system/graph.rs | 19 +++- lyra-ecs/src/system/mod.rs | 2 + lyra-ecs/src/world.rs | 103 ++++++++--------- 13 files changed, 396 insertions(+), 140 deletions(-) create mode 100644 lyra-ecs/src/command.rs create mode 100644 lyra-ecs/src/entity.rs diff --git a/lyra-ecs/Cargo.toml b/lyra-ecs/Cargo.toml index 9e49722..fe1e97f 100644 --- a/lyra-ecs/Cargo.toml +++ b/lyra-ecs/Cargo.toml @@ -13,6 +13,8 @@ lyra-ecs-derive = { path = "./lyra-ecs-derive" } lyra-math = { path = "../lyra-math", optional = true } anyhow = "1.0.75" thiserror = "1.0.50" +unique = "0.9.1" +paste = "1.0.14" [dev-dependencies] rand = "0.8.5" # used for tests diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index f91f512..d00f607 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,6 +1,6 @@ use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; -use crate::{world::{Entity, ArchetypeEntityId}, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick}; +use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity}; #[derive(Clone)] pub struct ComponentColumn { @@ -419,7 +419,7 @@ mod tests { use rand::Rng; - use crate::{tests::{Vec2, Vec3}, world::{Entity, EntityId}, bundle::Bundle, ComponentInfo, MemoryLayout, DynTypeId, DynamicBundle, Tick}; + use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, MemoryLayout, Tick}; use super::Archetype; diff --git a/lyra-ecs/src/command.rs b/lyra-ecs/src/command.rs new file mode 100644 index 0000000..d487d7c --- /dev/null +++ b/lyra-ecs/src/command.rs @@ -0,0 +1,114 @@ +use std::{cell::RefMut, collections::VecDeque, mem, ptr::{self, NonNull}}; + +use unique::Unique; + +use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World}; + +pub trait Command { + fn run(self, world: &mut World) -> anyhow::Result<()>; +} + +impl Command for F +where + F: FnOnce(&mut World) -> anyhow::Result<()> +{ + fn run(self, world: &mut World) -> anyhow::Result<()> { + self(world) + } +} + +type RunCommand = unsafe fn(cmd: Unique<()>, world: &mut World) -> anyhow::Result<()>; + +#[derive(Default)] +pub struct CommandQueue(VecDeque<(RunCommand, Unique<()>)>); + +pub struct Commands<'a, 'b> { + queue: &'b mut CommandQueue, + entities: &'a mut Entities, +} + +impl<'a, 'b> Commands<'a, 'b> { + pub fn new(queue: &'b mut CommandQueue, world: &'a mut World) -> Self { + Self { + queue, + entities: &mut world.entities, + } + } + + /// Add a command to the end of the command queue + pub fn add(&mut self, mut cmd: C) { + // get an owned pointer to the command, then forget it to ensure its destructor isn't ran + let ptr = Unique::from(&mut cmd).cast::<()>(); + mem::forget(cmd); + + let run_fn = |cmd_ptr: Unique<()>, world: &mut World| unsafe { + let cmd = cmd_ptr.cast::(); + let cmd = ptr::read(cmd.as_ptr()); + cmd.run(world)?; + + Ok(()) + }; + + self.queue.0.push_back((run_fn, ptr)); + } + + pub fn spawn(&mut self, mut bundle: B) -> Entity { + let e = self.entities.reserve(); + + let bundle_ptr = Unique::from(&mut bundle); + mem::forget(bundle); + //let bundle_box = Box::new(bundle); + + self.add(move |world: &mut World| { + let bundle = unsafe { ptr::read(bundle_ptr.as_ptr()) }; + world.spawn_into(e, bundle); + Ok(()) + }); + + e + } + + /// Execute all commands in the queue, in order of insertion + pub fn execute(&mut self, world: &mut World) -> anyhow::Result<()> { + while let Some((cmd_fn, cmd_ptr)) = self.queue.0.pop_front() { + unsafe { + cmd_fn(cmd_ptr, world)?; + } + } + + Ok(()) + } +} + +impl FnArgFetcher for Commands<'_, '_> { + type State = CommandQueue; + type Arg<'a, 'state> = Commands<'a, 'state>; + + fn world_access(&self) -> Access { + Access::Write + } + + unsafe fn get<'a, 'state>(state: &'state mut Self::State, mut world_ptr: ptr::NonNull) -> Self::Arg<'a, 'state> { + let world = world_ptr.as_mut(); + Commands::new(state, world) + } + + fn create_state(_: NonNull) -> Self::State { + CommandQueue::default() + } + + fn apply_deferred<'a>(mut state: Self::State, mut world_ptr: NonNull) { + let world = unsafe { world_ptr.as_mut() }; + + let mut cmds = Commands::new(&mut state, world); + // safety: Commands has a mut borrow to entities in the world + let world = unsafe { world_ptr.as_mut() }; + cmds.execute(world).unwrap() + } +} + +pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut) -> anyhow::Result<()> { + commands.execute(world)?; + + Ok(()) +} \ No newline at end of file diff --git a/lyra-ecs/src/entity.rs b/lyra-ecs/src/entity.rs new file mode 100644 index 0000000..7fefa67 --- /dev/null +++ b/lyra-ecs/src/entity.rs @@ -0,0 +1,58 @@ +use std::collections::{HashMap, VecDeque}; + +use crate::Record; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct EntityId(pub u64); + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Entity { + pub(crate) id: EntityId, + pub(crate) generation: u64, +} + +pub struct Entities { + pub(crate) arch_index: HashMap, + dead: VecDeque, + next_id: EntityId, +} + +impl Default for Entities { + fn default() -> Self { + Self { + arch_index: Default::default(), + dead: Default::default(), + next_id: EntityId(0), + } + } +} + +impl Entities { + pub fn reserve(&mut self) -> Entity { + match self.dead.pop_front() { + Some(mut e) => { + e.generation += 1; + e + } + None => { + println!("id is {}", self.next_id.0); + let new_id = self.next_id; + self.next_id.0 += 1; + + Entity { + id: new_id, + generation: 0, + } + } + } + } + + /// Retrieves the Archetype Record for the entity + pub(crate) fn entity_record(&self, entity: Entity) -> Option { + self.arch_index.get(&entity.id).cloned() + } + + pub(crate) fn insert_entity_record(&mut self, entity: Entity, record: Record) { + self.arch_index.insert(entity.id, record); + } +} diff --git a/lyra-ecs/src/lib.rs b/lyra-ecs/src/lib.rs index e22f7f8..a66af95 100644 --- a/lyra-ecs/src/lib.rs +++ b/lyra-ecs/src/lib.rs @@ -8,11 +8,19 @@ pub(crate) mod lyra_engine { } pub mod archetype; +use std::ops::BitOr; + pub use archetype::*; +pub mod entity; +pub use entity::*; + pub mod world; pub use world::*; +pub mod command; +pub use command::*; + pub mod bundle; pub use bundle::*; @@ -48,4 +56,18 @@ pub enum Access { None, Read, Write, +} + +impl BitOr for Access { + type Output = Access; + + fn bitor(self, rhs: Self) -> Self::Output { + if self == Access::Write || rhs == Access::Write { + Access::Write + } else if self == Access::Read || rhs == Access::Read { + Access::Read + } else { + Access::None + } + } } \ No newline at end of file diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index 4ee6739..ce13ac2 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -221,7 +221,7 @@ impl AsQuery for &mut T { mod tests { use std::{mem::size_of, marker::PhantomData, ptr::NonNull}; - use crate::{world::{World, Entity, EntityId}, archetype::{Archetype, ArchetypeId}, query::{View, Fetch}, tests::Vec2, bundle::Bundle, DynTypeId, Tick}; + use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, View}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs index b1c28df..a75c33c 100644 --- a/lyra-ecs/src/query/entities.rs +++ b/lyra-ecs/src/query/entities.rs @@ -1,4 +1,4 @@ -use crate::{world::{Entity, World}, archetype::Archetype}; +use crate::{archetype::Archetype, world::World, Entity}; use super::{Fetch, Query, AsQuery}; diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 43442d6..504c658 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -131,7 +131,7 @@ impl<'a, Q: Query> ViewOne<'a, Q> { } pub fn get(&self) -> Option> { - if let Some(record) = self.world.entity_index.get(&self.entity) { + if let Some(record) = self.world.entities.arch_index.get(&self.entity) { let arch = self.world.archetypes.get(&record.id) .expect("An invalid record was specified for an entity"); diff --git a/lyra-ecs/src/system/batched.rs b/lyra-ecs/src/system/batched.rs index a9ed48e..d924d0f 100644 --- a/lyra-ecs/src/system/batched.rs +++ b/lyra-ecs/src/system/batched.rs @@ -79,6 +79,10 @@ impl System for BatchedSystem { Ok(()) } + + fn execute_deferred(&mut self, _: std::ptr::NonNull) -> anyhow::Result<()> { + todo!() + } } impl IntoSystem<()> for BatchedSystem { diff --git a/lyra-ecs/src/system/fn_sys.rs b/lyra-ecs/src/system/fn_sys.rs index 08a6b9d..4252db7 100644 --- a/lyra-ecs/src/system/fn_sys.rs +++ b/lyra-ecs/src/system/fn_sys.rs @@ -1,13 +1,19 @@ use std::{ptr::NonNull, marker::PhantomData}; +use unique::Unique; +use paste::paste; use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}}; use super::{System, IntoSystem}; pub trait FnArgFetcher { - type Arg<'a>: FnArg; + /// stores data that persists after an execution of a system + type State: 'static; + + type Arg<'a, 'state>: FnArgFetcher; - fn new() -> Self; + //fn new() -> Self; + fn create_state(world: NonNull) -> Self::State; /// Return the appropriate world access if this fetcher gets the world directly. /// Return [`Access::None`] if you're only fetching components, or resources. @@ -20,7 +26,10 @@ pub trait FnArgFetcher { /// # Safety /// The system executor must ensure that on execution of the system, it will be safe to /// borrow the world. - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a>; + unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state>; + + /// Apply some action after the system was ran. + fn apply_deferred(state: Self::State, world: NonNull); } pub trait FnArg { @@ -29,8 +38,10 @@ pub trait FnArg { pub struct FnSystem { inner: F, - #[allow(dead_code)] - args: Args, + //#[allow(dead_code)] + //args: Args, + arg_state: Option>>, + _marker: PhantomData, } macro_rules! impl_fn_system_tuple { @@ -38,47 +49,87 @@ macro_rules! impl_fn_system_tuple { #[allow(non_snake_case)] impl System for FnSystem where - F: for<'a> FnMut($($name::Arg<'a>,)+) -> anyhow::Result<()>, + F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>, { fn world_access(&self) -> Access { todo!() } fn execute(&mut self, world: NonNull) -> anyhow::Result<()> { - $(let $name = unsafe { $name::new().get(world) };)+ + unsafe { + paste! { + $( + // get the arg fetcher, create its state, and get the arg + + let mut []: $name::State = $name::create_state(world); + let [<$name:lower>] = $name::get(&mut [], world); + + )+ + + (self.inner)($( [<$name:lower>] ),+)?; + + let mut state = Vec::new(); + $( + // type erase the now modified state, and store it + let [] = Unique::from(&mut []); + std::mem::forget([]); + state.push([].cast::<()>()); + )+ + + self.arg_state = Some(state); + } + + Ok(()) + } + } + + fn execute_deferred(&mut self, world: NonNull) -> anyhow::Result<()> { + $( + let s = self.arg_state.as_mut().expect("Somehow there was no state").pop().unwrap(); + let s = unsafe { std::ptr::read(s.cast::<$name::State>().as_ptr()) }; + $name::apply_deferred(s, world); + )+ - (self.inner)($($name,)+)?; - Ok(()) } } - /* impl IntoSystem for F + /* impl IntoSystem<($($name,)+)> for F where + /* for <'a> &'a mut F: + FnMut($($name,)+) -> anyhow::Result<()>, + FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>, */ F: FnMut($($name,)+) -> anyhow::Result<()>, - F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, + F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>, { type System = FnSystem; fn into_system(self) -> Self::System { FnSystem { - args: ($($name::Fetcher::new(),)+), - inner: self + //args: ($($name::Fetcher::new(),)+), + inner: self, + arg_state: None, + _marker: PhantomData::<($($name::Fetcher,)+)>::default(), } } } */ - impl IntoSystem<($($name,)+)> for F + impl IntoSystem<($($name,)+)> for F where + /* for <'a> &'a mut F: + FnMut($($name,)+) -> anyhow::Result<()>, + FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a, '_>,)+) -> anyhow::Result<()>, */ F: FnMut($($name,)+) -> anyhow::Result<()>, - F: for<'a> FnMut($(<$name::Fetcher as FnArgFetcher>::Arg<'a>,)+) -> anyhow::Result<()>, + F: for<'a> FnMut($($name::Arg<'a, '_>,)+) -> anyhow::Result<()>, { - type System = FnSystem; + type System = FnSystem; fn into_system(self) -> Self::System { FnSystem { - args: ($($name::Fetcher::new(),)+), - inner: self + //args: ($($name::Fetcher::new(),)+), + inner: self, + arg_state: None, + _marker: PhantomData::<($($name,)+)>::default(), } } } @@ -103,127 +154,116 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O } impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P } /// An ArgFetcher implementation for query [`View`]s -pub struct ViewArgFetcher { +/* pub struct ViewArgFetcher { query: Q::Query } impl<'a, Q: AsQuery> FnArg for View<'a, Q> { type Fetcher = ViewArgFetcher; -} +} */ -impl FnArgFetcher for ViewArgFetcher { - type Arg<'a> = View<'a, Q>; - - fn new() -> Self { - ViewArgFetcher { - query: ::new(), - } - } +impl<'c, Q> FnArgFetcher for View<'c, Q> +where + Q: AsQuery, + ::Query: 'static +{ + type State = Q::Query; + type Arg<'a, 'state> = View<'a, Q>; fn world_access(&self) -> Access { todo!() } - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { let world = &*world.as_ptr(); let arch = world.archetypes.values().collect(); - let v = View::new(world, self.query, arch); + let v = View::new(world, state.clone(), arch); v } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { + ::new() + } } /// An ArgFetcher implementation for borrowing the [`World`]. -pub struct WorldArgFetcher; +/* pub struct WorldArgFetcher; impl<'a> FnArg for &'a World { type Fetcher = WorldArgFetcher; -} +} */ -impl FnArgFetcher for WorldArgFetcher { - type Arg<'a> = &'a World; - - fn new() -> Self { - WorldArgFetcher - } +impl FnArgFetcher for &'_ World { + type State = (); + type Arg<'a, 'state> = &'a World; fn world_access(&self) -> Access { Access::Read } - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { &*world.as_ptr() } + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } -/// An ArgFetcher implementation for mutably borrowing the [`World`]. -pub struct WorldMutArgFetcher; - -impl<'a> FnArg for &'a mut World { - type Fetcher = WorldMutArgFetcher; -} - -impl FnArgFetcher for WorldMutArgFetcher { - type Arg<'a> = &'a mut World; - - fn new() -> Self { - WorldMutArgFetcher - } +impl FnArgFetcher for &'_ mut World { + type State = (); + type Arg<'a, 'state> = &'a mut World; fn world_access(&self) -> Access { Access::Write } - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { &mut *world.as_ptr() } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } -pub struct ResourceArgFetcher { +/* pub struct ResourceArgFetcher { phantom: PhantomData R> } impl<'a, R: ResourceObject> FnArg for Res<'a, R> { type Fetcher = ResourceArgFetcher; -} +} */ -impl FnArgFetcher for ResourceArgFetcher { - type Arg<'a> = Res<'a, R>; +impl FnArgFetcher for Res<'_, R> { + type State = (); + type Arg<'a, 'state> = Res<'a, R>; - fn new() -> Self { - ResourceArgFetcher { - phantom: PhantomData - } - } - - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { let world = world.as_ref(); Res(world.get_resource::()) } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } -pub struct ResourceMutArgFetcher { - phantom: PhantomData R> -} +impl FnArgFetcher for ResMut<'_, R> { + type State = (); + type Arg<'a, 'state> = ResMut<'a, R>; -impl<'a, R: ResourceObject> FnArg for ResMut<'a, R> { - type Fetcher = ResourceMutArgFetcher; -} - -impl FnArgFetcher for ResourceMutArgFetcher { - type Arg<'a> = ResMut<'a, R>; - - fn new() -> Self { - ResourceMutArgFetcher { - phantom: PhantomData - } - } - - unsafe fn get<'a>(&mut self, world: NonNull) -> Self::Arg<'a> { + unsafe fn get<'a, 'state>(_: &'state mut Self::State, world: NonNull) -> Self::Arg<'a, 'state> { let world = world.as_ref(); ResMut(world.get_resource_mut::()) } + + fn apply_deferred(_: Self::State, _: NonNull) { } + + fn create_state(_: NonNull) -> Self::State { () } } #[cfg(test)] diff --git a/lyra-ecs/src/system/graph.rs b/lyra-ecs/src/system/graph.rs index 4612fba..18ef23a 100644 --- a/lyra-ecs/src/system/graph.rs +++ b/lyra-ecs/src/system/graph.rs @@ -9,7 +9,9 @@ pub enum GraphExecutorError { #[error("could not find a system's dependency named `{0}`")] MissingSystem(String), #[error("system `{0}` returned with an error: `{1}`")] - SystemError(String, anyhow::Error) + SystemError(String, anyhow::Error), + #[error("a command returned with an error: `{0}`")] + Command(anyhow::Error) } /// A single system in the graph. @@ -56,7 +58,7 @@ impl GraphExecutor { } /// Executes the systems in the graph - pub fn execute(&mut self, world: NonNull, stop_on_error: bool) -> Result, GraphExecutorError> { + pub fn execute(&mut self, world_ptr: NonNull, stop_on_error: bool) -> Result, GraphExecutorError> { let mut stack = VecDeque::new(); let mut visited = HashSet::new(); @@ -69,7 +71,7 @@ impl GraphExecutor { while let Some(node) = stack.pop_front() { let system = self.systems.get_mut(node.as_str()).unwrap(); - if let Err(e) = system.system.execute(world) + if let Err(e) = system.system.execute(world_ptr) .map_err(|e| GraphExecutorError::SystemError(node, e)) { if stop_on_error { return Err(e); @@ -78,6 +80,17 @@ impl GraphExecutor { possible_errors.push(e); unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error } + + if let Err(e) = system.system.execute_deferred(world_ptr) + .map_err(|e| GraphExecutorError::Command(e)) { + + if stop_on_error { + return Err(e); + } + + possible_errors.push(e); + unimplemented!("Cannot resume topological execution from error"); // TODO: resume topological execution from error + } } Ok(possible_errors) diff --git a/lyra-ecs/src/system/mod.rs b/lyra-ecs/src/system/mod.rs index ab87811..5cc4395 100644 --- a/lyra-ecs/src/system/mod.rs +++ b/lyra-ecs/src/system/mod.rs @@ -27,6 +27,8 @@ pub trait System { let _ = world; Ok(()) } + + fn execute_deferred(&mut self, world: NonNull) -> anyhow::Result<()>; } pub trait IntoSystem { diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index cbc1695..9e56991 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,21 +1,12 @@ -use std::{collections::{HashMap, VecDeque}, any::TypeId, cell::{Ref, RefMut}, ptr::NonNull}; +use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull}; -use crate::{archetype::{ArchetypeId, Archetype}, bundle::Bundle, query::{Query, ViewIter, View, AsQuery}, resource::ResourceData, query::{dynamic::DynamicView, ViewOne}, ComponentInfo, DynTypeId, TickTracker, Tick, ResourceObject}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct EntityId(pub u64); +use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker}; /// The id of the entity for the Archetype. /// The Archetype struct uses this as the index in the component columns #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ArchetypeEntityId(pub u64); -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct Entity { - pub(crate) id: EntityId, - pub(crate) generation: u64, -} - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Record { pub id: ArchetypeId, @@ -25,11 +16,9 @@ pub struct Record { pub struct World { pub(crate) archetypes: HashMap, next_archetype_id: ArchetypeId, - pub(crate) entity_index: HashMap, - dead_entities: VecDeque, - next_entity_id: EntityId, resources: HashMap, tracker: TickTracker, + pub(crate) entities: Entities, } impl Default for World { @@ -37,11 +26,9 @@ impl Default for World { Self { archetypes: HashMap::new(), next_archetype_id: ArchetypeId(0), - entity_index: HashMap::new(), - dead_entities: VecDeque::new(), - next_entity_id: EntityId(0), resources: HashMap::new(), tracker: TickTracker::new(), + entities: Entities::default(), } } } @@ -51,32 +38,30 @@ impl World { Self::default() } - /// Gets a new Entity, will recycle dead entities and increment their generation. - fn get_new_entity(&mut self) -> Entity { - match self.dead_entities.pop_front() { - Some(mut e) => { - e.generation += 1; - e - }, - None => { - let new_id = self.next_entity_id; - self.next_entity_id.0 += 1; - - Entity { - id: new_id, - generation: 0, - } - } - } + /// Reserves an entity in the world + pub fn reserve_entity(&mut self) -> Entity { + self.entities.reserve() } - /// Spawns a new entity and inserts the component `bundle` into it. pub fn spawn(&mut self, bundle: B) -> Entity + where + B: Bundle + { + let new_entity = self.reserve_entity(); + self.spawn_into(new_entity, bundle); + new_entity + } + + /// Spawn the components into a reserved entity. Only do this with entities that + /// were 'reserved' with [`World::reserve`] + /// + /// # Safety + /// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior. + pub fn spawn_into(&mut self, entity: Entity, bundle: B) where B: Bundle { let bundle_types = bundle.type_ids(); - let new_entity = self.get_new_entity(); let tick = self.tick(); @@ -86,7 +71,11 @@ impl World { .find(|a| a.is_archetype_for(&bundle_types)); if let Some(archetype) = archetype { - let arche_idx = archetype.add_entity(new_entity, bundle, &tick); + // make at just one check to ensure you're not spawning twice + debug_assert!(!archetype.entities.contains_key(&entity), + "You attempted to spawn components into an entity that already exists!"); + + let arche_idx = archetype.add_entity(entity, bundle, &tick); // Create entity record and store it let record = Record { @@ -94,16 +83,17 @@ impl World { index: arche_idx, }; - self.entity_index.insert(new_entity.id, record); + self.entities.insert_entity_record(entity, record); } // create a new archetype if one isn't found else { // create archetype let new_arch_id = self.next_archetype_id.increment(); let mut archetype = Archetype::from_bundle_info(new_arch_id, bundle.info()); - let entity_arch_id = archetype.add_entity(new_entity, bundle, &tick); + let entity_arch_id = archetype.add_entity(entity, bundle, &tick); // store archetype + println!("About to store arch, cap is {}, len is {}", self.archetypes.capacity(), self.archetypes.len()); self.archetypes.insert(new_arch_id, archetype); // Create entity record and store it @@ -113,27 +103,25 @@ impl World { index: entity_arch_id, }; - self.entity_index.insert(new_entity.id, record); + self.entities.insert_entity_record(entity, record); } - - new_entity } /// Despawn an entity from the World pub fn despawn(&mut self, entity: Entity) { // Tick the tracker if the entity is spawned. This is done here instead of the `if let` // below due to the borrow checker complaining about multiple mutable borrows to self. - let tick = if self.entity_index.contains_key(&entity.id) { + let tick = if self.entities.arch_index.contains_key(&entity.id) { Some(self.tick()) } else { None }; - if let Some(record) = self.entity_index.get_mut(&entity.id) { + if let Some(record) = self.entities.arch_index.get_mut(&entity.id) { let tick = tick.unwrap(); let arch = self.archetypes.get_mut(&record.id).unwrap(); if let Some((moved, new_index)) = arch.remove_entity(entity, &tick) { // replace the archetype index of the moved index with its new index. - self.entity_index.get_mut(&moved.id).unwrap().index = new_index; + self.entities.arch_index.get_mut(&moved.id).unwrap().index = new_index; } } } @@ -152,7 +140,7 @@ impl World { let tick = self.tick(); - let record = *self.entity_index.get(&entity.id).unwrap(); + let record = self.entities.entity_record(entity).unwrap(); let current_arch = self.archetypes.get(&record.id).unwrap(); let mut col_types: Vec = current_arch.columns.iter().map(|c| c.info.type_id).collect(); @@ -188,7 +176,7 @@ impl World { id: arch.id, index: res_index, }; - self.entity_index.insert(entity.id, new_record); + self.entities.insert_entity_record(entity, new_record); } else { let new_arch_id = self.next_archetype_id.increment(); let mut archetype = Archetype::from_bundle_info(new_arch_id, col_infos); @@ -202,7 +190,7 @@ impl World { index: entity_arch_id, }; - self.entity_index.insert(entity.id, record); + self.entities.insert_entity_record(entity, record); } let current_arch = self.archetypes.get_mut(&record.id).unwrap(); @@ -210,12 +198,12 @@ impl World { } pub fn entity_archetype(&self, entity: Entity) -> Option<&Archetype> { - self.entity_index.get(&entity.id) + self.entities.entity_record(entity) .and_then(|record| self.archetypes.get(&record.id)) } pub fn entity_archetype_mut(&mut self, entity: Entity) -> Option<&mut Archetype> { - self.entity_index.get_mut(&entity.id) + self.entities.entity_record(entity) .and_then(|record| self.archetypes.get_mut(&record.id)) } @@ -255,6 +243,14 @@ impl World { .get_mut() } + /// Get a resource from the world, or insert it into the world as its default. + pub fn get_resource_or_default(&mut self) -> RefMut + { + self.resources.entry(TypeId::of::()) + .or_insert_with(|| ResourceData::new(T::default())) + .get_mut() + } + /// Gets a resource from the World. /// /// Will panic if the resource is not in the world. See [`try_get_resource`] for @@ -264,6 +260,11 @@ impl World { .get() } + /// Returns boolean indicating if the World contains a resource of type `T`. + pub fn has_resource(&self) -> bool { + self.resources.contains_key(&TypeId::of::()) + } + /// Attempts to get a resource from the World. /// /// Returns `None` if the resource was not found. @@ -364,7 +365,7 @@ mod tests { world.despawn(middle_en); - let record = world.entity_index.get(&last_en.id).unwrap(); + let record = world.entities.entity_record(last_en).unwrap(); assert_eq!(record.index.0, 1); }