From 21537481c93c61e597b55de533b32dcd629a5e13 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sat, 2 Mar 2024 20:20:38 -0500 Subject: [PATCH] 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()) }