diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 1bf6a22d189c7..5bb4d547f711f 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -691,6 +691,34 @@ impl<'a> MutUntyped<'a> { self.value.as_ref() } + /// Turn this [`MutUntyped`] into a [`Mut`] by mapping the inner [`PtrMut`] to another value, + /// without flagging a change. [`MutUntyped`] 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::() }); + /// ``` + /// 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(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`] with the same lifetime. /// /// # Safety @@ -754,6 +782,8 @@ impl std::fmt::Debug for MutUntyped<'_> { #[cfg(test)] mod tests { use bevy_ecs_macros::Resource; + use bevy_ptr::PtrMut; + use bevy_reflect::{FromType, ReflectFromPtr}; use crate::{ self as bevy_ecs, @@ -765,8 +795,7 @@ mod tests { world::World, }; - use super::DetectChanges; - use super::DetectChangesMut; + use super::{DetectChanges, DetectChangesMut, MutUntyped}; #[derive(Component, PartialEq)] struct C; @@ -990,4 +1019,39 @@ mod tests { "Resource must be changed after setting to a different value." ); } + + #[test] + fn mut_untyped_to_reflect() { + let (last_change_tick, change_tick) = (2, 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_change_tick, + change_tick, + }; + + let mut value: i32 = 5; + let value = MutUntyped { + value: PtrMut::from(&mut value), + ticks, + }; + + let reflect_from_ptr = >::from_type(); + + let mut new = value.map_unchanged(|ptr| { + // SAFETY: ptr has type of ReflectFromPtr + let value = unsafe { reflect_from_ptr.as_reflect_ptr_mut(ptr) }; + value + }); + + assert!(!new.is_changed()); + + new.reflect_mut(); + + assert!(new.is_changed()); + } }