Skip to content

Commit 54e32ee

Browse files
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.
1 parent 7d9e864 commit 54e32ee

File tree

1 file changed

+52
-2
lines changed

1 file changed

+52
-2
lines changed

crates/bevy_ecs/src/change_detection.rs

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1);
4343
/// ```
4444
///
4545
pub trait DetectChanges {
46+
/// The type contained within this smart pointer
47+
///
48+
/// For example, for `Res<T>` this would be `T`.
49+
type Inner;
50+
4651
/// Returns `true` if this value was added after the system last ran.
4752
fn is_added(&self) -> bool;
4853

@@ -57,19 +62,37 @@ pub trait DetectChanges {
5762
/// **Note**: This operation cannot be undone.
5863
fn set_changed(&mut self);
5964

60-
/// Returns the change tick recording the previous time this component (or resource) was changed.
65+
/// Returns the change tick recording the previous time this data was changed.
6166
///
6267
/// Note that components and resources are also marked as changed upon insertion.
6368
///
6469
/// For comparison, the previous change tick of a system can be read using the
6570
/// [`SystemChangeTick`](crate::system::SystemChangeTick)
6671
/// [`SystemParam`](crate::system::SystemParam).
6772
fn last_changed(&self) -> u32;
73+
74+
/// Manually sets the change tick recording the previous time this data was mutated.
75+
///
76+
/// # Warning
77+
/// This is a complex and error-prone operation, primarily intended for use with rollback networking strategies.
78+
/// If you merely want to flag this data as changed, use [`set_changed`](DetectChanges::set_changed) instead.
79+
/// If you want to avoid triggering change detection, use [`bypass_change_detection`](DetectChanges::bypass_change_detection) instead.
80+
fn set_last_changed(&mut self, last_change_tick: u32);
81+
82+
/// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick.
83+
///
84+
/// # Warning
85+
/// This is a risky operation, that can have unexpected consequences on any system relying on this code.
86+
/// However, it can be an essential escape hatch when, for example,
87+
/// you are trying to synchronize representations using change detection and need to avoid infinite recursion.
88+
fn bypass_change_detection(&mut self) -> &mut Self::Inner;
6889
}
6990

7091
macro_rules! change_detection_impl {
7192
($name:ident < $( $generics:tt ),+ >, $target:ty, $($traits:ident)?) => {
7293
impl<$($generics),* $(: $traits)?> DetectChanges for $name<$($generics),*> {
94+
type Inner = $target;
95+
7396
#[inline]
7497
fn is_added(&self) -> bool {
7598
self.ticks
@@ -95,6 +118,16 @@ macro_rules! change_detection_impl {
95118
fn last_changed(&self) -> u32 {
96119
self.ticks.last_change_tick
97120
}
121+
122+
#[inline]
123+
fn set_last_changed(&mut self, last_change_tick: u32) {
124+
self.ticks.last_change_tick = last_change_tick
125+
}
126+
127+
#[inline]
128+
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
129+
self.value
130+
}
98131
}
99132

100133
impl<$($generics),* $(: $traits)?> Deref for $name<$($generics),*> {
@@ -255,33 +288,50 @@ impl<'a> MutUntyped<'a> {
255288
/// Returns the pointer to the value, without marking it as changed.
256289
///
257290
/// In order to mark the value as changed, you need to call [`set_changed`](DetectChanges::set_changed) manually.
291+
#[inline]
258292
pub fn into_inner(self) -> PtrMut<'a> {
259293
self.value
260294
}
261295
}
262296

263-
impl DetectChanges for MutUntyped<'_> {
297+
impl<'a> DetectChanges for MutUntyped<'a> {
298+
type Inner = PtrMut<'a>;
299+
300+
#[inline]
264301
fn is_added(&self) -> bool {
265302
self.ticks
266303
.component_ticks
267304
.is_added(self.ticks.last_change_tick, self.ticks.change_tick)
268305
}
269306

307+
#[inline]
270308
fn is_changed(&self) -> bool {
271309
self.ticks
272310
.component_ticks
273311
.is_changed(self.ticks.last_change_tick, self.ticks.change_tick)
274312
}
275313

314+
#[inline]
276315
fn set_changed(&mut self) {
277316
self.ticks
278317
.component_ticks
279318
.set_changed(self.ticks.change_tick);
280319
}
281320

321+
#[inline]
282322
fn last_changed(&self) -> u32 {
283323
self.ticks.last_change_tick
284324
}
325+
326+
#[inline]
327+
fn set_last_changed(&mut self, last_change_tick: u32) {
328+
self.ticks.last_change_tick = last_change_tick;
329+
}
330+
331+
#[inline]
332+
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
333+
&mut self.value
334+
}
285335
}
286336

287337
impl std::fmt::Debug for MutUntyped<'_> {

0 commit comments

Comments
 (0)