Skip to content

Commit

Permalink
Added Ref to allow immutable access with change detection
Browse files Browse the repository at this point in the history
  • Loading branch information
Guvante committed Jan 5, 2023
1 parent 329b71f commit db59dd6
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 80 deletions.
219 changes: 164 additions & 55 deletions crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,30 +26,19 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1);
/// Types that implement reliable change detection.
///
/// ## Example
/// Using types that implement [`DetectChanges`], such as [`ResMut`], provide
/// Using types that implement [`DetectChanges`], such as [`ResRef`], provide
/// a way to query if a value has been mutated in another system.
/// Normally change detecting is triggered by either [`DerefMut`] or [`AsMut`], however
/// it can be manually triggered via [`DetectChanges::set_changed`].
///
/// To ensure that changes are only triggered when the value actually differs,
/// check if the value would change before assignment, such as by checking that `new != old`.
/// You must be *sure* that you are not mutably derefencing in this process.
///
/// [`set_if_neq`](DetectChanges::set_if_neq) is a helper
/// method for this common functionality.
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// #[derive(Resource)]
/// struct MyResource(u32);
///
/// fn my_system(mut resource: ResMut<MyResource>) {
/// fn my_system(mut resource: ResRef<MyResource>) {
/// if resource.is_changed() {
/// println!("My resource was mutated!");
/// }
///
/// resource.0 = 42; // triggers change detection via [`DerefMut`]
/// }
/// ```
///
Expand All @@ -65,14 +54,6 @@ pub trait DetectChanges {
/// Returns `true` if this value was added or mutably dereferenced after the system last ran.
fn is_changed(&self) -> bool;

/// Flags this value as having been changed.
///
/// Mutably accessing this smart pointer will automatically flag this value as having been changed.
/// However, mutation through interior mutability requires manual reporting.
///
/// **Note**: This operation cannot be undone.
fn set_changed(&mut self);

/// Returns the change tick recording the previous time this data was changed.
///
/// Note that components and resources are also marked as changed upon insertion.
Expand All @@ -81,6 +62,46 @@ pub trait DetectChanges {
/// [`SystemChangeTick`](crate::system::SystemChangeTick)
/// [`SystemParam`](crate::system::SystemParam).
fn last_changed(&self) -> u32;
}

/// Types that implement reliable change detection.
///
/// ## Example
/// Using types that implement [`DetectChangesMut`], such as [`ResMut`], provide
/// a way to query if a value has been mutated in another system.
/// Normally change detecting is triggered by either [`DerefMut`] or [`AsMut`], however
/// it can be manually triggered via [`DetectChanges::set_changed`].
///
/// To ensure that changes are only triggered when the value actually differs,
/// check if the value would change before assignment, such as by checking that `new != old`.
/// You must be *sure* that you are not mutably derefencing in this process.
///
/// [`set_if_neq`](DetectChanges::set_if_neq) is a helper
/// method for this common functionality.
///
/// ```
/// use bevy_ecs::prelude::*;
///
/// #[derive(Resource)]
/// struct MyResource(u32);
///
/// fn my_system(mut resource: ResMut<MyResource>) {
/// if resource.is_changed() {
/// println!("My resource was mutated!");
/// }
///
/// resource.0 = 42; // triggers change detection via [`DerefMut`]
/// }
/// ```
///
pub trait DetectChangesMut : DetectChanges {
/// Flags this value as having been changed.
///
/// Mutably accessing this smart pointer will automatically flag this value as having been changed.
/// However, mutation through interior mutability requires manual reporting.
///
/// **Note**: This operation cannot be undone.
fn set_changed(&mut self);

/// Manually sets the change tick recording the previous time this data was mutated.
///
Expand All @@ -90,6 +111,7 @@ pub trait DetectChanges {
/// If you want to avoid triggering change detection, use [`bypass_change_detection`](DetectChanges::bypass_change_detection) instead.
fn set_last_changed(&mut self, last_change_tick: u32);


/// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick.
///
/// # Warning
Expand Down Expand Up @@ -129,18 +151,40 @@ macro_rules! change_detection_impl {
.is_older_than(self.ticks.last_change_tick, self.ticks.change_tick)
}

#[inline]
fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
}
}

impl<$($generics),*: ?Sized $(+ $traits)?> Deref for $name<$($generics),*> {
type Target = $target;

#[inline]
fn deref(&self) -> &Self::Target {
self.value
}
}

impl<$($generics),* $(: $traits)?> AsRef<$target> for $name<$($generics),*> {
#[inline]
fn as_ref(&self) -> &$target {
self.deref()
}
}
}
}

macro_rules! change_detection_mut_impl {
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
impl<$($generics),* : ?Sized $(+ $traits)?> DetectChangesMut for $name<$($generics),*> {
#[inline]
fn set_changed(&mut self) {
self.ticks
.changed
.set_changed(self.ticks.change_tick);
}

#[inline]
fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
}

#[inline]
fn set_last_changed(&mut self, last_change_tick: u32) {
self.ticks.last_change_tick = last_change_tick
Expand All @@ -165,15 +209,6 @@ macro_rules! change_detection_impl {
}
}

impl<$($generics),*: ?Sized $(+ $traits)?> Deref for $name<$($generics),*> {
type Target = $target;

#[inline]
fn deref(&self) -> &Self::Target {
self.value
}
}

impl<$($generics),* : ?Sized $(+ $traits)?> DerefMut for $name<$($generics),*> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
Expand All @@ -182,13 +217,6 @@ macro_rules! change_detection_impl {
}
}

impl<$($generics),* $(: $traits)?> AsRef<$target> for $name<$($generics),*> {
#[inline]
fn as_ref(&self) -> &$target {
self.deref()
}
}

impl<$($generics),* $(: $traits)?> AsMut<$target> for $name<$($generics),*> {
#[inline]
fn as_mut(&mut self) -> &mut $target {
Expand Down Expand Up @@ -260,13 +288,38 @@ macro_rules! impl_debug {
}

pub(crate) struct Ticks<'a> {
pub(crate) added: &'a Tick,
pub(crate) changed: &'a Tick,
pub(crate) last_change_tick: u32,
pub(crate) change_tick: u32,
}

impl<'a> Ticks<'a> {
/// # Safety
/// This should never alias the underlying ticks with a mutable one such as TicksMut.
#[inline]
pub(crate) unsafe fn from_tick_cells(
cells: TickCells<'a>,
last_change_tick: u32,
change_tick: u32,
) -> Self {
Self {
added: cells.added.deref(),
changed: cells.changed.deref(),
last_change_tick,
change_tick,
}
}
}

pub(crate) struct TicksMut<'a> {
pub(crate) added: &'a mut Tick,
pub(crate) changed: &'a mut Tick,
pub(crate) last_change_tick: u32,
pub(crate) change_tick: u32,
}

impl<'a> Ticks<'a> {
impl<'a> TicksMut<'a> {
/// # Safety
/// This should never alias the underlying ticks. All access must be unique.
#[inline]
Expand All @@ -284,6 +337,56 @@ impl<'a> Ticks<'a> {
}
}

/// Shared borrow of an entity's component with change tracking metadata
pub struct Ref<'a, T: ?Sized> {
pub(crate) value: &'a T,
pub(crate) ticks: Ticks<'a>,
}

impl<'w, 'a, T> IntoIterator for &'a Ref<'w, T>
where
&'a T: IntoIterator,
{
type Item = <&'a T as IntoIterator>::Item;
type IntoIter = <&'a T as IntoIterator>::IntoIter;

fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}
change_detection_impl!(Ref<'a, T>, T,);
impl_debug!(Ref<'a, T>,);


/// Shared borrow of a [`Resource`] with change tracking metadata.
///
/// See the [`Resource`] documentation for usage.
///
/// # Panics
///
/// Panics when used as a [`SystemParam`](crate::system::SystemParam) if the resource does not exist.
///
/// Use `Option<ResRef<T>>` instead if the resource might not always exist.
pub struct ResRef<'a, T: ?Sized + Resource> {
pub(crate) value: &'a T,
pub(crate) ticks: Ticks<'a>,
}
change_detection_impl!(ResRef<'a, T>, T, Resource);

impl<'w, 'a, T: Resource> IntoIterator for &'a ResRef<'w, T>
where
&'a T: IntoIterator,
{
type Item = <&'a T as IntoIterator>::Item;
type IntoIter = <&'a T as IntoIterator>::IntoIter;

fn into_iter(self) -> Self::IntoIter {
self.value.into_iter()
}
}

impl_debug!(ResRef<'a, T>, Resource);

/// Unique mutable borrow of a [`Resource`].
///
/// See the [`Resource`] documentation for usage.
Expand All @@ -297,7 +400,7 @@ impl<'a> Ticks<'a> {
/// Use `Option<ResMut<T>>` instead if the resource might not always exist.
pub struct ResMut<'a, T: ?Sized + Resource> {
pub(crate) value: &'a mut T,
pub(crate) ticks: Ticks<'a>,
pub(crate) ticks: TicksMut<'a>,
}

impl<'w, 'a, T: Resource> IntoIterator for &'a ResMut<'w, T>
Expand Down Expand Up @@ -326,6 +429,7 @@ where
}

change_detection_impl!(ResMut<'a, T>, T, Resource);
change_detection_mut_impl!(ResMut<'a, T>, T, Resource);
impl_methods!(ResMut<'a, T>, T, Resource);
impl_debug!(ResMut<'a, T>, Resource);

Expand Down Expand Up @@ -354,10 +458,11 @@ impl<'a, T: Resource> From<ResMut<'a, T>> for Mut<'a, T> {
/// Use `Option<NonSendMut<T>>` instead if the resource might not always exist.
pub struct NonSendMut<'a, T: ?Sized + 'static> {
pub(crate) value: &'a mut T,
pub(crate) ticks: Ticks<'a>,
pub(crate) ticks: TicksMut<'a>,
}

change_detection_impl!(NonSendMut<'a, T>, T,);
change_detection_mut_impl!(NonSendMut<'a, T>, T,);
impl_methods!(NonSendMut<'a, T>, T,);
impl_debug!(NonSendMut<'a, T>,);

Expand All @@ -375,7 +480,7 @@ impl<'a, T: 'static> From<NonSendMut<'a, T>> for Mut<'a, T> {
/// Unique mutable borrow of an entity's component
pub struct Mut<'a, T: ?Sized> {
pub(crate) value: &'a mut T,
pub(crate) ticks: Ticks<'a>,
pub(crate) ticks: TicksMut<'a>,
}

impl<'w, 'a, T> IntoIterator for &'a Mut<'w, T>
Expand Down Expand Up @@ -404,6 +509,7 @@ where
}

change_detection_impl!(Mut<'a, T>, T,);
change_detection_mut_impl!(Mut<'a, T>, T,);
impl_methods!(Mut<'a, T>, T,);
impl_debug!(Mut<'a, T>,);

Expand All @@ -417,7 +523,7 @@ impl_debug!(Mut<'a, T>,);
/// or are defined outside of rust this can be used.
pub struct MutUntyped<'a> {
pub(crate) value: PtrMut<'a>,
pub(crate) ticks: Ticks<'a>,
pub(crate) ticks: TicksMut<'a>,
}

impl<'a> MutUntyped<'a> {
Expand Down Expand Up @@ -464,13 +570,15 @@ impl<'a> DetectChanges for MutUntyped<'a> {
}

#[inline]
fn set_changed(&mut self) {
self.ticks.changed.set_changed(self.ticks.change_tick);
fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
}
}

impl<'a> DetectChangesMut for MutUntyped<'a> {
#[inline]
fn last_changed(&self) -> u32 {
self.ticks.last_change_tick
fn set_changed(&mut self) {
self.ticks.changed.set_changed(self.ticks.change_tick);
}

#[inline]
Expand Down Expand Up @@ -511,14 +619,15 @@ mod tests {

use crate::{
self as bevy_ecs,
change_detection::{Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE},
change_detection::{Mut, NonSendMut, ResMut, TicksMut, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE},
component::{Component, ComponentTicks, Tick},
query::ChangeTrackers,
system::{IntoSystem, Query, System},
world::World,
};

use super::DetectChanges;
use super::DetectChangesMut;

#[derive(Component, PartialEq)]
struct C;
Expand Down Expand Up @@ -622,7 +731,7 @@ mod tests {
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = Ticks {
let ticks = TicksMut {
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_change_tick: 3,
Expand All @@ -647,7 +756,7 @@ mod tests {
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = Ticks {
let ticks = TicksMut {
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_change_tick: 3,
Expand Down Expand Up @@ -676,7 +785,7 @@ mod tests {
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = Ticks {
let ticks = TicksMut {
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_change_tick,
Expand Down
Loading

0 comments on commit db59dd6

Please sign in to comment.