ecs: add relations, improve docs

This commit is contained in:
SeanOMik 2024-03-02 20:20:38 -05:00
parent d4135e0216
commit 21537481c9
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
16 changed files with 618 additions and 110 deletions

View File

@ -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}; use crate::{world::ArchetypeEntityId, bundle::Bundle, component_info::ComponentInfo, DynTypeId, Tick, Entity};
@ -81,14 +81,15 @@ impl ComponentColumn {
/// # Safety /// # Safety
/// ///
/// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB. /// This column MUST have the entity. If it does not, it WILL NOT panic and will cause UB.
pub unsafe fn get<T>(&self, entity_index: usize) -> &T { pub unsafe fn get<T>(&self, entity_index: usize) -> Ref<T> {
let data = self.data.borrow(); let data = self.data.borrow();
let data = data.deref();
Ref::map(data, |data| {
let ptr = NonNull::new_unchecked(data.as_ptr() let ptr = NonNull::new_unchecked(data.as_ptr()
.add(entity_index * self.info.layout.size)) .add(entity_index * self.info.layout.size))
.cast(); .cast();
&*ptr.as_ptr() &*ptr.as_ptr()
})
} }
/// Get a component at an entities index. /// Get a component at an entities index.
@ -411,11 +412,15 @@ impl Archetype {
pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option<Entity> { pub fn entity_of_index(&self, id: ArchetypeEntityId) -> Option<Entity> {
self.ids_to_entity.get(&id).cloned() self.ids_to_entity.get(&id).cloned()
} }
pub fn has_entity(&self, e: Entity) -> bool {
self.entities.contains_key(&e)
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::{alloc::Layout, ptr::NonNull}; use std::{alloc::Layout, cell::Ref, ptr::NonNull};
use rand::Rng; use rand::Rng;
@ -435,7 +440,7 @@ mod tests {
let entity_arch_id = a.add_entity(entity, bundle, &Tick::default()); let entity_arch_id = a.add_entity(entity, bundle, &Tick::default());
let col = a.columns.get(0).unwrap(); let col = a.columns.get(0).unwrap();
let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) }; let vec2: Ref<Vec2> = unsafe { col.get(entity_arch_id.0 as usize) };
assert_eq!(vec2.clone(), bundle.0); assert_eq!(vec2.clone(), bundle.0);
} }
@ -451,11 +456,11 @@ mod tests {
let entity_arch_id = a.add_entity(entity, bundle, &Tick::default()); let entity_arch_id = a.add_entity(entity, bundle, &Tick::default());
let col = a.columns.get(0).unwrap(); let col = a.columns.get(0).unwrap();
let vec2: &Vec2 = unsafe { col.get(entity_arch_id.0 as usize) }; let vec2: Ref<Vec2> = unsafe { col.get(entity_arch_id.0 as usize) };
assert_eq!(vec2.clone(), bundle.0); assert_eq!(vec2.clone(), bundle.0);
let col = a.columns.get(1).unwrap(); let col = a.columns.get(1).unwrap();
let vec3: &Vec3 = unsafe { col.get(entity_arch_id.0 as usize) }; let vec3: Ref<Vec3> = unsafe { col.get(entity_arch_id.0 as usize) };
assert_eq!(vec3.clone(), bundle.1); assert_eq!(vec3.clone(), bundle.1);
} }
@ -477,9 +482,9 @@ mod tests {
let earch2 = a.add_entity(e2, b2, &Tick::default()); let earch2 = a.add_entity(e2, b2, &Tick::default());
let col = a.columns.get(0).unwrap(); let col = a.columns.get(0).unwrap();
let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) }; let vec2: Ref<Vec2> = unsafe { col.get(earch1.0 as usize) };
assert_eq!(vec2.clone(), b1.0); assert_eq!(vec2.clone(), b1.0);
let vec2: &Vec2 = unsafe { col.get(earch2.0 as usize) }; let vec2: Ref<Vec2> = unsafe { col.get(earch2.0 as usize) };
assert_eq!(vec2.clone(), b2.0); assert_eq!(vec2.clone(), b2.0);
} }
@ -501,15 +506,15 @@ mod tests {
let earch2 = a.add_entity(e2, b2, &Tick::default()); let earch2 = a.add_entity(e2, b2, &Tick::default());
let col = a.columns.get(0).unwrap(); let col = a.columns.get(0).unwrap();
let vec2: &Vec2 = unsafe { col.get(earch1.0 as usize) }; let vec2: Ref<Vec2> = unsafe { col.get(earch1.0 as usize) };
assert_eq!(vec2.clone(), b1.0); assert_eq!(vec2.clone(), b1.0);
let vec2: &Vec2 = unsafe { col.get(earch2.0 as usize) }; let vec2: Ref<Vec2> = unsafe { col.get(earch2.0 as usize) };
assert_eq!(vec2.clone(), b2.0); assert_eq!(vec2.clone(), b2.0);
let col = a.columns.get(1).unwrap(); let col = a.columns.get(1).unwrap();
let vec3: &Vec3 = unsafe { col.get(earch1.0 as usize) }; let vec3: Ref<Vec3> = unsafe { col.get(earch1.0 as usize) };
assert_eq!(vec3.clone(), b1.1); assert_eq!(vec3.clone(), b1.1);
let vec3: &Vec3 = unsafe { col.get(earch2.0 as usize) }; let vec3: Ref<Vec3> = unsafe { col.get(earch2.0 as usize) };
assert_eq!(vec3.clone(), b2.1); assert_eq!(vec3.clone(), b2.1);
} }
@ -552,7 +557,7 @@ mod tests {
let col = a.columns.get(0).unwrap(); let col = a.columns.get(0).unwrap();
for i in 0..bundle_count { for i in 0..bundle_count {
let vec2: &Vec2 = unsafe { col.get(i) }; let vec2: Ref<Vec2> = unsafe { col.get(i) };
assert_eq!(vec2.clone(), bundles[i].0); assert_eq!(vec2.clone(), bundles[i].0);
} }
} }

