add MutUntyped::map_unchanged (#9194)

### **Adopted #6430**

# Objective

`MutUntyped` is the untyped variant of `Mut<T>` that stores a `PtrMut`
instead of a `&mut T`. Working with a `MutUntyped` is a bit annoying,
because as soon you want to use the ptr e.g. as a `&mut dyn Reflect` you
cannot use a type like `Mut<dyn Reflect>` but instead need to carry
around a `&mut dyn Reflect` and a `impl FnMut()` to mark the value as
changed.
## Solution

* Provide a method `map_unchanged` to turn a `MutUntyped` into a
`Mut<T>` by mapping the `PtrMut<'a>` to a `&'a mut T`
      This can be used like this:


```rust
// SAFETY: ptr is of type `u8`
let val: Mut<u8> = mut_untyped.map_unchanged(|ptr| unsafe { ptr.deref_mut::<u8>() });

// SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped`
let val: Mut<dyn Reflect> = mut_untyped.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) });
```

Note that nothing prevents you from doing

```rust
mut_untyped.map_unchanged(|ptr| &mut ());
```

or using any other mutable reference you can get, but IMO that is fine
since that will only result in a `Mut` that will dereference to that
value and mark the original value as changed. The lifetimes here prevent
anything bad from happening.
## Alternatives

1. Make `Ticks` public and provide a method to get construct a `Mut`
from `Ticks` and `&mut T`. More powerful and more easy to misuse.
2. Do nothing. People can still do everything they want, but they need
to pass (`&mut dyn Reflect, impl FnMut() + '_)` around instead of
`Mut<dyn Reflect>`

## Changelog

- add `MutUntyped::map_unchanged` to turn a `MutUntyped` into its typed
counterpart

---------

Co-authored-by: Jakob Hellermann <jakob.hellermann@protonmail.com>
Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com>
This commit is contained in:
Serv 2023-07-23 05:17:31 +04:00 committed by GitHub
parent 630958a9f1
commit 3b1b60e7dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -735,6 +735,35 @@ impl<'a> MutUntyped<'a> {
self.value.as_ref() self.value.as_ref()
} }
/// Turn this [`MutUntyped`] into a [`Mut`] by mapping the inner [`PtrMut`] to another value,
/// without flagging a change.
/// This function is the untyped equivalent of [`Mut::map_unchanged`].
///
/// You should never modify the argument passed to the closure if you want to modify the data without flagging a change, consider using [`bypass_change_detection`](DetectChangesMut::bypass_change_detection) to make your intent explicit.
///
/// If you know the type of the value you can do
/// ```no_run
/// # use bevy_ecs::change_detection::{Mut, MutUntyped};
/// # let mut_untyped: MutUntyped = unimplemented!();
/// // SAFETY: ptr is of type `u8`
/// mut_untyped.map_unchanged(|ptr| unsafe { ptr.deref_mut::<u8>() });
/// ```
/// If you have a [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr) that you know belongs to this [`MutUntyped`],
/// you can do
/// ```no_run
/// # use bevy_ecs::change_detection::{Mut, MutUntyped};
/// # let mut_untyped: MutUntyped = unimplemented!();
/// # let reflect_from_ptr: bevy_reflect::ReflectFromPtr = unimplemented!();
/// // SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped`
/// mut_untyped.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) });
/// ```
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'a>) -> &'a mut T) -> Mut<'a, T> {
Mut {
value: f(self.value),
ticks: self.ticks,
}
}
/// Transforms this [`MutUntyped`] into a [`Mut<T>`] with the same lifetime. /// Transforms this [`MutUntyped`] into a [`Mut<T>`] with the same lifetime.
/// ///
/// # Safety /// # Safety
@ -798,6 +827,8 @@ impl std::fmt::Debug for MutUntyped<'_> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bevy_ecs_macros::Resource; use bevy_ecs_macros::Resource;
use bevy_ptr::PtrMut;
use bevy_reflect::{FromType, ReflectFromPtr};
use crate::{ use crate::{
self as bevy_ecs, self as bevy_ecs,
@ -809,8 +840,7 @@ mod tests {
world::World, world::World,
}; };
use super::DetectChanges; use super::{DetectChanges, DetectChangesMut, MutUntyped};
use super::DetectChangesMut;
#[derive(Component, PartialEq)] #[derive(Component, PartialEq)]
struct C; struct C;
@ -1034,4 +1064,40 @@ mod tests {
"Resource must be changed after setting to a different value." "Resource must be changed after setting to a different value."
); );
} }
#[test]
fn mut_untyped_to_reflect() {
let last_run = Tick::new(2);
let this_run = Tick::new(3);
let mut component_ticks = ComponentTicks {
added: Tick::new(1),
changed: Tick::new(2),
};
let ticks = TicksMut {
added: &mut component_ticks.added,
changed: &mut component_ticks.changed,
last_run,
this_run,
};
let mut value: i32 = 5;
let value = MutUntyped {
value: PtrMut::from(&mut value),
ticks,
};
let reflect_from_ptr = <ReflectFromPtr as FromType<i32>>::from_type();
let mut new = value.map_unchanged(|ptr| {
// SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`.
let value = unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) };
value
});
assert!(!new.is_changed());
new.reflect_mut();
assert!(new.is_changed());
}
} }