diff --git a/proc-macro/src/lib.rs b/proc-macro/src/lib.rs index da1fdac..0fbefdc 100644 --- a/proc-macro/src/lib.rs +++ b/proc-macro/src/lib.rs @@ -114,7 +114,7 @@ fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result let my_crate = proc_macro_crate::crate_name("bevy-trait-query").unwrap(); let my_crate = match my_crate { - proc_macro_crate::FoundCrate::Itself => quote! { crate }, + proc_macro_crate::FoundCrate::Itself => quote! { bevy_trait_query }, proc_macro_crate::FoundCrate::Name(x) => { let ident = quote::format_ident!("{x}"); quote! { #ident } @@ -156,7 +156,7 @@ fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result #where_clause { type Item<'__w> = #my_crate::ReadTraits<'__w, #trait_object>; - type Fetch<'__w> = #my_crate::ReadAllTraitsFetch<'__w, #trait_object>; + type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>; type ReadOnly = Self; type State = #my_crate::TraitQueryState<#trait_object>; @@ -264,7 +264,7 @@ fn impl_trait_query(arg: TokenStream, item: TokenStream) -> Result #where_clause { type Item<'__w> = #my_crate::WriteTraits<'__w, #trait_object>; - type Fetch<'__w> = #my_crate::WriteAllTraitsFetch<'__w, #trait_object>; + type Fetch<'__w> = <#my_crate::All<&'__a #trait_object> as #imports::WorldQuery>::Fetch<'__w>; type ReadOnly = &'__a #trait_object; type State = #my_crate::TraitQueryState<#trait_object>; diff --git a/src/all.rs b/src/all.rs index cfaa84d..f1f191f 100644 --- a/src/all.rs +++ b/src/all.rs @@ -1,15 +1,17 @@ +use bevy::ecs::{ + change_detection::{DetectChanges, Mut, Ref}, + component::{ComponentId, Tick}, + entity::Entity, + query::{QueryItem, ReadOnlyWorldQuery, WorldQuery}, + storage::{SparseSets, Table, TableRow}, + world::{unsafe_world_cell::UnsafeWorldCell, World}, +}; +use bevy::ptr::UnsafeCellDeref; + use crate::{ debug_unreachable, trait_registry_error, zip_exact, TraitImplMeta, TraitImplRegistry, TraitQuery, TraitQueryState, }; -use bevy::ecs::change_detection::Mut; -use bevy::ecs::component::{ComponentId, Tick}; -use bevy::ecs::entity::Entity; -use bevy::ecs::query::{QueryItem, ReadOnlyWorldQuery, WorldQuery}; -use bevy::ecs::storage::{SparseSets, Table, TableRow}; -use bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell; -use bevy::ecs::world::World; -use bevy::ptr::UnsafeCellDeref; /// Read-access to all components implementing a trait for a given entity. pub struct ReadTraits<'a, Trait: ?Sized + TraitQuery> { @@ -24,6 +26,8 @@ pub struct ReadTraits<'a, Trait: ?Sized + TraitQuery> { /// The fetch impl registers read-access for all of these components, /// so there will be no runtime conflicts. sparse_sets: &'a SparseSets, + last_run: Tick, + this_run: Tick, } #[doc(hidden)] @@ -39,10 +43,12 @@ pub struct ReadTableTraitsIter<'a, Trait: ?Sized> { // Grants shared access to the components corresponding to `components` in this table. // Not all components are guaranteed to exist in the table. table: &'a Table, + last_run: Tick, + this_run: Tick, } impl<'a, Trait: ?Sized + TraitQuery> Iterator for ReadTableTraitsIter<'a, Trait> { - type Item = &'a Trait; + type Item = Ref<'a, Trait>; fn next(&mut self) -> Option { // Iterate the remaining table components that are registered, // until we find one that exists in the table. @@ -55,7 +61,19 @@ impl<'a, Trait: ?Sized + TraitQuery> Iterator for ReadTableTraitsIter<'a, Trait> .byte_add(self.table_row.index() * meta.size_bytes) }; let trait_object = unsafe { meta.dyn_ctor.cast(ptr) }; - Some(trait_object) + + // SAFETY: we know that the `table_row` is a valid index. + // Read access has been registered, so we can dereference it immutably. + let added_tick = unsafe { column.get_added_ticks_unchecked(self.table_row).deref() }; + let changed_tick = unsafe { column.get_changed_ticks_unchecked(self.table_row).deref() }; + + Some(Ref::new( + trait_object, + added_tick, + changed_tick, + self.last_run, + self.this_run, + )) } } @@ -67,28 +85,37 @@ pub struct ReadSparseTraitsIter<'a, Trait: ?Sized> { entity: Entity, // Grants shared access to the components corresponding to both `components` and `entity`. sparse_sets: &'a SparseSets, + last_run: Tick, + this_run: Tick, } impl<'a, Trait: ?Sized + TraitQuery> Iterator for ReadSparseTraitsIter<'a, Trait> { - type Item = &'a Trait; + type Item = Ref<'a, Trait>; fn next(&mut self) -> Option { // Iterate the remaining sparse set components that are registered, // until we find one that exists in the archetype. - let (ptr, meta) = unsafe { zip_exact(&mut self.components, &mut self.meta) }.find_map( - |(&component, meta)| { - self.sparse_sets - .get(component) - .and_then(|set| set.get(self.entity)) - .zip(Some(meta)) - }, - )?; + let ((ptr, ticks_ptr), meta) = unsafe { zip_exact(&mut self.components, &mut self.meta) } + .find_map(|(&component, meta)| { + self.sparse_sets + .get(component) + .and_then(|set| set.get_with_ticks(self.entity)) + .zip(Some(meta)) + })?; let trait_object = unsafe { meta.dyn_ctor.cast(ptr) }; - Some(trait_object) + let added_tick = unsafe { ticks_ptr.added.deref() }; + let changed_tick = unsafe { ticks_ptr.changed.deref() }; + Some(Ref::new( + trait_object, + added_tick, + changed_tick, + self.last_run, + self.this_run, + )) } } impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for ReadTraits<'w, Trait> { - type Item = &'w Trait; + type Item = Ref<'w, Trait>; type IntoIter = CombinedReadTraitsIter<'w, Trait>; #[inline] fn into_iter(self) -> Self::IntoIter { @@ -97,19 +124,23 @@ impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for ReadTraits<'w, Trait> { meta: self.registry.table_meta.iter(), table: self.table, table_row: self.table_row, + last_run: self.last_run, + this_run: self.this_run, }; let sparse = ReadSparseTraitsIter { components: self.registry.sparse_components.iter(), meta: self.registry.sparse_meta.iter(), entity: self.table.entities()[self.table_row.index()], sparse_sets: self.sparse_sets, + last_run: self.last_run, + this_run: self.this_run, }; table.chain(sparse) } } impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for &ReadTraits<'w, Trait> { - type Item = &'w Trait; + type Item = Ref<'w, Trait>; type IntoIter = CombinedReadTraitsIter<'w, Trait>; #[inline] fn into_iter(self) -> Self::IntoIter { @@ -118,12 +149,16 @@ impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for &ReadTraits<'w, Trait> { meta: self.registry.table_meta.iter(), table: self.table, table_row: self.table_row, + last_run: self.last_run, + this_run: self.this_run, }; let sparse = ReadSparseTraitsIter { components: self.registry.sparse_components.iter(), meta: self.registry.sparse_meta.iter(), entity: self.table.entities()[self.table_row.index()], sparse_sets: self.sparse_sets, + last_run: self.last_run, + this_run: self.this_run, }; table.chain(sparse) } @@ -134,13 +169,27 @@ impl<'w, Trait: ?Sized + TraitQuery> ReadTraits<'w, Trait> { pub fn iter(&self) -> CombinedReadTraitsIter<'w, Trait> { self.into_iter() } + + /// Returns an iterator over the components implementing `Trait` for the current entity + /// that were added since the last time the system was run. + pub fn iter_added(&self) -> impl Iterator> { + self.iter().filter(DetectChanges::is_added) + } + + /// Returns an iterator over the components implementing `Trait` for the current entity + /// whose values were changed since the last time the system was run. + pub fn iter_changed(&self) -> impl Iterator> { + self.iter().filter(DetectChanges::is_changed) + } } #[doc(hidden)] -pub struct ReadAllTraitsFetch<'w, Trait: ?Sized> { +pub struct AllTraitsFetch<'w, Trait: ?Sized> { registry: &'w TraitImplRegistry, table: Option<&'w Table>, sparse_sets: &'w SparseSets, + last_run: Tick, + this_run: Tick, } /// Write-access to all components implementing a trait for a given entity. @@ -267,15 +316,40 @@ impl<'a, Trait: ?Sized + TraitQuery> Iterator for WriteSparseTraitsIter<'a, Trai } } -impl<'w, Trait: ?Sized + TraitQuery> WriteTraits<'w, Trait> { +impl WriteTraits<'_, Trait> { /// Returns an iterator over the components implementing `Trait` for the current entity. pub fn iter(&self) -> CombinedReadTraitsIter<'_, Trait> { self.into_iter() } + /// Returns a mutable iterator over the components implementing `Trait` for the current entity. pub fn iter_mut(&mut self) -> CombinedWriteTraitsIter<'_, Trait> { self.into_iter() } + + /// Returns an iterator over the components implementing `Trait` for the current entity + /// that were added since the last time the system was run. + pub fn iter_added(&self) -> impl Iterator> { + self.iter().filter(DetectChanges::is_added) + } + + /// Returns an iterator over the components implementing `Trait` for the current entity + /// whose values were changed since the last time the system was run. + pub fn iter_changed(&self) -> impl Iterator> { + self.iter().filter(DetectChanges::is_changed) + } + + /// Returns a mutable iterator over the components implementing `Trait` for the current entity + /// that were added since the last time the system was run. + pub fn iter_added_mut(&mut self) -> impl Iterator> { + self.iter_mut().filter(DetectChanges::is_added) + } + + /// Returns a mutable iterator over the components implementing `Trait` for the current entity + /// whose values were changed since the last time the system was run. + pub fn iter_changed_mut(&mut self) -> impl Iterator> { + self.iter_mut().filter(DetectChanges::is_changed) + } } impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for WriteTraits<'w, Trait> { @@ -306,7 +380,7 @@ impl<'w, Trait: ?Sized + TraitQuery> IntoIterator for WriteTraits<'w, Trait> { impl<'world, 'local, Trait: ?Sized + TraitQuery> IntoIterator for &'local WriteTraits<'world, Trait> { - type Item = &'local Trait; + type Item = Ref<'local, Trait>; type IntoIter = CombinedReadTraitsIter<'local, Trait>; #[inline] fn into_iter(self) -> Self::IntoIter { @@ -315,12 +389,16 @@ impl<'world, 'local, Trait: ?Sized + TraitQuery> IntoIterator meta: self.registry.table_meta.iter(), table: self.table, table_row: self.table_row, + last_run: self.last_run, + this_run: self.this_run, }; let sparse = ReadSparseTraitsIter { components: self.registry.sparse_components.iter(), meta: self.registry.sparse_meta.iter(), entity: self.table.entities()[self.table_row.index()], sparse_sets: self.sparse_sets, + last_run: self.last_run, + this_run: self.this_run, }; table.chain(sparse) } @@ -353,36 +431,19 @@ impl<'world, 'local, Trait: ?Sized + TraitQuery> IntoIterator } } -#[doc(hidden)] -pub struct WriteAllTraitsFetch<'w, Trait: ?Sized + TraitQuery> { - registry: &'w TraitImplRegistry, - table: Option<&'w Table>, - sparse_sets: &'w SparseSets, - last_run: Tick, - this_run: Tick, -} - /// `WorldQuery` adapter that fetches all implementations of a given trait for an entity. /// /// You can usually just use `&dyn Trait` or `&mut dyn Trait` as a `WorldQuery` directly. pub struct All(T); -/// `WorldQuery` adapter that fetches all implementations of a given trait for an entity, with -/// the additional condition that they have also changed since the last tick. -pub struct ChangedAll(T); - -/// `WorldQuery` adapter that fetches all implementations of a given trait for an entity, with -/// the additional condition that they have been added since the last tick. -pub struct AddedAll(T); - unsafe impl<'a, Trait: ?Sized + TraitQuery> ReadOnlyWorldQuery for All<&'a Trait> {} -/// SAFETY: We only access the components registered in the trait registry. -/// This is known to match the set of components in the `DynQueryState`, -/// which is used to match archetypes and register world access. +// SAFETY: We only access the components registered in the trait registry. +// This is known to match the set of components in the TraitQueryState, +// which is used to match archetypes and register world access. unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> { type Item<'w> = ReadTraits<'w, Trait>; - type Fetch<'w> = ReadAllTraitsFetch<'w, Trait>; + type Fetch<'w> = AllTraitsFetch<'w, Trait>; type ReadOnly = Self; type State = TraitQueryState; @@ -395,24 +456,28 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> { unsafe fn init_fetch<'w>( world: UnsafeWorldCell<'w>, _state: &Self::State, - _last_run: Tick, - _this_run: Tick, - ) -> ReadAllTraitsFetch<'w, Trait> { - ReadAllTraitsFetch { + last_run: Tick, + this_run: Tick, + ) -> Self::Fetch<'w> { + AllTraitsFetch { registry: world .get_resource() .unwrap_or_else(|| trait_registry_error()), table: None, sparse_sets: &world.storages().sparse_sets, + last_run, + this_run, } } #[inline] unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - ReadAllTraitsFetch { + AllTraitsFetch { registry: fetch.registry, table: fetch.table, sparse_sets: fetch.sparse_sets, + last_run: fetch.last_run, + this_run: fetch.this_run, } } @@ -421,7 +486,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> { #[inline] unsafe fn set_archetype<'w>( - fetch: &mut ReadAllTraitsFetch<'w, Trait>, + fetch: &mut Self::Fetch<'w>, _state: &Self::State, _archetype: &'w bevy::ecs::archetype::Archetype, table: &'w bevy::ecs::storage::Table, @@ -430,7 +495,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> { } unsafe fn set_table<'w>( - fetch: &mut ReadAllTraitsFetch<'w, Trait>, + fetch: &mut Self::Fetch<'w>, _state: &Self::State, table: &'w bevy::ecs::storage::Table, ) { @@ -450,6 +515,8 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> { table, table_row, sparse_sets: fetch.sparse_sets, + last_run: fetch.last_run, + this_run: fetch.this_run, } } @@ -494,12 +561,12 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a Trait> { } } -/// SAFETY: We only access the components registered in the trait registry. -/// This is known to match the set of components in the `DynQueryState`, -/// which is used to match archetypes and register world access. +// SAFETY: We only access the components registered in the trait registry. +// This is known to match the set of components in the TraitQueryState, +// which is used to match archetypes and register world access. unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> { type Item<'w> = WriteTraits<'w, Trait>; - type Fetch<'w> = WriteAllTraitsFetch<'w, Trait>; + type Fetch<'w> = AllTraitsFetch<'w, Trait>; type ReadOnly = All<&'a Trait>; type State = TraitQueryState; @@ -514,8 +581,8 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> { _state: &Self::State, last_run: Tick, this_run: Tick, - ) -> WriteAllTraitsFetch<'w, Trait> { - WriteAllTraitsFetch { + ) -> Self::Fetch<'w> { + AllTraitsFetch { registry: world .get_resource() .unwrap_or_else(|| trait_registry_error()), @@ -528,7 +595,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> { #[inline] unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - WriteAllTraitsFetch { + AllTraitsFetch { registry: fetch.registry, table: fetch.table, sparse_sets: fetch.sparse_sets, @@ -542,7 +609,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> { #[inline] unsafe fn set_archetype<'w>( - fetch: &mut WriteAllTraitsFetch<'w, Trait>, + fetch: &mut Self::Fetch<'w>, _state: &Self::State, _archetype: &'w bevy::ecs::archetype::Archetype, table: &'w bevy::ecs::storage::Table, @@ -552,7 +619,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for All<&'a mut Trait> { #[inline] unsafe fn set_table<'w>( - fetch: &mut WriteAllTraitsFetch<'w, Trait>, + fetch: &mut Self::Fetch<'w>, _state: &Self::State, table: &'w bevy::ecs::storage::Table, ) { diff --git a/src/lib.rs b/src/lib.rs index 87cda94..0319a0c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,7 @@ //! //! In order to be useful within bevy, you'll want to be able to query for this trait. //! -//! ```ignore +//! ``` //! # use bevy::prelude::*; //! # // Required to make the macro work, because cargo thinks //! # // we are in `bevy_trait_query` when compiling this example. @@ -167,7 +167,7 @@ //! you can use the filter [`One`](crate::one::One). This has significantly better performance than iterating //! over all trait impls. //! -//! ```ignore +//! ``` //! # use bevy::prelude::*; //! # use bevy_trait_query::*; //! # @@ -189,6 +189,58 @@ //! # bevy::ecs::system::assert_is_system(show_tooltips); //! ``` //! +//! Trait queries support basic change detection filtration. So to get all the components that +//! implement the target trait, and have also changed in some way since the last tick, you can: +//! ```no_run +//! # use bevy::prelude::*; +//! # use bevy_trait_query::*; +//! # +//! # #[bevy_trait_query::queryable] +//! # pub trait Tooltip { +//! # fn tooltip(&self) -> &str; +//! # } +//! # +//! fn show_tooltips( +//! tooltips_query: Query> +//! // ... +//! ) { +//! // Iterate over all entities with at least one component implementing `Tooltip` +//! for entity_tooltips in &tooltips_query { +//! // Iterate over each component for the current entity that changed since the last time the system was run. +//! for tooltip in entity_tooltips.iter_changed() { +//! println!("Changed Tooltip: {}", tooltip.tooltip()); +//! } +//! } +//! } +//! ``` +//! +//! Similar to [`iter_changed`](crate::all::All::iter_changed), we have [`iter_added`](crate::all::All::iter_added) +//! to detect entities which have had a trait-implementing component added since the last tick. +//! +//! If you know you have only one component that implements the target trait, +//! you can use `OneAdded` or `OneChanged` which behave more like the typical +//! `bevy` `Added/Changed` filters: +//! ```no_run +//! # use bevy::prelude::*; +//! # use bevy_trait_query::*; +//! # +//! # #[bevy_trait_query::queryable] +//! # pub trait Tooltip { +//! # fn tooltip(&self) -> &str; +//! # } +//! # +//! fn show_tooltips( +//! tooltips_query: Query, OneChanged> +//! // ... +//! ) { +//! // Iterate over each entity that has one tooltip implementing component that has also changed +//! for tooltip in &tooltips_query { +//! println!("Changed Tooltip: {}", tooltip.tooltip()); +//! } +//! } +//! ``` +//! Note in the above example how `OneChanged` does *not* take a reference to the trait object! +//! //! # Performance //! //! The performance of trait queries is quite competitive. Here are some benchmarks for simple cases: @@ -199,6 +251,7 @@ //! | 2 matches | 17.501 µs | - | 102.83 µs | //! | 1-2 matches | - | 16.959 µs | 82.179 µs | //! + use bevy::{ ecs::{ component::{ComponentId, StorageType}, diff --git a/src/one.rs b/src/one.rs index 56758cb..b3d3d91 100644 --- a/src/one.rs +++ b/src/one.rs @@ -1,65 +1,39 @@ -use crate::{debug_unreachable, zip_exact, TraitImplMeta, TraitQuery, TraitQueryState}; -use bevy::ecs::change_detection::Mut; -use bevy::ecs::component::{ComponentId, ComponentTicks, Tick}; -use bevy::ecs::entity::Entity; -use bevy::ecs::query::{QueryItem, ReadOnlyWorldQuery, WorldQuery}; -use bevy::ecs::storage::{ComponentSparseSet, SparseSets, TableRow}; -use bevy::ecs::world::unsafe_world_cell::UnsafeWorldCell; -use bevy::ecs::world::World; +use std::{cell::UnsafeCell, marker::PhantomData}; + +use bevy::ecs::{ + archetype::{Archetype, ArchetypeComponentId}, + change_detection::{Mut, Ref}, + component::{ComponentId, Tick}, + entity::Entity, + query::{Access, FilteredAccess, QueryItem, ReadOnlyWorldQuery, WorldQuery}, + storage::{ComponentSparseSet, SparseSets, Table, TableRow}, + world::{unsafe_world_cell::UnsafeWorldCell, World}, +}; use bevy::ptr::{Ptr, ThinSlicePtr, UnsafeCellDeref}; -use std::cell::UnsafeCell; -pub struct ReadTraitFetch<'w, Trait: ?Sized> { - // While we have shared access to all sparse set components, - // in practice we will only read the components specified in the `FetchState`. - // These accesses have been registered, which prevents runtime conflicts. - sparse_sets: &'w SparseSets, - // After `Fetch::set_archetype` or `set_table` has been called, - // this will carry the component data and metadata for the first trait impl found in the archetype. - storage: ReadStorage<'w, Trait>, -} +use crate::{ + debug_unreachable, trait_registry_error, zip_exact, TraitImplMeta, TraitImplRegistry, + TraitQuery, TraitQueryState, +}; -enum ReadStorage<'w, Trait: ?Sized> { - Uninit, - Table { - /// This points to one of the component table columns, - /// corresponding to one of the `ComponentId`s in the fetch state. - /// The fetch impl registers read access for all of these components, - /// so there will be no runtime conflicts. - column: Ptr<'w>, - ticks: Option>>, - meta: TraitImplMeta, - }, - SparseSet { - /// This gives us access to one of the components implementing the trait. - /// The fetch impl registers read access for all components implementing the trait, - /// so there will not be any runtime conflicts. - components: &'w ComponentSparseSet, - meta: TraitImplMeta, - }, -} - -#[doc(hidden)] -pub struct WriteTraitFetch<'w, Trait: ?Sized> { - // While we have shared mutable access to all sparse set components, - // in practice we will only modify the components specified in the `FetchState`. +pub struct OneTraitFetch<'w, Trait: ?Sized> { + // While we have shared access to all sparse set components, + // in practice we will only access the components specified in the `FetchState`. // These accesses have been registered, which prevents runtime conflicts. sparse_sets: &'w SparseSets, - // After `Fetch::set_archetype` or `set_table` has been called, // this will carry the component data and metadata for the first trait impl found in the archetype. - storage: WriteStorage<'w, Trait>, - + storage: FetchStorage<'w, Trait>, last_run: Tick, this_run: Tick, } -enum WriteStorage<'w, Trait: ?Sized> { +enum FetchStorage<'w, Trait: ?Sized> { Uninit, Table { - /// This is a shared mutable pointer to one of the component table columns, + /// This points to one of the component table columns, /// corresponding to one of the `ComponentId`s in the fetch state. - /// The fetch impl registers write access for all of these components, + /// The fetch impl registers access for all of these components, /// so there will be no runtime conflicts. column: Ptr<'w>, added_ticks: ThinSlicePtr<'w, UnsafeCell>, @@ -67,8 +41,9 @@ enum WriteStorage<'w, Trait: ?Sized> { meta: TraitImplMeta, }, SparseSet { - /// This gives us shared mutable access to one of the components implementing the trait. - /// The fetch impl registers write access for all components implementing the trait, so there will be no runtime conflicts. + /// This gives us access to one of the components implementing the trait. + /// The fetch impl registers access for all components implementing the trait, + /// so there will not be any runtime conflicts. components: &'w ComponentSparseSet, meta: TraitImplMeta, }, @@ -79,11 +54,11 @@ pub struct One(pub T); unsafe impl<'a, T: ?Sized + TraitQuery> ReadOnlyWorldQuery for One<&'a T> {} -/// SAFETY: We only access the components registered in `DynQueryState`. -/// This same set of components is used to match archetypes, and used to register world access. +// SAFETY: We only access the components registered in TraitQueryState. +// This same set of components is used to match archetypes, and used to register world access. unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { - type Item<'w> = &'w Trait; - type Fetch<'w> = ReadTraitFetch<'w, Trait>; + type Item<'w> = Ref<'w, Trait>; + type Fetch<'w> = OneTraitFetch<'w, Trait>; type ReadOnly = Self; type State = TraitQueryState; @@ -98,32 +73,38 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { _state: &Self::State, _last_run: Tick, _this_run: Tick, - ) -> ReadTraitFetch<'w, Trait> { - ReadTraitFetch { - storage: ReadStorage::Uninit, + ) -> OneTraitFetch<'w, Trait> { + OneTraitFetch { + storage: FetchStorage::Uninit, + last_run: Tick::new(0), sparse_sets: &world.storages().sparse_sets, + this_run: Tick::new(0), } } #[inline] unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - ReadTraitFetch { + OneTraitFetch { storage: match fetch.storage { - ReadStorage::Uninit => ReadStorage::Uninit, - ReadStorage::Table { + FetchStorage::Uninit => FetchStorage::Uninit, + FetchStorage::Table { column, - ticks, + added_ticks, + changed_ticks, meta, - } => ReadStorage::Table { + } => FetchStorage::Table { column, - ticks, + added_ticks, + changed_ticks, meta, }, - ReadStorage::SparseSet { components, meta } => { - ReadStorage::SparseSet { components, meta } + FetchStorage::SparseSet { components, meta } => { + FetchStorage::SparseSet { components, meta } } }, + last_run: Tick::new(0), sparse_sets: fetch.sparse_sets, + this_run: Tick::new(0), } } @@ -132,7 +113,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { #[inline] unsafe fn set_archetype<'w>( - fetch: &mut ReadTraitFetch<'w, Trait>, + fetch: &mut OneTraitFetch<'w, Trait>, state: &Self::State, _archetype: &'w bevy::ecs::archetype::Archetype, table: &'w bevy::ecs::storage::Table, @@ -141,9 +122,10 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { // We check the table components first since it is faster to retrieve data of this type. for (&component, &meta) in zip_exact(&*state.components, &*state.meta) { if let Some(column) = table.get_column(component) { - fetch.storage = ReadStorage::Table { + fetch.storage = FetchStorage::Table { column: column.get_data_ptr(), - ticks: None, + added_ticks: column.get_added_ticks_slice().into(), + changed_ticks: column.get_changed_ticks_slice().into(), meta, }; return; @@ -151,7 +133,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { } for (&component, &meta) in zip_exact(&*state.components, &*state.meta) { if let Some(sparse_set) = fetch.sparse_sets.get(component) { - fetch.storage = ReadStorage::SparseSet { + fetch.storage = FetchStorage::SparseSet { components: sparse_set, meta, }; @@ -164,16 +146,17 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { #[inline] unsafe fn set_table<'w>( - fetch: &mut ReadTraitFetch<'w, Trait>, + fetch: &mut OneTraitFetch<'w, Trait>, state: &Self::State, table: &'w bevy::ecs::storage::Table, ) { // Search for a registered trait impl that is present in the table. for (&component, &meta) in std::iter::zip(&*state.components, &*state.meta) { if let Some(column) = table.get_column(component) { - fetch.storage = ReadStorage::Table { + fetch.storage = FetchStorage::Table { column: column.get_data_ptr(), - ticks: None, + added_ticks: column.get_added_ticks_slice().into(), + changed_ticks: column.get_changed_ticks_slice().into(), meta, } } @@ -189,21 +172,49 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { table_row: TableRow, ) -> Self::Item<'w> { let table_row = table_row.index(); - match fetch.storage { + let dyn_ctor; + let (ptr, added, changed) = match fetch.storage { // SAFETY: This function must have been called after `set_archetype`, // so we know that `self.storage` has been initialized. - ReadStorage::Uninit => debug_unreachable(), - ReadStorage::Table { column, meta, .. } => { + FetchStorage::Uninit => debug_unreachable(), + FetchStorage::Table { + column, + added_ticks, + changed_ticks, + meta, + } => { + dyn_ctor = meta.dyn_ctor; let ptr = column.byte_add(table_row * meta.size_bytes); - meta.dyn_ctor.cast(ptr) + ( + ptr, + // SAFETY: We have read access to the component, so by extension + // we have access to the corresponding `ComponentTicks`. + added_ticks.get(table_row).deref(), + changed_ticks.get(table_row).deref(), + ) } - ReadStorage::SparseSet { components, meta } => { - let ptr = components - .get(entity) + FetchStorage::SparseSet { components, meta } => { + dyn_ctor = meta.dyn_ctor; + let (ptr, ticks) = components + .get_with_ticks(entity) .unwrap_or_else(|| debug_unreachable()); - meta.dyn_ctor.cast(ptr) + ( + ptr, + // SAFETY: We have read access to the component, so by extension + // we have access to the corresponding `ComponentTicks`. + ticks.added.deref(), + ticks.changed.deref(), + ) } - } + }; + + Ref::new( + dyn_ctor.cast(ptr), + added, + changed, + fetch.last_run, + fetch.this_run, + ) } #[inline] @@ -248,11 +259,11 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a Trait> { } } -/// SAFETY: We only access the components registered in `DynQueryState`. -/// This same set of components is used to match archetypes, and used to register world access. +// SAFETY: We only access the components registered in TraitQueryState. +// This same set of components is used to match archetypes, and used to register world access. unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { type Item<'w> = Mut<'w, Trait>; - type Fetch<'w> = WriteTraitFetch<'w, Trait>; + type Fetch<'w> = OneTraitFetch<'w, Trait>; type ReadOnly = One<&'a Trait>; type State = TraitQueryState; @@ -267,9 +278,9 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { _state: &Self::State, last_run: Tick, this_run: Tick, - ) -> WriteTraitFetch<'w, Trait> { - WriteTraitFetch { - storage: WriteStorage::Uninit, + ) -> OneTraitFetch<'w, Trait> { + OneTraitFetch { + storage: FetchStorage::Uninit, sparse_sets: &world.storages().sparse_sets, last_run, this_run, @@ -278,22 +289,22 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { #[inline] unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - WriteTraitFetch { + OneTraitFetch { storage: match fetch.storage { - WriteStorage::Uninit => WriteStorage::Uninit, - WriteStorage::Table { + FetchStorage::Uninit => FetchStorage::Uninit, + FetchStorage::Table { column, meta, added_ticks, changed_ticks, - } => WriteStorage::Table { + } => FetchStorage::Table { column, meta, added_ticks, changed_ticks, }, - WriteStorage::SparseSet { components, meta } => { - WriteStorage::SparseSet { components, meta } + FetchStorage::SparseSet { components, meta } => { + FetchStorage::SparseSet { components, meta } } }, sparse_sets: fetch.sparse_sets, @@ -307,7 +318,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { #[inline] unsafe fn set_archetype<'w>( - fetch: &mut WriteTraitFetch<'w, Trait>, + fetch: &mut OneTraitFetch<'w, Trait>, state: &Self::State, _archetype: &'w bevy::ecs::archetype::Archetype, table: &'w bevy::ecs::storage::Table, @@ -315,7 +326,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { // Search for a registered trait impl that is present in the archetype. for (&component, &meta) in zip_exact(&*state.components, &*state.meta) { if let Some(column) = table.get_column(component) { - fetch.storage = WriteStorage::Table { + fetch.storage = FetchStorage::Table { column: column.get_data_ptr(), added_ticks: column.get_added_ticks_slice().into(), changed_ticks: column.get_changed_ticks_slice().into(), @@ -326,7 +337,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { } for (&component, &meta) in zip_exact(&*state.components, &*state.meta) { if let Some(sparse_set) = fetch.sparse_sets.get(component) { - fetch.storage = WriteStorage::SparseSet { + fetch.storage = FetchStorage::SparseSet { components: sparse_set, meta, }; @@ -339,14 +350,14 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { #[inline] unsafe fn set_table<'w>( - fetch: &mut WriteTraitFetch<'w, Trait>, + fetch: &mut OneTraitFetch<'w, Trait>, state: &Self::State, table: &'w bevy::ecs::storage::Table, ) { // Search for a registered trait impl that is present in the table. for (&component, &meta) in std::iter::zip(&*state.components, &*state.meta) { if let Some(column) = table.get_column(component) { - fetch.storage = WriteStorage::Table { + fetch.storage = FetchStorage::Table { column: column.get_data_ptr(), added_ticks: column.get_added_ticks_slice().into(), changed_ticks: column.get_changed_ticks_slice().into(), @@ -370,8 +381,8 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { let (ptr, added, changed) = match fetch.storage { // SAFETY: This function must have been called after `set_archetype`, // so we know that `self.storage` has been initialized. - WriteStorage::Uninit => debug_unreachable(), - WriteStorage::Table { + FetchStorage::Uninit => debug_unreachable(), + FetchStorage::Table { column, added_ticks, changed_ticks, @@ -390,7 +401,7 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { changed_ticks.get(table_row).deref_mut(), ) } - WriteStorage::SparseSet { components, meta } => { + FetchStorage::SparseSet { components, meta } => { dyn_ctor = meta.dyn_ctor; let (ptr, ticks) = components .get_with_ticks(entity) @@ -457,3 +468,344 @@ unsafe impl<'a, Trait: ?Sized + TraitQuery> WorldQuery for One<&'a mut Trait> { state.matches_component_set_one(set_contains_id) } } + +enum ChangeDetectionStorage<'w> { + Uninit, + Table { + /// This points to one of the component table columns, + /// corresponding to one of the `ComponentId`s in the fetch state. + /// The fetch impl registers read access for all of these components, + /// so there will be no runtime conflicts. + ticks: ThinSlicePtr<'w, UnsafeCell>, + }, + SparseSet { + /// This gives us access to one of the components implementing the trait. + /// The fetch impl registers read access for all components implementing the trait, + /// so there will not be any runtime conflicts. + components: &'w ComponentSparseSet, + }, +} + +/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component +/// implementing a trait, whose value has changed since the last time the system ran. +pub struct OneAdded { + marker: PhantomData<&'static Trait>, +} + +pub struct ChangeDetectionFetch<'w, Trait: ?Sized + TraitQuery> { + registry: &'w TraitImplRegistry, + storage: ChangeDetectionStorage<'w>, + sparse_sets: &'w SparseSets, + last_run: Tick, + this_run: Tick, +} + +unsafe impl WorldQuery for OneAdded { + type Item<'w> = bool; + type Fetch<'w> = ChangeDetectionFetch<'w, Trait>; + type ReadOnly = Self; + type State = TraitQueryState; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + _state: &Self::State, + last_run: Tick, + this_run: Tick, + ) -> Self::Fetch<'w> { + Self::Fetch::<'w> { + registry: world + .get_resource() + .unwrap_or_else(|| trait_registry_error()), + storage: ChangeDetectionStorage::Uninit, + sparse_sets: &world.storages().sparse_sets, + last_run, + this_run, + } + } + + unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { + Self::Fetch { + registry: fetch.registry, + storage: match fetch.storage { + ChangeDetectionStorage::Uninit => ChangeDetectionStorage::Uninit, + ChangeDetectionStorage::Table { ticks } => ChangeDetectionStorage::Table { ticks }, + ChangeDetectionStorage::SparseSet { components } => { + ChangeDetectionStorage::SparseSet { components } + } + }, + sparse_sets: fetch.sparse_sets, + last_run: fetch.last_run, + this_run: fetch.this_run, + } + } + + // This will always be false for us, as we (so far) do not know at compile time whether the + // components our trait has been impl'd for are stored in table or in sparse set + const IS_DENSE: bool = false; + const IS_ARCHETYPAL: bool = false; + + #[inline] + unsafe fn set_archetype<'w>( + fetch: &mut Self::Fetch<'w>, + state: &Self::State, + _archetype: &'w Archetype, + table: &'w Table, + ) { + // Search for a registered trait impl that is present in the archetype. + // We check the table components first since it is faster to retrieve data of this type. + for &component in &*state.components { + if let Some(column) = table.get_column(component) { + fetch.storage = ChangeDetectionStorage::Table { + ticks: ThinSlicePtr::from(column.get_added_ticks_slice()), + }; + return; + } + } + for &component in &*state.components { + if let Some(components) = fetch.sparse_sets.get(component) { + fetch.storage = ChangeDetectionStorage::SparseSet { components }; + return; + } + } + // At least one of the components must be present in the table/sparse set. + debug_unreachable() + } + + #[inline] + unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + // only gets called if IS_DENSE == true, which does not hold for us + debug_unreachable() + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w> { + let ticks_ptr = match fetch.storage { + ChangeDetectionStorage::Uninit => { + // set_archetype must have been called already + debug_unreachable() + } + ChangeDetectionStorage::Table { ticks } => ticks.get(table_row.index()), + ChangeDetectionStorage::SparseSet { components } => components + .get_added_ticks(entity) + .unwrap_or_else(|| debug_unreachable()), + }; + + (*ticks_ptr) + .deref() + .is_newer_than(fetch.last_run, fetch.this_run) + } + + #[inline(always)] + unsafe fn filter_fetch( + fetch: &mut Self::Fetch<'_>, + entity: Entity, + table_row: TableRow, + ) -> bool { + Self::fetch(fetch, entity, table_row) + } + + #[inline] + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + for &component in &*state.components { + assert!( + !access.access().has_write(component), + "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", + std::any::type_name::(), + ); + access.add_read(component); + } + } + + #[inline] + fn update_archetype_component_access( + state: &Self::State, + archetype: &Archetype, + access: &mut Access, + ) { + for &component in &*state.components { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(component) { + access.add_read(archetype_component_id); + } + } + } + + fn init_state(world: &mut World) -> Self::State { + TraitQueryState::init(world) + } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + state.matches_component_set_one(set_contains_id) + } +} + +/// SAFETY: read-only access +unsafe impl ReadOnlyWorldQuery for OneAdded {} + +/// [`WorldQuery`] filter for entities with exactly [one](crate::One) component +/// implementing a trait, which was added since the last time the system ran. +pub struct OneChanged { + marker: PhantomData<&'static Trait>, +} + +unsafe impl WorldQuery for OneChanged { + type Item<'w> = bool; + type Fetch<'w> = ChangeDetectionFetch<'w, Trait>; + type ReadOnly = Self; + type State = TraitQueryState; + + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + item + } + + unsafe fn init_fetch<'w>( + world: UnsafeWorldCell<'w>, + _state: &Self::State, + last_run: Tick, + this_run: Tick, + ) -> Self::Fetch<'w> { + Self::Fetch::<'w> { + registry: world + .get_resource() + .unwrap_or_else(|| trait_registry_error()), + storage: ChangeDetectionStorage::Uninit, + sparse_sets: &world.storages().sparse_sets, + last_run, + this_run, + } + } + + unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { + Self::Fetch { + registry: fetch.registry, + storage: match fetch.storage { + ChangeDetectionStorage::Uninit => ChangeDetectionStorage::Uninit, + ChangeDetectionStorage::Table { ticks } => ChangeDetectionStorage::Table { ticks }, + ChangeDetectionStorage::SparseSet { components } => { + ChangeDetectionStorage::SparseSet { components } + } + }, + sparse_sets: fetch.sparse_sets, + last_run: fetch.last_run, + this_run: fetch.this_run, + } + } + + // This will always be false for us, as we (so far) do not know at compile time whether the + // components our trait has been impl'd for are stored in table or in sparse set + const IS_DENSE: bool = false; + const IS_ARCHETYPAL: bool = false; + + #[inline] + unsafe fn set_archetype<'w>( + fetch: &mut Self::Fetch<'w>, + state: &Self::State, + _archetype: &'w Archetype, + table: &'w Table, + ) { + // Search for a registered trait impl that is present in the archetype. + // We check the table components first since it is faster to retrieve data of this type. + for &component in &*state.components { + if let Some(column) = table.get_column(component) { + fetch.storage = ChangeDetectionStorage::Table { + ticks: column.get_changed_ticks_slice().into(), + }; + return; + } + } + for &component in &*state.components { + if let Some(components) = fetch.sparse_sets.get(component) { + fetch.storage = ChangeDetectionStorage::SparseSet { components }; + return; + } + } + // At least one of the components must be present in the table/sparse set. + debug_unreachable() + } + + #[inline] + unsafe fn set_table<'w>(_fetch: &mut Self::Fetch<'w>, _state: &Self::State, _table: &'w Table) { + // only gets called if IS_DENSE == true, which does not hold for us + debug_unreachable() + } + + #[inline(always)] + unsafe fn fetch<'w>( + fetch: &mut Self::Fetch<'w>, + entity: Entity, + table_row: TableRow, + ) -> Self::Item<'w> { + let ticks_ptr = match fetch.storage { + ChangeDetectionStorage::Uninit => { + // set_archetype must have been called already + debug_unreachable() + } + ChangeDetectionStorage::Table { ticks } => ticks.get(table_row.index()), + ChangeDetectionStorage::SparseSet { components } => components + .get_changed_ticks(entity) + .unwrap_or_else(|| debug_unreachable()), + }; + + (*ticks_ptr) + .deref() + .is_newer_than(fetch.last_run, fetch.this_run) + } + + #[inline(always)] + unsafe fn filter_fetch( + fetch: &mut Self::Fetch<'_>, + entity: Entity, + table_row: TableRow, + ) -> bool { + Self::fetch(fetch, entity, table_row) + } + + #[inline] + fn update_component_access(state: &Self::State, access: &mut FilteredAccess) { + for &component in &*state.components { + assert!( + !access.access().has_write(component), + "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", + std::any::type_name::(), + ); + access.add_read(component); + } + } + + #[inline] + fn update_archetype_component_access( + state: &Self::State, + archetype: &Archetype, + access: &mut Access, + ) { + for &component in &*state.components { + if let Some(archetype_component_id) = archetype.get_archetype_component_id(component) { + access.add_read(archetype_component_id); + } + } + } + + fn init_state(world: &mut World) -> Self::State { + TraitQueryState::init(world) + } + + fn matches_component_set( + state: &Self::State, + set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + state.matches_component_set_one(set_contains_id) + } +} + +/// SAFETY: read-only access +unsafe impl ReadOnlyWorldQuery for OneChanged {} diff --git a/src/tests.rs b/src/tests.rs index 69e91a2..cf47323 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,6 +1,9 @@ use super::*; use std::fmt::{Debug, Display}; +// Required for proc macros. +use crate as bevy_trait_query; + #[derive(Resource, Default)] pub struct Output(Vec); @@ -181,6 +184,315 @@ fn age_up_not(mut q: Query<&mut dyn Person, Without>) { } } +#[test] +fn added_all() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + world.spawn(Human("Henry".to_owned(), 22)); + + let mut schedule = Schedule::new(); + schedule.add_systems((print_added_all_info, age_up_fem).chain()); + + schedule.run(&mut world); + + world.spawn((Human("Garbanzo".to_owned(), 17), Fem, Dolphin(17))); + + schedule.run(&mut world); + + // only changes will occur now to the ages of Garbanzo/Reginald, so nothing should be printed + + schedule.run(&mut world); + + println!("{:?}", world.resource::().0); + + assert_eq!( + world.resource::().0, + &[ + "Added people:", + "Henry: 22", + "", + "Added people:", + "Garbanzo: 17", + "Reginald: 17", + "", + "Added people:", + "" + ] + ); +} + +// Prints the name and age of every newly added `Person`. +fn print_added_all_info(people: Query>, mut output: ResMut) { + output.0.push("Added people:".to_string()); + for person in people.iter().flat_map(|p| p.iter_added()) { + output + .0 + .push(format!("{}: {}", person.name(), person.age())); + } + output.0.push(default()); +} + +#[test] +fn changed_all() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + let mut schedule = Schedule::new(); + schedule.add_systems((print_changed_all_info, age_up_fem).chain()); + + // Henry is newly added, so we expect him to be printed + world.spawn(Human("Henry".to_owned(), 22)); + + schedule.run(&mut world); + + // Garbanzo and Dolphin (Reginald) are newly added, so we expect them to be printed + world.spawn((Human("Garbanzo".to_owned(), 17), Fem, Dolphin(17))); + + schedule.run(&mut world); + + // Garbanzo and Dolphin (Reginald) will both be incremented in age by one by `age_up_fem`, so + // they should be printed again + + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + &[ + "Changed people:", + "Henry: 22", + "", + "Changed people:", + "Garbanzo: 17", + "Reginald: 17", + "", + "Changed people:", + "Garbanzo: 18", + "Reginald: 18", + "", + ] + ); +} + +// Prints the name and age of every `Person` whose info has changed in some way +fn print_changed_all_info(people: Query>, mut output: ResMut) { + output.0.push("Changed people:".to_string()); + for person in people.iter().flat_map(|p| p.iter_changed()) { + output + .0 + .push(format!("{}: {}", person.name(), person.age())); + } + output.0.push(default()); +} + +#[test] +fn added_one() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + world.spawn(Human("Henry".to_owned(), 22)); + + let mut schedule = Schedule::new(); + schedule.add_systems((print_added_one_info, (age_up_fem, age_up_not)).chain()); + + schedule.run(&mut world); + + world.spawn((Dolphin(27), Fem)); + + schedule.run(&mut world); + + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + &[ + "Added people:", + "Henry: 22", + "", + "Added people:", + "Reginald: 27", + "", + "Added people:", + "", + ] + ); +} + +// Prints the name and age of every newly added `Person`. +fn print_added_one_info( + people: Query, OneAdded>, + mut output: ResMut, +) { + output.0.push("Added people:".to_string()); + for person in &people { + output + .0 + .push(format!("{}: {}", person.name(), person.age())); + } + output.0.push(default()); +} + +#[test] +fn changed_one() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + let mut schedule = Schedule::new(); + schedule.add_systems((print_changed_one_info, age_up_fem).chain()); + + world.spawn(Human("Henry".to_owned(), 22)); + + schedule.run(&mut world); + + world.spawn((Dolphin(27), Fem)); + + schedule.run(&mut world); + + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + &[ + "Changed people:", + "Henry: 22", + "", + "Changed people:", + "Reginald: 27", + "", + "Changed people:", + "Reginald: 28", + "" + ] + ); +} + +// Prints the name and age of every `Person` whose info has changed in some way +fn print_changed_one_info( + people: Query, OneChanged>, + mut output: ResMut, +) { + output.0.push("Changed people:".to_string()); + for person in &people { + output + .0 + .push(format!("{}: {}", person.name(), person.age())); + } + output.0.push(default()); +} + +#[test] +fn one_added_filter() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + world.spawn(Human("Henry".to_owned(), 22)); + + let mut schedule = Schedule::new(); + schedule.add_systems((print_one_added_filter_info, (age_up_fem, age_up_not)).chain()); + + schedule.run(&mut world); + + world.spawn((Dolphin(27), Fem)); + + schedule.run(&mut world); + + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + &[ + "Added people:", + "Henry: 22", + "", + "Added people:", + "Reginald: 27", + "", + "Added people:", + "", + ] + ); +} + +// Prints the name and age of every newly added `Person`. +fn print_one_added_filter_info( + people: Query, OneAdded>, + mut output: ResMut, +) { + output.0.push("Added people:".to_string()); + for person in (&people).into_iter() { + output + .0 + .push(format!("{}: {}", person.name(), person.age())); + } + output.0.push(default()); +} + +#[test] +fn one_changed_filter() { + let mut world = World::new(); + world.init_resource::(); + world + .register_component_as::() + .register_component_as::(); + + world.spawn(Human("Henry".to_owned(), 22)); + + let mut schedule = Schedule::new(); + schedule.add_systems((print_one_changed_filter_info, age_up_fem).chain()); + + schedule.run(&mut world); + + world.spawn((Dolphin(27), Fem)); + + schedule.run(&mut world); + + schedule.run(&mut world); + + assert_eq!( + world.resource::().0, + &[ + "Changed people:", + "Henry: 22", + "", + "Changed people:", + "Reginald: 27", + "", + "Changed people:", + "Reginald: 28", + "" + ] + ); +} + +// Prints the name and age of every `Person` whose info has changed in some way +fn print_one_changed_filter_info( + people: Query, OneChanged>, + mut output: ResMut, +) { + output.0.push("Changed people:".to_string()); + for person in (&people).into_iter() { + output + .0 + .push(format!("{}: {}", person.name(), person.age())); + } + output.0.push(default()); +} + #[queryable] pub trait Messages { fn send(&mut self, _: &dyn Display);