From 21537481c93c61e597b55de533b32dcd629a5e13 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Mar 2024 20:20:38 -0500 Subject: [PATCH 1/5] ecs: add relations, improve docs --- lyra-ecs/src/archetype.rs | 41 ++++--- lyra-ecs/src/command.rs | 4 +- lyra-ecs/src/lib.rs | 21 ++-- lyra-ecs/src/query/borrow.rs | 8 +- lyra-ecs/src/query/dynamic/mod.rs | 2 +- lyra-ecs/src/query/mod.rs | 50 ++++++-- lyra-ecs/src/query/tuple.rs | 44 +++++++ lyra-ecs/src/query/view.rs | 69 +++++++---- lyra-ecs/src/relation/mod.rs | 175 +++++++++++++++++++++++++++ lyra-ecs/src/relation/relate_pair.rs | 95 +++++++++++++++ lyra-ecs/src/relation/relates_to.rs | 111 +++++++++++++++++ lyra-ecs/src/system/criteria.rs | 9 ++ lyra-ecs/src/system/fn_sys.rs | 61 +++++----- lyra-ecs/src/system/graph.rs | 8 +- lyra-ecs/src/system/mod.rs | 8 +- lyra-ecs/src/world.rs | 22 +++- 16 files changed, 618 insertions(+), 110 deletions(-) create mode 100644 lyra-ecs/src/relation/mod.rs create mode 100644 lyra-ecs/src/relation/relate_pair.rs create mode 100644 lyra-ecs/src/relation/relates_to.rs diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index d00f607..33e1d51 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -1,4 +1,4 @@ -use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::{DerefMut, Deref}}; +use std::{ptr::{NonNull, self}, alloc::{self, Layout, alloc, dealloc}, mem, collections::HashMap, cell::{RefCell, Ref, RefMut, BorrowError, BorrowMutError}, ops::DerefMut}; use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity}; @@ -81,14 +81,15 @@ impl ComponentColumn { /// # Safety /// /// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB. - pub unsafe fn get(&self, entity_index: usize) -> &T { + pub unsafe fn get(&self, entity_index: usize) -> Ref { let data = self.data.borrow(); - let data = data.deref(); - let ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * self.info.layout.size)) - .cast(); - &*ptr.as_ptr() + Ref::map(data, |data| { + let ptr = NonNull::new_unchecked(data.as_ptr() + .add(entity_index * self.info.layout.size)) + .cast(); + &*ptr.as_ptr() + }) } /// Get a component at an entities index. @@ -411,11 +412,15 @@ impl Archetype { pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option { self.ids_to_entity.get(&id).cloned() } + + pub fn has_entity(&self, e: Entity) -> bool { + self.entities.contains_key(&e) + } } #[cfg(test)] mod tests { - use std::{alloc::Layout, ptr::NonNull}; + use std::{alloc::Layout, cell::Ref, ptr::NonNull}; use rand::Rng; @@ -435,7 +440,7 @@ mod tests { let entity_arch_id = a.add_entity(entity, bundle, &Tick::default()); let col = a.columns.get(0).unwrap(); - let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) }; + let vec2: Ref = unsafe { col.get(entity_arch_id.0 as usize) }; assert_eq!(vec2.clone(), bundle.0); } @@ -451,11 +456,11 @@ mod tests { let entity_arch_id = a.add_entity(entity, bundle, &Tick::default()); let col = a.columns.get(0).unwrap(); - let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) }; + let vec2: Ref = unsafe { col.get(entity_arch_id.0 as usize) }; assert_eq!(vec2.clone(), bundle.0); let col = a.columns.get(1).unwrap(); - let vec3: &Vec3 = unsafe { col.get(entity_arch_id.0 as usize) }; + let vec3: Ref = unsafe { col.get(entity_arch_id.0 as usize) }; assert_eq!(vec3.clone(), bundle.1); } @@ -477,9 +482,9 @@ mod tests { let earch2 = a.add_entity(e2, b2, &Tick::default()); let col = a.columns.get(0).unwrap(); - let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) }; + let vec2: Ref = unsafe { col.get(earch1.0 as usize) }; assert_eq!(vec2.clone(), b1.0); - let vec2: &Vec2 = unsafe { col.get(earch2.0 as usize) }; + let vec2: Ref = unsafe { col.get(earch2.0 as usize) }; assert_eq!(vec2.clone(), b2.0); } @@ -501,15 +506,15 @@ mod tests { let earch2 = a.add_entity(e2, b2, &Tick::default()); let col = a.columns.get(0).unwrap(); - let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) }; + let vec2: Ref = unsafe { col.get(earch1.0 as usize) }; assert_eq!(vec2.clone(), b1.0); - let vec2: &Vec2 = unsafe { col.get(earch2.0 as usize) }; + let vec2: Ref = unsafe { col.get(earch2.0 as usize) }; assert_eq!(vec2.clone(), b2.0); let col = a.columns.get(1).unwrap(); - let vec3: &Vec3 = unsafe { col.get(earch1.0 as usize) }; + let vec3: Ref = unsafe { col.get(earch1.0 as usize) }; assert_eq!(vec3.clone(), b1.1); - let vec3: &Vec3 = unsafe { col.get(earch2.0 as usize) }; + let vec3: Ref = unsafe { col.get(earch2.0 as usize) }; assert_eq!(vec3.clone(), b2.1); } @@ -552,7 +557,7 @@ mod tests { let col = a.columns.get(0).unwrap(); for i in 0..bundle_count { - let vec2: &Vec2 = unsafe { col.get(i) }; + let vec2: Ref = unsafe { col.get(i) }; assert_eq!(vec2.clone(), bundles[i].0); } } diff --git a/lyra-ecs/src/command.rs b/lyra-ecs/src/command.rs index 137f8f1..56d7aca 100644 --- a/lyra-ecs/src/command.rs +++ b/lyra-ecs/src/command.rs @@ -114,7 +114,7 @@ pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut()).unwrap(); - let vec2: &Vec2 = unsafe { col.get(3) }; + let vec2: Ref = unsafe { col.get(3) }; assert_eq!(vec2.clone(), spawned_vec); } } \ No newline at end of file diff --git a/lyra-ecs/src/lib.rs b/lyra-ecs/src/lib.rs index a66af95..43cc726 100644 --- a/lyra-ecs/src/lib.rs +++ b/lyra-ecs/src/lib.rs @@ -7,39 +7,42 @@ pub(crate) mod lyra_engine { } } -pub mod archetype; use std::ops::BitOr; +mod archetype; pub use archetype::*; -pub mod entity; +mod entity; pub use entity::*; -pub mod world; +mod world; pub use world::*; -pub mod command; +mod command; pub use command::*; -pub mod bundle; +mod bundle; pub use bundle::*; -pub mod component; +mod component; pub use component::*; pub mod query; //pub use query::*; -pub mod component_info; +mod relation; +pub use relation::Relation; + +mod component_info; pub use component_info::*; -pub mod resource; +mod resource; pub use resource::*; pub mod system; //pub use system::*; -pub mod tick; +mod tick; pub use tick::*; /// Implements Component for glam math types diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index ce13ac2..a3e4ec6 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::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, View}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick}; + use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, ViewState}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; @@ -244,7 +244,7 @@ mod tests { _phantom: std::marker::PhantomData, }; let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::>::new(&world, borrow, archetypes); + let v = ViewState::, ()>::new(&world, borrow, (), archetypes); for e in v.into_iter() { println!("Found entity at {:?}", e); @@ -260,7 +260,7 @@ mod tests { _phantom: std::marker::PhantomData, }; let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::>::new(&world, borrow, archetypes); + let v = ViewState::, ()>::new(&world, borrow, (), archetypes); let mut orig = vec![]; @@ -277,7 +277,7 @@ mod tests { _phantom: std::marker::PhantomData, }; let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::>::new(&world, borrow, archetypes); + let v = ViewState::, ()>::new(&world, borrow, (), archetypes); for (new, orig) in v.into_iter().zip(orig.iter()) { assert!(new.x - orig.x == 10.0); diff --git a/lyra-ecs/src/query/dynamic/mod.rs b/lyra-ecs/src/query/dynamic/mod.rs index 729021d..221870c 100644 --- a/lyra-ecs/src/query/dynamic/mod.rs +++ b/lyra-ecs/src/query/dynamic/mod.rs @@ -2,7 +2,7 @@ use std::ptr::NonNull; use crate::{world::World, ComponentColumn, ComponentInfo}; -pub mod view; +mod view; pub use view::*; use super::Fetch; diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index 842763c..cc7d699 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -3,27 +3,27 @@ use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, Tick}; pub mod view; pub use view::*; -pub mod entities; +mod entities; #[allow(unused_imports)] pub use entities::*; -pub mod borrow; +mod borrow; #[allow(unused_imports)] pub use borrow::*; -pub mod tuple; +mod tuple; #[allow(unused_imports)] pub use tuple::*; -pub mod resource; +mod resource; #[allow(unused_imports)] pub use resource::*; -pub mod tick; +mod tick; #[allow(unused_imports)] pub use tick::*; -pub mod world; +mod world; #[allow(unused_imports)] pub use world::*; @@ -87,11 +87,45 @@ pub trait IntoQuery { fn into_query(self) -> Self; } +impl<'a> Fetch<'a> for () { + type Item = (); + + fn dangling() -> Self { + unreachable!() + } + + unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item { + () + } +} + +impl Query for () { + type Item<'a> = (); + + type Fetch<'a> = (); + + fn new() -> Self { + () + } + + fn can_visit_archetype(&self, archetype: &Archetype) -> bool { + true + } + + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a Archetype, tick: Tick) -> Self::Fetch<'a> { + () + } +} + +impl AsQuery for () { + type Query = (); +} + #[cfg(test)] mod tests { use crate::{world::World, archetype::Archetype, tests::Vec2}; - use super::{View, Entities}; + use super::{ViewState, Entities}; #[test] fn simple_view() { @@ -104,7 +138,7 @@ mod tests { let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); - let v = View::::new(&world, entities, archetypes); + let v = ViewState::::new(&world, entities, (), archetypes); for e in v.into_iter() { println!("Got entity! {:?}", e); diff --git a/lyra-ecs/src/query/tuple.rs b/lyra-ecs/src/query/tuple.rs index cb5807c..13ab692 100644 --- a/lyra-ecs/src/query/tuple.rs +++ b/lyra-ecs/src/query/tuple.rs @@ -2,6 +2,50 @@ use crate::world::World; use super::{Query, Fetch, AsQuery}; +impl<'a, F1> Fetch<'a> for (F1,) +where + F1: Fetch<'a>, +{ + type Item = (F1::Item,); + + fn dangling() -> Self { + (F1::dangling(),) + } + + fn can_visit_item(&mut self, entity: crate::world::ArchetypeEntityId) -> bool { + let (f1,) = self; + f1.can_visit_item(entity) + } + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + let (f1,) = self; + ( f1.get_item(entity), ) + } +} + +impl Query for (Q1,) +where + Q1: Query, +{ + type Item<'a> = (Q1::Item<'a>,); + + type Fetch<'a> = (Q1::Fetch<'a>,); + + fn new() -> Self { + (Q1::new(),) + } + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + let (q1,) = self; + q1.can_visit_archetype(archetype) + } + + unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { + let (q1,) = self; + ( q1.fetch(world, archetype, tick), ) + } +} + // Technically all of these implementations for a 2-sized tuple // can be implemented by the macro near the end of the file, but // these are left here for development. diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 504c658..4dc3a3c 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -4,41 +4,64 @@ use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, T use super::{Query, Fetch, AsQuery}; -pub struct View<'a, Q: AsQuery> { +pub type View<'a, Q, F = ()> = ViewState<'a, ::Query, ::Query>; + +pub struct ViewState<'a, Q: Query, F: Query> { world: &'a World, - query: Q::Query, + query: Q, + filter: F, archetypes: Vec<&'a Archetype>, } -impl<'a, Q> View<'a, Q> +impl<'a, Q, F> ViewState<'a, Q, F> where - Q: AsQuery, + Q: Query, + F: Query, { - pub fn new(world: &'a World, query: Q::Query, archetypes: Vec<&'a Archetype>) -> Self { + pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self { Self { world, query, + filter, archetypes, } } + + /// Converts self into an iterator + pub fn iter(self) -> ViewIter<'a, Q, F> { + self.into_iter() + } + + /// Consumes `self`, adding a query to the view. + pub fn expand(self, query: U::Query) -> ViewState<'a, (Q, U::Query), F> { + ViewState::new(self.world, (self.query, query), self.filter, self.archetypes) + } + + /// Consumes `self`, adding a filter to the view. + pub fn with(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> { + ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes) + } } -impl<'a, Q> IntoIterator for View<'a, Q> +impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F> where - Q: AsQuery, + Q: Query, + F: Query, { - type Item = ::Item<'a>; + type Item = Q::Item<'a>; - type IntoIter = ViewIter<'a, Q::Query>; + type IntoIter = ViewIter<'a, Q, F>; fn into_iter(self) -> Self::IntoIter { - let tick = self.world.tick_tracker().tick_when(Q::Query::MUTATES); + let tick = self.world.tick_tracker().tick_when(Q::MUTATES); ViewIter { world: self.world, tick, query: self.query, + filter: self.filter, fetcher: None, + filter_fetcher: None, archetypes: self.archetypes, next_archetype: 0, component_indices: 0..0, @@ -46,25 +69,28 @@ where } } -pub struct ViewIter<'a, Q: Query> { +pub struct ViewIter<'a, Q: Query, F: Query> { world: &'a World, tick: Tick, query: Q, + filter: F, fetcher: Option>, + filter_fetcher: Option>, archetypes: Vec<&'a Archetype>, next_archetype: usize, component_indices: Range, } -impl<'a, Q> Iterator for ViewIter<'a, Q> +impl<'a, Q, F> Iterator for ViewIter<'a, Q, F> where Q: Query, + F: Query, { type Item = Q::Item<'a>; fn next(&mut self) -> Option { loop { - if Q::ALWAYS_FETCHES { + if Q::ALWAYS_FETCHES && F::ALWAYS_FETCHES { // only fetch this query once. // fetcher gets set to Some after this `next` call. if self.fetcher.is_none() { @@ -80,10 +106,10 @@ where if let Some(entity_index) = self.component_indices.next() { let fetcher = self.fetcher.as_mut().unwrap(); + let filter_fetcher = self.filter_fetcher.as_mut().unwrap(); let entity_index = ArchetypeEntityId(entity_index); - if !fetcher.can_visit_item(entity_index) { - continue; - } else { + + if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) { let i = unsafe { fetcher.get_item(entity_index) }; return Some(i); } @@ -100,12 +126,13 @@ where continue; } - if !self.query.can_visit_archetype(arch) { - continue; + if self.query.can_visit_archetype(arch) && self.filter.can_visit_archetype(arch) { + unsafe { + self.fetcher = Some(self.query.fetch(self.world, arch, self.tick)); + self.filter_fetcher = Some(self.filter.fetch(self.world, arch, self.tick)); + } + self.component_indices = 0..arch.entities.len() as u64; } - - self.fetcher = unsafe { Some(self.query.fetch(self.world, arch, self.tick)) }; - self.component_indices = 0..arch.entities.len() as u64; } } } diff --git a/lyra-ecs/src/relation/mod.rs b/lyra-ecs/src/relation/mod.rs new file mode 100644 index 0000000..1b693b0 --- /dev/null +++ b/lyra-ecs/src/relation/mod.rs @@ -0,0 +1,175 @@ +use std::marker::PhantomData; + +use lyra_ecs_derive::Component; + +use crate::query::Query; +use crate::query::ViewState; +use crate::Entity; + +use crate::lyra_engine; +use crate::World; + +mod relates_to; +#[doc(hidden)] +pub use relates_to::*; + +mod relate_pair; +#[doc(hidden)] +pub use relate_pair::*; + +pub trait Relation: 'static { + /// called when a relation of this type is set on a target + fn relation_add(&self, origin: Entity, target: Entity) { } + /// called when a relation is removed + fn relation_remove(&self, origin: Entity, target: Entity) { } +} + +/// A component that stores the target of a relation. +/// +/// This component is on the origin of the relation and can be used to find all +/// entities that the relation targets. +#[derive(Component)] +pub struct RelationOriginComponent { + pub(crate) relation: R, + target: Entity, +} + +/// A component that stores the origin of a relation. +/// +/// This component is on the target of the relation and can be used to find the +/// origin of the relation -- the entity that targets this entity. +#[derive(Component)] +pub struct RelationTargetComponent { + origin: Entity, + _marker: PhantomData, +} + +impl World { + /// Creates a relation between two entities. + /// + /// ```compile_fail + /// struct ChildOf; + /// + /// impl Relation for ChildOf { /* snip */ }; + /// + /// let a = world.spawn((Vec2 { x: 10.0, y: 20.0 },)); + /// let b = world.spawn((Vec2 { x: 158.0, y: 65.0 },)); + /// + /// // Entity a is a child of entity b + /// world.add_relation(a, ChildOf, b); + /// + /// // Construct a view for querying the entities that have a relation to entity `b`. + /// let v = world.view::() + /// .relates_to::(b); + /// + /// // Iterate through the entities. ChildOf has no members, so it will be unused. + /// for (e, _childof) in v.into_iter() { + /// println!("{:?} is a child of {:?}", e, b); + /// } + /// ``` + pub fn add_relation(&mut self, origin: Entity, relation: R, target: Entity) + where + R: Relation + { + let comp = RelationTargetComponent { + origin, + _marker: PhantomData::, + }; + self.insert(target, comp); + + let comp = RelationOriginComponent { + relation, + target, + }; + + comp.relation.relation_add(origin, target); + self.insert(origin, comp); + } +} +impl<'a, Q, F> ViewState<'a, Q, F> +where + Q: Query, + F: Query, +{ + /// Consumes `self` to return a view that fetches the relation to a specific target entity. + pub fn relates_to(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo), F> + where + R: Relation, + { + let rel = QueryRelatesTo::new(Some(target)); + self.expand::>(rel) + } + + /// Consumes `self` to return a view that fetches the origin, target, and a reference to + /// the relation that the entities have together. + pub fn relate_pair(self) -> ViewState<'a, (Q, QueryRelatePair), F> + where + R: Relation, + { + let rel = QueryRelatePair::::new(); + self.expand::>(rel) + } +} + +#[cfg(test)] +mod tests { + use crate::{query::{Entities, ViewState}, relation::QueryRelatesTo, tests::Vec2, World}; + + use super::{RelatePair, Relation}; + + struct ChildOf; + + impl Relation for ChildOf { + + } + + #[test] + fn simple_relates_to() { + let mut world = World::new(); + + let a = world.spawn((Vec2::new(10.0, 20.0),)); + let b = world.spawn((Vec2::new(158.0, 65.0),)); + + world.add_relation(a, ChildOf, b); + + let archetypes = world.archetypes.values().collect(); + let v = ViewState::<(Entities, QueryRelatesTo), ()>::new(&world, (Entities::default(), QueryRelatesTo::new(Some(b))), (), archetypes); + + for (e, _relation) in v.into_iter() { + println!("{:?} is a child of {:?}", e, b); + }; + } + + #[test] + fn simple_relates_to_view() { + let mut world = World::new(); + + let a = world.spawn((Vec2::new(10.0, 20.0),)); + let b = world.spawn((Vec2::new(158.0, 65.0),)); + + world.add_relation(a, ChildOf, b); + + let v = world.view::() + .relates_to::(b); + + for (e, _rel) in v.into_iter() { + println!("{:?} is a child of {:?}", e, b); + } + } + + #[test] + fn simple_relate_pair_view() { + let mut world = World::new(); + + let a = world.spawn((Vec2::new(10.0, 20.0),)); + let b = world.spawn((Vec2::new(158.0, 65.0),)); + + world.add_relation(a, ChildOf, b); + + let v = world.view::>(); + + for (origin, _childof, target) in v.into_iter() { + println!("{:?} is a child of {:?}", origin, target); + } + } +} \ No newline at end of file diff --git a/lyra-ecs/src/relation/relate_pair.rs b/lyra-ecs/src/relation/relate_pair.rs new file mode 100644 index 0000000..ab58b3c --- /dev/null +++ b/lyra-ecs/src/relation/relate_pair.rs @@ -0,0 +1,95 @@ +use std::{any::TypeId, cell::Ref, marker::PhantomData}; + +use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World}; + +use super::{Relation, RelationOriginComponent}; + +pub struct FetchRelatePair<'a, T> { + col: &'a ComponentColumn, + arch: &'a Archetype, + _phantom: PhantomData<&'a T> +} + +impl<'a, R> Fetch<'a> for FetchRelatePair<'a, R> +where + R: Relation, +{ + type Item = (Entity, Ref<'a, R>, Entity); + + fn dangling() -> Self { + unreachable!() + } + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + let comp: Ref> = self.col.get(entity.0 as usize); + let rel_target = comp.target; + let rel_origin = self.arch.entity_of_index(entity).unwrap(); + + let comp = Ref::map(comp, |r| &r.relation); + (rel_origin, comp, rel_target) + } +} + +pub struct QueryRelatePair { + _marker: PhantomData, +} + +impl Copy for QueryRelatePair {} + +impl Clone for QueryRelatePair { + fn clone(&self) -> Self { + *self + } +} + +impl QueryRelatePair { + pub fn new() -> Self { + Self { + _marker: PhantomData + } + } +} + +impl Query for QueryRelatePair +where + R: Relation + 'static +{ + type Item<'a> = (Entity, Ref<'a, R>, Entity); + + type Fetch<'a> = FetchRelatePair<'a, R>; + + fn new() -> Self { + QueryRelatePair::::new() + } + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + let tyid = crate::DynTypeId::Rust(TypeId::of::>()); + archetype.has_column(tyid) + } + + unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { + let _ = tick; + let tyid = crate::DynTypeId::Rust(TypeId::of::>()); + let col = archetype.columns.iter().find(|c| c.info.type_id == tyid) + .expect("You ignored 'can_visit_archetype'!"); + + FetchRelatePair { + col, + arch: archetype, + _phantom: PhantomData, + } + } +} + +/// A query that fetches the origin, and target of a relation of type `R`. +/// +/// It provides it as a tuple in the following format: `(origin, relation, target)`. +/// Similar to [`RelatesTo`](super::RelatesTo), you can use [`ViewState::relate_pair`] to get a view that fetches the +/// pair, or unlike [`RelatesTo`](super::RelatesTo), you can do the common procedure of using [`World::view`]. +pub struct RelatePair { + _marker: PhantomData, +} + +impl AsQuery for RelatePair { + type Query = QueryRelatePair; +} diff --git a/lyra-ecs/src/relation/relates_to.rs b/lyra-ecs/src/relation/relates_to.rs new file mode 100644 index 0000000..7cd6657 --- /dev/null +++ b/lyra-ecs/src/relation/relates_to.rs @@ -0,0 +1,111 @@ +use std::{any::TypeId, cell::Ref, marker::PhantomData}; + +use crate::{query::{AsQuery, Fetch, Query}, ComponentColumn, Entity, World}; + +use super::{Relation, RelationOriginComponent}; + +pub struct FetchRelatesTo<'a, T> { + col: &'a ComponentColumn, + target: Entity, + _phantom: PhantomData<&'a T> +} + +impl<'a, R> Fetch<'a> for FetchRelatesTo<'a, R> +where + R: Relation, +{ + type Item = Ref<'a, R>; + + fn dangling() -> Self { + unreachable!() + } + + fn can_visit_item(&mut self, entity: crate::ArchetypeEntityId) -> bool { + unsafe { + let comp: Ref> = self.col.get(entity.0 as usize); + comp.target == self.target + } + } + + unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { + let comp: Ref> = self.col.get(entity.0 as usize); + let comp = Ref::map(comp, |r| &r.relation); + comp + } +} + +pub struct QueryRelatesTo { + target: Option, + _marker: PhantomData, +} + +impl Copy for QueryRelatesTo {} + +impl Clone for QueryRelatesTo { + fn clone(&self) -> Self { + *self + } +} + +impl QueryRelatesTo { + pub fn new(target: Option) -> Self { + Self { + target, + _marker: PhantomData + } + } +} + +impl Query for QueryRelatesTo +where + R: Relation + 'static +{ + type Item<'a> = Ref<'a, R>; + + type Fetch<'a> = FetchRelatesTo<'a, R>; + + fn new() -> Self { + panic!("RelatesTo MUST be made with View::relates_to since it requires State provided by \ + that function.") + } + + fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { + let tyid = crate::DynTypeId::Rust(TypeId::of::>()); + archetype.has_column(tyid) + } + + unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { + let _ = tick; + let tyid = crate::DynTypeId::Rust(TypeId::of::>()); + let col = archetype.columns.iter().find(|c| c.info.type_id == tyid) + .expect("You ignored 'can_visit_archetype'!"); + + FetchRelatesTo { + col, + target: self.target.expect("Filter not initialized"), + _phantom: PhantomData, + } + } +} + +/// A query that fetches the relation to a specific target entity. +/// +/// This can be combined with Entities, to query all entities that have a relation targeting +/// the target entity. +/// +/// ```compile_fail +/// let v = world.view::() +/// .relates_to::(b); +/// +/// // Iterate through the entities. ChildOf has no members, so it will be unused. +/// for (e, _childof) in v.into_iter() { +/// println!("{:?} is a child of {:?}", e, b); +/// } +/// ``` +pub struct RelatesTo { + _marker: PhantomData, +} + +impl AsQuery for RelatesTo { + type Query = QueryRelatesTo; +} diff --git a/lyra-ecs/src/system/criteria.rs b/lyra-ecs/src/system/criteria.rs index 1abbaa9..e33e5ae 100644 --- a/lyra-ecs/src/system/criteria.rs +++ b/lyra-ecs/src/system/criteria.rs @@ -2,14 +2,23 @@ use std::ptr::NonNull; use lyra_ecs::world::World; +/// An enum that is used to control if the Criteria was met or not. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] pub enum CriteriaSchedule { + /// The criteria was completely met and the system can continue to run. Yes, + /// The criteria was not met and must run next time the system batch is ran. No, + /// The criteria was met and the system can run. + /// After the system runs, the criteria should be checked again and may cause another + /// execution of the system. YesAndLoop, + /// The criteria was not met, but it should be checked again during this tick before giving + /// up. If the criteria returns `Yes` next check, the systems will run. NoAndLoop, } +/// A Criteria can be used to conditionally execute [`BatchedSystems`](super::BatchedSystems). pub trait Criteria { /// Checks if this Criteria can run, and if it should check it again. /// diff --git a/lyra-ecs/src/system/fn_sys.rs b/lyra-ecs/src/system/fn_sys.rs index fd7ac5c..b9079fd 100644 --- a/lyra-ecs/src/system/fn_sys.rs +++ b/lyra-ecs/src/system/fn_sys.rs @@ -1,10 +1,11 @@ use std::{any::Any, marker::PhantomData, ptr::NonNull}; use paste::paste; -use crate::{world::World, Access, ResourceObject, query::{Query, View, AsQuery, ResMut, Res}}; +use crate::{world::World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}}; use super::{System, IntoSystem}; +/// A trait that is used for fetching an argument for a [`FnSystem`]. pub trait FnArgFetcher { /// stores data that persists after an execution of a system type State: 'static; @@ -30,14 +31,20 @@ pub trait FnArgFetcher { fn apply_deferred(state: Self::State, world: NonNull); } -pub trait FnArg { - type Fetcher: FnArgFetcher; -} - +/// A system that is implemented as a function. +/// +/// The arguments of the functions must implement `FnArgFetcher` so that the arguments can be +/// fetched on the fly. +/// +/// ```fail_compile +/// fn enemy_movement_system(enemies: ViewState<(&Health, &EnemyStats, &Movement)>) -> anyhow::Result<()> { +/// for (health, stats, movement) in enemies.iter() { +/// // ... +/// } +/// } +/// ``` pub struct FnSystem { inner: F, - //#[allow(dead_code)] - //args: Args, arg_state: Option>>, _marker: PhantomData, } @@ -129,22 +136,14 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N } 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 { - query: Q::Query -} - -impl<'a, Q: AsQuery> FnArg for View<'a, Q> { - type Fetcher = ViewArgFetcher; -} */ - -impl<'c, Q> FnArgFetcher for View<'c, Q> +/// An ArgFetcher implementation for query [`ViewState`]s +impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F> where - Q: AsQuery, - ::Query: 'static + Q: Query + 'static, + F: Query + 'static, { - type State = Q::Query; - type Arg<'a, 'state> = View<'a, Q>; + type State = (Q, F); + type Arg<'a, 'state> = ViewState<'a, Q, F>; fn world_access(&self) -> Access { todo!() @@ -153,7 +152,8 @@ where 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, state.clone(), arch); + let (query, filter) = state.clone(); + let v = ViewState::new(world, query, filter, arch); v } @@ -161,17 +161,10 @@ where fn apply_deferred(_: Self::State, _: NonNull) { } fn create_state(_: NonNull) -> Self::State { - ::new() + (Q::new(), F::new()) } } -/// An ArgFetcher implementation for borrowing the [`World`]. -/* pub struct WorldArgFetcher; - -impl<'a> FnArg for &'a World { - type Fetcher = WorldArgFetcher; -} */ - impl FnArgFetcher for &'_ World { type State = (); type Arg<'a, 'state> = &'a World; @@ -246,7 +239,7 @@ impl FnArgFetcher for ResMut<'_, R> { mod tests { use std::ptr::NonNull; - use crate::{tests::{Vec2, Vec3}, world::World, query::{QueryBorrow, View, ResMut}}; + use crate::{tests::{Vec2, Vec3}, world::World, query::{QueryBorrow, ViewState, ResMut}}; use super::{System, IntoSystem}; struct SomeCounter(u32); @@ -262,7 +255,7 @@ mod tests { let mut count = 0; - let test_system = |view: View>| -> anyhow::Result<()> { + let test_system = |view: ViewState, ()>| -> anyhow::Result<()> { let mut vecs = vecs.to_vec(); for v in view.into_iter() { let pos = vecs.iter().position(|vec| *vec == *v) @@ -291,7 +284,7 @@ mod tests { let mut count = 0; - let test_system = |view: View<(QueryBorrow, QueryBorrow)>| -> anyhow::Result<()> { + let test_system = |view: ViewState<(QueryBorrow, QueryBorrow), ()>| -> anyhow::Result<()> { for (v2, v3) in view.into_iter() { println!("Got v2 at '{:?}' and v3 at: '{:?}'", v2, v3); count += 1; @@ -392,7 +385,7 @@ mod tests { world.spawn((Vec2::rand(), )); world.add_resource(SomeCounter(0)); - let test_system = |mut counter: ResMut, view: View>| -> anyhow::Result<()> { + let test_system = |mut counter: ResMut, view: ViewState, ()>| -> anyhow::Result<()> { for v2 in view.into_iter() { println!("Got v2 at '{:?}'", v2); // .0 is twice here since ResMut's tuple field is pub(crate). diff --git a/lyra-ecs/src/system/graph.rs b/lyra-ecs/src/system/graph.rs index 40ac0cc..4dd9044 100644 --- a/lyra-ecs/src/system/graph.rs +++ b/lyra-ecs/src/system/graph.rs @@ -140,7 +140,7 @@ impl GraphExecutor { mod tests { use std::ptr::NonNull; - use crate::{world::World, query::{ResMut, View}, system::IntoSystem}; + use crate::{query::{ResMut, View}, system::IntoSystem, world::World}; use super::GraphExecutor; @@ -152,7 +152,7 @@ mod tests { let mut exec = GraphExecutor::new(); - let a_system = |view: View>>| -> anyhow::Result<()> { + let a_system = |view: View>, ()>| -> anyhow::Result<()> { println!("System 'a' ran!"); let mut order = view.into_iter().next().unwrap(); @@ -161,7 +161,7 @@ mod tests { Ok(()) }; - let b_system = |view: View>>| -> anyhow::Result<()> { + let b_system = |view: View>, ()>| -> anyhow::Result<()> { println!("System 'b' ran!"); let mut order = view.into_iter().next().unwrap(); @@ -170,7 +170,7 @@ mod tests { Ok(()) }; - let c_system = |view: View>>| -> anyhow::Result<()> { + let c_system = |view: View>, ()>| -> anyhow::Result<()> { println!("System 'c' ran!"); let mut order = view.into_iter().next().unwrap(); diff --git a/lyra-ecs/src/system/mod.rs b/lyra-ecs/src/system/mod.rs index 5cc4395..0471b89 100644 --- a/lyra-ecs/src/system/mod.rs +++ b/lyra-ecs/src/system/mod.rs @@ -2,16 +2,16 @@ use std::ptr::NonNull; use crate::{world::World, Access}; -pub mod graph; +mod graph; pub use graph::*; -pub mod criteria; +mod criteria; pub use criteria::*; -pub mod batched; +mod batched; pub use batched::*; -pub mod fn_sys; +mod fn_sys; pub use fn_sys::*; /// A system that does not mutate the world diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 1e06f71..4472bb8 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -1,9 +1,10 @@ use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull}; -use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, View, ViewIter, ViewOne}, resource::ResourceData, ComponentInfo, DynTypeId, Entities, Entity, ResourceObject, Tick, TickTracker}; +use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewState, 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 +/// +/// The Archetype uses this as the index in the component columns #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct ArchetypeEntityId(pub u64); @@ -207,9 +208,20 @@ impl World { } /// View into the world for a set of entities that satisfy the queries. - pub fn view_iter(&self) -> ViewIter { + pub fn view(&self) -> ViewState { + self.filtered_view::() + } + + /// View into the world for a set of entities that satisfy the query and the filter. + pub fn filtered_view(&self) -> ViewState { let archetypes = self.archetypes.values().collect(); - let v = View::::new(self, T::Query::new(), archetypes); + ViewState::::new(self, Q::Query::new(), F::Query::new(), archetypes) + } + + /// View into the world for a set of entities that satisfy the queries. + pub fn view_iter(&self) -> ViewIter { + let archetypes = self.archetypes.values().collect(); + let v = ViewState::new(self, Q::Query::new(), (), archetypes); v.into_iter() } @@ -217,7 +229,7 @@ impl World { DynamicView::new(self) } - pub fn view_one(&self, entity: Entity) -> ViewOne { + pub fn view_one(&self, entity: Entity) -> ViewOne { ViewOne::new(self, entity.id, T::Query::new()) } From 7d90b1d38f2c4c370bb90c3fdfaec25f66826b8c Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Mar 2024 21:20:19 -0500 Subject: [PATCH 2/5] ecs: improve docs --- lyra-ecs/src/archetype.rs | 142 +++++++++++++++++---------- lyra-ecs/src/bundle.rs | 2 +- lyra-ecs/src/command.rs | 41 ++++++-- lyra-ecs/src/component_info.rs | 49 +-------- lyra-ecs/src/query/borrow.rs | 4 +- lyra-ecs/src/query/dynamic/mod.rs | 2 +- lyra-ecs/src/query/dynamic/view.rs | 8 +- lyra-ecs/src/query/entities.rs | 2 +- lyra-ecs/src/query/mod.rs | 8 +- lyra-ecs/src/query/resource.rs | 18 ++-- lyra-ecs/src/query/view.rs | 4 +- lyra-ecs/src/relation/mod.rs | 4 +- lyra-ecs/src/relation/relate_pair.rs | 2 +- lyra-ecs/src/relation/relates_to.rs | 2 +- lyra-ecs/src/system/criteria.rs | 2 +- lyra-ecs/src/tick.rs | 4 +- lyra-ecs/src/world.rs | 16 +-- 17 files changed, 165 insertions(+), 145 deletions(-) diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index 33e1d51..e6853ec 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -19,8 +19,8 @@ impl Drop for ComponentColumn { unsafe { // layout of current alloc - let layout = Layout::from_size_align_unchecked(self.info.layout.size * self.capacity, - self.info.layout.alignment); + let layout = Layout::from_size_align_unchecked(self.info.layout.size() * self.capacity, + self.info.layout.align()); dealloc(data, layout); } } @@ -42,7 +42,7 @@ impl ComponentColumn { } pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self { - let data = ComponentColumn::alloc(info.layout.into_layout().unwrap(), capacity); + let data = ComponentColumn::alloc(info.layout, capacity); Self { data: RefCell::new(data), @@ -64,8 +64,9 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); let data = data.deref_mut(); - let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * self.info.layout.size)); - ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), self.info.layout.size); + let size = self.info.layout.size(); + let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size)); + ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size); // check if a component spot is being set twice and that the entity's tick is // already stored @@ -86,7 +87,7 @@ impl ComponentColumn { Ref::map(data, |data| { let ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * self.info.layout.size)) + .add(entity_index * self.info.layout.size())) .cast(); &*ptr.as_ptr() }) @@ -105,7 +106,7 @@ impl ComponentColumn { let p = data.as_ptr() .cast::() - .add(entity_index * self.info.layout.size); + .add(entity_index * self.info.layout.size()); &mut *p } @@ -124,17 +125,17 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); - let mut new_ptr = Self::alloc(self.info.layout.into_layout().unwrap(), new_capacity); + let mut new_ptr = Self::alloc(self.info.layout, new_capacity); if self.len > 0 { - ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), self.len * self.info.layout.size); + ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), self.len * self.info.layout.size()); } // dont attempt to free if we weren't able to store anything anyway if self.capacity != 0 { let old_layout = Layout::from_size_align_unchecked( - self.info.layout.size.checked_mul(self.capacity).unwrap(), - self.info.layout.alignment + self.info.layout.size().checked_mul(self.capacity).unwrap(), + self.info.layout.align() ); mem::swap(data.deref_mut(), &mut new_ptr); @@ -153,15 +154,16 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); let data = data.deref_mut(); + let size = self.info.layout.size(); let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * self.info.layout.size)); + .add(entity_index * size)); let moved_index = if entity_index != self.len - 1 { let moved_index = self.len - 1; let mut new_comp_ptr = NonNull::new_unchecked(data.as_ptr() - .add(moved_index * self.info.layout.size)); + .add(moved_index * size)); - ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), self.info.layout.size); + ptr::copy_nonoverlapping(new_comp_ptr.as_ptr(), old_comp_ptr.as_ptr(), size); mem::swap(&mut old_comp_ptr, &mut new_comp_ptr); // new_comp_ptr is now the old ptr @@ -194,6 +196,7 @@ impl ComponentColumn { } } +/// An id of an Archetype #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] pub struct ArchetypeId(pub u64); @@ -207,20 +210,41 @@ impl ArchetypeId { } } +/// Stores a group of entities with matching components. +/// +/// An Archetype can be thought of as a table, with entities as the rows and the entity's +/// components as each column. This means you can have tightly packed components of entities and +/// quickly iterate through entities with the same components. #[derive(Clone, Default)] pub struct Archetype { - pub id: ArchetypeId, - pub(crate) entities: HashMap, + id: ArchetypeId, + /// The indexes of the entities in the archetype. + pub(crate) entity_ids: HashMap, /// map an Archetype entity id to an entity - pub(crate) ids_to_entity: HashMap, + //pub(crate) ids_to_entity: HashMap, + /// The entities in the Archetype. + /// + /// Can be used to map `ArchetypeEntityId` to an Entity since `ArchetypeEntityId` has + /// the index that the entity is stored at. + pub(crate) entities: Vec, pub(crate) columns: Vec, - pub capacity: usize, + capacity: usize, } /// The default capacity of the columns const DEFAULT_CAPACITY: usize = 32; impl Archetype { + /// Returns the id of the Archetype + pub fn id(&self) -> ArchetypeId { + self.id + } + + /// Returns the max amount of Entities that the Archetype can store without reallocating. + pub fn capacity(&self) -> usize { + self.capacity + } + pub fn from_bundle_info(new_id: ArchetypeId, bundle_info: Vec) -> Archetype { let columns = bundle_info.into_iter().map(|i| { unsafe { ComponentColumn::new(i, DEFAULT_CAPACITY) } @@ -228,8 +252,8 @@ impl Archetype { Archetype { id: new_id, - entities: HashMap::new(), - ids_to_entity: HashMap::new(), + entity_ids: HashMap::new(), + entities: Vec::new(), columns, capacity: DEFAULT_CAPACITY, } @@ -244,15 +268,16 @@ impl Archetype { where B: Bundle { - if self.capacity == self.entities.len() { + if self.capacity == self.entity_ids.len() { let new_cap = self.capacity * 2; self.grow_columns(new_cap); self.capacity = new_cap; } - let entity_index = ArchetypeEntityId(self.entities.len() as u64); - self.entities.insert(entity, entity_index); - self.ids_to_entity.insert(entity_index, entity); + debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced"); + let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64); + self.entity_ids.insert(entity, entity_index); + self.entities.push(entity); bundle.take(|data, type_id, _size| { let col = self.get_column_mut(type_id).unwrap(); @@ -263,39 +288,41 @@ impl Archetype { entity_index } - /// Removes an entity from the Archetype and frees its components. Returns the entity record that took its place in the component column. + /// Removes an entity from the Archetype and frees its components. Returns the entity record + /// that took its place in the component column. pub(crate) fn remove_entity(&mut self, entity: Entity, tick: &Tick) -> Option<(Entity, ArchetypeEntityId)> { - let entity_index = *self.entities.get(&entity) + let entity_index = *self.entity_ids.get(&entity) .expect("The entity is not in this Archetype!"); let mut removed_entity: Option<(Entity, ArchetypeEntityId)> = None; for c in self.columns.iter_mut() { let moved_entity = unsafe { c.remove_component(entity_index.0 as usize, tick) }; - // Make sure that the moved entity is the same as what was moved in other columns. - // If this is the first move, find the EntityId that points to the column index. - // If there wasn't a moved entity, make sure no other columns moved something. if let Some(res) = moved_entity { - if let Some((_, aid)) = removed_entity { - assert!(res as u64 == aid.0); // make sure all columns removed the same entity + // Make sure that the moved entity is the same as what was moved in other columns. + assert!(res as u64 == aid.0); } else { - let replaced_entity = self.entities.iter().find(|(_, a)| a.0 == res as u64) - .map(|(e, _a)| *e).expect("Failure to find entity for moved component!"); - removed_entity = Some((replaced_entity, ArchetypeEntityId(res as u64))); + // This is the first move, so find the EntityId that points to the column index. + let just_removed = self.entities[res]; + removed_entity = Some((just_removed, ArchetypeEntityId(res as u64))); } } else { + // If there wasn't a moved entity, make sure no other columns moved something. assert!(removed_entity.is_none()); } } // safe from the .expect at the start of this method. - self.entities.remove(&entity).unwrap(); - self.ids_to_entity.remove(&entity_index).unwrap(); + self.entity_ids.remove(&entity).unwrap(); + if self.entities.len() > 1 { + let len = self.entities.len(); + self.entities.swap(entity_index.0 as _, len - 1); + } + self.entities.pop().unwrap(); // now change the ArchetypeEntityId to be the index that the moved entity was moved into. removed_entity.map(|(e, _a)| (e, entity_index)) - } /// Returns a boolean indicating whether this archetype can store the TypeIds given @@ -312,12 +339,12 @@ impl Archetype { /// Returns a boolean indicating whether this archetype is empty or not. pub fn is_empty(&self) -> bool { - self.entities.is_empty() + self.entity_ids.is_empty() } /// Returns the amount of entities that are stored in the archetype. pub fn len(&self) -> usize { - self.entities.len() + self.entity_ids.len() } /// Grows columns in the archetype @@ -338,28 +365,32 @@ impl Archetype { self.capacity = new_capacity; } - pub fn get_column(&self, type_id: DynTypeId) -> Option<&ComponentColumn> { + /// Attempts to find the column storing components of `type_id` + pub fn get_column>(&self, type_id: I) -> Option<&ComponentColumn> { + let type_id = type_id.into(); self.columns.iter().find(|c| c.info.type_id == type_id) } /// Returns a mutable borrow to a component column for `type_id`. /// /// Note: This does not modify the tick for the column! - pub fn get_column_mut(&mut self, type_id: DynTypeId) -> Option<&mut ComponentColumn> { + pub fn get_column_mut>(&mut self, type_id: I) -> Option<&mut ComponentColumn> { + let type_id = type_id.into(); self.columns.iter_mut().find(|c| c.info.type_id == type_id) } /// Reserves a slot in the columns for an entity and returns the index of that reserved spot pub fn reserve_one(&mut self, entity: Entity) -> ArchetypeEntityId { - if self.capacity == self.entities.len() { + if self.capacity == self.entity_ids.len() { let new_cap = self.capacity * 2; self.grow_columns(new_cap); self.capacity = new_cap; } - let entity_index = ArchetypeEntityId(self.entities.len() as u64); - self.entities.insert(entity, entity_index); - self.ids_to_entity.insert(entity_index, entity); + debug_assert_eq!(self.entity_ids.len(), self.entities.len(), "Somehow the Archetype's entity storage got unsynced"); + let entity_index = ArchetypeEntityId(self.entity_ids.len() as u64); + self.entity_ids.insert(entity, entity_index); + self.entities.push(entity); for col in self.columns.iter_mut() { col.len += 1; @@ -371,8 +402,8 @@ impl Archetype { /// Moves the entity from this archetype into another one. /// /// # Safety - /// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`]. - /// It was done this way because I had some borrow check issues when writing [`World::insert`] + /// The entity IS NOT removed from the old archetype. You must manually call [`Archetype::remove_entity`](crate::Archetype). + /// It was done this way because I had some borrow check issues when writing [`World::insert`](crate::World) /// related to borrowing mutably from self more than once. /* pub fn move_into(&self, into_arch: &mut Archetype, entity: Entity, new_components: B) where @@ -405,16 +436,19 @@ impl Archetype { //self.remove_entity(entity); } */ - pub fn entities(&self) -> &HashMap { - &self.entities + /// Returns a borrow to the map used to find the column indices of the entity. + pub fn entity_indexes(&self) -> &HashMap { + &self.entity_ids } - pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option { - self.ids_to_entity.get(&id).cloned() + /// Returns the Entity that is stored at the column matching `id`. + pub fn entity_at_index(&self, id: ArchetypeEntityId) -> Option { + self.entities.get(id.0 as usize).cloned() } + /// Returns a boolean indicating if the Archetype is storing the entity. pub fn has_entity(&self, e: Entity) -> bool { - self.entities.contains_key(&e) + self.entity_ids.contains_key(&e) } } @@ -424,7 +458,7 @@ mod tests { use rand::Rng; - use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, MemoryLayout, Tick}; + use crate::{bundle::Bundle, tests::{Vec2, Vec3}, ComponentInfo, DynTypeId, DynamicBundle, Entity, EntityId, Tick}; use super::Archetype; @@ -604,7 +638,7 @@ mod tests { /// This test simulates an archetype that stores types that rust does not know about. #[test] fn dynamic_archetype() { - let layout = MemoryLayout::from(Layout::new::()); + let layout = Layout::new::(); let info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", layout); let infos = vec![info.clone()]; diff --git a/lyra-ecs/src/bundle.rs b/lyra-ecs/src/bundle.rs index 98be308..85bc4ae 100644 --- a/lyra-ecs/src/bundle.rs +++ b/lyra-ecs/src/bundle.rs @@ -152,7 +152,7 @@ impl Bundle for DynamicBundle { fn take(self, mut f: impl FnMut(NonNull, DynTypeId, usize)) { for (data, info) in self.bundle.iter() { - f(*data, info.type_id, info.layout.size); + f(*data, info.type_id, info.layout.size()); } } diff --git a/lyra-ecs/src/command.rs b/lyra-ecs/src/command.rs index 56d7aca..b7f6f91 100644 --- a/lyra-ecs/src/command.rs +++ b/lyra-ecs/src/command.rs @@ -2,30 +2,55 @@ use std::{any::Any, cell::RefMut, collections::VecDeque, ptr::{self, NonNull}}; use crate::{system::FnArgFetcher, Access, Bundle, Entities, Entity, World}; +/// A Command be used to delay mutation of the world until after this system is ran. pub trait Command: Any { fn as_any_boxed(self: Box) -> Box; - fn run(self, world: &mut World) -> anyhow::Result<()>; + /// Executes the command + fn run(self, world: &mut World); } impl Command for F where - F: FnOnce(&mut World) -> anyhow::Result<()> + 'static + F: FnOnce(&mut World) + 'static { fn as_any_boxed(self: Box) -> Box { self } - fn run(self, world: &mut World) -> anyhow::Result<()> { + fn run(self, world: &mut World) { self(world) } } -type RunCommand = unsafe fn(cmd: Box, world: &mut World) -> anyhow::Result<()>; +type RunCommand = unsafe fn(cmd: Box, world: &mut World); +/// Stores a queue of commands that will get executed after the system is ran. +/// +/// This struct can be inserted as a resource into the world, and the commands will be +/// executed by the [`GraphExecutor`](crate::system::GraphExecutor) after the system is executed. #[derive(Default)] pub struct CommandQueue(VecDeque<(RunCommand, Box)>); +/// Used in a system to queue up commands that will run right after this system. +/// +/// This can be used to delay the mutation of the world until after the system is ran. These +/// must be used if you're mutating the world inside a [`ViewState`](crate::query::ViewState). +/// +/// ```nobuild +/// fn particle_spawner_system( +/// commands: Commands, +/// view: ViewState<(&Campfire, &Transform)> +/// ) -> anyhow::Result<()> { +/// for (campfire, pos) in view.iter() { +/// // If you do not use commands to spawn this, the next iteration +/// // of the view will cause a segfault. +/// commands.spawn((pos, Particle::new(/* ... */))); +/// } +/// +/// Ok(()) +/// } +/// ``` pub struct Commands<'a, 'b> { queue: &'b mut CommandQueue, entities: &'a mut Entities, @@ -48,20 +73,18 @@ impl<'a, 'b> Commands<'a, 'b> { .downcast::() .unwrap(); - cmd.run(world)?; - - Ok(()) + cmd.run(world); }; self.queue.0.push_back((run_fn, cmd)); } + /// Spawn an entity into the World. See [`World::spawn`] pub fn spawn(&mut self, bundle: B) -> Entity { let e = self.entities.reserve(); self.add(move |world: &mut World| { world.spawn_into(e, bundle); - Ok(()) }); e @@ -71,7 +94,7 @@ impl<'a, 'b> Commands<'a, 'b> { 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)?; + cmd_fn(cmd_ptr, world); } } diff --git a/lyra-ecs/src/component_info.rs b/lyra-ecs/src/component_info.rs index 7437e16..a819f72 100644 --- a/lyra-ecs/src/component_info.rs +++ b/lyra-ecs/src/component_info.rs @@ -1,41 +1,4 @@ -use std::{any::TypeId, alloc::{Layout, LayoutError}}; - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct MemoryLayout { - pub size: usize, - pub alignment: usize -} - -impl TryInto for MemoryLayout { - type Error = LayoutError; - - fn try_into(self) -> Result { - Layout::from_size_align(self.size, self.alignment) - } -} - -impl From for MemoryLayout { - fn from(value: Layout) -> Self { - Self { - size: value.size(), - alignment: value.align(), - } - } -} - -impl MemoryLayout { - pub fn new(size: usize, alignment: usize) -> Self { - MemoryLayout { - size, - alignment - } - } - - /// Converts self into a Layout. - pub fn into_layout(self) -> Result { - Layout::from_size_align(self.size, self.alignment) - } -} +use std::{any::TypeId, alloc::Layout}; /// A dynamic type id. Supports types that are not known to Rust. #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)] @@ -88,31 +51,29 @@ impl DynTypeId { } } +/// Some information about a component. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ComponentInfo { pub type_id: DynTypeId, - //pub name: String, - pub layout: MemoryLayout, + pub(crate) layout: Layout, } impl ComponentInfo { pub fn new() -> Self { Self { type_id: TypeId::of::().into(), - //name: type_name::().to_string(), - layout: MemoryLayout::from(Layout::new::()), + layout: Layout::new::(), } } /// Create ComponentInfo from a type that is not known to rust - pub fn new_unknown(type_id: D, name: &str, layout: MemoryLayout) -> Self + pub fn new_unknown(type_id: D, name: &str, layout: Layout) -> Self where D: Into, { let _ = name; // would be used at some point Self { type_id: type_id.into(), - //name: name.to_string(), layout, } } diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index a3e4ec6..f1cc5de 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -91,7 +91,7 @@ where FetchBorrow { col, - size: col.info.layout.size, + size: col.info.layout.size(), _phantom: PhantomData, } } @@ -194,7 +194,7 @@ where unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) .expect("You ignored 'can_visit_archetype'!"); - let layout_size = col.info.layout.size; + let layout_size = col.info.layout.size(); let col = NonNull::from(col); // TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated. diff --git a/lyra-ecs/src/query/dynamic/mod.rs b/lyra-ecs/src/query/dynamic/mod.rs index 221870c..9ef43fc 100644 --- a/lyra-ecs/src/query/dynamic/mod.rs +++ b/lyra-ecs/src/query/dynamic/mod.rs @@ -38,7 +38,7 @@ impl<'a> Fetch<'a> for FetchDynamicType<'a> { unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item { let ptr = self.col.borrow_ptr(); let ptr = NonNull::new_unchecked(ptr.as_ptr() - .add(entity.0 as usize * self.info.layout.size)); + .add(entity.0 as usize * self.info.layout.size())); DynamicType { info: self.info, diff --git a/lyra-ecs/src/query/dynamic/view.rs b/lyra-ecs/src/query/dynamic/view.rs index fb88b85..e2cbd8c 100644 --- a/lyra-ecs/src/query/dynamic/view.rs +++ b/lyra-ecs/src/query/dynamic/view.rs @@ -83,7 +83,7 @@ impl<'a> Iterator for DynamicViewIter<'a> { self.next_archetype += 1; let arch = unsafe { self.archetypes.get_unchecked(arch_id) }; - if arch.entities.is_empty() { + if arch.entity_ids.is_empty() { continue; } @@ -94,7 +94,7 @@ impl<'a> Iterator for DynamicViewIter<'a> { self.fetchers = self.queries.iter() .map(|q| unsafe { q.fetch(self.world, ArchetypeId(arch_id as u64), arch) } ) .collect(); - self.component_indices = 0..arch.entities.len() as u64; + self.component_indices = 0..arch.entity_ids.len() as u64; } } } @@ -104,13 +104,13 @@ impl<'a> Iterator for DynamicViewIter<'a> { mod tests { use std::{alloc::Layout, ptr::NonNull}; - use crate::{world::World, MemoryLayout, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType}; + use crate::{world::World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType}; use super::DynamicView; #[test] fn single_dynamic_view() { - let comp_layout = MemoryLayout::from(Layout::new::()); + let comp_layout = Layout::new::(); let comp_info = ComponentInfo::new_unknown(DynTypeId::Unknown(100), "u32", comp_layout); let mut dynamic_bundle = DynamicBundle::default(); diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs index a75c33c..95f2137 100644 --- a/lyra-ecs/src/query/entities.rs +++ b/lyra-ecs/src/query/entities.rs @@ -41,7 +41,7 @@ impl Query for Entities { unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let _ = tick; // ignore unused warnings EntitiesFetch { - entities: archetype.entities.keys().cloned().collect::>(), + entities: archetype.entity_ids.keys().cloned().collect::>(), } } } diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index cc7d699..02c5e62 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -94,7 +94,7 @@ impl<'a> Fetch<'a> for () { unreachable!() } - unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item { + unsafe fn get_item(&mut self, _: ArchetypeEntityId) -> Self::Item { () } } @@ -104,15 +104,17 @@ impl Query for () { type Fetch<'a> = (); + const ALWAYS_FETCHES: bool = true; + fn new() -> Self { () } - fn can_visit_archetype(&self, archetype: &Archetype) -> bool { + fn can_visit_archetype(&self, _: &Archetype) -> bool { true } - unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a Archetype, tick: Tick) -> Self::Fetch<'a> { + unsafe fn fetch<'a>(&self, _: &'a World, _: &'a Archetype, _: Tick) -> Self::Fetch<'a> { () } } diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs index 377f5de..1f20667 100644 --- a/lyra-ecs/src/query/resource.rs +++ b/lyra-ecs/src/query/resource.rs @@ -192,9 +192,7 @@ impl<'a, T: ResourceObject> AsQuery for ResMut<'a, T> { mod tests { use std::ops::{Deref, DerefMut}; - use crate::{world::World, tests::{Vec2, Vec3}, query::QueryResourceMut}; - - use super::QueryResource; + use crate::{query::{Res, ResMut}, tests::{Vec2, Vec3}, world::World}; struct SomeCounter(u32); @@ -207,7 +205,7 @@ mod tests { println!("Added resource"); } - let mut res_iter = world.view_iter::>(); + let mut res_iter = world.view_iter::>(); let res = res_iter.next().unwrap(); let res = res.deref(); assert_eq!(res.0, 0); @@ -228,16 +226,16 @@ mod tests { println!("Added resource"); } - let i = world.view_iter::<(QueryResource, &Vec2)>(); + let i = world.view_iter::<(Res, &Vec2)>(); assert_eq!(i.count(), 3); - let i = world.view_iter::<(&Vec2, QueryResource)>(); + let i = world.view_iter::<(&Vec2, Res)>(); assert_eq!(i.count(), 3); - for (res, e) in world.view_iter::<(QueryResource, &Vec2)>() { + for (res, e) in world.view_iter::<(Res, &Vec2)>() { println!("Got res {}! and entity at {:?}", res.deref().0, e); } - let i = world.view_iter::>(); + let i = world.view_iter::>(); assert_eq!(i.count(), 1); } @@ -251,14 +249,14 @@ mod tests { } { - let mut resmut_iter = world.view_iter::>(); + let mut resmut_iter = world.view_iter::>(); let mut resmut = resmut_iter.next().unwrap(); let resmut = resmut.deref_mut(); assert_eq!(resmut.0, 0); resmut.0 += 20; } - let mut res_iter = world.view_iter::>(); + let mut res_iter = world.view_iter::>(); let res = res_iter.next().unwrap(); let res = res.deref(); assert_eq!(res.0, 20); diff --git a/lyra-ecs/src/query/view.rs b/lyra-ecs/src/query/view.rs index 4dc3a3c..93c67a0 100644 --- a/lyra-ecs/src/query/view.rs +++ b/lyra-ecs/src/query/view.rs @@ -122,7 +122,7 @@ where self.next_archetype += 1; let arch = unsafe { self.archetypes.get_unchecked(arch_id) }; - if arch.entities.is_empty() { + if arch.entity_ids.is_empty() { continue; } @@ -131,7 +131,7 @@ where self.fetcher = Some(self.query.fetch(self.world, arch, self.tick)); self.filter_fetcher = Some(self.filter.fetch(self.world, arch, self.tick)); } - self.component_indices = 0..arch.entities.len() as u64; + self.component_indices = 0..arch.entity_ids.len() as u64; } } } diff --git a/lyra-ecs/src/relation/mod.rs b/lyra-ecs/src/relation/mod.rs index 1b693b0..1acb0ce 100644 --- a/lyra-ecs/src/relation/mod.rs +++ b/lyra-ecs/src/relation/mod.rs @@ -17,6 +17,7 @@ mod relate_pair; #[doc(hidden)] pub use relate_pair::*; +#[allow(unused_variables)] pub trait Relation: 'static { /// called when a relation of this type is set on a target fn relation_add(&self, origin: Entity, target: Entity) { } @@ -40,6 +41,7 @@ pub struct RelationOriginComponent { /// origin of the relation -- the entity that targets this entity. #[derive(Component)] pub struct RelationTargetComponent { + #[allow(dead_code)] origin: Entity, _marker: PhantomData, } @@ -47,7 +49,7 @@ pub struct RelationTargetComponent { impl World { /// Creates a relation between two entities. /// - /// ```compile_fail + /// ```nobuild /// struct ChildOf; /// /// impl Relation for ChildOf { /* snip */ }; diff --git a/lyra-ecs/src/relation/relate_pair.rs b/lyra-ecs/src/relation/relate_pair.rs index ab58b3c..1ab8f96 100644 --- a/lyra-ecs/src/relation/relate_pair.rs +++ b/lyra-ecs/src/relation/relate_pair.rs @@ -23,7 +23,7 @@ where unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { let comp: Ref> = self.col.get(entity.0 as usize); let rel_target = comp.target; - let rel_origin = self.arch.entity_of_index(entity).unwrap(); + let rel_origin = self.arch.entity_at_index(entity).unwrap(); let comp = Ref::map(comp, |r| &r.relation); (rel_origin, comp, rel_target) diff --git a/lyra-ecs/src/relation/relates_to.rs b/lyra-ecs/src/relation/relates_to.rs index 7cd6657..e6588f6 100644 --- a/lyra-ecs/src/relation/relates_to.rs +++ b/lyra-ecs/src/relation/relates_to.rs @@ -93,7 +93,7 @@ where /// This can be combined with Entities, to query all entities that have a relation targeting /// the target entity. /// -/// ```compile_fail +/// ```nobuild /// let v = world.view::() /// .relates_to::(b); /// diff --git a/lyra-ecs/src/system/criteria.rs b/lyra-ecs/src/system/criteria.rs index e33e5ae..e7dcf07 100644 --- a/lyra-ecs/src/system/criteria.rs +++ b/lyra-ecs/src/system/criteria.rs @@ -18,7 +18,7 @@ pub enum CriteriaSchedule { NoAndLoop, } -/// A Criteria can be used to conditionally execute [`BatchedSystems`](super::BatchedSystems). +/// A Criteria can be used to conditionally execute [`BatchedSystem`](super::BatchedSystem). pub trait Criteria { /// Checks if this Criteria can run, and if it should check it again. /// diff --git a/lyra-ecs/src/tick.rs b/lyra-ecs/src/tick.rs index 02dd4c7..bf3c916 100644 --- a/lyra-ecs/src/tick.rs +++ b/lyra-ecs/src/tick.rs @@ -1,9 +1,9 @@ use std::sync::atomic::{AtomicU64, Ordering}; -/// TickTracker is used for tracking changes of [`Component`]s and entities. +/// TickTracker is used for tracking changes of [`Component`](crate::Component)s and entities. /// /// TickTracker stores an [`AtomicU64`], making all operations on `TickTracker`, atomic as well. -/// Note that [`Tick::Clone`] only clones the inner value of atomic, and not the atomic itself. +/// Note that [`Tick::clone`] only clones the inner value of atomic, and not the atomic itself. #[derive(Debug, Default)] pub struct TickTracker { tick: AtomicU64, diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index 4472bb8..bc38646 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -54,7 +54,7 @@ impl World { } /// Spawn the components into a reserved entity. Only do this with entities that - /// were 'reserved' with [`World::reserve`] + /// were reserved with [`World::reserve_entity`]. /// /// # Safety /// Do not use this method with an entity that is currently alive, it WILL cause undefined behavior. @@ -73,14 +73,14 @@ impl World { if let Some(archetype) = archetype { // make at just one check to ensure you're not spawning twice - debug_assert!(!archetype.entities.contains_key(&entity), + debug_assert!(!archetype.entity_ids.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 { - id: archetype.id, + id: archetype.id(), index: arche_idx, }; @@ -158,7 +158,7 @@ impl World { for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) { unsafe { let ptr = NonNull::new_unchecked(col_ptr.as_ptr() - .add(res_index.0 as usize * col_info.layout.size)); + .add(res_index.0 as usize * col_info.layout.size())); let col = arch.get_column_mut(col_type).unwrap(); col.set_at(res_index.0 as _, ptr, tick); } @@ -170,10 +170,10 @@ impl World { col.len += 1; }); - arch.entities.insert(entity, res_index); + arch.entity_ids.insert(entity, res_index); let new_record = Record { - id: arch.id, + id: arch.id(), index: res_index, }; self.entities.insert_entity_record(entity, new_record); @@ -264,7 +264,7 @@ impl World { /// Gets a resource from the World. /// - /// Will panic if the resource is not in the world. See [`try_get_resource`] for + /// Will panic if the resource is not in the world. See [`World::try_get_resource`] for /// a function that returns an option. pub fn get_resource(&self) -> Ref { self.resources.get(&TypeId::of::()) @@ -287,7 +287,7 @@ impl World { /// Gets a mutable borrow of a resource from the World. /// - /// Will panic if the resource is not in the world. See [`try_get_resource_mut`] for + /// Will panic if the resource is not in the world. See [`World::try_get_resource_mut`] for /// a function that returns an option. pub fn get_resource_mut(&self) -> RefMut { self.resources.get(&TypeId::of::()) From 6210778e9d8c942206b740e2f0764b633d920830 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Mar 2024 21:34:20 -0500 Subject: [PATCH 3/5] ecs: some code cleanup --- lyra-ecs/src/archetype.rs | 51 +++++++++++++++------------- lyra-ecs/src/bundle.rs | 4 +-- lyra-ecs/src/component_info.rs | 12 +++++-- lyra-ecs/src/query/borrow.rs | 40 ++++++---------------- lyra-ecs/src/query/dynamic/mod.rs | 10 +++--- lyra-ecs/src/query/dynamic/view.rs | 4 +-- lyra-ecs/src/query/entities.rs | 2 +- lyra-ecs/src/query/mod.rs | 2 +- lyra-ecs/src/query/resource.rs | 4 +-- lyra-ecs/src/query/tick.rs | 6 ++-- lyra-ecs/src/query/tuple.rs | 4 +-- lyra-ecs/src/relation/relate_pair.rs | 5 ++- lyra-ecs/src/relation/relates_to.rs | 6 ++-- lyra-ecs/src/system/batched.rs | 2 +- lyra-ecs/src/system/criteria.rs | 2 +- lyra-ecs/src/system/fn_sys.rs | 4 +-- lyra-ecs/src/system/graph.rs | 4 +-- lyra-ecs/src/system/mod.rs | 2 +- lyra-ecs/src/world.rs | 4 +-- 19 files changed, 78 insertions(+), 90 deletions(-) diff --git a/lyra-ecs/src/archetype.rs b/lyra-ecs/src/archetype.rs index e6853ec..d9577db 100644 --- a/lyra-ecs/src/archetype.rs +++ b/lyra-ecs/src/archetype.rs @@ -18,9 +18,8 @@ impl Drop for ComponentColumn { let data = data.as_ptr(); unsafe { - // layout of current alloc - let layout = Layout::from_size_align_unchecked(self.info.layout.size() * self.capacity, - self.info.layout.align()); + // TODO: trigger drop on the components + let layout = self.info.layout(); dealloc(data, layout); } } @@ -42,7 +41,7 @@ impl ComponentColumn { } pub unsafe fn new(info: ComponentInfo, capacity: usize) -> Self { - let data = ComponentColumn::alloc(info.layout, capacity); + let data = ComponentColumn::alloc(info.layout(), capacity); Self { data: RefCell::new(data), @@ -64,7 +63,7 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); let data = data.deref_mut(); - let size = self.info.layout.size(); + let size = self.info.layout().size(); let dest = NonNull::new_unchecked(data.as_ptr().add(entity_index * size)); ptr::copy_nonoverlapping(comp_src.as_ptr(), dest.as_ptr(), size); @@ -87,27 +86,28 @@ impl ComponentColumn { Ref::map(data, |data| { let ptr = NonNull::new_unchecked(data.as_ptr() - .add(entity_index * self.info.layout.size())) + .add(entity_index * self.info.layout().size())) .cast(); &*ptr.as_ptr() }) } - /// Get a component at an entities index. + /// Get a mutable borrow to the component at an entities index, ticking the entity. /// /// # Safety /// /// This column must have the entity. - pub unsafe fn get_mut(&mut self, entity_index: usize, tick: &Tick) -> &mut T { + pub unsafe fn get_mut(&mut self, entity_index: usize, tick: &Tick) -> RefMut { self.entity_ticks[entity_index].tick_to(tick); - let mut data = self.data.borrow_mut(); - let data = data.deref_mut(); + let data = self.data.borrow_mut(); - let p = data.as_ptr() - .cast::() - .add(entity_index * self.info.layout.size()); - &mut *p + RefMut::map(data, |data| { + let ptr = NonNull::new_unchecked(data.as_ptr() + .add(entity_index * self.info.layout().size())) + .cast(); + &mut *ptr.as_ptr() + }) } /// Grow the column to fit `new_capacity` amount of components. @@ -125,17 +125,19 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); - let mut new_ptr = Self::alloc(self.info.layout, new_capacity); + let layout = self.info.layout(); + let mut new_ptr = Self::alloc(layout, new_capacity); if self.len > 0 { - ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), self.len * self.info.layout.size()); + ptr::copy_nonoverlapping(data.as_ptr(), new_ptr.as_ptr(), self.len * layout.size()); } // dont attempt to free if we weren't able to store anything anyway if self.capacity != 0 { + // create a layout with the same alignment, but expand the size of the buffer. let old_layout = Layout::from_size_align_unchecked( - self.info.layout.size().checked_mul(self.capacity).unwrap(), - self.info.layout.align() + layout.size().checked_mul(self.capacity).unwrap(), + layout.align() ); mem::swap(data.deref_mut(), &mut new_ptr); @@ -154,7 +156,7 @@ impl ComponentColumn { let mut data = self.data.borrow_mut(); let data = data.deref_mut(); - let size = self.info.layout.size(); + let size = self.info.layout().size(); let mut old_comp_ptr = NonNull::new_unchecked(data.as_ptr() .add(entity_index * size)); @@ -328,13 +330,14 @@ impl Archetype { /// Returns a boolean indicating whether this archetype can store the TypeIds given pub fn is_archetype_for(&self, types: &Vec) -> bool { if types.len() == self.columns.len() { - self.columns.iter().all(|c| types.contains(&c.info.type_id)) + self.columns.iter().all(|c| types.contains(&c.info.type_id())) } else { false } } /// Returns a boolean indicating whether this archetype has a column for `comp_type` - pub fn has_column(&self, comp_type: DynTypeId) -> bool { - self.columns.iter().any(|c| comp_type == c.info.type_id) + pub fn has_column>(&self, comp_type: I) -> bool { + let comp_type = comp_type.into(); + self.columns.iter().any(|c| comp_type == c.info.type_id()) } /// Returns a boolean indicating whether this archetype is empty or not. @@ -368,7 +371,7 @@ impl Archetype { /// Attempts to find the column storing components of `type_id` pub fn get_column>(&self, type_id: I) -> Option<&ComponentColumn> { let type_id = type_id.into(); - self.columns.iter().find(|c| c.info.type_id == type_id) + self.columns.iter().find(|c| c.info.type_id() == type_id) } /// Returns a mutable borrow to a component column for `type_id`. @@ -376,7 +379,7 @@ impl Archetype { /// Note: This does not modify the tick for the column! pub fn get_column_mut>(&mut self, type_id: I) -> Option<&mut ComponentColumn> { let type_id = type_id.into(); - self.columns.iter_mut().find(|c| c.info.type_id == type_id) + self.columns.iter_mut().find(|c| c.info.type_id() == type_id) } /// Reserves a slot in the columns for an entity and returns the index of that reserved spot diff --git a/lyra-ecs/src/bundle.rs b/lyra-ecs/src/bundle.rs index 85bc4ae..31d1952 100644 --- a/lyra-ecs/src/bundle.rs +++ b/lyra-ecs/src/bundle.rs @@ -143,7 +143,7 @@ impl DynamicBundle { impl Bundle for DynamicBundle { fn type_ids(&self) -> Vec { - self.bundle.iter().map(|b| b.1.type_id).collect() + self.bundle.iter().map(|b| b.1.type_id()).collect() } fn info(&self) -> Vec { @@ -152,7 +152,7 @@ impl Bundle for DynamicBundle { fn take(self, mut f: impl FnMut(NonNull, DynTypeId, usize)) { for (data, info) in self.bundle.iter() { - f(*data, info.type_id, info.layout.size()); + f(*data, info.type_id(), info.layout().size()); } } diff --git a/lyra-ecs/src/component_info.rs b/lyra-ecs/src/component_info.rs index a819f72..e363494 100644 --- a/lyra-ecs/src/component_info.rs +++ b/lyra-ecs/src/component_info.rs @@ -54,8 +54,8 @@ impl DynTypeId { /// Some information about a component. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct ComponentInfo { - pub type_id: DynTypeId, - pub(crate) layout: Layout, + type_id: DynTypeId, + layout: Layout, } impl ComponentInfo { @@ -77,4 +77,12 @@ impl ComponentInfo { layout, } } + + pub fn type_id(&self) -> DynTypeId { + self.type_id + } + + pub fn layout(&self) -> Layout { + self.layout + } } \ No newline at end of file diff --git a/lyra-ecs/src/query/borrow.rs b/lyra-ecs/src/query/borrow.rs index f1cc5de..d5a1493 100644 --- a/lyra-ecs/src/query/borrow.rs +++ b/lyra-ecs/src/query/borrow.rs @@ -1,13 +1,12 @@ use std::{marker::PhantomData, ptr::NonNull, cell::{Ref, RefMut}}; -use crate::{world::World, ComponentColumn, DynTypeId, Tick, Component}; +use crate::{World, ComponentColumn, DynTypeId, Tick, Component}; use super::{Fetch, Query, AsQuery}; /// Fetcher for borrowing components from archetypes. pub struct FetchBorrow<'a, T> { col: &'a ComponentColumn, - size: usize, _phantom: PhantomData<&'a T> } @@ -22,13 +21,7 @@ where } unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { - let ptr = self.col.borrow_ptr(); - Ref::map(ptr, |ptr| { - let ptr = NonNull::new_unchecked(ptr.as_ptr() - .add(entity.0 as usize * self.size)) - .cast(); - &*ptr.as_ptr() - }) + self.col.get(entity.0 as _) } } @@ -81,17 +74,16 @@ where } fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { - archetype.columns.iter().any(|c| c.info.type_id == self.type_id) + archetype.has_column(self.type_id) } unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let _ = tick; - let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) + let col = archetype.get_column(self.type_id) .expect("You ignored 'can_visit_archetype'!"); FetchBorrow { col, - size: col.info.layout.size(), _phantom: PhantomData, } } @@ -108,7 +100,6 @@ impl AsQuery for &T { /// A fetcher for mutably borrowing components from archetypes. pub struct FetchBorrowMut<'a, T> { col: NonNull, - size: usize, tick: Tick, _phantom: PhantomData<&'a T> } @@ -125,15 +116,7 @@ where unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item { let col = unsafe { self.col.as_mut() }; - col.entity_ticks[entity.0 as usize] = self.tick; - let ptr = col.borrow_mut_ptr(); - - RefMut::map(ptr, |ptr| { - let ptr = NonNull::new_unchecked(ptr.as_ptr() - .add(entity.0 as usize * self.size)) - .cast(); - &mut *ptr.as_ptr() - }) + col.get_mut(entity.0 as _, &self.tick) } } @@ -188,13 +171,12 @@ where } fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { - archetype.columns.iter().any(|c| c.info.type_id == self.type_id) + archetype.has_column(self.type_id) } unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { - let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) + let col = archetype.get_column(self.type_id) .expect("You ignored 'can_visit_archetype'!"); - let layout_size = col.info.layout.size(); let col = NonNull::from(col); // TODO: find a way to get the component column mutable with a borrowed archetype so its tick can be updated. @@ -202,7 +184,6 @@ where FetchBorrowMut { col, - size: layout_size, tick, _phantom: PhantomData, } @@ -219,9 +200,9 @@ impl AsQuery for &mut T { #[cfg(test)] mod tests { - use std::{mem::size_of, marker::PhantomData, ptr::NonNull}; + use std::{marker::PhantomData, ptr::NonNull}; - use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, ViewState}, tests::Vec2, world::World, DynTypeId, Entity, EntityId, Tick}; + use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{Fetch, ViewState}, tests::Vec2, World, DynTypeId, Entity, EntityId, Tick}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut}; @@ -298,12 +279,11 @@ mod tests { }, (Vec2::rand(),), &Tick::default()); } - let col = a.columns.iter().find(|c| c.info.type_id == DynTypeId::of::()).unwrap(); + let col = a.get_column(DynTypeId::of::()).unwrap(); let mut bmut = FetchBorrowMut:: { col: NonNull::from(col), tick: Tick::default(), - size: size_of::(), _phantom: PhantomData, }; let item = unsafe { bmut.get_item(crate::ArchetypeEntityId(0)) }; diff --git a/lyra-ecs/src/query/dynamic/mod.rs b/lyra-ecs/src/query/dynamic/mod.rs index 9ef43fc..7d0a343 100644 --- a/lyra-ecs/src/query/dynamic/mod.rs +++ b/lyra-ecs/src/query/dynamic/mod.rs @@ -1,6 +1,6 @@ use std::ptr::NonNull; -use crate::{world::World, ComponentColumn, ComponentInfo}; +use crate::{World, ComponentColumn, ComponentInfo}; mod view; pub use view::*; @@ -15,7 +15,7 @@ pub struct DynamicType { impl DynamicType { pub fn is(&self) -> bool { - self.info.type_id.is::() + self.info.type_id().is::() } } @@ -38,7 +38,7 @@ impl<'a> Fetch<'a> for FetchDynamicType<'a> { unsafe fn get_item(&mut self, entity: crate::ArchetypeEntityId) -> Self::Item { let ptr = self.col.borrow_ptr(); let ptr = NonNull::new_unchecked(ptr.as_ptr() - .add(entity.0 as usize * self.info.layout.size())); + .add(entity.0 as usize * self.info.layout().size())); DynamicType { info: self.info, @@ -63,11 +63,11 @@ impl QueryDynamicType { } pub fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { - archetype.has_column(self.info.type_id) + archetype.has_column(self.info.type_id()) } pub unsafe fn fetch<'a>(&self, _world: &'a World, _arch_id: crate::archetype::ArchetypeId, archetype: &'a crate::archetype::Archetype) -> FetchDynamicType<'a> { - let col = archetype.columns.iter().find(|c| c.info.type_id == self.info.type_id) + let col = archetype.get_column(self.info.type_id()) .expect("You ignored 'can_visit_archetype'!"); FetchDynamicType { diff --git a/lyra-ecs/src/query/dynamic/view.rs b/lyra-ecs/src/query/dynamic/view.rs index e2cbd8c..107a429 100644 --- a/lyra-ecs/src/query/dynamic/view.rs +++ b/lyra-ecs/src/query/dynamic/view.rs @@ -1,6 +1,6 @@ use std::ops::Range; -use crate::{world::World, Archetype, ArchetypeEntityId, ArchetypeId, query::Fetch}; +use crate::{World, Archetype, ArchetypeEntityId, ArchetypeId, query::Fetch}; use super::{QueryDynamicType, FetchDynamicType, DynamicType}; @@ -104,7 +104,7 @@ impl<'a> Iterator for DynamicViewIter<'a> { mod tests { use std::{alloc::Layout, ptr::NonNull}; - use crate::{world::World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType}; + use crate::{World, ComponentInfo, DynTypeId, DynamicBundle, query::dynamic::QueryDynamicType}; use super::DynamicView; diff --git a/lyra-ecs/src/query/entities.rs b/lyra-ecs/src/query/entities.rs index 95f2137..9f2cef6 100644 --- a/lyra-ecs/src/query/entities.rs +++ b/lyra-ecs/src/query/entities.rs @@ -1,4 +1,4 @@ -use crate::{archetype::Archetype, world::World, Entity}; +use crate::{archetype::Archetype, World, Entity}; use super::{Fetch, Query, AsQuery}; diff --git a/lyra-ecs/src/query/mod.rs b/lyra-ecs/src/query/mod.rs index 02c5e62..a71e5c8 100644 --- a/lyra-ecs/src/query/mod.rs +++ b/lyra-ecs/src/query/mod.rs @@ -125,7 +125,7 @@ impl AsQuery for () { #[cfg(test)] mod tests { - use crate::{world::World, archetype::Archetype, tests::Vec2}; + use crate::{World, archetype::Archetype, tests::Vec2}; use super::{ViewState, Entities}; diff --git a/lyra-ecs/src/query/resource.rs b/lyra-ecs/src/query/resource.rs index 1f20667..328f2b2 100644 --- a/lyra-ecs/src/query/resource.rs +++ b/lyra-ecs/src/query/resource.rs @@ -1,6 +1,6 @@ use std::{marker::PhantomData, cell::{Ref, RefMut}}; -use crate::{world::World, resource::ResourceObject}; +use crate::{World, resource::ResourceObject}; use super::{Query, Fetch, AsQuery}; @@ -192,7 +192,7 @@ impl<'a, T: ResourceObject> AsQuery for ResMut<'a, T> { mod tests { use std::ops::{Deref, DerefMut}; - use crate::{query::{Res, ResMut}, tests::{Vec2, Vec3}, world::World}; + use crate::{query::{Res, ResMut}, tests::{Vec2, Vec3}, World}; struct SomeCounter(u32); diff --git a/lyra-ecs/src/query/tick.rs b/lyra-ecs/src/query/tick.rs index f2f4376..b942c79 100644 --- a/lyra-ecs/src/query/tick.rs +++ b/lyra-ecs/src/query/tick.rs @@ -1,6 +1,6 @@ use std::marker::PhantomData; -use crate::{ComponentColumn, Tick, DynTypeId, world::World}; +use crate::{ComponentColumn, Tick, DynTypeId, World}; use super::{Query, Fetch, AsQuery}; @@ -90,11 +90,11 @@ where } fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { - archetype.columns.iter().any(|c| c.info.type_id == self.type_id) + archetype.has_column(self.type_id) } unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, _tick: crate::Tick) -> Self::Fetch<'a> { - let col = archetype.columns.iter().find(|c| c.info.type_id == self.type_id) + let col = archetype.get_column(self.type_id) .expect("You ignored 'can_visit_archetype'!"); FetchTickOf { diff --git a/lyra-ecs/src/query/tuple.rs b/lyra-ecs/src/query/tuple.rs index 13ab692..2d423cb 100644 --- a/lyra-ecs/src/query/tuple.rs +++ b/lyra-ecs/src/query/tuple.rs @@ -1,4 +1,4 @@ -use crate::world::World; +use crate::World; use super::{Query, Fetch, AsQuery}; @@ -175,7 +175,7 @@ impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5, Q6, Q7, Q8, Q9, Q10, Q11, Q12, Q13, Q14 #[cfg(test)] mod tests { - use crate::{world::World, tests::{Vec2, Vec3}}; + use crate::{World, tests::{Vec2, Vec3}}; #[test] fn tuple_queries() { diff --git a/lyra-ecs/src/relation/relate_pair.rs b/lyra-ecs/src/relation/relate_pair.rs index 1ab8f96..429554e 100644 --- a/lyra-ecs/src/relation/relate_pair.rs +++ b/lyra-ecs/src/relation/relate_pair.rs @@ -1,4 +1,4 @@ -use std::{any::TypeId, cell::Ref, marker::PhantomData}; +use std::{any::{Any, TypeId}, cell::Ref, marker::PhantomData}; use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World}; @@ -69,8 +69,7 @@ where unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let _ = tick; - let tyid = crate::DynTypeId::Rust(TypeId::of::>()); - let col = archetype.columns.iter().find(|c| c.info.type_id == tyid) + let col = archetype.get_column(self.type_id()) .expect("You ignored 'can_visit_archetype'!"); FetchRelatePair { diff --git a/lyra-ecs/src/relation/relates_to.rs b/lyra-ecs/src/relation/relates_to.rs index e6588f6..ad04311 100644 --- a/lyra-ecs/src/relation/relates_to.rs +++ b/lyra-ecs/src/relation/relates_to.rs @@ -70,14 +70,12 @@ where } fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool { - let tyid = crate::DynTypeId::Rust(TypeId::of::>()); - archetype.has_column(tyid) + archetype.has_column(TypeId::of::>()) } unsafe fn fetch<'a>(&self, _world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> { let _ = tick; - let tyid = crate::DynTypeId::Rust(TypeId::of::>()); - let col = archetype.columns.iter().find(|c| c.info.type_id == tyid) + let col = archetype.get_column(TypeId::of::>()) .expect("You ignored 'can_visit_archetype'!"); FetchRelatesTo { diff --git a/lyra-ecs/src/system/batched.rs b/lyra-ecs/src/system/batched.rs index d924d0f..73a945c 100644 --- a/lyra-ecs/src/system/batched.rs +++ b/lyra-ecs/src/system/batched.rs @@ -1,4 +1,4 @@ -use lyra_ecs::world::World; +use lyra_ecs::World; use crate::Access; diff --git a/lyra-ecs/src/system/criteria.rs b/lyra-ecs/src/system/criteria.rs index e7dcf07..d0a2868 100644 --- a/lyra-ecs/src/system/criteria.rs +++ b/lyra-ecs/src/system/criteria.rs @@ -1,6 +1,6 @@ use std::ptr::NonNull; -use lyra_ecs::world::World; +use lyra_ecs::World; /// An enum that is used to control if the Criteria was met or not. #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] diff --git a/lyra-ecs/src/system/fn_sys.rs b/lyra-ecs/src/system/fn_sys.rs index b9079fd..c768161 100644 --- a/lyra-ecs/src/system/fn_sys.rs +++ b/lyra-ecs/src/system/fn_sys.rs @@ -1,7 +1,7 @@ use std::{any::Any, marker::PhantomData, ptr::NonNull}; use paste::paste; -use crate::{world::World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}}; +use crate::{World, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}}; use super::{System, IntoSystem}; @@ -239,7 +239,7 @@ impl FnArgFetcher for ResMut<'_, R> { mod tests { use std::ptr::NonNull; - use crate::{tests::{Vec2, Vec3}, world::World, query::{QueryBorrow, ViewState, ResMut}}; + use crate::{tests::{Vec2, Vec3}, World, query::{QueryBorrow, ViewState, ResMut}}; use super::{System, IntoSystem}; struct SomeCounter(u32); diff --git a/lyra-ecs/src/system/graph.rs b/lyra-ecs/src/system/graph.rs index 4dd9044..73fbc17 100644 --- a/lyra-ecs/src/system/graph.rs +++ b/lyra-ecs/src/system/graph.rs @@ -2,7 +2,7 @@ use std::{collections::{HashMap, VecDeque, HashSet}, ptr::NonNull}; use super::System; -use crate::{world::World, CommandQueue, Commands}; +use crate::{World, CommandQueue, Commands}; #[derive(thiserror::Error, Debug)] pub enum GraphExecutorError { @@ -140,7 +140,7 @@ impl GraphExecutor { mod tests { use std::ptr::NonNull; - use crate::{query::{ResMut, View}, system::IntoSystem, world::World}; + use crate::{query::{ResMut, View}, system::IntoSystem, World}; use super::GraphExecutor; diff --git a/lyra-ecs/src/system/mod.rs b/lyra-ecs/src/system/mod.rs index 0471b89..625a166 100644 --- a/lyra-ecs/src/system/mod.rs +++ b/lyra-ecs/src/system/mod.rs @@ -1,6 +1,6 @@ use std::ptr::NonNull; -use crate::{world::World, Access}; +use crate::{World, Access}; mod graph; pub use graph::*; diff --git a/lyra-ecs/src/world.rs b/lyra-ecs/src/world.rs index bc38646..6ea08a6 100644 --- a/lyra-ecs/src/world.rs +++ b/lyra-ecs/src/world.rs @@ -143,7 +143,7 @@ impl World { 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(); + let mut col_types: Vec = current_arch.columns.iter().map(|c| c.info.type_id()).collect(); let orig_col = col_types.clone(); col_types.extend(bundle.type_ids()); @@ -158,7 +158,7 @@ impl World { for (col_type, (col_ptr, col_info)) in orig_col.into_iter().zip(col_ptrs.into_iter()) { unsafe { let ptr = NonNull::new_unchecked(col_ptr.as_ptr() - .add(res_index.0 as usize * col_info.layout.size())); + .add(res_index.0 as usize * col_info.layout().size())); let col = arch.get_column_mut(col_type).unwrap(); col.set_at(res_index.0 as _, ptr, tick); } From c0df9f2d651fd0764319aed6ffff5078bef32542 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Mar 2024 21:35:23 -0500 Subject: [PATCH 4/5] ecs: fix a typo in docs --- lyra-ecs/src/system/fn_sys.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lyra-ecs/src/system/fn_sys.rs b/lyra-ecs/src/system/fn_sys.rs index c768161..0ffac88 100644 --- a/lyra-ecs/src/system/fn_sys.rs +++ b/lyra-ecs/src/system/fn_sys.rs @@ -37,7 +37,7 @@ pub trait FnArgFetcher { /// fetched on the fly. /// /// ```fail_compile -/// fn enemy_movement_system(enemies: ViewState<(&Health, &EnemyStats, &Movement)>) -> anyhow::Result<()> { +/// fn enemy_movement_system(enemies: View<(&Health, &EnemyStats, &Movement)>) -> anyhow::Result<()> { /// for (health, stats, movement) in enemies.iter() { /// // ... /// } From 70fecc8cdde61678a3a22ec4cbc0e6c5cd769a08 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Mar 2024 21:37:21 -0500 Subject: [PATCH 5/5] fixes across crates after changes to ecs --- examples/testbed/src/free_fly_camera.rs | 5 +---- examples/testbed/src/main.rs | 8 +++++++- lyra-game/src/delta_time.rs | 2 +- lyra-game/src/game.rs | 2 +- lyra-game/src/input/action.rs | 2 +- lyra-game/src/input/system.rs | 2 +- lyra-game/src/render/light/mod.rs | 2 +- lyra-game/src/render/renderer.rs | 2 +- lyra-game/src/render/window.rs | 2 +- lyra-reflect/src/lib.rs | 2 +- lyra-resource/src/loader/model.rs | 2 ++ lyra-scripting/src/lua/dynamic_iter.rs | 10 +++++----- lyra-scripting/src/lua/world.rs | 10 +++++----- lyra-scripting/src/world.rs | 2 +- 14 files changed, 29 insertions(+), 24 deletions(-) diff --git a/examples/testbed/src/free_fly_camera.rs b/examples/testbed/src/free_fly_camera.rs index f4d7411..f2fd972 100644 --- a/examples/testbed/src/free_fly_camera.rs +++ b/examples/testbed/src/free_fly_camera.rs @@ -1,8 +1,5 @@ use lyra_engine::{ - game::Game, - input::{ActionHandler, CommonActionLabel}, - math::{Quat, Vec3, EulerRot}, - plugin::Plugin, ecs::{Component, query::{Res, View}}, DeltaTime, scene::CameraComponent, + ecs::{query::{Res, View}, Component}, game::Game, input::ActionHandler, math::{EulerRot, Quat, Vec3}, plugin::Plugin, scene::CameraComponent, DeltaTime }; /* enum FreeFlyCameraActions { diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index 36db6dc..cc34877 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -1,6 +1,6 @@ use std::ptr::NonNull; -use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, world::World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList}; +use lyra_engine::{math::{self, Vec3}, math::Transform, input::{KeyCode, ActionHandler, Action, ActionKind, LayoutId, ActionMapping, ActionSource, ActionMappingId, InputActionPlugin, MouseInput, MouseAxis, CommonActionLabel}, game::Game, render::{window::{CursorGrabMode, WindowOptions}, light::{PointLight, directional::DirectionalLight, SpotLight}}, change_tracker::Ct, ecs::{system::{Criteria, CriteriaSchedule, BatchedSystem, IntoSystem}, World, Component}, DeltaTime, scene::{ModelComponent, CameraComponent}, lua::{LuaScriptingPlugin, LuaScript}, Script, ScriptList}; use lyra_engine::assets::{ResourceManager, Model}; mod free_fly_camera; @@ -88,6 +88,7 @@ async fn main() { //let cube_model = resman.request::("assets/cube-texture-bin.glb").unwrap(); let cube_model = resman.request::("assets/texture-sep/texture-sep.gltf").unwrap(); let crate_model = resman.request::("assets/crate/crate.gltf").unwrap(); + //let sponza_model = resman.request::("assets/sponza/Sponza.gltf").unwrap(); drop(resman); /* world.spawn(( @@ -95,6 +96,11 @@ async fn main() { Transform::from_xyz(0.0, -5.0, -10.0), )); */ + /* world.spawn(( + ModelComponent(sponza_model), + Transform::from_xyz(0.0, 0.0, 0.0), + )); */ + { let cube_tran = Transform::from_xyz(-3.5, 0.0, -8.0); //cube_tran.rotate_y(math::Angle::Degrees(180.0)); diff --git a/lyra-game/src/delta_time.rs b/lyra-game/src/delta_time.rs index d074677..2c19944 100644 --- a/lyra-game/src/delta_time.rs +++ b/lyra-game/src/delta_time.rs @@ -1,5 +1,5 @@ use instant::Instant; -use lyra_ecs::{Component, world::World}; +use lyra_ecs::{Component, World}; use lyra_reflect::Reflect; use crate::{plugin::Plugin, game::GameStages}; diff --git a/lyra-game/src/game.rs b/lyra-game/src/game.rs index 953b462..fe9f9d9 100755 --- a/lyra-game/src/game.rs +++ b/lyra-game/src/game.rs @@ -2,7 +2,7 @@ use std::{sync::Arc, collections::VecDeque, ptr::NonNull}; use async_std::task::block_on; -use lyra_ecs::{world::World, system::{System, IntoSystem}}; +use lyra_ecs::{World, system::{System, IntoSystem}}; use tracing::{info, error, Level}; use tracing_appender::non_blocking; use tracing_subscriber::{ diff --git a/lyra-game/src/input/action.rs b/lyra-game/src/input/action.rs index aa756bf..bbb5f10 100644 --- a/lyra-game/src/input/action.rs +++ b/lyra-game/src/input/action.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, ops::Deref, hash::{Hash, DefaultHasher, Hasher}, fmt::Debug}; use glam::Vec2; -use lyra_ecs::world::World; +use lyra_ecs::World; use lyra_reflect::Reflect; use crate::{plugin::Plugin, game::GameStages, EventQueue}; diff --git a/lyra-game/src/input/system.rs b/lyra-game/src/input/system.rs index ec6b20f..fb04a0f 100755 --- a/lyra-game/src/input/system.rs +++ b/lyra-game/src/input/system.rs @@ -1,7 +1,7 @@ use std::ptr::NonNull; use glam::Vec2; -use lyra_ecs::{world::World, system::IntoSystem}; +use lyra_ecs::{World, system::IntoSystem}; use winit::event::MouseScrollDelta; use crate::{EventQueue, plugin::Plugin, game::GameStages}; diff --git a/lyra-game/src/render/light/mod.rs b/lyra-game/src/render/light/mod.rs index adbe0b5..ee3c81b 100644 --- a/lyra-game/src/render/light/mod.rs +++ b/lyra-game/src/render/light/mod.rs @@ -2,7 +2,7 @@ pub mod point; pub mod directional; pub mod spotlight; -use lyra_ecs::{Entity, Tick, world::World, query::{Entities, TickOf}}; +use lyra_ecs::{Entity, Tick, World, query::{Entities, TickOf}}; pub use point::*; pub use spotlight::*; diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index a513d8f..48d77d2 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -7,7 +7,7 @@ use instant::Instant; use itertools::izip; use lyra_ecs::Entity; use lyra_ecs::query::{Entities, TickOf}; -use lyra_ecs::world::World; +use lyra_ecs::World; use tracing::{debug, warn}; use wgpu::{BindGroupLayout, Limits}; use wgpu::util::DeviceExt; diff --git a/lyra-game/src/render/window.rs b/lyra-game/src/render/window.rs index d577996..c63c632 100644 --- a/lyra-game/src/render/window.rs +++ b/lyra-game/src/render/window.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use glam::{Vec2, IVec2}; -use lyra_ecs::world::World; +use lyra_ecs::World; use tracing::{warn, error}; use winit::{window::{Window, Fullscreen}, dpi::{LogicalPosition, LogicalSize, PhysicalPosition}, error::ExternalError}; diff --git a/lyra-reflect/src/lib.rs b/lyra-reflect/src/lib.rs index 0b8437f..6389a0a 100644 --- a/lyra-reflect/src/lib.rs +++ b/lyra-reflect/src/lib.rs @@ -2,7 +2,7 @@ use std::{any::TypeId, any::Any, cell::{Ref, RefMut}}; -use lyra_ecs::{world::World, DynamicBundle, Component, Entity, ComponentInfo}; +use lyra_ecs::World; extern crate self as lyra_reflect; diff --git a/lyra-resource/src/loader/model.rs b/lyra-resource/src/loader/model.rs index 396eae5..b5ed36d 100644 --- a/lyra-resource/src/loader/model.rs +++ b/lyra-resource/src/loader/model.rs @@ -62,6 +62,8 @@ impl ModelLoader { fn process_node(buffers: &Vec>, materials: &Vec, node: gltf::Node<'_>) -> Vec { let mut meshes = vec![]; + //node.transform() + if let Some(mesh) = node.mesh() { for prim in mesh.primitives() { let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice())); diff --git a/lyra-scripting/src/lua/dynamic_iter.rs b/lyra-scripting/src/lua/dynamic_iter.rs index c504470..9a9cc46 100644 --- a/lyra-scripting/src/lua/dynamic_iter.rs +++ b/lyra-scripting/src/lua/dynamic_iter.rs @@ -27,7 +27,7 @@ impl<'a> Fetch<'a> for FetchDynamicType { unsafe fn get_item(&mut self, entity: ArchetypeEntityId) -> Self::Item { let ptr = unsafe { self.col.as_ref().borrow_ptr() }; let ptr = NonNull::new_unchecked(ptr.as_ptr() - .add(entity.0 as usize * self.info.layout.size)); + .add(entity.0 as usize * self.info.layout().size())); DynamicType { info: self.info, @@ -96,7 +96,7 @@ impl Iterator for DynamicViewIter { } let arch = unsafe { self.archetypes.get_unchecked(self.next_archetype - 1).as_ref() }; - let entity = arch.entity_of_index(entity_index).unwrap(); + let entity = arch.entity_at_index(entity_index).unwrap(); let row = DynamicViewRow { entity, item: fetch_res, @@ -112,7 +112,7 @@ impl Iterator for DynamicViewIter { self.next_archetype += 1; let arch = unsafe { self.archetypes.get_unchecked(arch_id).as_ref() }; - if arch.entities().len() == 0 { + if arch.entity_indexes().len() == 0 { continue; } @@ -126,7 +126,7 @@ impl Iterator for DynamicViewIter { .map(|q| unsafe { q.fetch(world, ArchetypeId(arch_id as u64), arch) } ) .map(|f| FetchDynamicType::from(f)) .collect(); - self.component_indices = 0..arch.entities().len() as u64; + self.component_indices = 0..arch.entity_indexes().len() as u64; } } } @@ -167,7 +167,7 @@ impl ReflectedIterator { let mut dynamic_row = vec![]; for d in row.item.iter() { - let id = d.info.type_id.as_rust(); + let id = d.info.type_id().as_rust(); let reflected_components = unsafe { self.reflected_components.as_ref().unwrap().as_ref() }; diff --git a/lyra-scripting/src/lua/world.rs b/lyra-scripting/src/lua/world.rs index 848bd86..83e316b 100644 --- a/lyra-scripting/src/lua/world.rs +++ b/lyra-scripting/src/lua/world.rs @@ -192,14 +192,14 @@ impl elua::Userdata for ScriptWorldPtr { let lua_comp = reflect_user_data(ud); let refl_comp = lua_comp.reflect_branch.as_component_unchecked(); - refl_comp.info.type_id.as_rust() - } + refl_comp.info.type_id().as_rust() + }, elua::Value::Table(tbl) => { let name: String = tbl.get(elua::MetaMethod::Name)?; let lookup = world.get_resource::(); *lookup.typeid_from_name.get(&name).unwrap() - } + }, _ => { panic!("A userdata or table value was not returned!"); // TODO: Handle properly @@ -209,8 +209,8 @@ impl elua::Userdata for ScriptWorldPtr { // update the component tick let world = unsafe { this.inner.as_mut() }; let arch = world.entity_archetype_mut(row.entity).unwrap(); - let idx = arch.entities().get(&row.entity).unwrap().clone(); - let c = arch.get_column_mut(lua_typeid.into()).unwrap(); + let idx = arch.entity_indexes().get(&row.entity).unwrap().clone(); + let c = arch.get_column_mut(lua_typeid).unwrap(); c.entity_ticks[idx.0 as usize] = current; // apply the new component data diff --git a/lyra-scripting/src/world.rs b/lyra-scripting/src/world.rs index c2b2a1f..fd27bc8 100644 --- a/lyra-scripting/src/world.rs +++ b/lyra-scripting/src/world.rs @@ -1,6 +1,6 @@ use std::ptr::NonNull; -use lyra_ecs::{world::World, Entity}; +use lyra_ecs::{World, Entity}; #[derive(Clone)] pub struct ScriptEntity(pub Entity);