From 54e32ee681e5ab820cd2287018da65d50b92b14b Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 9 Sep 2022 16:26:52 +0000 Subject: [PATCH] Add a change detection bypass and manual control over change ticks (#5635) # Objective - Our existing change detection API is not flexible enough for advanced users: particularly those attempting to do rollback networking. - This is an important use case, and with adequate warnings we can make mucking about with change ticks scary enough that users generally won't do it. - Fixes #5633. - Closes #2363. ## Changelog - added `ChangeDetection::set_last_changed` to manually mutate the `last_change_ticks` field" - the `ChangeDetection` trait now requires an `Inner` associated type, which contains the value being wrapped. - added `ChangeDetection::bypass_change_detection`, which hands out a raw `&mut Inner` ## Migration Guide Add the `Inner` associated type and new methods to any type that you've implemented `DetectChanges` for. --- crates/bevy_ecs/src/change_detection.rs | 54 ++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index b35d996f1a5e0..83cd15dd6a05b 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; @@ -57,7 +62,7 @@ pub trait DetectChanges { /// **Note**: This operation cannot be undone. fn set_changed(&mut self); - /// Returns the change tick recording the previous time this component (or resource) was changed. + /// Returns the change tick recording the previous time this data was changed. /// /// Note that components and resources are also marked as changed upon insertion. /// @@ -65,11 +70,29 @@ pub trait DetectChanges { /// [`SystemChangeTick`](crate::system::SystemChangeTick) /// [`SystemParam`](crate::system::SystemParam). fn last_changed(&self) -> u32; + + /// Manually sets the change tick recording the previous time this data was mutated. + /// + /// # Warning + /// This is a complex and error-prone operation, primarily intended for use with rollback networking strategies. + /// 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. + /// + /// # Warning + /// 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),*> { + type Inner = $target; + #[inline] fn is_added(&self) -> bool { self.ticks @@ -95,6 +118,16 @@ macro_rules! change_detection_impl { fn last_changed(&self) -> u32 { self.ticks.last_change_tick } + + #[inline] + 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),*> { @@ -255,33 +288,50 @@ 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>; + + #[inline] fn is_added(&self) -> bool { self.ticks .component_ticks .is_added(self.ticks.last_change_tick, self.ticks.change_tick) } + #[inline] fn is_changed(&self) -> bool { self.ticks .component_ticks .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) } + #[inline] fn set_changed(&mut self) { self.ticks .component_ticks .set_changed(self.ticks.change_tick); } + #[inline] fn last_changed(&self) -> u32 { self.ticks.last_change_tick } + + #[inline] + 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 { + &mut self.value + } } impl std::fmt::Debug for MutUntyped<'_> {