Skip to content

Commit

Permalink
mutate observer
Browse files Browse the repository at this point in the history
  • Loading branch information
xiejiaen committed Nov 4, 2024
1 parent 5656166 commit 7f1357d
Show file tree
Hide file tree
Showing 17 changed files with 578 additions and 28 deletions.
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2914,6 +2914,17 @@ description = "Demonstrates how to create a node with a border"
category = "UI (User Interface)"
wasm = true

[[example]]
name = "responding_to_changes"
path = "examples/ecs/responding_to_changes.rs"
doc-scrape-examples = true

[package.metadata.example.reactivity]
name = "Responding to Changes"
description = "Demonstrates how and when to use change detection and `OnMutate` hooks and observers"
category = "ECS (Entity Component System)"
wasm = true

[[example]]
name = "box_shadow"
path = "examples/ui/box_shadow.rs"
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_ecs/macros/src/world_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ pub(crate) fn world_query_impl(
}

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

/// SAFETY: we call `set_archetype` for each member that implements `Fetch`
#[inline]
Expand Down
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::{ParallelChanges, 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 ParallelChanges)>,
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 7f1357d

Please sign in to comment.