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.
This commit is contained in:
		
							parent
							
								
									7d9e864d9c
								
							
						
					
					
						commit
						54e32ee681
					
				| @ -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<T>` 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<'_> { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Alice Cecile
						Alice Cecile