ecs: implement an actual Filter trait, create a Changed filter

This commit is contained in:
SeanOMik 2024-09-21 14:06:21 -04:00
parent 2107b8f7b0
commit 782d64f6cf
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
9 changed files with 186 additions and 24 deletions

View File

@ -1,3 +1,5 @@
#![feature(associated_type_defaults)]
extern crate self as lyra_ecs; extern crate self as lyra_ecs;
#[allow(unused_imports)] #[allow(unused_imports)]

View File

@ -0,0 +1,97 @@
use std::marker::PhantomData;
use crate::{query::{AsFilter, AsQuery, Fetch, Filter, Query}, Component, ComponentColumn, DynTypeId, Tick, World};
pub struct ChangedFetcher<'a, T> {
col: &'a ComponentColumn,
tick: Tick,
_phantom: PhantomData<&'a T>,
}
impl<'a, T> Fetch<'a> for ChangedFetcher<'a, T>
where
T: 'a,
{
type Item = bool;
fn dangling() -> Self {
unreachable!()
}
unsafe fn get_item(&mut self, entity: crate::world::ArchetypeEntityId) -> Self::Item {
let tick = self.col.entity_ticks[entity.0 as usize];
tick >= self.tick
}
}
/// A filter that fetches components that have changed.
///
/// Since [`AsQuery`] is implemented for `&T`, you can use this query like this:
/// ```nobuild
/// for ts in world.view::<&T>() {
/// println!("Got a &T!");
/// }
/// ```
pub struct Changed<T> {
type_id: DynTypeId,
_phantom: PhantomData<T>
}
impl<T: Component> Default for Changed<T> {
fn default() -> Self {
Self {
type_id: DynTypeId::of::<T>(),
_phantom: PhantomData,
}
}
}
// manually implemented to avoid a Copy bound on T
impl<T> Copy for Changed<T> {}
// manually implemented to avoid a Clone bound on T
impl<T> Clone for Changed<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: Component> Changed<T> {
pub fn new() -> Self {
Self::default()
}
}
impl<T: Component> Query for Changed<T>
where
T: 'static
{
type Item<'a> = bool;
type Fetch<'a> = ChangedFetcher<'a, T>;
fn new() -> Self {
Changed::<T>::new()
}
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
archetype.has_column(self.type_id)
}
unsafe fn fetch<'a>(&self, w: &'a World, a: &'a crate::archetype::Archetype, _: crate::Tick) -> Self::Fetch<'a> {
ChangedFetcher {
col: a.get_column(self.type_id).unwrap(),
tick: w.current_tick(),
_phantom: PhantomData::<&T>,
}
}
}
impl<T: Component> AsQuery for Changed<T> {
type Query = Self;
}
impl<'a, T: Component> Filter for Changed<T> { }
impl<T: Component> AsFilter for Changed<T> {
type Filter = Self;
}

View File

@ -5,4 +5,7 @@ mod or;
pub use or::*; pub use or::*;
mod not; mod not;
pub use not::*; pub use not::*;
mod changed;
pub use changed::*;

View File

