Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add a change detection bypass and manual control over change ticks #5635

Closed
Closed
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 51 additions & 2 deletions crates/bevy_ecs/src/change_detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -57,19 +62,36 @@ 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.
///
/// For comparison, the previous change tick of a system can be read using the
/// [`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);
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved

/// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick.
///
/// This is a risky operation, that can have unexpected consequences on any system relying on this code.
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
/// 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;
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
}

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
Expand All @@ -95,6 +117,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),*> {
Expand Down Expand Up @@ -255,33 +287,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
alice-i-cecile marked this conversation as resolved.
Show resolved Hide resolved
}

#[inline]
fn bypass_change_detection(&mut self) -> &mut Self::Inner {
&mut self.value
}
}

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