Skip to content

Commit e851496

Browse files
author
xiejiaen
committed
mutate observer
1 parent 03372e5 commit e851496

File tree

20 files changed

+469
-128
lines changed

20 files changed

+469
-128
lines changed

crates/bevy_app/src/sub_app.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
3737
///
3838
/// // Create a sub-app with the same resource and a single schedule.
3939
/// let mut sub_app = SubApp::new();
40-
/// sub_app.update_schedule = Some(Main.intern());
4140
/// sub_app.insert_resource(Val(100));
4241
///
4342
/// // Setup an extract function to copy the resource's value in the main world.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
3+
//! In this example we add a counter resource and increase its value in one system,
4+
//! while a different system prints the current count to the console.
5+
6+
#![expect(clippy::std_instead_of_core)]
7+
8+
use bevy_ecs::prelude::*;
9+
use bevy_ecs::world::OnMutate;
10+
use bevy_reflect::Reflect;
11+
12+
fn main() {
13+
let mut world = World::new();
14+
15+
world.add_observer(ob);
16+
17+
let mut schedule = Schedule::default();
18+
19+
schedule
20+
.add_systems(
21+
(
22+
first,
23+
second,
24+
)
25+
.chain()
26+
);
27+
28+
schedule.run(&mut world);
29+
30+
println!("{:?}", world);
31+
}
32+
33+
#[derive(Reflect, Default, Component, Debug)]
34+
struct Count(usize);
35+
36+
fn first(mut commands: Commands) {
37+
commands.spawn(Count(0));
38+
}
39+
40+
fn second(mut counts: Query<Mut<Count>>) {
41+
counts.iter_mut().for_each(|mut count| {
42+
count.0 += 1;
43+
})
44+
}
45+
46+
fn ob(
47+
trigger: Trigger<OnMutate, Count>,
48+
counts: Query<&Count>,
49+
) {
50+
let Ok(count) = counts.get(trigger.entity()) else {
51+
return;
52+
};
53+
dbg!(count);
54+
}

crates/bevy_ecs/src/archetype.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ bitflags::bitflags! {
352352
const ON_INSERT_OBSERVER = (1 << 5);
353353
const ON_REPLACE_OBSERVER = (1 << 6);
354354
const ON_REMOVE_OBSERVER = (1 << 7);
355+
const ON_MUTATE_OBSERVER = (1 << 8);
355356
}
356357
}
357358

@@ -697,6 +698,12 @@ impl Archetype {
697698
pub fn has_remove_observer(&self) -> bool {
698699
self.flags().contains(ArchetypeFlags::ON_REMOVE_OBSERVER)
699700
}
701+
702+
/// Returns true if any of the components in this archetype have at least one [`OnMutate`] observer
703+
#[inline]
704+
pub fn has_mutate_observer(&self) -> bool {
705+
self.flags().contains(ArchetypeFlags::ON_MUTATE_OBSERVER)
706+
}
700707
}
701708

702709
/// The next [`ArchetypeId`] in an [`Archetypes`] collection.

crates/bevy_ecs/src/change_detection.rs

Lines changed: 94 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use {
1515
bevy_ptr::ThinSlicePtr,
1616
core::{cell::UnsafeCell, panic::Location},
1717
};
18+
use crate::storage::{Changes, EntityChange};
1819

