Skip to content

Commit 0e4cdd7

Browse files
author
xiejiaen
committed
mutate observer
1 parent 5656166 commit 0e4cdd7

18 files changed

+589
-37
lines changed

Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2914,6 +2914,17 @@ description = "Demonstrates how to create a node with a border"
29142914
category = "UI (User Interface)"
29152915
wasm = true
29162916

2917+
[[example]]
2918+
name = "responding_to_changes"
2919+
path = "examples/ecs/responding_to_changes.rs"
2920+
doc-scrape-examples = true
2921+
2922+
[package.metadata.example.reactivity]
2923+
name = "Responding to Changes"
2924+
description = "Demonstrates how and when to use change detection and `OnMutate` hooks and observers"
2925+
category = "ECS (Entity Component System)"
2926+
wasm = true
2927+
29172928
[[example]]
29182929
name = "box_shadow"
29192930
path = "examples/ui/box_shadow.rs"

crates/bevy_ecs/macros/src/world_query.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ pub(crate) fn world_query_impl(
151151
}
152152

153153
const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*;
154+
const IS_MUTATE: bool = false #(|| <#field_types>::IS_MUTATE)*;
154155

155156
/// SAFETY: we call `set_archetype` for each member that implements `Fetch`
156157
#[inline]

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: 95 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,13 @@ use core::{
1010
mem,
1111
ops::{Deref, DerefMut},
1212
};
13+
use std::cell::RefCell;
1314
#[cfg(feature = "track_change_detection")]
1415
use {
1516
bevy_ptr::ThinSlicePtr,
1617
core::{cell::UnsafeCell, panic::Location},
1718
};
19+
use crate::storage::{EntityChange, EntityChanges, ParallelEntityChanges};
1820

1921
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
2022
///
@@ -387,6 +389,7 @@ macro_rules! impl_methods {
387389
/// <T>`, but you need a `Mut<T>`.
388390
pub fn reborrow(&mut self) -> Mut<'_, $target> {
389391
Mut {
392+
on_change: self.on_change,
390393
value: self.value,
391394
ticks: TicksMut {
392395
added: self.ticks.added,
@@ -423,6 +426,7 @@ macro_rules! impl_methods {
423426
/// ```
424427
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'w, U> {
425428
Mut {
429+
on_change: self.on_change,
426430
value: f(self.value),
427431
ticks: self.ticks,
428432
#[cfg(feature = "track_change_detection")]
@@ -437,6 +441,7 @@ macro_rules! impl_methods {
437441
pub fn filter_map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> Option<&mut U>) -> Option<Mut<'w, U>> {
438442
let value = f(self.value);
439443
value.map(|value| Mut {
444+
on_change: self.on_change,
440445
value,
441446
ticks: self.ticks,
442447
#[cfg(feature = "track_change_detection")]
@@ -659,22 +664,31 @@ where
659664

660665
change_detection_impl!(ResMut<'w, T>, T, Resource);
661666
change_detection_mut_impl!(ResMut<'w, T>, T, Resource);
662-
impl_methods!(ResMut<'w, T>, T, Resource);
667+
// impl_methods!(ResMut<'w, T>, T, Resource);
663668
impl_debug!(ResMut<'w, T>, Resource);
664669

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-
}
670+
impl<'w, T: ?Sized + Resource> ResMut<'w, T> {
671+
672+
/// TODO
673+
pub fn into_inner(mut self) -> &'w mut T {
674+
self.set_changed();
675+
self.value
675676
}
676677
}
677678

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

696710
change_detection_impl!(NonSendMut<'w, T>, T,);
697711
change_detection_mut_impl!(NonSendMut<'w, T>, T,);
698-
impl_methods!(NonSendMut<'w, T>, T,);
712+
// impl_methods!(NonSendMut<'w, T>, T,);
699713
impl_debug!(NonSendMut<'w, T>,);
700714

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-
}
715+
// impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
716+
// /// Convert this `NonSendMut` into a `Mut`. This allows keeping the change-detection feature of `Mut`
717+
// /// while losing the specificity of `NonSendMut`.
718+
// fn from(other: NonSendMut<'w, T>) -> Mut<'w, T> {
719+
// Mut {
720+
// value: other.value,
721+
// ticks: other.ticks,
722+
// #[cfg(feature = "track_change_detection")]
723+
// changed_by: other.changed_by,
724+
// }
725+
// }
726+
// }
713727

714728
/// Shared borrow of an entity's component with access to change detection.
715729
/// Similar to [`Mut`] but is immutable and so doesn't require unique access.
@@ -869,6 +883,7 @@ impl_debug!(Ref<'w, T>,);
869883
/// # fn update_player_position(player: &Player, new_position: Position) {}
870884
/// ```
871885
pub struct Mut<'w, T: ?Sized> {
886+
pub(crate) on_change: Option<(EntityChange, &'w RefCell<EntityChanges>)>,
872887
pub(crate) value: &'w mut T,
873888
pub(crate) ticks: TicksMut<'w>,
874889
#[cfg(feature = "track_change_detection")]
@@ -900,6 +915,7 @@ impl<'w, T: ?Sized> Mut<'w, T> {
900915
#[cfg(feature = "track_change_detection")] caller: &'w mut &'static Location<'static>,
901916
) -> Self {
902917
Self {
918+
on_change: None,
903919
value,
904920
ticks: TicksMut {
905921
added,
@@ -949,8 +965,58 @@ where
949965
}
950966
}
951967

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

@@ -1041,6 +1107,7 @@ impl<'w> MutUntyped<'w> {
10411107
/// ```
10421108
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'w>) -> &'w mut T) -> Mut<'w, T> {
10431109
Mut {
1110+
on_change: None, // TODO
10441111
value: f(self.value),
10451112
ticks: self.ticks,
10461113
#[cfg(feature = "track_change_detection")]
@@ -1054,6 +1121,7 @@ impl<'w> MutUntyped<'w> {
10541121
/// - `T` must be the erased pointee type for this [`MutUntyped`].
10551122
pub unsafe fn with_type<T>(self) -> Mut<'w, T> {
10561123
Mut {
1124+
on_change: None,
10571125
// SAFETY: `value` is `Aligned` and caller ensures the pointee type is `T`.
10581126
value: unsafe { self.value.deref_mut() },
10591127
ticks: self.ticks,
@@ -1423,6 +1491,7 @@ mod tests {
14231491
let mut caller = Location::caller();
14241492

14251493
let ptr = Mut {
1494+
on_change: None,
14261495
value: &mut outer,
14271496
ticks,
14281497
#[cfg(feature = "track_change_detection")]
@@ -1551,6 +1620,7 @@ mod tests {
15511620
let mut caller = Location::caller();
15521621

15531622
let mut_typed = Mut {
1623+
on_change: None,
15541624
value: &mut c,
15551625
ticks,
15561626
#[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)