Skip to content

Commit 7f1357d

Browse files
author
xiejiaen
committed
mutate observer
1 parent 5656166 commit 7f1357d

File tree

17 files changed

+578
-28
lines changed

17 files changed

+578
-28
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: 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::{ParallelChanges, 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 ParallelChanges)>,
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)