Add replace_if_neq to DetectChangesMut (#9418)

# Objective

Just like
[`set_if_neq`](https://docs.rs/bevy_ecs/latest/bevy_ecs/change_detection/trait.DetectChangesMut.html#method.set_if_neq),
being able to express the "I don't want to unnecessarily trigger the
change detection" but with the ability to handle the previous value if
change occurs.

## Solution

Add `replace_if_neq` to `DetectChangesMut`.

---

## Changelog

- Added `DetectChangesMut::replace_if_neq`: like `set_if_neq` change the
value only if the new value if different from the current one, but
return the previous value if the change occurs.
This commit is contained in:
Tristan Guichaoua 2023-08-11 23:10:16 +02:00 committed by GitHub
parent 1abb6b0758
commit cfb65c1eaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -6,6 +6,7 @@ use crate::{
system::Resource, system::Resource,
}; };
use bevy_ptr::{Ptr, UnsafeCellDeref}; use bevy_ptr::{Ptr, UnsafeCellDeref};
use std::mem;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans.
@ -129,6 +130,8 @@ pub trait DetectChangesMut: DetectChanges {
/// This is useful to ensure change detection is only triggered when the underlying value /// This is useful to ensure change detection is only triggered when the underlying value
/// changes, instead of every time it is mutably accessed. /// changes, instead of every time it is mutably accessed.
/// ///
/// If you need to handle the previous value, use [`replace_if_neq`](DetectChangesMut::replace_if_neq).
///
/// # Examples /// # Examples
/// ///
/// ``` /// ```
@ -167,6 +170,77 @@ pub trait DetectChangesMut: DetectChanges {
self.set_changed(); self.set_changed();
} }
} }
/// Overwrites this smart pointer with the given value, if and only if `*self != value`
/// returning the previous value if this occurs.
///
/// This is useful to ensure change detection is only triggered when the underlying value
/// changes, instead of every time it is mutably accessed.
///
/// If you don't need to handle the previous value, use [`set_if_neq`](DetectChangesMut::set_if_neq).
///
/// # Examples
///
/// ```
/// # use bevy_ecs::{prelude::*, schedule::common_conditions::{resource_changed, on_event}};
/// #[derive(Resource, PartialEq, Eq)]
/// pub struct Score(u32);
///
/// #[derive(Event, PartialEq, Eq)]
/// pub struct ScoreChanged {
/// current: u32,
/// previous: u32,
/// }
///
/// fn reset_score(mut score: ResMut<Score>, mut score_changed: EventWriter<ScoreChanged>) {
/// // Set the score to zero, unless it is already zero.
/// let new_score = 0;
/// if let Some(Score(previous_score)) = score.replace_if_neq(Score(new_score)) {
/// // If `score` change, emit a `ScoreChanged` event.
/// score_changed.send(ScoreChanged {
/// current: new_score,
/// previous: previous_score,
/// });
/// }
/// }
/// # let mut world = World::new();
/// # world.insert_resource(Events::<ScoreChanged>::default());
/// # world.insert_resource(Score(1));
/// # let mut score_changed = IntoSystem::into_system(resource_changed::<Score>());
/// # score_changed.initialize(&mut world);
/// # score_changed.run((), &mut world);
/// #
/// # let mut score_changed_event = IntoSystem::into_system(on_event::<ScoreChanged>());
/// # score_changed_event.initialize(&mut world);
/// # score_changed_event.run((), &mut world);
/// #
/// # let mut schedule = Schedule::new();
/// # schedule.add_systems(reset_score);
/// #
/// # // first time `reset_score` runs, the score is changed.
/// # schedule.run(&mut world);
/// # assert!(score_changed.run((), &mut world));
/// # assert!(score_changed_event.run((), &mut world));
/// # // second time `reset_score` runs, the score is not changed.
/// # schedule.run(&mut world);
/// # assert!(!score_changed.run((), &mut world));
/// # assert!(!score_changed_event.run((), &mut world));
/// ```
#[inline]
#[must_use = "If you don't need to handle the previous value, use `set_if_neq` instead."]
fn replace_if_neq(&mut self, value: Self::Inner) -> Option<Self::Inner>
where
Self::Inner: Sized + PartialEq,
{
let old = self.bypass_change_detection();
if *old != value {
let previous = mem::replace(old, value);
self.set_changed();
Some(previous)
} else {
None
}
}
} }
macro_rules! change_detection_impl { macro_rules! change_detection_impl {