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 { | 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.
 |     /// Returns `true` if this value was added after the system last ran.
 | ||||||
|     fn is_added(&self) -> bool; |     fn is_added(&self) -> bool; | ||||||
| 
 | 
 | ||||||
| @ -57,7 +62,7 @@ pub trait DetectChanges { | |||||||
|     /// **Note**: This operation cannot be undone.
 |     /// **Note**: This operation cannot be undone.
 | ||||||
|     fn set_changed(&mut self); |     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.
 |     /// Note that components and resources are also marked as changed upon insertion.
 | ||||||
|     ///
 |     ///
 | ||||||
| @ -65,11 +70,29 @@ pub trait DetectChanges { | |||||||
|     /// [`SystemChangeTick`](crate::system::SystemChangeTick)
 |     /// [`SystemChangeTick`](crate::system::SystemChangeTick)
 | ||||||
|     /// [`SystemParam`](crate::system::SystemParam).
 |     /// [`SystemParam`](crate::system::SystemParam).
 | ||||||
|     fn last_changed(&self) -> u32; |     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 { | macro_rules! change_detection_impl { | ||||||
|     ($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => { |     ($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => { | ||||||
|         impl<$($generics),* $(: $traits)?> DetectChanges for $name<$($generics),*> { |         impl<$($generics),* $(: $traits)?> DetectChanges for $name<$($generics),*> { | ||||||
|  |             type Inner = $target; | ||||||
|  | 
 | ||||||
|             #[inline] |             #[inline] | ||||||
|             fn is_added(&self) -> bool { |             fn is_added(&self) -> bool { | ||||||
|                 self.ticks |                 self.ticks | ||||||
| @ -95,6 +118,16 @@ macro_rules! change_detection_impl { | |||||||
|             fn last_changed(&self) -> u32 { |             fn last_changed(&self) -> u32 { | ||||||
|                 self.ticks.last_change_tick |                 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),*> { |         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.
 |     /// 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.
 |     /// 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> { |     pub fn into_inner(self) -> PtrMut<'a> { | ||||||
|         self.value |         self.value | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl DetectChanges for MutUntyped<'_> { | impl<'a> DetectChanges for MutUntyped<'a> { | ||||||
|  |     type Inner = PtrMut<'a>; | ||||||
|  | 
 | ||||||
|  |     #[inline] | ||||||
|     fn is_added(&self) -> bool { |     fn is_added(&self) -> bool { | ||||||
|         self.ticks |         self.ticks | ||||||
|             .component_ticks |             .component_ticks | ||||||
|             .is_added(self.ticks.last_change_tick, self.ticks.change_tick) |             .is_added(self.ticks.last_change_tick, self.ticks.change_tick) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[inline] | ||||||
|     fn is_changed(&self) -> bool { |     fn is_changed(&self) -> bool { | ||||||
|         self.ticks |         self.ticks | ||||||
|             .component_ticks |             .component_ticks | ||||||
|             .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) |             .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[inline] | ||||||
|     fn set_changed(&mut self) { |     fn set_changed(&mut self) { | ||||||
|         self.ticks |         self.ticks | ||||||
|             .component_ticks |             .component_ticks | ||||||
|             .set_changed(self.ticks.change_tick); |             .set_changed(self.ticks.change_tick); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     #[inline] | ||||||
|     fn last_changed(&self) -> u32 { |     fn last_changed(&self) -> u32 { | ||||||
|         self.ticks.last_change_tick |         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<'_> { | impl std::fmt::Debug for MutUntyped<'_> { | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Alice Cecile
						Alice Cecile