1920
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
2021
///
@@ -387,6 +388,7 @@ macro_rules! impl_methods {
387388
/// <T>`, but you need a `Mut<T>`.
388389
pub fn reborrow(&mut self) -> Mut<'_, $target> {
389390
Mut {
391+
on_change: self.on_change,
390392
value: self.value,
391393
ticks: TicksMut {
392394
added: self.ticks.added,
@@ -423,6 +425,7 @@ macro_rules! impl_methods {
423425
/// ```
424426
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'w, U> {
425427
Mut {
428+
on_change: self.on_change,
426429
value: f(self.value),
427430
ticks: self.ticks,
428431
#[cfg(feature = "track_change_detection")]
@@ -437,6 +440,7 @@ macro_rules! impl_methods {
437440
pub fn filter_map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> Option<&mut U>) -> Option<Mut<'w, U>> {
438441
let value = f(self.value);
439442
value.map(|value| Mut {
443+
on_change: self.on_change,
440444
value,
441445
ticks: self.ticks,
442446
#[cfg(feature = "track_change_detection")]
@@ -659,22 +663,31 @@ where
659663

660664
change_detection_impl!(ResMut<'w, T>, T, Resource);
661665
change_detection_mut_impl!(ResMut<'w, T>, T, Resource);
662-
impl_methods!(ResMut<'w, T>, T, Resource);
666+
// impl_methods!(ResMut<'w, T>, T, Resource);
663667
impl_debug!(ResMut<'w, T>, Resource);
664668

665-
impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
666-
/// Convert this `ResMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
667-
/// while losing the specificity of `ResMut` for resources.
668-
fn from(other: ResMut<'w, T>) -> Mut<'w, T> {
669-
Mut {
670-
value: other.value,
671-
ticks: other.ticks,
672-
#[cfg(feature = "track_change_detection")]
673-
changed_by: other.changed_by,
674-
}
669+
impl<'w, T: ?Sized + Resource> ResMut<'w, T> {
670+
671+
/// TODO
672+
pub fn into_inner(mut self) -> &'w mut T {
673+
self.set_changed();
674+
self.value
675675
}
676676
}
677677

678+
// impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
679+
// /// Convert this `ResMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
680+
// /// while losing the specificity of `ResMut` for resources.
681+
// fn from(other: ResMut<'w, T>) -> Mut<'w, T> {
682+
// Mut {
683+
// value: other.value,
684+
// ticks: other.ticks,
685+
// #[cfg(feature = "track_change_detection")]
686+
// changed_by: other.changed_by,
687+
// }
688+
// }
689+
// }
690+
678691
/// Unique borrow of a non-[`Send`] resource.
679692
///
680693
/// Only [`Send`] resources may be accessed with the [`ResMut`] [`SystemParam`](crate::system::SystemParam). In case that the
@@ -695,21 +708,21 @@ pub struct NonSendMut<'w, T: ?Sized + 'static> {
695708

696709
change_detection_impl!(NonSendMut<'w, T>, T,);
697710
change_detection_mut_impl!(NonSendMut<'w, T>, T,);
698-
impl_methods!(NonSendMut<'w, T>, T,);
711+
// impl_methods!(NonSendMut<'w, T>, T,);
699712
impl_debug!(NonSendMut<'w, T>,);
700713

701-
impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
702-
/// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
703-
/// while losing the specificity of `NonSendMut`.
704-
fn from(other: NonSendMut<'w, T>) -> Mut<'w, T> {
705-
Mut {
706-
value: other.value,
707-
ticks: other.ticks,
708-
#[cfg(feature = "track_change_detection")]
709-
changed_by: other.changed_by,
710-
}
711-
}
712-
}
714+
// impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
715+
// /// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
716+
// /// while losing the specificity of `NonSendMut`.
717+
// fn from(other: NonSendMut<'w, T>) -> Mut<'w, T> {
718+
// Mut {
719+
// value: other.value,
720+
// ticks: other.ticks,
721+
// #[cfg(feature = "track_change_detection")]
722+
// changed_by: other.changed_by,
723+
// }
724+
// }
725+
// }
713726

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

967+
impl<'w, T: ?Sized> DetectChangesMut for Mut<'w, T> {
968+
type Inner = T;
969+
#[inline]
970+
#[track_caller]
971+
fn set_changed(&mut self) {
972+
*self.ticks.changed = self.ticks.this_run;
973+
if let Some((change, changes)) = self.on_change {
974+
changes.push(change);
975+
}
976+
#[cfg(feature = "track_change_detection")]
977+
{
978+
*self.changed_by = Location::caller();
979+
}
980+
}
981+
#[inline]
982+
#[track_caller]
983+
fn set_last_changed(&mut self, last_changed: Tick) {
984+
*self.ticks.changed = last_changed;
985+
#[cfg(feature = "track_change_detection")]
986+
{
987+
*self.changed_by = Location::caller();
988+
}
989+
}
990+
991+
#[inline]
992+
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
993+
self.value
994+
}
995+
}
996+
997+
impl<'w, T: ?Sized> DerefMut for Mut<'w, T> {
998+
#[inline]
999+
#[track_caller]
1000+
fn deref_mut(&mut self) -> &mut Self::Target {
1001+
self.set_changed();
1002+
#[cfg(feature = "track_change_detection")]
1003+
{
1004+
*self.changed_by = Location::caller();
1005+
}
1006+
self.value
1007+
}
1008+
}
1009+
1010+
impl<'w, T: ?Sized> AsMut<T> for Mut<'w, T> {
1011+
#[inline]
1012+
fn as_mut(&mut self) -> &mut T {
1013+
self.deref_mut()
1014+
}
1015+
}
1016+
9521017
change_detection_impl!(Mut<'w, T>, T,);
953-
change_detection_mut_impl!(Mut<'w, T>, T,);
1018+
// change_detection_mut_impl!(Mut<'w, T>, T,);
9541019
impl_methods!(Mut<'w, T>, T,);
9551020
impl_debug!(Mut<'w, T>,);
9561021

@@ -1041,6 +1106,7 @@ impl<'w> MutUntyped<'w> {
10411106
/// ```
10421107
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'w>) -> &'w mut T) -> Mut<'w, T> {
10431108
Mut {
1109+
on_change: None, // TODO
10441110
value: f(self.value),
10451111
ticks: self.ticks,
10461112
#[cfg(feature = "track_change_detection")]
@@ -1054,6 +1120,7 @@ impl<'w> MutUntyped<'w> {
10541120
/// - `T` must be the erased pointee type for this [`MutUntyped`].
10551121
pub unsafe fn with_type<T>(self) -> Mut<'w, T> {
10561122
Mut {
1123+
on_change: None,
10571124
// SAFETY: `value` is `Aligned` and caller ensures the pointee type is `T`.
10581125
value: unsafe { self.value.deref_mut() },
10591126
ticks: self.ticks,
@@ -1423,6 +1490,7 @@ mod tests {
14231490
let mut caller = Location::caller();
14241491

14251492
let ptr = Mut {
1493+
on_change: None,
14261494
value: &mut outer,
14271495
ticks,
14281496
#[cfg(feature = "track_change_detection")]
@@ -1551,6 +1619,7 @@ mod tests {
15511619
let mut caller = Location::caller();
15521620

15531621
let mut_typed = Mut {
1622+
on_change: None,
15541623
value: &mut c,
15551624
ticks,
15561625
#[cfg(feature = "track_change_detection")]

crates/bevy_ecs/src/observer/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ pub struct Observers {
247247
on_insert: CachedObservers,
248248
on_replace: CachedObservers,
249249
on_remove: CachedObservers,
250+
on_mutate: CachedObservers,
250251
// Map from trigger type to set of observers
251252
cache: HashMap<ComponentId, CachedObservers>,
252253
}
@@ -258,6 +259,7 @@ impl Observers {
258259
ON_INSERT => &mut self.on_insert,
259260
ON_REPLACE => &mut self.on_replace,
260261
ON_REMOVE => &mut self.on_remove,
262+
ON_MUTATE => &mut self.on_mutate,
261263
_ => self.cache.entry(event_type).or_default(),
262264
}
263265
}
@@ -268,6 +270,7 @@ impl Observers {
268270
ON_INSERT => Some(&self.on_insert),
269271
ON_REPLACE => Some(&self.on_replace),
270272
ON_REMOVE => Some(&self.on_remove),
273+
ON_MUTATE => Some(&self.on_mutate),
271274
_ => self.cache.get(&event_type),
272275
}
273276
}
@@ -342,6 +345,7 @@ impl Observers {
342345
ON_INSERT => Some(ArchetypeFlags::ON_INSERT_OBSERVER),
343346
ON_REPLACE => Some(ArchetypeFlags::ON_REPLACE_OBSERVER),
344347
ON_REMOVE => Some(ArchetypeFlags::ON_REMOVE_OBSERVER),
348+
ON_MUTATE => Some(ArchetypeFlags::ON_MUTATE_OBSERVER),
345349
_ => None,
346350
}
347351
}
@@ -378,6 +382,14 @@ impl Observers {
378382
{
379383
flags.insert(ArchetypeFlags::ON_REMOVE_OBSERVER);
380384
}
385+
386+
if self
387+
.on_mutate
388+
.component_observers
389+
.contains_key(&component_id)
390+
{
391+
flags.insert(ArchetypeFlags::ON_MUTATE_OBSERVER);
392+
}
381393
}
382394
}
383395

0 commit comments

Comments
 (0)