diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 8291a2741b289a..47857715e6de3c 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -43,6 +43,11 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// ``` /// pub trait DetectChanges { + /// The type contained within this smart pointer + /// + /// For example, for `Res` this would be `T`. + type Inner; + /// Returns `true` if this value was added after the system last ran. fn is_added(&self) -> bool; @@ -73,11 +78,21 @@ pub trait DetectChanges { /// If you merely want to flag this data as changed, use [`set_changed`](DetectChanges::set_changed) instead. /// If you want to avoid triggering change detection, use [`bypass_change_detection`](DetectChanges::bypass_change_detection) instead. fn set_last_changed(&mut self, last_change_tick: u32); + + /// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick. + /// + /// This is a risky operation, that can have unexpected consequences on any system relying on this code. + /// However, it can be an essential escape hatch when, for example, + /// you are trying to synchronize representations using change detection and need to avoid infinite recursion. + fn bypass_change_detection(&mut self) -> &mut Self::Inner; } macro_rules! change_detection_impl { ($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => { impl<$($generics),* $(: $traits)?> DetectChanges for $name<$($generics),*> { + // FIXME: this is requesting *all* generics, I just want the first generic type + type Inner = $generics; + #[inline] fn is_added(&self) -> bool { self.ticks @@ -108,6 +123,11 @@ macro_rules! change_detection_impl { fn set_last_changed(&mut self, last_change_tick: u32) { self.ticks.last_change_tick = last_change_tick } + + #[inline] + fn bypass_change_detection(&mut self) -> &mut Self::Inner { + self.value + } } impl<$($generics),* $(: $traits)?> Deref for $name<$($generics),*> { @@ -268,12 +288,15 @@ impl<'a> MutUntyped<'a> { /// Returns the pointer to the value, without marking it as changed. /// /// In order to mark the value as changed, you need to call [`set_changed`](DetectChanges::set_changed) manually. + #[inline] pub fn into_inner(self) -> PtrMut<'a> { self.value } } -impl DetectChanges for MutUntyped<'_> { +impl<'a> DetectChanges for MutUntyped<'a> { + type Inner = PtrMut<'a>; + fn is_added(&self) -> bool { self.ticks .component_ticks @@ -300,6 +323,10 @@ impl DetectChanges for MutUntyped<'_> { fn set_last_changed(&mut self, last_change_tick: u32) { self.ticks.last_change_tick = last_change_tick } + + fn bypass_change_detection(&mut self) -> &mut Self::Inner { + &mut self.into_inner() + } } impl std::fmt::Debug for MutUntyped<'_> {