From d91117d6e751f235ab881fe61c967db8a8daf1f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Thu, 31 Dec 2020 23:29:08 +0100 Subject: [PATCH] add `Flags` as a query to get flags of component (#1172) add `Flags` as a query to get flags of component --- Cargo.toml | 4 ++ crates/bevy_ecs/src/core/mod.rs | 2 +- crates/bevy_ecs/src/core/query.rs | 114 +++++++++++++++++++++++++++++- crates/bevy_ecs/src/lib.rs | 4 +- examples/README.md | 1 + examples/ecs/change_detection.rs | 53 ++++++++++++++ 6 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 examples/ecs/change_detection.rs diff --git a/Cargo.toml b/Cargo.toml index c4a5ca03cd..abb490e3e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -198,6 +198,10 @@ path = "examples/diagnostics/custom_diagnostic.rs" name = "log_diagnostics" path = "examples/diagnostics/log_diagnostics.rs" +[[example]] +name = "change_detection" +path = "examples/ecs/change_detection.rs" + [[example]] name = "event" path = "examples/ecs/event.rs" diff --git a/crates/bevy_ecs/src/core/mod.rs b/crates/bevy_ecs/src/core/mod.rs index 426e7bc879..512bb10e48 100644 --- a/crates/bevy_ecs/src/core/mod.rs +++ b/crates/bevy_ecs/src/core/mod.rs @@ -51,7 +51,7 @@ pub use entities::{Entity, EntityReserver, Location, NoSuchEntity}; pub use entity_builder::{BuiltEntity, EntityBuilder}; pub use entity_map::*; pub use filter::{Added, Changed, EntityFilter, Mutated, Or, QueryFilter, With, Without}; -pub use query::{Batch, BatchedIter, Mut, QueryIter, ReadOnlyFetch, WorldQuery}; +pub use query::{Batch, BatchedIter, Flags, Mut, QueryIter, ReadOnlyFetch, WorldQuery}; pub use world::{ArchetypesGeneration, Component, ComponentError, SpawnBatchIter, World}; pub use world_builder::*; diff --git a/crates/bevy_ecs/src/core/query.rs b/crates/bevy_ecs/src/core/query.rs index bf7c604ea9..d3d3e53fb6 100644 --- a/crates/bevy_ecs/src/core/query.rs +++ b/crates/bevy_ecs/src/core/query.rs @@ -130,6 +130,41 @@ impl WorldQuery for Option { type Fetch = TryFetch; } +/// Flags on component `T` that happened since the start of the frame. +#[derive(Debug, Clone)] +pub struct Flags { + _marker: std::marker::PhantomData, + with: bool, + added: bool, + mutated: bool, +} + +impl Flags { + /// Does the entity have this component + pub fn with(&self) -> bool { + self.with + } + + /// Has this component been added since the start of the frame. + pub fn added(&self) -> bool { + self.added + } + + /// Has this component been mutated since the start of the frame. + pub fn mutated(&self) -> bool { + self.mutated + } + + /// Has this component been either mutated or added since the start of the frame. + pub fn changed(&self) -> bool { + self.added || self.mutated + } +} + +impl WorldQuery for Flags { + type Fetch = FlagsFetch; +} + /// Unique borrow of an entity's component pub struct Mut<'a, T: Component> { pub(crate) value: &'a mut T, @@ -237,6 +272,51 @@ impl<'a, T: Fetch<'a>> Fetch<'a> for TryFetch { } } +#[doc(hidden)] +pub struct FlagsFetch(Option>, PhantomData); +unsafe impl ReadOnlyFetch for FlagsFetch {} + +impl<'a, T: Component> Fetch<'a> for FlagsFetch { + type Item = Flags; + + const DANGLING: Self = Self(None, PhantomData::); + + #[inline] + fn access() -> QueryAccess { + QueryAccess::read::() + } + + unsafe fn get(archetype: &'a Archetype, offset: usize) -> Option { + Some(Self( + archetype + .get_type_state(std::any::TypeId::of::()) + .map(|type_state| { + NonNull::new_unchecked(type_state.component_flags().as_ptr().add(offset)) + }), + PhantomData::, + )) + } + + unsafe fn fetch(&self, n: usize) -> Self::Item { + if let Some(flags) = self.0.as_ref() { + let flags = *flags.as_ptr().add(n); + Self::Item { + _marker: PhantomData::, + with: true, + added: flags.contains(ComponentFlags::ADDED), + mutated: flags.contains(ComponentFlags::MUTATED), + } + } else { + Self::Item { + _marker: PhantomData::, + with: false, + added: false, + mutated: false, + } + } + } +} + struct ChunkInfo { fetch: Q::Fetch, filter: F::EntityFilter, @@ -466,7 +546,7 @@ smaller_tuples_too!(tuple_impl, O, N, M, L, K, J, I, H, G, F, E, D, C, B, A); #[cfg(test)] mod tests { - use crate::core::{Added, Changed, Component, Entity, Mutated, Or, QueryFilter, World}; + use crate::core::{Added, Changed, Component, Entity, Flags, Mutated, Or, QueryFilter, World}; use std::{vec, vec::Vec}; use super::Mut; @@ -640,6 +720,38 @@ mod tests { assert_eq!(get_changed(&world), vec![e1]); } + #[test] + fn flags_query() { + let mut world = World::default(); + let e1 = world.spawn((A(0), B(0))); + world.spawn((B(0),)); + + fn get_flags(world: &World) -> Vec> { + world.query::>().collect::>>() + } + let flags = get_flags(&world); + assert!(flags[0].with()); + assert!(flags[0].added()); + assert!(!flags[0].mutated()); + assert!(flags[0].changed()); + assert!(!flags[1].with()); + assert!(!flags[1].added()); + assert!(!flags[1].mutated()); + assert!(!flags[1].changed()); + world.clear_trackers(); + let flags = get_flags(&world); + assert!(flags[0].with()); + assert!(!flags[0].added()); + assert!(!flags[0].mutated()); + assert!(!flags[0].changed()); + *world.get_mut(e1).unwrap() = A(1); + let flags = get_flags(&world); + assert!(flags[0].with()); + assert!(!flags[0].added()); + assert!(flags[0].mutated()); + assert!(flags[0].changed()); + } + #[test] fn exact_size_query() { let mut world = World::default(); diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 0931d9f64d..70be39c666 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -16,7 +16,7 @@ pub mod prelude { resource::{ChangedRes, FromResources, Local, Res, ResMut, Resource, Resources}, schedule::{Schedule, State, StateStage, SystemStage}, system::{Commands, IntoSystem, Query, System}, - Added, Bundle, Changed, Component, Entity, In, IntoChainSystem, Mut, Mutated, Or, QuerySet, - Ref, RefMut, With, Without, World, + Added, Bundle, Changed, Component, Entity, Flags, In, IntoChainSystem, Mut, Mutated, Or, + QuerySet, Ref, RefMut, With, Without, World, }; } diff --git a/examples/README.md b/examples/README.md index 16a8b4d212..1dbf2128aa 100644 --- a/examples/README.md +++ b/examples/README.md @@ -114,6 +114,7 @@ Example | File | Description Example | File | Description --- | --- | --- +`change_detection` | [`ecs/change_detection.rs`](./ecs/change_detection.rs) | Change detection on components `ecs_guide` | [`ecs/ecs_guide.rs`](./ecs/ecs_guide.rs) | Full guide to Bevy's ECS `event` | [`ecs/event.rs`](./ecs/event.rs) | Illustrates event creation, activation, and reception `hierarchy` | [`ecs/hierarchy.rs`](./ecs/hierarchy.rs) | Creates a hierarchy of parents and children entities diff --git a/examples/ecs/change_detection.rs b/examples/ecs/change_detection.rs new file mode 100644 index 0000000000..9a2a06e709 --- /dev/null +++ b/examples/ecs/change_detection.rs @@ -0,0 +1,53 @@ +use bevy::prelude::*; +use rand::Rng; + +// This example illustrates how to react to component change +fn main() { + App::build() + .add_plugins(DefaultPlugins) + .add_startup_system(setup.system()) + .add_system(change_component.system()) + .add_system(change_detection.system()) + .add_system(flags_monitoring.system()) + .run(); +} + +#[derive(Debug)] +struct MyComponent(f64); + +fn setup(commands: &mut Commands) { + commands.spawn((MyComponent(0.),)); + commands.spawn((Transform::default(),)); +} + +fn change_component(time: Res