ecs: implement an actual Filter trait, create a Changed filter
This commit is contained in:
parent
2107b8f7b0
commit
782d64f6cf
|
@ -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)]
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -6,3 +6,6 @@ pub use or::*;
|
||||||
|
|
||||||
mod not;
|
mod not;
|
||||||
pub use not::*;
|
pub use not::*;
|
||||||
|
|
||||||
|
mod changed;
|
||||||
|
pub use changed::*;
|
|
@ -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};
|
||||||
|
|
|
@ -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 }
|
||||||
|
|
|
@ -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) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>;
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue