Skip to content

Add set_if_neq method to DetectChanges trait #5373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 13 commits into from
Closed
59 changes: 58 additions & 1 deletion crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1);
/// 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 flagged when the value actually differs,
/// check if the value is equal before assignment.
/// You must be *sure* that you are not mutably derefencing in this process.
///
/// The [`set_if_differs`](DetectChanges::set_if_differs) method
/// provides a helper method for this common functionality.
///
/// ```
/// use bevy_ecs::prelude::*;
///
Expand Down Expand Up @@ -65,6 +72,25 @@ pub trait DetectChanges {
/// [`SystemChangeTick`](crate::system::SystemChangeTick)
/// [`SystemParam`](crate::system::SystemParam).
fn last_changed(&self) -> u32;

/// Sets `self` to `value`, if and only if `*self != *value`
///
/// `T` is the type stored within the smart pointer (e.g. [`Mut`] or [`ResMut`]).
///
/// This is useful to avoid accidentally triggering change detection when no change is made,
/// as changes are usually deemed to be made when [`DerefMut`] is used.
#[inline]
fn set_if_differs<T>(&mut self, value: T)
where
Self: Deref<Target = T> + DerefMut<Target = T>,
T: PartialEq,
{
let immutable_ref: &T = self.deref();
if *immutable_ref != value {
let mutable_ref: &mut T = self.deref_mut();
*mutable_ref = value;
}
}
}

macro_rules! change_detection_impl {
Expand Down Expand Up @@ -293,9 +319,14 @@ mod tests {
world::World,
};

#[derive(Component)]
use super::DetectChanges;

#[derive(Component, PartialEq)]
struct C;

#[derive(PartialEq)]
struct R(u8);

#[test]
fn change_expiration() {
fn change_detected(query: Query<ChangeTrackers<C>>) -> bool {
Expand Down Expand Up @@ -382,4 +413,30 @@ mod tests {
assert!(ticks_since_change == MAX_CHANGE_AGE);
}
}

#[test]
fn set_if_differs() {
let mut world = World::new();

world.insert_resource(R(0));
// Resources are Changed when first added
world.increment_change_tick();
// This is required to update world::last_change_tick
world.clear_trackers();

let mut r = world.resource_mut::<R>();
assert!(!r.is_changed(), "Resource must begin unchanged.");

r.set_if_differs(R(0));
assert!(
!r.is_changed(),
"Resource must not be changed after setting to the same value."
);

r.set_if_differs(R(3));
assert!(
r.is_changed(),
"Resource must be changed after setting to a different value."
);
}
}