-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
OnMutate
Observers/on_mutate
Hooks
#16143
Changes from all commits
fb7b3e0
b5f13d3
c16d009
eccdd3a
bd1f748
2e784c7
4bcc317
88e1d5c
135fc8d
95e7310
87f3fae
b98de9b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: instead add these to the |
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<ComponentId, CachedObservers>, | ||
} | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: write tests |
||
#[cfg(test)] | ||
mod tests { | ||
use alloc::vec; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,22 +4,21 @@ use crate::{ | |
bundle::Bundles, | ||
change_detection::{Ticks, TicksMut}, | ||
component::{ComponentId, ComponentTicks, Components, Tick}, | ||
entity::Entities, | ||
entity::{Entities, Entity}, | ||
query::{ | ||
Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, | ||
QueryState, ReadOnlyQueryData, | ||
}, | ||
storage::ResourceData, | ||
system::{Query, Single, SystemMeta}, | ||
world::{ | ||
unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, | ||
FromWorld, World, | ||
unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, FromWorld, Mut, World, ON_MUTATE | ||
}, | ||
}; | ||
use bevy_ecs_macros::impl_param_set; | ||
pub use bevy_ecs_macros::{Resource, SystemParam}; | ||
use bevy_ptr::UnsafeCellDeref; | ||
use bevy_utils::{all_tuples, synccell::SyncCell}; | ||
use bevy_utils::{all_tuples, synccell::SyncCell, Entry, HashMap, HashSet}; | ||
#[cfg(feature = "track_change_detection")] | ||
use core::panic::Location; | ||
use core::{ | ||
|
@@ -194,7 +193,7 @@ pub unsafe trait SystemParam: Sized { | |
/// and creates a new instance of this param's [`State`](SystemParam::State). | ||
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State; | ||
|
||
/// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable).a | ||
/// For the specified [`Archetype`], registers the components accessed by this [`SystemParam`] (if applicable). | ||
/// | ||
/// # Safety | ||
/// `archetype` must be from the [`World`] used to initialize `state` in [`SystemParam::init_state`]. | ||
|
@@ -320,8 +319,59 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any performance/code quality suggestions, especially here, are much appreciated. A lot of what's here should probably be somewhere else, but I'm not sure where. It seems like a bad idea to iterate over every archetype a mutable query touches in |
||
world.init_resource::<ComponentMutationBuffer>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This probably should not be initialized here, but I'm not really sure where it should be. Should this be in |
||
world.resource_scope(|world, mut buffer: Mut<ComponentMutationBuffer>| { | ||
let (writes, cant_write) = state.component_access.access.component_writes(); | ||
let mutable_components = if cant_write { Vec::new() } else { writes.collect() }; | ||
if mutable_components.is_empty() { return } | ||
|
||
let buffer_started_empty = buffer.0.is_empty(); | ||
|
||
for archetype_id in state.matched_archetypes() { | ||
let Some(archetype) = world.archetypes().get(archetype_id) else { continue; }; | ||
if !archetype.has_mutate_observer() { continue; } | ||
|
||
for archetype_entity in archetype.entities() { | ||
let entity = archetype_entity.id(); | ||
let entity_ref = world.entity(entity); | ||
let mutated_observers = mutable_components | ||
.iter() | ||
.cloned() | ||
.filter(|c| entity_ref | ||
.get_change_ticks_by_id(*c) | ||
.map(|ticks| ticks.changed.get() >= system_meta.last_run.get()) // FIXME GRACE: get this_run | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: don't compare change detection without this_run |
||
.unwrap_or(false) | ||
); | ||
let values = buffer.0.entry(entity).or_default(); | ||
values.extend(mutated_observers); | ||
} | ||
} | ||
|
||
if buffer_started_empty && !buffer.0.is_empty() { | ||
world.commands().queue(flush_mutations); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a jank way that I'm deduplicating |
||
} | ||
}); | ||
} | ||
} | ||
|
||
fn flush_mutations(world: &mut World) { | ||
world.resource_scope(|world, mut buffer: Mut<ComponentMutationBuffer>| { | ||
let mut deferred_world = DeferredWorld::from(world); | ||
for (entity, components) in buffer.0.drain() { | ||
unsafe { | ||
deferred_world.trigger_observers(ON_MUTATE, entity, components.iter().cloned()) | ||
} | ||
} | ||
}); | ||
} | ||
|
||
#[derive(Default)] | ||
pub(crate) struct ComponentMutationBuffer(HashMap<Entity, HashSet<ComponentId>>); | ||
|
||
impl Resource for ComponentMutationBuffer {} | ||
|
||
pub(crate) fn init_query_param<D: QueryData + 'static, F: QueryFilter + 'static>( | ||
world: &mut World, | ||
system_meta: &mut SystemMeta, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. TODO: remove |
||
#![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<ButtonInput<KeyCode>>, 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<OnMutate, A>) { | ||
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<OnAdd, A>) { | ||
// println!("Abafjgklasdfgjp;kl") | ||
// } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: remove later