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