Add a method for mapping Mut<T>
-> Mut<U>
(#6199)
# Objective When designing an API, you may wish to provide access only to a specific field of a component or resource. The current options for doing this in safe code are * `*Mut::into_inner`, which flags a change no matter what. * `*Mut::bypass_change_detection`, which misses all changes. ## Solution Add the method `map_unchanged`. ### Example ```rust // When run, zeroes the translation of every entity. fn reset_all(mut transforms: Query<&mut Transform>) { for transform in &mut transforms { // We pinky promise not to modify `t` within the closure. let translation = transform.map_unchanged(|t| &mut t.translation); // Only reset the translation if it isn't already zero. translation.set_if_not_equal(Vec2::ZERO); } } ``` --- ## Changelog + Added the method `map_unchanged` to types `Mut<T>`, `ResMut<T>`, and `NonSendMut<T>`.
This commit is contained in:
parent
eb0a9e1586
commit
2cff2278ca
@ -163,7 +163,7 @@ macro_rules! change_detection_impl {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_into_inner {
|
macro_rules! impl_methods {
|
||||||
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
|
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
|
||||||
impl<$($generics),* : ?Sized $(+ $traits)?> $name<$($generics),*> {
|
impl<$($generics),* : ?Sized $(+ $traits)?> $name<$($generics),*> {
|
||||||
/// Consume `self` and return a mutable reference to the
|
/// Consume `self` and return a mutable reference to the
|
||||||
@ -173,6 +173,38 @@ macro_rules! impl_into_inner {
|
|||||||
self.set_changed();
|
self.set_changed();
|
||||||
self.value
|
self.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Maps to an inner value by applying a function to the contained reference, without flagging a change.
|
||||||
|
///
|
||||||
|
/// You should never modify the argument passed to the closure -- if you want to modify the data
|
||||||
|
/// without flagging a change, consider using [`DetectChanges::bypass_change_detection`] to make your intent explicit.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use bevy_ecs::prelude::*;
|
||||||
|
/// # pub struct Vec2;
|
||||||
|
/// # impl Vec2 { pub const ZERO: Self = Self; }
|
||||||
|
/// # #[derive(Component)] pub struct Transform { translation: Vec2 }
|
||||||
|
/// # mod my_utils {
|
||||||
|
/// # pub fn set_if_not_equal<T>(x: bevy_ecs::prelude::Mut<T>, val: T) { unimplemented!() }
|
||||||
|
/// # }
|
||||||
|
/// // When run, zeroes the translation of every entity.
|
||||||
|
/// fn reset_positions(mut transforms: Query<&mut Transform>) {
|
||||||
|
/// for transform in &mut transforms {
|
||||||
|
/// // We pinky promise not to modify `t` within the closure.
|
||||||
|
/// // Breaking this promise will result in logic errors, but will never cause undefined behavior.
|
||||||
|
/// let translation = transform.map_unchanged(|t| &mut t.translation);
|
||||||
|
/// // Only reset the translation if it isn't already zero;
|
||||||
|
/// my_utils::set_if_not_equal(translation, Vec2::ZERO);
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// # bevy_ecs::system::assert_is_system(reset_positions);
|
||||||
|
/// ```
|
||||||
|
pub fn map_unchanged<U: ?Sized>(self, f: impl FnOnce(&mut $target) -> &mut U) -> Mut<'a, U> {
|
||||||
|
Mut {
|
||||||
|
value: f(self.value),
|
||||||
|
ticks: self.ticks,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -215,7 +247,7 @@ pub struct ResMut<'a, T: ?Sized + Resource> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
change_detection_impl!(ResMut<'a, T>, T, Resource);
|
change_detection_impl!(ResMut<'a, T>, T, Resource);
|
||||||
impl_into_inner!(ResMut<'a, T>, T, Resource);
|
impl_methods!(ResMut<'a, T>, T, Resource);
|
||||||
impl_debug!(ResMut<'a, T>, Resource);
|
impl_debug!(ResMut<'a, T>, Resource);
|
||||||
|
|
||||||
impl<'a, T: Resource> From<ResMut<'a, T>> for Mut<'a, T> {
|
impl<'a, T: Resource> From<ResMut<'a, T>> for Mut<'a, T> {
|
||||||
@ -247,7 +279,7 @@ pub struct NonSendMut<'a, T: ?Sized + 'static> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
change_detection_impl!(NonSendMut<'a, T>, T,);
|
change_detection_impl!(NonSendMut<'a, T>, T,);
|
||||||
impl_into_inner!(NonSendMut<'a, T>, T,);
|
impl_methods!(NonSendMut<'a, T>, T,);
|
||||||
impl_debug!(NonSendMut<'a, T>,);
|
impl_debug!(NonSendMut<'a, T>,);
|
||||||
|
|
||||||
impl<'a, T: 'static> From<NonSendMut<'a, T>> for Mut<'a, T> {
|
impl<'a, T: 'static> From<NonSendMut<'a, T>> for Mut<'a, T> {
|
||||||
@ -268,7 +300,7 @@ pub struct Mut<'a, T: ?Sized> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
change_detection_impl!(Mut<'a, T>, T,);
|
change_detection_impl!(Mut<'a, T>, T,);
|
||||||
impl_into_inner!(Mut<'a, T>, T,);
|
impl_methods!(Mut<'a, T>, T,);
|
||||||
impl_debug!(Mut<'a, T>,);
|
impl_debug!(Mut<'a, T>,);
|
||||||
|
|
||||||
/// Unique mutable borrow of resources or an entity's component.
|
/// Unique mutable borrow of resources or an entity's component.
|
||||||
@ -497,4 +529,38 @@ mod tests {
|
|||||||
assert_eq!(3, into_mut.ticks.last_change_tick);
|
assert_eq!(3, into_mut.ticks.last_change_tick);
|
||||||
assert_eq!(4, into_mut.ticks.change_tick);
|
assert_eq!(4, into_mut.ticks.change_tick);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn map_mut() {
|
||||||
|
use super::*;
|
||||||
|
struct Outer(i64);
|
||||||
|
|
||||||
|
let mut component_ticks = ComponentTicks {
|
||||||
|
added: 1,
|
||||||
|
changed: 2,
|
||||||
|
};
|
||||||
|
let (last_change_tick, change_tick) = (2, 3);
|
||||||
|
let ticks = Ticks {
|
||||||
|
component_ticks: &mut component_ticks,
|
||||||
|
last_change_tick,
|
||||||
|
change_tick,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut outer = Outer(0);
|
||||||
|
let ptr = Mut {
|
||||||
|
value: &mut outer,
|
||||||
|
ticks,
|
||||||
|
};
|
||||||
|
assert!(!ptr.is_changed());
|
||||||
|
|
||||||
|
// Perform a mapping operation.
|
||||||
|
let mut inner = ptr.map_unchanged(|x| &mut x.0);
|
||||||
|
assert!(!inner.is_changed());
|
||||||
|
|
||||||
|
// Mutate the inner value.
|
||||||
|
*inner = 64;
|
||||||
|
assert!(inner.is_changed());
|
||||||
|
// Modifying one field of a component should flag a change for the entire component.
|
||||||
|
assert!(component_ticks.is_changed(last_change_tick, change_tick));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user