Skip to content

Commit

Permalink
mutate observer
Browse files Browse the repository at this point in the history
  • Loading branch information
xiejiaen committed Oct 31, 2024
1 parent 03372e5 commit e851496
Show file tree
Hide file tree
Showing 20 changed files with 469 additions and 128 deletions.
1 change: 0 additions & 1 deletion crates/bevy_app/src/sub_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
///
/// // Create a sub-app with the same resource and a single schedule.
/// let mut sub_app = SubApp::new();
/// sub_app.update_schedule = Some(Main.intern());
/// sub_app.insert_resource(Val(100));
///
/// // Setup an extract function to copy the resource's value in the main world.
Expand Down
54 changes: 54 additions & 0 deletions crates/bevy_ecs/examples/mutate_obsover.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@


//! In this example we add a counter resource and increase its value in one system,
//! while a different system prints the current count to the console.
#![expect(clippy::std_instead_of_core)]

use bevy_ecs::prelude::*;
use bevy_ecs::world::OnMutate;
use bevy_reflect::Reflect;

fn main() {
let mut world = World::new();

world.add_observer(ob);

let mut schedule = Schedule::default();

schedule
.add_systems(
(
first,
second,
)
.chain()
);

schedule.run(&mut world);

println!("{:?}", world);
}

#[derive(Reflect, Default, Component, Debug)]
struct Count(usize);

fn first(mut commands: Commands) {
commands.spawn(Count(0));
}

fn second(mut counts: Query<Mut<Count>>) {
counts.iter_mut().for_each(|mut count| {
count.0 += 1;
})
}

fn ob(
trigger: Trigger<OnMutate, Count>,
counts: Query<&Count>,
) {
let Ok(count) = counts.get(trigger.entity()) else {
return;
};
dbg!(count);
}
7 changes: 7 additions & 0 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -697,6 +698,12 @@ impl Archetype {
pub fn has_remove_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
}

/// Returns true if any of the components in this archetype have at least one [`OnMutate`] observer
#[inline]
pub fn has_mutate_observer(&self) -> bool {
self.flags().contains(ArchetypeFlags::ON_MUTATE_OBSERVER)
}
}

/// The next [`ArchetypeId`] in an [`Archetypes`] collection.
Expand Down
119 changes: 94 additions & 25 deletions crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use {
bevy_ptr::ThinSlicePtr,
core::{cell::UnsafeCell, panic::Location},
};
use crate::storage::{Changes, EntityChange};