View File

@ -114,7 +114,7 @@ pub fn execute_deferred_commands(world: &mut World, mut commands: RefMut<Command
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::ptr::NonNull; use std::{cell::Ref, ptr::NonNull};
use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World}; use crate::{system::{GraphExecutor, IntoSystem}, tests::Vec2, Commands, DynTypeId, World};
@ -144,7 +144,7 @@ mod tests {
// there's only one archetype // there's only one archetype
let arch = world.archetypes.values().next().unwrap(); let arch = world.archetypes.values().next().unwrap();
let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap(); let col = arch.get_column(DynTypeId::of::<Vec2>()).unwrap();
let vec2: &Vec2 = unsafe { col.get(3) }; let vec2: Ref<Vec2> = unsafe { col.get(3) };
assert_eq!(vec2.clone(), spawned_vec); assert_eq!(vec2.clone(), spawned_vec);
} }
} }

View File

@ -7,39 +7,42 @@ pub(crate) mod lyra_engine {
} }
} }
pub mod archetype;
use std::ops::BitOr; use std::ops::BitOr;
mod archetype;
pub use archetype::*; pub use archetype::*;
pub mod entity; mod entity;
pub use entity::*; pub use entity::*;
pub mod world; mod world;
pub use world::*; pub use world::*;
pub mod command; mod command;
pub use command::*; pub use command::*;
pub mod bundle; mod bundle;
pub use bundle::*; pub use bundle::*;
pub mod component; mod component;
pub use component::*; pub use component::*;
pub mod query; pub mod query;
//pub use query::*; //pub use query::*;
pub mod component_info; mod relation;
pub use relation::Relation;
mod component_info;
pub use component_info::*; pub use component_info::*;
pub mod resource; mod resource;
pub use resource::*; pub use resource::*;
pub mod system; pub mod system;
//pub use system::*; //pub use system::*;
pub mod tick; mod tick;
pub use tick::*; pub use tick::*;
/// Implements Component for glam math types /// Implements Component for glam math types

View File

