Skip to content

Commit 3b1b60e

Browse files
Serveratorjakobhellermannjoseph-gio
authored
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 <[email protected]> Co-authored-by: JoJoJet <[email protected]>
1 parent 630958a commit 3b1b60e

File tree

1 file changed

+68
-2
lines changed

1 file changed

+68
-2
lines changed

crates/bevy_ecs/src/change_detection.rs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,35 @@ impl<'a> MutUntyped<'a> {
735735
self.value.as_ref()
736736
}
737737

738+
/// Turn this [`MutUntyped`] into a [`Mut`] by mapping the inner [`PtrMut`] to another value,
739+
/// without flagging a change.
740+
/// This function is the untyped equivalent of [`Mut::map_unchanged`].
741+
///
742+
/// 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.
743+
///
744+
/// If you know the type of the value you can do
745+
/// ```no_run
746+
/// # use bevy_ecs::change_detection::{Mut, MutUntyped};
747+
/// # let mut_untyped: MutUntyped = unimplemented!();
748+
/// // SAFETY: ptr is of type `u8`
749+
/// mut_untyped.map_unchanged(|ptr| unsafe { ptr.deref_mut::<u8>() });
750+
/// ```
751+
/// If you have a [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr) that you know belongs to this [`MutUntyped`],
752+
/// you can do
753+
/// ```no_run
754+
/// # use bevy_ecs::change_detection::{Mut, MutUntyped};
755+
/// # let mut_untyped: MutUntyped = unimplemented!();
756+
/// # let reflect_from_ptr: bevy_reflect::ReflectFromPtr = unimplemented!();
757+
/// // SAFETY: from the context it is known that `ReflectFromPtr` was made for the type of the `MutUntyped`
758+
/// mut_untyped.map_unchanged(|ptr| unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) });
759+
/// ```
760+
pub fn map_unchanged<T: ?Sized>(self, f: impl FnOnce(PtrMut<'a>) -> &'a mut T) -> Mut<'a, T> {
761+
Mut {
762+
value: f(self.value),
763+
ticks: self.ticks,
764+
}
765+
}
766+
738767
/// Transforms this [`MutUntyped`] into a [`Mut<T>`] with the same lifetime.
739768
///
740769
/// # Safety
@@ -798,6 +827,8 @@ impl std::fmt::Debug for MutUntyped<'_> {
798827
#[cfg(test)]
799828
mod tests {
800829
use bevy_ecs_macros::Resource;
830+
use bevy_ptr::PtrMut;
831+
use bevy_reflect::{FromType, ReflectFromPtr};
801832

802833
use crate::{
803834
self as bevy_ecs,
@@ -809,8 +840,7 @@ mod tests {
809840
world::World,
810841
};
811842

812-
use super::DetectChanges;
813-
use super::DetectChangesMut;
843+
use super::{DetectChanges, DetectChangesMut, MutUntyped};
814844

815845
#[derive(Component, PartialEq)]
816846
struct C;
@@ -1034,4 +1064,40 @@ mod tests {
10341064
"Resource must be changed after setting to a different value."
10351065
);
10361066
}
1067+
1068+
#[test]
1069+
fn mut_untyped_to_reflect() {
1070+
let last_run = Tick::new(2);
1071+
let this_run = Tick::new(3);
1072+
let mut component_ticks = ComponentTicks {
1073+
added: Tick::new(1),
1074+
changed: Tick::new(2),
1075+
};
1076+
let ticks = TicksMut {
1077+
added: &mut component_ticks.added,
1078+
changed: &mut component_ticks.changed,
1079+
last_run,
1080+
this_run,
1081+
};
1082+
1083+
let mut value: i32 = 5;
1084+
let value = MutUntyped {
1085+
value: PtrMut::from(&mut value),
1086+
ticks,
1087+
};
1088+
1089+
let reflect_from_ptr = <ReflectFromPtr as FromType<i32>>::from_type();
1090+
1091+
let mut new = value.map_unchanged(|ptr| {
1092+
// SAFETY: The underlying type of `ptr` matches `reflect_from_ptr`.
1093+
let value = unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) };
1094+
value
1095+
});
1096+
1097+
assert!(!new.is_changed());
1098+
1099+
new.reflect_mut();
1100+
1101+
assert!(new.is_changed());
1102+
}
10371103
}

0 commit comments

Comments
 (0)