/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
///
Expand Down Expand Up @@ -387,6 +388,7 @@ macro_rules! impl_methods {
/// <T>`, but you need a `Mut<T>`.
pub fn reborrow(&mut self) -> Mut<'_, $target> {
Mut {
on_change: self.on_change,
value: self.value,
ticks: TicksMut {
added: self.ticks.added,
Expand Down Expand Up @@ -423,6 +425,7 @@ macro_rules! impl_methods {
/// ```
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'w, U> {
Mut {
on_change: self.on_change,
value: f(self.value),
ticks: self.ticks,
#[cfg(feature = "track_change_detection")]
Expand All @@ -437,6 +440,7 @@ macro_rules! impl_methods {
pub fn filter_map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> Option<&mut U>) -> Option<Mut<'w, U>> {
let value = f(self.value);
value.map(|value| Mut {
on_change: self.on_change,
value,
ticks: self.ticks,
#[cfg(feature = "track_change_detection")]
Expand Down Expand Up @@ -659,22 +663,31 @@ where

change_detection_impl!(ResMut<'w, T>, T, Resource);
change_detection_mut_impl!(ResMut<'w, T>, T, Resource);
impl_methods!(ResMut<'w, T>, T, Resource);
// impl_methods!(ResMut<'w, T>, T, Resource);
impl_debug!(ResMut<'w, T>, Resource);

impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
/// Convert this `ResMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
/// while losing the specificity of `ResMut` for resources.
fn from(other: ResMut<'w, T>) -> Mut<'w, T> {
Mut {
value: other.value,
ticks: other.ticks,
#[cfg(feature = "track_change_detection")]
changed_by: other.changed_by,
}
impl<'w, T: ?Sized + Resource> ResMut<'w, T> {

/// TODO
pub fn into_inner(mut self) -> &'w mut T {
self.set_changed();
self.value
}
}

// impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
// /// Convert this `ResMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
// /// while losing the specificity of `ResMut` for resources.
// fn from(other: ResMut<'w, T>) -> Mut<'w, T> {
// Mut {
// value: other.value,
// ticks: other.ticks,
// #[cfg(feature = "track_change_detection")]
// changed_by: other.changed_by,
// }
// }
// }

/// Unique borrow of a non-[`Send`] resource.
///
/// Only [`Send`] resources may be accessed with the [`ResMut`] [`SystemParam`](crate::system::SystemParam). In case that the
Expand All @@ -695,21 +708,21 @@ pub struct NonSendMut<'w, T: ?Sized + 'static> {

change_detection_impl!(NonSendMut<'w, T>, T,);
change_detection_mut_impl!(NonSendMut<'w, T>, T,);
impl_methods!(NonSendMut<'w, T>, T,);
// impl_methods!(NonSendMut<'w, T>, T,);
impl_debug!(NonSendMut<'w, T>,);

impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
/// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
/// while losing the specificity of `NonSendMut`.
fn from(other: NonSendMut<'w, T>) -> Mut<'w, T> {
Mut {
value: other.value,
ticks: other.ticks,
#[cfg(feature = "track_change_detection")]
changed_by: other.changed_by,
}
}
}
// impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
// /// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
// /// while losing the specificity of `NonSendMut`.
// fn from(other: NonSendMut<'w, T>) -> Mut<'w, T> {
// Mut {
// value: other.value,
// ticks: other.ticks,
// #[cfg(feature = "track_change_detection")]
// changed_by: other.changed_by,
// }
// }
// }

/// Shared borrow of an entity's component with access to change detection.
/// Similar to [`Mut`] but is immutable and so doesn't require unique access.
Expand Down Expand Up @@ -869,6 +882,7 @@ impl_debug!(Ref<'w, T>,);
/// # fn update_player_position(player: &Player, new_position: Position) {}
/// ```
pub struct Mut<'w, T: ?Sized> {
pub(crate) on_change: Option<(EntityChange, &'w Changes)>,
pub(crate) value: &'w mut T,
pub(crate) ticks: TicksMut<'w>,
#[cfg(feature = "track_change_detection")]
Expand Down Expand Up @@ -900,6 +914,7 @@ impl<'w, T: ?Sized> Mut<'w, T> {
#[cfg(feature = "track_change_detection")] caller: &'w mut &'static Location<'static>,
) -> Self {
Self {
on_change: None,
value,
ticks: TicksMut {
added,
Expand Down Expand Up @@ -949,8 +964,58 @@ where
}
}

impl<'w, T: ?Sized> DetectChangesMut for Mut<'w, T> {
type Inner = T;
#[inline]
#[track_caller]
fn set_changed(&mut self) {
*self.ticks.changed = self.ticks.this_run;
if let Some((change, changes)) = self.on_change {
changes.push(change);
}
#[cfg(feature = "track_change_detection")]
{
*self.changed_by = Location::caller();
}
}
#[inline]
#[track_caller]
fn set_last_changed(&mut self, last_changed: Tick) {
*self.ticks.changed = last_changed;
#[cfg(feature = "track_change_detection")]
{
*self.changed_by = Location::caller();
}
}

#[inline]
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
self.value
}
}

impl<'w, T: ?Sized> DerefMut for Mut<'w, T> {
#[inline]
#[track_caller]
fn deref_mut(&mut self) -> &mut Self::Target {
self.set_changed();
#[cfg(feature = "track_change_detection")]
{
*self.changed_by = Location::caller();
}
self.value
}
}

impl<'w, T: ?Sized> AsMut<T> for Mut<'w, T> {
#[inline]
fn as_mut(&mut self) -> &mut T {
self.deref_mut()
}
}

change_detection_impl!(Mut<'w, T>, T,);
change_detection_mut_impl!(Mut<'w, T>, T,);
// change_detection_mut_impl!(Mut<'w, T>, T,);
impl_methods!(Mut<'w, T>, T,);
impl_debug!(Mut<'w, T>,);

Expand Down Expand Up @@ -1041,6 +1106,7 @@ impl<'w> MutUntyped<'w> {
/// ```
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'w>) -> &'w mut T) -> Mut<'w, T> {
Mut {
on_change: None, // TODO
value: f(self.value),
ticks: self.ticks,
#[cfg(feature = "track_change_detection")]
Expand All @@ -1054,6 +1120,7 @@ impl<'w> MutUntyped<'w> {
/// - `T` must be the erased pointee type for this [`MutUntyped`].
pub unsafe fn with_type<T>(self) -> Mut<'w, T> {
Mut {
on_change: None,
// SAFETY: `value` is `Aligned` and caller ensures the pointee type is `T`.
value: unsafe { self.value.deref_mut() },
ticks: self.ticks,
Expand Down Expand Up @@ -1423,6 +1490,7 @@ mod tests {
let mut caller = Location::caller();

let ptr = Mut {
on_change: None,
value: &mut outer,
ticks,
#[cfg(feature = "track_change_detection")]
Expand Down Expand Up @@ -1551,6 +1619,7 @@ mod tests {
let mut caller = Location::caller();

let mut_typed = Mut {
on_change: None,
value: &mut c,
ticks,
#[cfg(feature = "track_change_detection")]
Expand Down
12 changes: 12 additions & 0 deletions crates/bevy_ecs/src/observer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
Expand All @@ -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(),
}
}
Expand All @@ -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),
}
}
Expand Down Expand Up @@ -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,
}
}
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
Loading

0 comments on commit e851496

Please sign in to comment.