@ -221,7 +221,7 @@ impl<T: Component> AsQuery for &mut T {
mod tests { mod tests {
use std::{mem::size_of, marker::PhantomData, ptr::NonNull}; 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}; use super::{QueryBorrow, QueryBorrowMut, FetchBorrowMut};
@ -244,7 +244,7 @@ mod tests {
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
}; };
let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
let v = View::<QueryBorrow<Vec2>>::new(&world, borrow, archetypes); let v = ViewState::<QueryBorrow<Vec2>, ()>::new(&world, borrow, (), archetypes);
for e in v.into_iter() { for e in v.into_iter() {
println!("Found entity at {:?}", e); println!("Found entity at {:?}", e);
@ -260,7 +260,7 @@ mod tests {
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
}; };
let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
let v = View::<QueryBorrowMut<Vec2>>::new(&world, borrow, archetypes); let v = ViewState::<QueryBorrowMut<Vec2>, ()>::new(&world, borrow, (), archetypes);
let mut orig = vec![]; let mut orig = vec![];
@ -277,7 +277,7 @@ mod tests {
_phantom: std::marker::PhantomData, _phantom: std::marker::PhantomData,
}; };
let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
let v = View::<QueryBorrow<Vec2>>::new(&world, borrow, archetypes); let v = ViewState::<QueryBorrow<Vec2>, ()>::new(&world, borrow, (), archetypes);
for (new, orig) in v.into_iter().zip(orig.iter()) { for (new, orig) in v.into_iter().zip(orig.iter()) {
assert!(new.x - orig.x == 10.0); assert!(new.x - orig.x == 10.0);

View File

@ -2,7 +2,7 @@ use std::ptr::NonNull;
use crate::{world::World, ComponentColumn, ComponentInfo}; use crate::{world::World, ComponentColumn, ComponentInfo};
pub mod view; mod view;
pub use view::*; pub use view::*;
use super::Fetch; use super::Fetch;

View File

@ -3,27 +3,27 @@ use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, Tick};
pub mod view; pub mod view;
pub use view::*; pub use view::*;
pub mod entities; mod entities;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use entities::*; pub use entities::*;
pub mod borrow; mod borrow;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use borrow::*; pub use borrow::*;
pub mod tuple; mod tuple;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use tuple::*; pub use tuple::*;
pub mod resource; mod resource;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use resource::*; pub use resource::*;
pub mod tick; mod tick;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use tick::*; pub use tick::*;
pub mod world; mod world;
#[allow(unused_imports)] #[allow(unused_imports)]
pub use world::*; pub use world::*;
@ -87,11 +87,45 @@ pub trait IntoQuery {
fn into_query(self) -> Self; 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)] #[cfg(test)]
mod tests { mod tests {
use crate::{world::World, archetype::Archetype, tests::Vec2}; use crate::{world::World, archetype::Archetype, tests::Vec2};
use super::{View, Entities}; use super::{ViewState, Entities};
#[test] #[test]
fn simple_view() { fn simple_view() {
@ -104,7 +138,7 @@ mod tests {
let archetypes: Vec<&Archetype> = world.archetypes.values().collect(); let archetypes: Vec<&Archetype> = world.archetypes.values().collect();
let v = View::<Entities>::new(&world, entities, archetypes); let v = ViewState::<Entities, ()>::new(&world, entities, (), archetypes);
for e in v.into_iter() { for e in v.into_iter() {
println!("Got entity! {:?}", e); println!("Got entity! {:?}", e);

View File

@ -2,6 +2,50 @@ use crate::world::World;
use super::{Query, Fetch, AsQuery}; 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<Q1> 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 // Technically all of these implementations for a 2-sized tuple
// can be implemented by the macro near the end of the file, but // can be implemented by the macro near the end of the file, but
// these are left here for development. // these are left here for development.

View File

@ -4,41 +4,64 @@ use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, T
use super::{Query, Fetch, AsQuery}; use super::{Query, Fetch, AsQuery};
pub struct View<'a, Q: AsQuery> { pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
pub struct ViewState<'a, Q: Query, F: Query> {
world: &'a World, world: &'a World,
query: Q::Query, query: Q,
filter: F,
archetypes: Vec<&'a Archetype>, archetypes: Vec<&'a Archetype>,
} }
impl<'a, Q> View<'a, Q> impl<'a, Q, F> ViewState<'a, Q, F>
where 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 { Self {
world, world,
query, query,
filter,
archetypes, archetypes,
} }
} }
/// Converts self into an iterator
pub fn iter(self) -> ViewIter<'a, Q, F> {
self.into_iter()
} }
impl<'a, Q> IntoIterator for View<'a, Q> /// Consumes `self`, adding a query to the view.
where pub fn expand<U: AsQuery>(self, query: U::Query) -> ViewState<'a, (Q, U::Query), F> {
Q: AsQuery, ViewState::new(self.world, (self.query, query), self.filter, self.archetypes)
{ }
type Item = <Q::Query as Query>::Item<'a>;
type IntoIter = ViewIter<'a, Q::Query>; /// Consumes `self`, adding a filter to the view.
pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> {
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
}
}
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
where
Q: Query,
F: Query,
{
type Item = Q::Item<'a>;
type IntoIter = ViewIter<'a, Q, F>;
fn into_iter(self) -> Self::IntoIter { 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 { ViewIter {
world: self.world, world: self.world,
tick, tick,
query: self.query, query: self.query,
filter: self.filter,
fetcher: None, fetcher: None,
filter_fetcher: None,
archetypes: self.archetypes, archetypes: self.archetypes,
next_archetype: 0, next_archetype: 0,
component_indices: 0..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, world: &'a World,
tick: Tick, tick: Tick,
query: Q, query: Q,
filter: F,
fetcher: Option<Q::Fetch<'a>>, fetcher: Option<Q::Fetch<'a>>,
filter_fetcher: Option<F::Fetch<'a>>,
archetypes: Vec<&'a Archetype>, archetypes: Vec<&'a Archetype>,
next_archetype: usize, next_archetype: usize,
component_indices: Range<u64>, component_indices: Range<u64>,
} }
impl<'a, Q> Iterator for ViewIter<'a, Q> impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
where where
Q: Query, Q: Query,
F: Query,
{ {
type Item = Q::Item<'a>; type Item = Q::Item<'a>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
loop { loop {
if Q::ALWAYS_FETCHES { if Q::ALWAYS_FETCHES && F::ALWAYS_FETCHES {
// only fetch this query once. // only fetch this query once.
// fetcher gets set to Some after this `next` call. // fetcher gets set to Some after this `next` call.
if self.fetcher.is_none() { if self.fetcher.is_none() {
@ -80,10 +106,10 @@ where
if let Some(entity_index) = self.component_indices.next() { if let Some(entity_index) = self.component_indices.next() {
let fetcher = self.fetcher.as_mut().unwrap(); let fetcher = self.fetcher.as_mut().unwrap();
let filter_fetcher = self.filter_fetcher.as_mut().unwrap();
let entity_index = ArchetypeEntityId(entity_index); let entity_index = ArchetypeEntityId(entity_index);
if !fetcher.can_visit_item(entity_index) {
continue; if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
} else {
let i = unsafe { fetcher.get_item(entity_index) }; let i = unsafe { fetcher.get_item(entity_index) };
return Some(i); return Some(i);
} }
@ -100,16 +126,17 @@ where
continue; continue;
} }
if !self.query.can_visit_archetype(arch) { if self.query.can_visit_archetype(arch) && self.filter.can_visit_archetype(arch) {
continue; 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.fetcher = unsafe { Some(self.query.fetch(self.world, arch, self.tick)) };
self.component_indices = 0..arch.entities.len() as u64; self.component_indices = 0..arch.entities.len() as u64;
} }
} }
} }
} }
}
pub struct ViewOne<'a, Q: Query> { pub struct ViewOne<'a, Q: Query> {
world: &'a World, world: &'a World,

View File

@ -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<R: Relation> {
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<R: Relation> {
origin: Entity,
_marker: PhantomData<R>,
}
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::<Entities>()
/// .relates_to::<ChildOf>(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<R>(&mut self, origin: Entity, relation: R, target: Entity)
where
R: Relation
{
let comp = RelationTargetComponent {
origin,
_marker: PhantomData::<R>,
};
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<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>
where
R: Relation,
{
let rel = QueryRelatesTo::new(Some(target));
self.expand::<RelatesTo<R>>(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<R>(self) -> ViewState<'a, (Q, QueryRelatePair<R>), F>
where
R: Relation,
{
let rel = QueryRelatePair::<R>::new();
self.expand::<RelatePair<R>>(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<ChildOf>), ()>::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::<Entities>()
.relates_to::<ChildOf>(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::<RelatePair<ChildOf>>();
for (origin, _childof, target) in v.into_iter() {
println!("{:?} is a child of {:?}", origin, target);
}
}
}

View File

@ -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<RelationOriginComponent<R>> = 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<R> {
_marker: PhantomData<R>,
}
impl<R> Copy for QueryRelatePair<R> {}
impl<R> Clone for QueryRelatePair<R> {
fn clone(&self) -> Self {
*self
}
}
impl<R> QueryRelatePair<R> {
pub fn new() -> Self {
Self {
_marker: PhantomData
}
}
}
impl<R> Query for QueryRelatePair<R>
where
R: Relation + 'static
{
type Item<'a> = (Entity, Ref<'a, R>, Entity);
type Fetch<'a> = FetchRelatePair<'a, R>;
fn new() -> Self {
QueryRelatePair::<R>::new()
}
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
let tyid = crate::DynTypeId::Rust(TypeId::of::<RelationOriginComponent<R>>());
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::<RelationOriginComponent<R>>());
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<R: Relation> {
_marker: PhantomData<R>,
}
impl<R: Relation> AsQuery for RelatePair<R> {
type Query = QueryRelatePair<R>;
}

View File

@ -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<RelationOriginComponent<R>> = 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<RelationOriginComponent<R>> = self.col.get(entity.0 as usize);
let comp = Ref::map(comp, |r| &r.relation);
comp
}
}
pub struct QueryRelatesTo<R> {
target: Option<Entity>,
_marker: PhantomData<R>,
}
impl<R> Copy for QueryRelatesTo<R> {}
impl<R> Clone for QueryRelatesTo<R> {
fn clone(&self) -> Self {
*self
}
}
impl<R> QueryRelatesTo<R> {
pub fn new(target: Option<Entity>) -> Self {
Self {
target,
_marker: PhantomData
}
}
}
impl<R> Query for QueryRelatesTo<R>
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::<RelationOriginComponent<R>>());
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::<RelationOriginComponent<R>>());
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::<Entities>()
/// .relates_to::<ChildOf>(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<R: Relation> {
_marker: PhantomData<R>,
}
impl<R: Relation> AsQuery for RelatesTo<R> {
type Query = QueryRelatesTo<R>;
}

View File

@ -2,14 +2,23 @@ use std::ptr::NonNull;
use lyra_ecs::world::World; 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)] #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum CriteriaSchedule { pub enum CriteriaSchedule {
/// The criteria was completely met and the system can continue to run.
Yes, Yes,
/// The criteria was not met and must run next time the system batch is ran.
No, 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, 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, NoAndLoop,
} }
/// A Criteria can be used to conditionally execute [`BatchedSystems`](super::BatchedSystems).
pub trait Criteria { pub trait Criteria {
/// Checks if this Criteria can run, and if it should check it again. /// Checks if this Criteria can run, and if it should check it again.
/// ///

View File

@ -1,10 +1,11 @@
use std::{any::Any, marker::PhantomData, ptr::NonNull}; use std::{any::Any, marker::PhantomData, ptr::NonNull};
use paste::paste; 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}; use super::{System, IntoSystem};
/// A trait that is used for fetching an argument for a [`FnSystem`].
pub trait FnArgFetcher { pub trait FnArgFetcher {
/// stores data that persists after an execution of a system /// stores data that persists after an execution of a system
type State: 'static; type State: 'static;
@ -30,14 +31,20 @@ pub trait FnArgFetcher {
fn apply_deferred(state: Self::State, world: NonNull<World>); fn apply_deferred(state: Self::State, world: NonNull<World>);
} }
pub trait FnArg { /// A system that is implemented as a function.
type Fetcher: FnArgFetcher; ///
} /// 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<F, Args> { pub struct FnSystem<F, Args> {
inner: F, inner: F,
//#[allow(dead_code)]
//args: Args,
arg_state: Option<Vec<Box<dyn Any>>>, arg_state: Option<Vec<Box<dyn Any>>>,
_marker: PhantomData<Args>, _marker: PhantomData<Args>,
} }
@ -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 }
impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P } 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 /// An ArgFetcher implementation for query [`ViewState`]s
/* pub struct ViewArgFetcher<Q: AsQuery> { impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
query: Q::Query
}
impl<'a, Q: AsQuery> FnArg for View<'a, Q> {
type Fetcher = ViewArgFetcher<Q>;
} */
impl<'c, Q> FnArgFetcher for View<'c, Q>
where where
Q: AsQuery, Q: Query + 'static,
<Q as AsQuery>::Query: 'static F: Query + 'static,
{ {
type State = Q::Query; type State = (Q, F);
type Arg<'a, 'state> = View<'a, Q>; type Arg<'a, 'state> = ViewState<'a, Q, F>;
fn world_access(&self) -> Access { fn world_access(&self) -> Access {
todo!() todo!()
@ -153,7 +152,8 @@ where
unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> { unsafe fn get<'a, 'state>(state: &'state mut Self::State, world: NonNull<World>) -> Self::Arg<'a, 'state> {
let world = &*world.as_ptr(); let world = &*world.as_ptr();
let arch = world.archetypes.values().collect(); 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 v
} }
@ -161,17 +161,10 @@ where
fn apply_deferred(_: Self::State, _: NonNull<World>) { } fn apply_deferred(_: Self::State, _: NonNull<World>) { }
fn create_state(_: NonNull<World>) -> Self::State { fn create_state(_: NonNull<World>) -> Self::State {
<Q::Query as Query>::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 { impl FnArgFetcher for &'_ World {
type State = (); type State = ();
type Arg<'a, 'state> = &'a World; type Arg<'a, 'state> = &'a World;
@ -246,7 +239,7 @@ impl<R: ResourceObject> FnArgFetcher for ResMut<'_, R> {
mod tests { mod tests {
use std::ptr::NonNull; 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}; use super::{System, IntoSystem};
struct SomeCounter(u32); struct SomeCounter(u32);
@ -262,7 +255,7 @@ mod tests {
let mut count = 0; let mut count = 0;
let test_system = |view: View<QueryBorrow<Vec2>>| -> anyhow::Result<()> { let test_system = |view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
let mut vecs = vecs.to_vec(); let mut vecs = vecs.to_vec();
for v in view.into_iter() { for v in view.into_iter() {
let pos = vecs.iter().position(|vec| *vec == *v) let pos = vecs.iter().position(|vec| *vec == *v)
@ -291,7 +284,7 @@ mod tests {
let mut count = 0; let mut count = 0;
let test_system = |view: View<(QueryBorrow<Vec2>, QueryBorrow<Vec3>)>| -> anyhow::Result<()> { let test_system = |view: ViewState<(QueryBorrow<Vec2>, QueryBorrow<Vec3>), ()>| -> anyhow::Result<()> {
for (v2, v3) in view.into_iter() { for (v2, v3) in view.into_iter() {
println!("Got v2 at '{:?}' and v3 at: '{:?}'", v2, v3); println!("Got v2 at '{:?}' and v3 at: '{:?}'", v2, v3);
count += 1; count += 1;
@ -392,7 +385,7 @@ mod tests {
world.spawn((Vec2::rand(), )); world.spawn((Vec2::rand(), ));
world.add_resource(SomeCounter(0)); world.add_resource(SomeCounter(0));
let test_system = |mut counter: ResMut<SomeCounter>, view: View<QueryBorrow<Vec2>>| -> anyhow::Result<()> { let test_system = |mut counter: ResMut<SomeCounter>, view: ViewState<QueryBorrow<Vec2>, ()>| -> anyhow::Result<()> {
for v2 in view.into_iter() { for v2 in view.into_iter() {
println!("Got v2 at '{:?}'", v2); println!("Got v2 at '{:?}'", v2);
// .0 is twice here since ResMut's tuple field is pub(crate). // .0 is twice here since ResMut's tuple field is pub(crate).

View File

@ -140,7 +140,7 @@ impl GraphExecutor {
mod tests { mod tests {
use std::ptr::NonNull; 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; use super::GraphExecutor;
@ -152,7 +152,7 @@ mod tests {
let mut exec = GraphExecutor::new(); let mut exec = GraphExecutor::new();
let a_system = |view: View<ResMut<Vec<String>>>| -> anyhow::Result<()> { let a_system = |view: View<ResMut<Vec<String>>, ()>| -> anyhow::Result<()> {
println!("System 'a' ran!"); println!("System 'a' ran!");
let mut order = view.into_iter().next().unwrap(); let mut order = view.into_iter().next().unwrap();
@ -161,7 +161,7 @@ mod tests {
Ok(()) Ok(())
}; };
let b_system = |view: View<ResMut<Vec<String>>>| -> anyhow::Result<()> { let b_system = |view: View<ResMut<Vec<String>>, ()>| -> anyhow::Result<()> {
println!("System 'b' ran!"); println!("System 'b' ran!");
let mut order = view.into_iter().next().unwrap(); let mut order = view.into_iter().next().unwrap();
@ -170,7 +170,7 @@ mod tests {
Ok(()) Ok(())
}; };
let c_system = |view: View<ResMut<Vec<String>>>| -> anyhow::Result<()> { let c_system = |view: View<ResMut<Vec<String>>, ()>| -> anyhow::Result<()> {
println!("System 'c' ran!"); println!("System 'c' ran!");
let mut order = view.into_iter().next().unwrap(); let mut order = view.into_iter().next().unwrap();

View File

@ -2,16 +2,16 @@ use std::ptr::NonNull;
use crate::{world::World, Access}; use crate::{world::World, Access};
pub mod graph; mod graph;
pub use graph::*; pub use graph::*;
pub mod criteria; mod criteria;
pub use criteria::*; pub use criteria::*;
pub mod batched; mod batched;
pub use batched::*; pub use batched::*;
pub mod fn_sys; mod fn_sys;
pub use fn_sys::*; pub use fn_sys::*;
/// A system that does not mutate the world /// A system that does not mutate the world

View File

@ -1,9 +1,10 @@
use std::{any::TypeId, cell::{Ref, RefMut}, collections::HashMap, ptr::NonNull}; 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 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)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct ArchetypeEntityId(pub u64); pub struct ArchetypeEntityId(pub u64);
@ -207,9 +208,20 @@ impl World {
} }
/// View into the world for a set of entities that satisfy the queries. /// View into the world for a set of entities that satisfy the queries.
pub fn view_iter<T: 'static + AsQuery>(&self) -> ViewIter<T::Query> { pub fn view<Q: AsQuery>(&self) -> ViewState<Q::Query, ()> {
self.filtered_view::<Q, ()>()
}
/// View into the world for a set of entities that satisfy the query and the filter.
pub fn filtered_view<Q: AsQuery, F: AsQuery>(&self) -> ViewState<Q::Query, F::Query> {
let archetypes = self.archetypes.values().collect(); let archetypes = self.archetypes.values().collect();
let v = View::<T>::new(self, T::Query::new(), archetypes); ViewState::<Q::Query, F::Query>::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<Q: AsQuery>(&self) -> ViewIter<Q::Query, ()> {
let archetypes = self.archetypes.values().collect();
let v = ViewState::new(self, Q::Query::new(), (), archetypes);
v.into_iter() v.into_iter()
} }
@ -217,7 +229,7 @@ impl World {
DynamicView::new(self) DynamicView::new(self)
} }
pub fn view_one<T: 'static + AsQuery>(&self, entity: Entity) -> ViewOne<T::Query> { pub fn view_one<T: AsQuery>(&self, entity: Entity) -> ViewOne<T::Query> {
ViewOne::new(self, entity.id, T::Query::new()) ViewOne::new(self, entity.id, T::Query::new())
} }