diff --git a/Cargo.toml b/Cargo.toml index b12b688e589c1..0413d125eabc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3852,3 +3852,8 @@ doc-scrape-examples = true [package.metadata.example.testbed_3d] hidden = true + +[[example]] +name = "playground" +path = "examples/playground.rs" +doc-scrape-examples = false diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 2c2241e29ec6d..a78d9ece6a02c 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -352,6 +352,7 @@ bitflags::bitflags! { const ON_INSERT_OBSERVER = (1 << 5); const ON_REPLACE_OBSERVER = (1 << 6); const ON_REMOVE_OBSERVER = (1 << 7); + const ON_MUTATE_OBSERVER = (1 << 8); } } @@ -697,6 +698,11 @@ impl Archetype { pub fn has_remove_observer(&self) -> bool { self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER) } + + #[inline] + pub fn has_mutate_observer(&self) -> bool { + self.flags().contains(ArchetypeFlags::ON_MUTATE_OBSERVER) + } } /// The next [`ArchetypeId`] in an [`Archetypes`] collection. diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 384b7517c77b2..2f0c8db2ea90a 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -18,7 +18,7 @@ use crate::{ prelude::World, query::DebugCheckedUnwrap, storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow}, - world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT, ON_REPLACE}, + world::{unsafe_world_cell::UnsafeWorldCell, ON_ADD, ON_INSERT, ON_MUTATE, ON_REPLACE}, }; use bevy_ptr::{ConstNonNull, OwningPtr}; use bevy_utils::{all_tuples, HashMap, HashSet, TypeIdMap}; @@ -1093,6 +1093,13 @@ impl<'w> BundleInserter<'w> { add_bundle.iter_inserted(), ); } + if new_archetype.has_mutate_observer() { + deferred_world.trigger_observers( + ON_MUTATE, + entity, + add_bundle.iter_inserted(), + ); + } } InsertMode::Keep => { // insert triggers only for new components if we're not replacing them (since @@ -1109,6 +1116,13 @@ impl<'w> BundleInserter<'w> { add_bundle.iter_added(), ); } + if new_archetype.has_mutate_observer() { + deferred_world.trigger_observers( + ON_MUTATE, + entity, + add_bundle.iter_added(), + ); + } } } } @@ -1249,6 +1263,13 @@ impl<'w> BundleSpawner<'w> { bundle_info.iter_contributed_components(), ); } + if archetype.has_mutate_observer() { + deferred_world.trigger_observers( + ON_MUTATE, + entity, + bundle_info.iter_contributed_components(), + ); + } }; location diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 67254d6298f11..ff2276ea0ef4e 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -64,7 +64,7 @@ pub mod prelude { }, world::{ Command, EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut, - FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, World, + FromWorld, OnAdd, OnInsert, OnRemove, OnReplace, OnMutate, World, }, }; diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 674f98f650725..7b5724ce87bb2 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -247,6 +247,7 @@ pub struct Observers { on_insert: CachedObservers, on_replace: CachedObservers, on_remove: CachedObservers, + on_mutate: CachedObservers, // Map from trigger type to set of observers cache: HashMap, } @@ -258,6 +259,7 @@ impl Observers { ON_INSERT => &mut self.on_insert, ON_REPLACE => &mut self.on_replace, ON_REMOVE => &mut self.on_remove, + ON_MUTATE => &mut self.on_mutate, _ => self.cache.entry(event_type).or_default(), } } @@ -268,6 +270,7 @@ impl Observers { ON_INSERT => Some(&self.on_insert), ON_REPLACE => Some(&self.on_replace), ON_REMOVE => Some(&self.on_remove), + ON_MUTATE => Some(&self.on_mutate), _ => self.cache.get(&event_type), } } @@ -342,6 +345,7 @@ impl Observers { ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER), ON_REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER), ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER), + ON_MUTATE => Some(ArchetypeFlags::ON_MUTATE_OBSERVER), _ => None, } } @@ -378,6 +382,14 @@ impl Observers { { flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER); } + + if self + .on_mutate + .component_observers + .contains_key(&component_id) + { + flags.insert(ArchetypeFlags::ON_MUTATE_OBSERVER); + } } } @@ -565,6 +577,7 @@ impl World { } } +// TODO GRACE: write tests here #[cfg(test)] mod tests { use alloc::vec; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 4e349efd48503..0b98bb92a62c1 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -1,9 +1,9 @@ pub use crate::change_detection::{NonSendMut, Res, ResMut}; use crate::{ - archetype::{Archetype, Archetypes}, + archetype::{self, Archetype, Archetypes}, bundle::Bundles, change_detection::{Ticks, TicksMut}, - component::{ComponentId, ComponentTicks, Components, Tick}, + component::{self, ComponentId, ComponentTicks, Components, Tick}, entity::Entities, query::{ Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, @@ -12,8 +12,7 @@ use crate::{ storage::ResourceData, system::{Query, Single, SystemMeta}, world::{ - unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, - FromWorld, World, + unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, FromWorld, OnMutate, World, ON_MUTATE }, }; use bevy_ecs_macros::impl_param_set; @@ -29,7 +28,7 @@ use core::{ ops::{Deref, DerefMut}, }; -use super::Populated; +use super::{IntoSystem, Populated}; /// A parameter that can be used in a [`System`](super::System). /// @@ -320,6 +319,64 @@ unsafe impl SystemParam for Qu // world data that the query needs. unsafe { Query::new(world, state, system_meta.last_run, change_tick) } } + + fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + let mut vec = Vec::new(); + for archetype_id in state.matched_archetypes() { + let Some(archetype) = world.archetypes().get(archetype_id) else { continue; }; + if !archetype.has_mutate_observer() { continue; } + // println!("{:?}", archetype_id); + let (writes, cant_write) = system_meta.component_access_set.combined_access().component_writes(); + let writes = if cant_write { vec![] } else { writes.collect() }; + // println!("{:?}", writes); + + for archetype_entity in archetype.entities() { + let entity = archetype_entity.id(); + let entity_ref = world.entity(entity); + let writes_here = writes.iter().cloned().filter(|c| + entity_ref + .get_change_ticks_by_id(*c) + .map(|ticks| { + // println!("{:?}", ticks); + ticks.changed.get() >= system_meta.last_run.get() + }) // FIXME GRACE: get this_run + .unwrap_or(false) + ).collect::>(); + vec.push((entity, writes_here)); + } + } + + let mut deferred_world = DeferredWorld::from(world); + for (e, modified_components) in vec { + unsafe { + deferred_world.trigger_observers(ON_MUTATE, e, modified_components.iter().cloned()); + } + } + } + + // fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) { + + + + // for archetype_id in state.matched_archetypes() { + // let Some(archetype) = world.archetypes().get(archetype_id) else { continue; }; + // if !archetype.has_mutate_observer() { continue; } + // let (writes, are_writes) = system_meta.archetype_component_access.component_writes(); + // if are_writes { + // for comp in writes { + + // } + // } + // for archetype_entity in archetype.entities() { + + // let entity = archetype_entity.id(); + // for component in archetype.components() { + + // } + // } + // } + // // world.commands().trigger_targets(OnMutate, targets); + // } } pub(crate) fn init_query_param( diff --git a/crates/bevy_ecs/src/world/component_constants.rs b/crates/bevy_ecs/src/world/component_constants.rs index 5eea8dc6229ef..73b21e5ca53ce 100644 --- a/crates/bevy_ecs/src/world/component_constants.rs +++ b/crates/bevy_ecs/src/world/component_constants.rs @@ -13,6 +13,8 @@ pub const ON_INSERT: ComponentId = ComponentId::new(1); pub const ON_REPLACE: ComponentId = ComponentId::new(2); /// [`ComponentId`] for [`OnRemove`] pub const ON_REMOVE: ComponentId = ComponentId::new(3); +/// [`ComponentId`] for [`OnMutate`] +pub const ON_MUTATE: ComponentId = ComponentId::new(4); /// Trigger emitted when a component is added to an entity. See [`crate::component::ComponentHooks::on_add`] /// for more information. @@ -41,3 +43,9 @@ pub struct OnReplace; #[cfg_attr(feature = "bevy_reflect", derive(Reflect))] #[cfg_attr(feature = "bevy_reflect", reflect(Debug))] pub struct OnRemove; + +/// Trigger emitted when a component is mutated. +#[derive(Event, Debug)] +#[cfg_attr(feature = "bevy_reflect", derive(Reflect))] +#[cfg_attr(feature = "bevy_reflect", reflect(Debug))] +pub struct OnMutate; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d50f8f421cfc2..8f9de144214ff 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -182,6 +182,7 @@ impl World { assert_eq!(ON_INSERT, self.register_component::()); assert_eq!(ON_REPLACE, self.register_component::()); assert_eq!(ON_REMOVE, self.register_component::()); + assert_eq!(ON_MUTATE, self.register_component::()); } /// Creates a new empty [`World`]. /// diff --git a/examples/playground.rs b/examples/playground.rs new file mode 100644 index 0000000000000..a85d1fcd9ba69 --- /dev/null +++ b/examples/playground.rs @@ -0,0 +1,55 @@ + +#![allow(missing_docs)] + +use bevy::prelude::*; + +// TODO GRACE: remove this file + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .add_systems(Startup, setup) + .add_systems(Update, on_space_press) + .add_observer(on_a_mutated) + .run(); +} + +#[derive(Component)] +struct A(i32); + +fn setup(mut commands: Commands) { + commands.spawn(A(0)); +} + +fn on_space_press(input: Res>, mut query: Query<&mut A>) { + if input.just_pressed(KeyCode::Space) { + for mut a in query.iter_mut() { + println!("asdfjkaskldfjlksadfljsdfk"); + a.0 = 100; + } + } +} + +fn on_a_mutated(trigger: Trigger) { + println!("A mutation happened!"); +} + + +// fn main() { +// App::new() +// .add_plugins(DefaultPlugins) +// .add_systems(Startup, setup) +// .add_observer(on_a_added) +// .run(); +// } + +// #[derive(Component)] +// struct A(i32); + +// fn setup(mut commands: Commands) { +// commands.spawn_empty().insert(A(0)).insert(A(1)); +// } + +// fn on_a_added(trigger: Trigger) { +// println!("Abafjgklasdfgjp;kl") +// } \ No newline at end of file