@ -89,6 +89,12 @@ pub trait AsQuery {
type Query: Query; type Query: Query;
} }
/// A trait for getting the filter of a type.
pub trait AsFilter {
/// The query for this type
type Filter: Filter;
}
pub trait IntoQuery { pub trait IntoQuery {
fn into_query(self) -> Self; fn into_query(self) -> Self;
} }
@ -125,10 +131,22 @@ impl Query for () {
} }
} }
impl Filter for () {
type Item<'a> = bool;
}
impl AsQuery for () { impl AsQuery for () {
type Query = (); type Query = ();
} }
pub trait Filter: Query {
type Item<'a> = bool;
}
impl AsFilter for () {
type Filter = ();
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{World, archetype::Archetype, tests::Vec2}; use crate::{World, archetype::Archetype, tests::Vec2};

View File

@ -1,8 +1,8 @@
use crate::World; use crate::World;
use super::{Query, Fetch, AsQuery}; use super::{Query, Fetch, AsQuery, Filter, AsFilter};
impl<'a, F1> Fetch<'a> for (F1,) /* impl<'a, F1> Fetch<'a> for (F1,)
where where
F1: Fetch<'a>, F1: Fetch<'a>,
{ {
@ -102,7 +102,7 @@ where
Q2: AsQuery, Q2: AsQuery,
{ {
type Query = (Q1::Query, Q2::Query); type Query = (Q1::Query, Q2::Query);
} } */
macro_rules! impl_bundle_tuple { macro_rules! impl_bundle_tuple {
( $($name: ident),+ ) => ( ( $($name: ident),+ ) => (
@ -154,10 +154,39 @@ macro_rules! impl_bundle_tuple {
impl<$($name: AsQuery),+> AsQuery for ($($name,)+) { impl<$($name: AsQuery),+> AsQuery for ($($name,)+) {
type Query = ($($name::Query,)+); type Query = ($($name::Query,)+);
} }
#[allow(non_snake_case)]
impl<$($name: Filter),+> Filter for ($($name,)+) {
//type Item<'a> = ($(<$name as Query>::Item<'a>,)+);
//type Fetch<'a> = ($(<$name as Query>::Fetch<'a>,)+);
/* fn new() -> Self {
( $($name::new(),)+ )
}
fn can_visit_archetype(&self, archetype: &crate::archetype::Archetype) -> bool {
let ( $($name,)+ ) = self;
// this is the only way I could figure out how to do an 'and'
let bools = vec![$($name.can_visit_archetype(archetype),)+];
bools.iter().all(|b| *b)
}
unsafe fn fetch<'a>(&self, world: &'a World, archetype: &'a crate::archetype::Archetype, tick: crate::Tick) -> Self::Fetch<'a> {
let ( $($name,)+ ) = self;
( $($name.fetch(world, archetype, tick),)+ )
} */
}
impl<$($name: AsFilter),+> AsFilter for ($($name,)+) {
type Filter = ($($name::Filter,)+);
}
); );
} }
// Hopefully up to 16 queries in a SINGLE view is enough // Hopefully up to 16 queries in a SINGLE view is enough
impl_bundle_tuple! { Q1 }
impl_bundle_tuple! { Q1, Q2 }
impl_bundle_tuple! { Q1, Q2, Q3 } impl_bundle_tuple! { Q1, Q2, Q3 }
impl_bundle_tuple! { Q1, Q2, Q3, Q4 } impl_bundle_tuple! { Q1, Q2, Q3, Q4 }
impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 } impl_bundle_tuple! { Q1, Q2, Q3, Q4, Q5 }

View File

@ -2,11 +2,11 @@ use std::ops::Range;
use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick}; use crate::{archetype::Archetype, world::{ArchetypeEntityId, World}, EntityId, Tick};
use super::{Query, Fetch, AsQuery}; use super::{AsFilter, AsQuery, Fetch, Filter, Query};
pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>; pub type View<'a, Q, F = ()> = ViewState<'a, <Q as AsQuery>::Query, <F as AsQuery>::Query>;
pub struct ViewState<'a, Q: Query, F: Query> { pub struct ViewState<'a, Q: Query, F: Filter> {
world: &'a World, world: &'a World,
query: Q, query: Q,
filter: F, filter: F,
@ -16,7 +16,7 @@ pub struct ViewState<'a, Q: Query, F: Query> {
impl<'a, Q, F> ViewState<'a, Q, F> impl<'a, Q, F> ViewState<'a, Q, F>
where where
Q: Query, Q: Query,
F: Query, F: Filter,
{ {
pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self { pub fn new(world: &'a World, query: Q, filter: F, archetypes: Vec<&'a Archetype>) -> Self {
Self { Self {
@ -38,7 +38,7 @@ where
} }
/// Consumes `self`, adding a filter to the view. /// Consumes `self`, adding a filter to the view.
pub fn with<U: AsQuery>(self, filter: U::Query) -> ViewState<'a, Q, (F, U::Query)> { pub fn with<U: AsFilter>(self, filter: U::Filter) -> ViewState<'a, Q, (F, U::Filter)> {
ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes) ViewState::new(self.world, self.query, (self.filter, filter), self.archetypes)
} }
} }
@ -46,18 +46,19 @@ where
impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F> impl<'a, Q, F> IntoIterator for ViewState<'a, Q, F>
where where
Q: Query, Q: Query,
F: Query, F: Filter,
{ {
type Item = Q::Item<'a>; type Item = Q::Item<'a>;
type IntoIter = ViewIter<'a, Q, F>; 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::MUTATES); //let tick = self.world.tick_tracker().tick_when(Q::MUTATES);
ViewIter { ViewIter {
world: self.world, world: self.world,
tick, tick: self.world.current_tick(),
has_ticked: false,
query: self.query, query: self.query,
filter: self.filter, filter: self.filter,
fetcher: None, fetcher: None,
@ -69,9 +70,10 @@ where
} }
} }
pub struct ViewIter<'a, Q: Query, F: Query> { pub struct ViewIter<'a, Q: Query, F: Filter> {
world: &'a World, world: &'a World,
tick: Tick, tick: Tick,
has_ticked: bool,
query: Q, query: Q,
filter: F, filter: F,
fetcher: Option<Q::Fetch<'a>>, fetcher: Option<Q::Fetch<'a>>,
@ -84,7 +86,7 @@ pub struct ViewIter<'a, Q: Query, F: Query> {
impl<'a, Q, F> Iterator for ViewIter<'a, Q, F> impl<'a, Q, F> Iterator for ViewIter<'a, Q, F>
where where
Q: Query, Q: Query,
F: Query, F: Filter,
{ {
type Item = Q::Item<'a>; type Item = Q::Item<'a>;
@ -110,6 +112,13 @@ where
let entity_index = ArchetypeEntityId(entity_index); let entity_index = ArchetypeEntityId(entity_index);
if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) { if fetcher.can_visit_item(entity_index) && filter_fetcher.can_visit_item(entity_index) {
// only tick the world if the filter has fetched, and when the world hasn't
// been ticked yet.
if Q::MUTATES && !self.has_ticked {
self.has_ticked = true;
self.tick = self.world.tick();
}
let i = unsafe { fetcher.get_item(entity_index) }; let i = unsafe { fetcher.get_item(entity_index) };
return Some(i); return Some(i);
} }
@ -147,17 +156,17 @@ pub struct ViewOne<'a, Q: Query> {
impl<'a, Q: Query> ViewOne<'a, Q> { impl<'a, Q: Query> ViewOne<'a, Q> {
pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self { pub fn new(world: &'a World, entity: EntityId, query: Q) -> Self {
let tick = world.tick_tracker().tick_when(Q::MUTATES); //let tick = world.tick_tracker().tick_when(Q::MUTATES);
Self { Self {
world, world,
tick, tick: world.current_tick(),
entity, entity,
query query
} }
} }
pub fn get(&self) -> Option<Q::Item<'a>> { pub fn get(self) -> Option<Q::Item<'a>> {
if let Some(record) = self.world.entities.arch_index.get(&self.entity) { if let Some(record) = self.world.entities.arch_index.get(&self.entity) {
let arch = self.world.archetypes.get(&record.id) let arch = self.world.archetypes.get(&record.id)
.expect("An invalid record was specified for an entity"); .expect("An invalid record was specified for an entity");
@ -165,6 +174,9 @@ impl<'a, Q: Query> ViewOne<'a, Q> {
if self.query.can_visit_archetype(arch) { if self.query.can_visit_archetype(arch) {
let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) }; let mut fetch = unsafe { self.query.fetch(self.world, arch, self.tick) };
if fetch.can_visit_item(record.index) { if fetch.can_visit_item(record.index) {
// only tick the world when something is actually fetched.
self.world.tick();
return Some(unsafe { fetch.get_item(record.index) }); return Some(unsafe { fetch.get_item(record.index) });
} }
} }

View File

@ -2,6 +2,7 @@ use std::marker::PhantomData;
use lyra_ecs_derive::Component; use lyra_ecs_derive::Component;
use crate::query::Filter;
use crate::query::Query; use crate::query::Query;
use crate::query::ViewState; use crate::query::ViewState;
use crate::Entity; use crate::Entity;
@ -98,7 +99,7 @@ impl World {
impl<'a, Q, F> ViewState<'a, Q, F> impl<'a, Q, F> ViewState<'a, Q, F>
where where
Q: Query, Q: Query,
F: Query, F: Filter,
{ {
/// Consumes `self` to return a view that fetches the relation to a specific target entity. /// 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> pub fn relates_to<R>(self, target: Entity) -> ViewState<'a, (Q, QueryRelatesTo<R>), F>

View File

@ -1,7 +1,7 @@
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, Access, ResourceObject, query::{Query, ViewState, ResMut, Res}}; use crate::{World, Access, ResourceObject, query::{Query, Filter, ViewState, ResMut, Res}};
use super::{System, IntoSystem}; use super::{System, IntoSystem};
@ -140,7 +140,7 @@ impl_fn_system_tuple!{ A, B, C, D, E, F2, G, H, I, J, K, L, M, N, O, P }
impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F> impl<'c, Q, F> FnArgFetcher for ViewState<'c, Q, F>
where where
Q: Query + 'static, Q: Query + 'static,
F: Query + 'static, F: Filter + 'static,
{ {
type State = (Q, F); type State = (Q, F);
type Arg<'a, 'state> = ViewState<'a, Q, F>; type Arg<'a, 'state> = ViewState<'a, Q, F>;

View File

@ -2,7 +2,7 @@ use std::{any::TypeId, collections::HashMap, ptr::NonNull};
use atomic_refcell::{AtomicRef, AtomicRefMut}; use atomic_refcell::{AtomicRef, AtomicRefMut};
use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker}; use crate::{archetype::{Archetype, ArchetypeId}, bundle::Bundle, query::{dynamic::DynamicView, AsFilter, AsQuery, Query, ViewIter, ViewOne, ViewState}, resource::ResourceData, ComponentInfo, DynTypeId, DynamicBundle, Entities, Entity, ResourceObject, Tick, TickTracker};
/// The id of the entity for the Archetype. /// The id of the entity for the Archetype.
/// ///
@ -347,9 +347,9 @@ impl World {
} }
/// View into the world for a set of entities that satisfy the query and the filter. /// 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> { pub fn filtered_view<Q: AsQuery, F: AsFilter>(&self) -> ViewState<Q::Query, F::Filter> {
let archetypes = self.archetypes.values().collect(); let archetypes = self.archetypes.values().collect();
ViewState::<Q::Query, F::Query>::new(self, Q::Query::new(), F::Query::new(), archetypes) ViewState::<Q::Query, F::Filter>::new(self, Q::Query::new(), F::Filter::new(), archetypes)
} }
/// 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.
@ -360,9 +360,9 @@ 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 filtered_view_iter<Q: AsQuery, F: AsQuery>(&self) -> ViewIter<Q::Query, F::Query> { pub fn filtered_view_iter<Q: AsQuery, F: AsFilter>(&self) -> ViewIter<Q::Query, F::Filter> {
let archetypes = self.archetypes.values().collect(); let archetypes = self.archetypes.values().collect();
let v = ViewState::new(self, Q::Query::new(), F::Query::new(), archetypes); let v = ViewState::new(self, Q::Query::new(), F::Filter::new(), archetypes);
v.into_iter() v.into_iter()
} }