Skip to content

Commit 1175027

Browse files
author
xiejiaen
committed
mutate observer
1 parent 5656166 commit 1175027

20 files changed

+655
-14
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.responding_to_changes]
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: 168 additions & 2 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::world::entity_change::{EntityChange, EntityChanges};
1820

1921
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
2022
///
@@ -370,6 +372,62 @@ macro_rules! change_detection_mut_impl {
370372
};
371373
}
372374

375+
macro_rules! change_detection_mut_with_onchange_impl {
376+
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
377+
impl<$($generics),* : ?Sized $(+ $traits)?> DetectChangesMut for $name<$($generics),*> {
378+
type Inner = $target;
379+
380+
#[inline]
381+
#[track_caller]
382+
fn set_changed(&mut self) {
383+
*self.ticks.changed = self.ticks.this_run;
384+
if let Some((change, changes)) = self.on_change {
385+
changes.borrow_mut().push(change);
386+
}
387+
#[cfg(feature = "track_change_detection")]
388+
{
389+
*self.changed_by = Location::caller();
390+
}
391+
}
392+
393+
#[inline]
394+
#[track_caller]
395+
fn set_last_changed(&mut self, last_changed: Tick) {
396+
*self.ticks.changed = last_changed;
397+
#[cfg(feature = "track_change_detection")]
398+
{
399+
*self.changed_by = Location::caller();
400+
}
401+
}
402+
403+
#[inline]
404+
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
405+
self.value
406+
}
407+
}
408+
409+
impl<$($generics),* : ?Sized $(+ $traits)?> DerefMut for $name<$($generics),*> {
410+
#[inline]
411+
#[track_caller]
412+
fn deref_mut(&mut self) -> &mut Self::Target {
413+
self.set_changed();
414+
#[cfg(feature = "track_change_detection")]
415+
{
416+
*self.changed_by = Location::caller();
417+
}
418+
self.value
419+
}
420+
}
421+
422+
impl<$($generics),* $(: $traits)?> AsMut<$target> for $name<$($generics),*> {
423+
#[inline]
424+
fn as_mut(&mut self) -> &mut $target {
425+
self.deref_mut()
426+
}
427+
}
428+
};
429+
}
430+
373431
macro_rules! impl_methods {
374432
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
375433
impl<$($generics),* : ?Sized $(+ $traits)?> $name<$($generics),*> {
@@ -387,6 +445,96 @@ macro_rules! impl_methods {
387445
/// <T>`, but you need a `Mut<T>`.
388446
pub fn reborrow(&mut self) -> Mut<'_, $target> {
389447
Mut {
448+
on_change: None,
449+
value: self.value,
450+
ticks: TicksMut {
451+
added: self.ticks.added,
452+
changed: self.ticks.changed,
453+
last_run: self.ticks.last_run,
454+
this_run: self.ticks.this_run,
455+
},
456+
#[cfg(feature = "track_change_detection")]
457+
changed_by: self.changed_by,
458+
}
459+
}
460+
461+
/// Maps to an inner value by applying a function to the contained reference, without flagging a change.
462+
///
463+
/// You should never modify the argument passed to the closure -- if you want to modify the data
464+
/// without flagging a change, consider using [`DetectChangesMut::bypass_change_detection`] to make your intent explicit.
465+
///
466+
/// ```
467+
/// # use bevy_ecs::prelude::*;
468+
/// # #[derive(PartialEq)] pub struct Vec2;
469+
/// # impl Vec2 { pub const ZERO: Self = Self; }
470+
/// # #[derive(Component)] pub struct Transform { translation: Vec2 }
471+
/// // When run, zeroes the translation of every entity.
472+
/// fn reset_positions(mut transforms: Query<&mut Transform>) {
473+
/// for transform in &mut transforms {
474+
/// // We pinky promise not to modify `t` within the closure.
475+
/// // Breaking this promise will result in logic errors, but will never cause undefined behavior.
476+
/// let mut translation = transform.map_unchanged(|t| &mut t.translation);
477+
/// // Only reset the translation if it isn't already zero;
478+
/// translation.set_if_neq(Vec2::ZERO);
479+
/// }
480+
/// }
481+
/// # bevy_ecs::system::assert_is_system(reset_positions);
482+
/// ```
483+
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'w, U> {
484+
Mut {
485+
on_change: None,
486+
value: f(self.value),
487+
ticks: self.ticks,
488+
#[cfg(feature = "track_change_detection")]
489+
changed_by: self.changed_by,
490+
}
491+
}
492+
493+
/// Optionally maps to an inner value by applying a function to the contained reference.
494+
/// This is useful in a situation where you need to convert a `Mut<T>` to a `Mut<U>`, but only if `T` contains `U`.
495+
///
496+
/// As with `map_unchanged`, you should never modify the argument passed to the closure.
497+
pub fn filter_map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> Option<&mut U>) -> Option<Mut<'w, U>> {
498+
let value = f(self.value);
499+
value.map(|value| Mut {
500+
on_change: None,
501+
value,
502+
ticks: self.ticks,
503+
#[cfg(feature = "track_change_detection")]
504+
changed_by: self.changed_by,
505+
})
506+
}
507+
508+
/// Allows you access to the dereferenced value of this pointer without immediately
509+
/// triggering change detection.
510+
pub fn as_deref_mut(&mut self) -> Mut<'_, <$target as Deref>::Target>
511+
where $target: DerefMut
512+
{
513+
self.reborrow().map_unchanged(|v| v.deref_mut())
514+
}
515+
516+
}
517+
};
518+
}
519+
520+
macro_rules! impl_methods_with_onchange {
521+
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
522+
impl<$($generics),* : ?Sized $(+ $traits)?> $name<$($generics),*> {
523+
/// Consume `self` and return a mutable reference to the
524+
/// contained value while marking `self` as "changed".
525+
#[inline]
526+
pub fn into_inner(mut self) -> &'w mut $target {
527+
self.set_changed();
528+
self.value
529+
}
530+
531+
/// Returns a `Mut<>` with a smaller lifetime.
532+
/// This is useful if you have `&mut
533+
#[doc = stringify!($name)]
534+
/// <T>`, but you need a `Mut<T>`.
535+
pub fn reborrow(&mut self) -> Mut<'_, $target> {
536+
Mut {
537+
on_change: self.on_change,
390538
value: self.value,
391539
ticks: TicksMut {
392540
added: self.ticks.added,
@@ -423,6 +571,7 @@ macro_rules! impl_methods {
423571
/// ```
424572
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'w, U> {
425573
Mut {
574+
on_change: self.on_change,
426575
value: f(self.value),
427576
ticks: self.ticks,
428577
#[cfg(feature = "track_change_detection")]
@@ -437,6 +586,7 @@ macro_rules! impl_methods {
437586
pub fn filter_map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> Option<&mut U>) -> Option<Mut<'w, U>> {
438587
let value = f(self.value);
439588
value.map(|value| Mut {
589+
on_change: self.on_change,
440590
value,
441591
ticks: self.ticks,
442592
#[cfg(feature = "track_change_detection")]
@@ -667,6 +817,7 @@ impl<'w, T: Resource> From<ResMut<'w, T>> for Mut<'w, T> {
667817
/// while losing the specificity of `ResMut` for resources.
668818
fn from(other: ResMut<'w, T>) -> Mut<'w, T> {
669819
Mut {
820+
on_change: None,
670821
value: other.value,
671822
ticks: other.ticks,
672823
#[cfg(feature = "track_change_detection")]
@@ -703,6 +854,7 @@ impl<'w, T: 'static> From<NonSendMut<'w, T>> for Mut<'w, T> {
703854
/// while losing the specificity of `NonSendMut`.
704855
fn from(other: NonSendMut<'w, T>) -> Mut<'w, T> {
705856
Mut {
857+
on_change: None,
706858
value: other.value,
707859
ticks: other.ticks,
708860
#[cfg(feature = "track_change_detection")]
@@ -869,6 +1021,7 @@ impl_debug!(Ref<'w, T>,);
8691021
/// # fn update_player_position(player: &Player, new_position: Position) {}
8701022
/// ```
8711023
pub struct Mut<'w, T: ?Sized> {
1024+
pub(crate) on_change: Option<(EntityChange, &'w RefCell<EntityChanges>)>,
8721025
pub(crate) value: &'w mut T,
8731026
pub(crate) ticks: TicksMut<'w>,
8741027
#[cfg(feature = "track_change_detection")]
@@ -900,6 +1053,7 @@ impl<'w, T: ?Sized> Mut<'w, T> {
9001053
#[cfg(feature = "track_change_detection")] caller: &'w mut &'static Location<'static>,
9011054
) -> Self {
9021055
Self {
1056+
on_change: None,
9031057
value,
9041058
ticks: TicksMut {
9051059
added,
@@ -949,9 +1103,10 @@ where
9491103
}
9501104
}
9511105

1106+
9521107
change_detection_impl!(Mut<'w, T>, T,);
953-
change_detection_mut_impl!(Mut<'w, T>, T,);
954-
impl_methods!(Mut<'w, T>, T,);
1108+
change_detection_mut_with_onchange_impl!(Mut<'w, T>, T,);
1109+
impl_methods_with_onchange!(Mut<'w, T>, T,);
9551110
impl_debug!(Mut<'w, T>,);
9561111

9571112
/// Unique mutable borrow of resources or an entity's component.
@@ -963,6 +1118,7 @@ impl_debug!(Mut<'w, T>,);
9631118
/// [`Mut`], but in situations where the types are not known at compile time
9641119
/// or are defined outside of rust this can be used.
9651120
pub struct MutUntyped<'w> {
1121+
pub(crate) on_change: Option<(EntityChange, &'w RefCell<EntityChanges>)>,
9661122
pub(crate) value: PtrMut<'w>,
9671123
pub(crate) ticks: TicksMut<'w>,
9681124
#[cfg(feature = "track_change_detection")]
@@ -984,6 +1140,7 @@ impl<'w> MutUntyped<'w> {
9841140
#[inline]
9851141
pub fn reborrow(&mut self) -> MutUntyped {
9861142
MutUntyped {
1143+
on_change: self.on_change,
9871144
value: self.value.reborrow(),
9881145
ticks: TicksMut {
9891146
added: self.ticks.added,
@@ -1041,6 +1198,7 @@ impl<'w> MutUntyped<'w> {
10411198
/// ```
10421199
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'w>) -> &'w mut T) -> Mut<'w, T> {
10431200
Mut {
1201+
on_change: None, // TODO
10441202
value: f(self.value),
10451203
ticks: self.ticks,
10461204
#[cfg(feature = "track_change_detection")]
@@ -1054,6 +1212,7 @@ impl<'w> MutUntyped<'w> {
10541212
/// - `T` must be the erased pointee type for this [`MutUntyped`].
10551213
pub unsafe fn with_type<T>(self) -> Mut<'w, T> {
10561214
Mut {
1215+
on_change: None,
10571216
// SAFETY: `value` is `Aligned` and caller ensures the pointee type is `T`.
10581217
value: unsafe { self.value.deref_mut() },
10591218
ticks: self.ticks,
@@ -1098,6 +1257,9 @@ impl<'w> DetectChangesMut for MutUntyped<'w> {
10981257
#[track_caller]
10991258
fn set_changed(&mut self) {
11001259
*self.ticks.changed = self.ticks.this_run;
1260+
if let Some((change, changes)) = self.on_change {
1261+
changes.borrow_mut().push(change);
1262+
}
11011263
#[cfg(feature = "track_change_detection")]
11021264
{
11031265
*self.changed_by = Location::caller();
@@ -1132,6 +1294,7 @@ impl core::fmt::Debug for MutUntyped<'_> {
11321294
impl<'w, T> From<Mut<'w, T>> for MutUntyped<'w> {
11331295
fn from(value: Mut<'w, T>) -> Self {
11341296
MutUntyped {
1297+
on_change: value.on_change,
11351298
value: value.value.into(),
11361299
ticks: value.ticks,
11371300
#[cfg(feature = "track_change_detection")]
@@ -1423,6 +1586,7 @@ mod tests {
14231586
let mut caller = Location::caller();
14241587

14251588
let ptr = Mut {
1589+
on_change: None,
14261590
value: &mut outer,
14271591
ticks,
14281592
#[cfg(feature = "track_change_detection")]
@@ -1513,6 +1677,7 @@ mod tests {
15131677
let mut caller = Location::caller();
15141678

15151679
let value = MutUntyped {
1680+
on_change: None,
15161681
value: PtrMut::from(&mut value),
15171682
ticks,
15181683
#[cfg(feature = "track_change_detection")]
@@ -1551,6 +1716,7 @@ mod tests {
15511716
let mut caller = Location::caller();
15521717

15531718
let mut_typed = Mut {
1719+
on_change: None,
15541720
value: &mut c,
15551721
ticks,
15561722
#[cfg(feature = "track_change_detection")]

0 commit comments

Comments
 (0)