From 9b84ff11f430bffbb30a480f3ddf7e09c81a6ab3 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Mon, 14 Feb 2022 20:23:14 -0800 Subject: [PATCH 01/23] increase change lifespan, make change lifespan consistent, improve documentation --- crates/bevy_ecs/src/change_detection.rs | 6 ++++ crates/bevy_ecs/src/component.rs | 37 +++++++++++++++---------- crates/bevy_ecs/src/schedule/stage.rs | 32 +++++++++++---------- crates/bevy_ecs/src/system/system.rs | 19 +++++++------ 4 files changed, 57 insertions(+), 37 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index e09e4501dd36d..6ad1801815150 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -5,6 +5,12 @@ use crate::{component::ComponentTicks, system::Resource}; use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; +/// The minimum number of tick increments between consecutive check tick scans. The maximum is twice this number. +pub const CHANGE_DETECTION_CHECK_THRESHOLD: u32 = u32::MAX / 2048; + +/// The maximum change tick difference that won't overflow before the next check. +pub const CHANGE_DETECTION_MAX_DELTA: u32 = u32::MAX - (2 * CHANGE_DETECTION_CHECK_THRESHOLD); + /// Types that implement reliable change detection. /// /// ## Example diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 92767386742c7..536ca4b5b3ab7 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,6 +1,7 @@ //! Types for declaring and storing [`Component`]s. use crate::{ + change_detection::CHANGE_DETECTION_MAX_DELTA, storage::{SparseSetIndex, Storages}, system::Resource, }; @@ -354,21 +355,29 @@ pub struct ComponentTicks { impl ComponentTicks { #[inline] pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { - // The comparison is relative to `change_tick` so that we can detect changes over the whole - // `u32` range. Comparing directly the ticks would limit to half that due to overflow - // handling. - let component_delta = change_tick.wrapping_sub(self.added); - let system_delta = change_tick.wrapping_sub(last_change_tick); + // The comparison is relative to the world tick (`change_tick`) so that we can use + // the full `u32` range. Comparing the system and component ticks directly would limit the + // maximum delta to `u32::MAX / 2` because of wraparound. + let ticks_since_insert = change_tick + .wrapping_sub(self.added) + .min(CHANGE_DETECTION_MAX_DELTA); + let ticks_since_system = change_tick + .wrapping_sub(last_change_tick) + .min(CHANGE_DETECTION_MAX_DELTA); - component_delta < system_delta + ticks_since_system > ticks_since_insert } #[inline] pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { - let component_delta = change_tick.wrapping_sub(self.changed); - let system_delta = change_tick.wrapping_sub(last_change_tick); + let ticks_since_change = change_tick + .wrapping_sub(self.changed) + .min(CHANGE_DETECTION_MAX_DELTA); + let ticks_since_system = change_tick + .wrapping_sub(last_change_tick) + .min(CHANGE_DETECTION_MAX_DELTA); - component_delta < system_delta + ticks_since_system > ticks_since_change } pub(crate) fn new(change_tick: u32) -> Self { @@ -402,10 +411,10 @@ impl ComponentTicks { } fn check_tick(last_change_tick: &mut u32, change_tick: u32) { - let tick_delta = change_tick.wrapping_sub(*last_change_tick); - const MAX_DELTA: u32 = (u32::MAX / 4) * 3; - // Clamp to max delta - if tick_delta > MAX_DELTA { - *last_change_tick = change_tick.wrapping_sub(MAX_DELTA); + let delta = change_tick.wrapping_sub(*last_change_tick); + // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true + // so long as a check runs at least every 2 * `CHANGE_DETECTION_CHECK_THRESHOLD` ticks. + if delta > CHANGE_DETECTION_MAX_DELTA { + *last_change_tick = change_tick.wrapping_sub(CHANGE_DETECTION_MAX_DELTA); } } diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index c2a040d648517..773be6318a6d8 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -1,4 +1,5 @@ use crate::{ + change_detection::CHANGE_DETECTION_CHECK_THRESHOLD, component::ComponentId, prelude::IntoSystem, schedule::{ @@ -423,15 +424,15 @@ impl SystemStage { /// Rearranges all systems in topological orders. Systems must be initialized. fn rebuild_orders_and_dependencies(&mut self) { - // This assertion is there to document that a maximum of `u32::MAX / 8` systems should be - // added to a stage to guarantee that change detection has no false positive, but it - // can be circumvented using exclusive or chained systems + // This assertion exists to document that the number of systems in a stage is limited + // to guarantee that change detection never has any false positives. However, it's possible + // (but still unlikely) to circumvent this by abusing exclusive or chained systems. assert!( self.exclusive_at_start.len() + self.exclusive_before_commands.len() + self.exclusive_at_end.len() + self.parallel.len() - < (u32::MAX / 8) as usize + < (CHANGE_DETECTION_CHECK_THRESHOLD) as usize ); debug_assert!( self.uninitialized_run_criteria.is_empty() @@ -561,17 +562,21 @@ impl SystemStage { } } - /// Checks for old component and system change ticks + /// System and component change ticks are checked for risk of delta overflow once at least + /// `CHANGE_DETECTION_CHECK_THRESHOLD` systems have run since the previous check. No more + /// than twice that number should run between checks. + /// + /// During each check, any change ticks older than `CHANGE_DETECTION_MAX_DELTA` are clamped + /// to that value. + /// + /// Because of that, a system will never miss a change so long as its true age and + /// and the true age of a change are not *both* greater than or equal to `CHANGE_DETECTION_MAX_DELTA`. fn check_change_ticks(&mut self, world: &mut World) { let change_tick = world.change_tick(); - let time_since_last_check = change_tick.wrapping_sub(self.last_tick_check); - // Only check after at least `u32::MAX / 8` counts, and at most `u32::MAX / 4` counts - // since the max number of [System] in a [SystemStage] is limited to `u32::MAX / 8` - // and this function is called at the end of each [SystemStage] loop - const MIN_TIME_SINCE_LAST_CHECK: u32 = u32::MAX / 8; + let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); - if time_since_last_check > MIN_TIME_SINCE_LAST_CHECK { - // Check all system change ticks + if ticks_since_last_check > CHANGE_DETECTION_CHECK_THRESHOLD { + // Check all system change ticks. for exclusive_system in &mut self.exclusive_at_start { exclusive_system.system_mut().check_change_tick(change_tick); } @@ -585,9 +590,8 @@ impl SystemStage { parallel_system.system_mut().check_change_tick(change_tick); } - // Check component ticks + // Check all component change ticks. world.check_change_ticks(); - self.last_tick_check = change_tick; } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 92f30532caea1..873adb7e923d5 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,8 +1,8 @@ use bevy_utils::tracing::warn; use crate::{ - archetype::ArchetypeComponentId, component::ComponentId, query::Access, schedule::SystemLabel, - world::World, + archetype::ArchetypeComponentId, change_detection::CHANGE_DETECTION_MAX_DELTA, + component::ComponentId, query::Access, schedule::SystemLabel, world::World, }; use std::borrow::Cow; @@ -69,14 +69,15 @@ pub(crate) fn check_system_change_tick( change_tick: u32, system_name: &str, ) { - let tick_delta = change_tick.wrapping_sub(*last_change_tick); - const MAX_DELTA: u32 = (u32::MAX / 4) * 3; - // Clamp to max delta - if tick_delta > MAX_DELTA { + let delta = change_tick.wrapping_sub(*last_change_tick); + // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true + // so long as a check runs at least every 2 * `CHANGE_DETECTION_CHECK_THRESHOLD` ticks. + if delta > CHANGE_DETECTION_MAX_DELTA { warn!( - "Too many intervening systems have run since the last time System '{}' was last run; it may fail to detect changes.", - system_name + "{} intervening systems have run since system '{}' last ran. \ + Systems cannot detect changes older than {}.", + delta, system_name, CHANGE_DETECTION_MAX_DELTA, ); - *last_change_tick = change_tick.wrapping_sub(MAX_DELTA); + *last_change_tick = change_tick.wrapping_sub(CHANGE_DETECTION_MAX_DELTA); } } From 48544378244ed914864ee5b78d23a17aa330bfe3 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Feb 2022 08:54:17 -0800 Subject: [PATCH 02/23] forgot to carry the 1 --- crates/bevy_ecs/src/change_detection.rs | 6 +++--- crates/bevy_ecs/src/system/system.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 6ad1801815150..41bb67814ab8d 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -5,11 +5,11 @@ use crate::{component::ComponentTicks, system::Resource}; use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; -/// The minimum number of tick increments between consecutive check tick scans. The maximum is twice this number. +/// The minimum number of tick increments between consecutive `check_tick` scans. pub const CHANGE_DETECTION_CHECK_THRESHOLD: u32 = u32::MAX / 2048; -/// The maximum change tick difference that won't overflow before the next check. -pub const CHANGE_DETECTION_MAX_DELTA: u32 = u32::MAX - (2 * CHANGE_DETECTION_CHECK_THRESHOLD); +/// The maximum change tick difference that we can guarantee won't overflow before the next scan. +pub const CHANGE_DETECTION_MAX_DELTA: u32 = u32::MAX - (2 * CHANGE_DETECTION_CHECK_THRESHOLD - 1); /// Types that implement reliable change detection. /// diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 873adb7e923d5..c7f0c07fffdef 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -76,7 +76,7 @@ pub(crate) fn check_system_change_tick( warn!( "{} intervening systems have run since system '{}' last ran. \ Systems cannot detect changes older than {}.", - delta, system_name, CHANGE_DETECTION_MAX_DELTA, + delta, system_name, CHANGE_DETECTION_MAX_DELTA - 1, ); *last_change_tick = change_tick.wrapping_sub(CHANGE_DETECTION_MAX_DELTA); } From 605692d4f412abb3a16250b0f53ae7573704a712 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Feb 2022 09:05:33 -0800 Subject: [PATCH 03/23] pick a nicer magic number, update using alice's feedback, bikeshed a little --- crates/bevy_ecs/src/change_detection.rs | 18 +++++++++++++----- crates/bevy_ecs/src/component.rs | 16 ++++++++-------- crates/bevy_ecs/src/schedule/stage.rs | 21 +++++++++------------ crates/bevy_ecs/src/system/system.rs | 13 +++++++------ 4 files changed, 37 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 41bb67814ab8d..21617698e1572 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -5,11 +5,19 @@ use crate::{component::ComponentTicks, system::Resource}; use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; -/// The minimum number of tick increments between consecutive `check_tick` scans. -pub const CHANGE_DETECTION_CHECK_THRESHOLD: u32 = u32::MAX / 2048; - -/// The maximum change tick difference that we can guarantee won't overflow before the next scan. -pub const CHANGE_DETECTION_MAX_DELTA: u32 = u32::MAX - (2 * CHANGE_DETECTION_CHECK_THRESHOLD - 1); +/// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. +/// +/// Change ticks can only be scanned when systems aren't running. Thus, if the threshold is `N`, +/// the maximum is `2 * N - 1` (i.e. world ticks `N - 1` times, then `N` times). +/// +/// If a change is older than `u32::MAX - (2 * N - 1)`, the difference that calculates its age +/// might overflow and wraparound before the next scan, causing false positives. +pub const CHECK_TICK_THRESHOLD: u32 = 10_000_000; + +/// The maximum change tick difference that won't overflow before the next `check_tick` scan. +/// +/// Changes stop being detected once they become this old. +pub const MAX_TICK_DELTA: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// Types that implement reliable change detection. /// diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 536ca4b5b3ab7..7f0b186419798 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,7 +1,7 @@ //! Types for declaring and storing [`Component`]s. use crate::{ - change_detection::CHANGE_DETECTION_MAX_DELTA, + change_detection::MAX_TICK_DELTA, storage::{SparseSetIndex, Storages}, system::Resource, }; @@ -360,10 +360,10 @@ impl ComponentTicks { // maximum delta to `u32::MAX / 2` because of wraparound. let ticks_since_insert = change_tick .wrapping_sub(self.added) - .min(CHANGE_DETECTION_MAX_DELTA); + .min(MAX_TICK_DELTA); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) - .min(CHANGE_DETECTION_MAX_DELTA); + .min(MAX_TICK_DELTA); ticks_since_system > ticks_since_insert } @@ -372,10 +372,10 @@ impl ComponentTicks { pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { let ticks_since_change = change_tick .wrapping_sub(self.changed) - .min(CHANGE_DETECTION_MAX_DELTA); + .min(MAX_TICK_DELTA); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) - .min(CHANGE_DETECTION_MAX_DELTA); + .min(MAX_TICK_DELTA); ticks_since_system > ticks_since_change } @@ -413,8 +413,8 @@ impl ComponentTicks { fn check_tick(last_change_tick: &mut u32, change_tick: u32) { let delta = change_tick.wrapping_sub(*last_change_tick); // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true - // so long as a check runs at least every 2 * `CHANGE_DETECTION_CHECK_THRESHOLD` ticks. - if delta > CHANGE_DETECTION_MAX_DELTA { - *last_change_tick = change_tick.wrapping_sub(CHANGE_DETECTION_MAX_DELTA); + // so long as this check runs always runs before that can happen. + if delta > MAX_TICK_DELTA { + *last_change_tick = change_tick.wrapping_sub(MAX_TICK_DELTA); } } diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 773be6318a6d8..0b68426aa7f79 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -1,5 +1,5 @@ use crate::{ - change_detection::CHANGE_DETECTION_CHECK_THRESHOLD, + change_detection::CHECK_TICK_THRESHOLD, component::ComponentId, prelude::IntoSystem, schedule::{ @@ -425,14 +425,14 @@ impl SystemStage { /// Rearranges all systems in topological orders. Systems must be initialized. fn rebuild_orders_and_dependencies(&mut self) { // This assertion exists to document that the number of systems in a stage is limited - // to guarantee that change detection never has any false positives. However, it's possible + // to guarantee that change detection never yields false positives. However, it's possible // (but still unlikely) to circumvent this by abusing exclusive or chained systems. assert!( self.exclusive_at_start.len() + self.exclusive_before_commands.len() + self.exclusive_at_end.len() + self.parallel.len() - < (CHANGE_DETECTION_CHECK_THRESHOLD) as usize + < (CHECK_TICK_THRESHOLD as usize) ); debug_assert!( self.uninitialized_run_criteria.is_empty() @@ -562,20 +562,17 @@ impl SystemStage { } } - /// System and component change ticks are checked for risk of delta overflow once at least - /// `CHANGE_DETECTION_CHECK_THRESHOLD` systems have run since the previous check. No more - /// than twice that number should run between checks. + /// All system and component change ticks are scanned for risk of delta overflow once the world + /// counter has incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) + /// times since the previous `check_tick` scan. /// - /// During each check, any change ticks older than `CHANGE_DETECTION_MAX_DELTA` are clamped - /// to that value. - /// - /// Because of that, a system will never miss a change so long as its true age and - /// and the true age of a change are not *both* greater than or equal to `CHANGE_DETECTION_MAX_DELTA`. + /// During each scan, any change ticks older than [`MAX_TICK_DELTA`](crate::change_detection::MAX_TICK_DELTA) + /// are clamped to that difference, potential false positives due to overflow. fn check_change_ticks(&mut self, world: &mut World) { let change_tick = world.change_tick(); let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); - if ticks_since_last_check > CHANGE_DETECTION_CHECK_THRESHOLD { + if ticks_since_last_check > CHECK_TICK_THRESHOLD { // Check all system change ticks. for exclusive_system in &mut self.exclusive_at_start { exclusive_system.system_mut().check_change_tick(change_tick); diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index c7f0c07fffdef..0bf6f9b66ac00 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -71,13 +71,14 @@ pub(crate) fn check_system_change_tick( ) { let delta = change_tick.wrapping_sub(*last_change_tick); // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true - // so long as a check runs at least every 2 * `CHANGE_DETECTION_CHECK_THRESHOLD` ticks. - if delta > CHANGE_DETECTION_MAX_DELTA { + // so long as this check runs always runs before that can happen. + if delta > MAX_TICK_DELTA { + // TODO: Don't spam this warning over and over for the same system. warn!( - "{} intervening systems have run since system '{}' last ran. \ - Systems cannot detect changes older than {}.", - delta, system_name, CHANGE_DETECTION_MAX_DELTA - 1, + "System '{}' has not run for {} ticks. \ + Changes older than {} ticks will not be detected.", + system_name, delta, MAX_TICK_DELTA - 1, ); - *last_change_tick = change_tick.wrapping_sub(CHANGE_DETECTION_MAX_DELTA); + *last_change_tick = change_tick.wrapping_sub(MAX_TICK_DELTA); } } From 56448e489e81889a08360e6694564bf358d38e27 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:53:33 -0800 Subject: [PATCH 04/23] fix typo --- crates/bevy_ecs/src/schedule/stage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 0b68426aa7f79..1efad0842d840 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -567,7 +567,7 @@ impl SystemStage { /// times since the previous `check_tick` scan. /// /// During each scan, any change ticks older than [`MAX_TICK_DELTA`](crate::change_detection::MAX_TICK_DELTA) - /// are clamped to that difference, potential false positives due to overflow. + /// are clamped to that difference, preventing potential false positives due to overflow. fn check_change_ticks(&mut self, world: &mut World) { let change_tick = world.change_tick(); let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); From 41756000bb5966929911db6f090aafc570425386 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:59:18 -0800 Subject: [PATCH 05/23] fix another typo --- crates/bevy_ecs/src/component.rs | 2 +- crates/bevy_ecs/src/system/system.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 7f0b186419798..45fc12d050049 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -413,7 +413,7 @@ impl ComponentTicks { fn check_tick(last_change_tick: &mut u32, change_tick: u32) { let delta = change_tick.wrapping_sub(*last_change_tick); // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true - // so long as this check runs always runs before that can happen. + // so long as this check always runs before that can happen. if delta > MAX_TICK_DELTA { *last_change_tick = change_tick.wrapping_sub(MAX_TICK_DELTA); } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 0bf6f9b66ac00..327ce8087544f 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -71,7 +71,7 @@ pub(crate) fn check_system_change_tick( ) { let delta = change_tick.wrapping_sub(*last_change_tick); // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true - // so long as this check runs always runs before that can happen. + // so long as this check always runs before that can happen. if delta > MAX_TICK_DELTA { // TODO: Don't spam this warning over and over for the same system. warn!( From 6a649f164ac87daeed4a90e8c07e93ef0d2ed101 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Wed, 16 Feb 2022 08:19:46 -0800 Subject: [PATCH 06/23] reword a comment --- crates/bevy_ecs/src/change_detection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 21617698e1572..cecad0e42c1c0 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -8,10 +8,10 @@ use std::ops::{Deref, DerefMut}; /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. /// /// Change ticks can only be scanned when systems aren't running. Thus, if the threshold is `N`, -/// the maximum is `2 * N - 1` (i.e. world ticks `N - 1` times, then `N` times). +/// the maximum is `2 * N - 1` (i.e. the world ticks `N - 1` times, then `N` times). /// -/// If a change is older than `u32::MAX - (2 * N - 1)`, the difference that calculates its age -/// might overflow and wraparound before the next scan, causing false positives. +/// If no change is older than `u32::MAX - (2 * N - 1)` following a scan, none of their ages can +/// overflow and cause false positives. pub const CHECK_TICK_THRESHOLD: u32 = 10_000_000; /// The maximum change tick difference that won't overflow before the next `check_tick` scan. From 7412a830ed2fc14201e886498cf2fc754116efc9 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Fri, 18 Feb 2022 10:52:01 -0800 Subject: [PATCH 07/23] update check tick threshold back to larger value :( --- crates/bevy_ecs/src/change_detection.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index cecad0e42c1c0..7ffeaa7b46fb4 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -12,7 +12,8 @@ use std::ops::{Deref, DerefMut}; /// /// If no change is older than `u32::MAX - (2 * N - 1)` following a scan, none of their ages can /// overflow and cause false positives. -pub const CHECK_TICK_THRESHOLD: u32 = 10_000_000; +// (518,400,000 = 1000 ticks per frame * 144 frames per second * 3600 seconds per hour) +pub const CHECK_TICK_THRESHOLD: u32 = 518_400_000; /// The maximum change tick difference that won't overflow before the next `check_tick` scan. /// From bed24aa5d8142e9a6611b73008424cc2d4ab094b Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Fri, 18 Feb 2022 15:02:44 -0800 Subject: [PATCH 08/23] fix my own misunderstanding, warnings are not spammed AFAIK Since `check_tick` scans are infrequent, you just might see the warnings for a bunch of different systems (might be worth to batch and print a single message). It's true tho that we'll repeat warnings on the next scan if nothing changes. --- crates/bevy_ecs/src/system/system.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 327ce8087544f..457daf1ff660a 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -73,7 +73,6 @@ pub(crate) fn check_system_change_tick( // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true // so long as this check always runs before that can happen. if delta > MAX_TICK_DELTA { - // TODO: Don't spam this warning over and over for the same system. warn!( "System '{}' has not run for {} ticks. \ Changes older than {} ticks will not be detected.", From cdc62517541cf51a44787498c173ce3d6d13ae2e Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:41:33 -0800 Subject: [PATCH 09/23] add some missing docs and one more bike in the shed --- crates/bevy_ecs/src/change_detection.rs | 2 +- crates/bevy_ecs/src/component.rs | 29 +++++++++++++--------- crates/bevy_ecs/src/system/system.rs | 16 ++++++------ crates/bevy_ecs/src/system/system_param.rs | 12 +++------ 4 files changed, 31 insertions(+), 28 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 7ffeaa7b46fb4..0f92afdf59882 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -18,7 +18,7 @@ pub const CHECK_TICK_THRESHOLD: u32 = 518_400_000; /// The maximum change tick difference that won't overflow before the next `check_tick` scan. /// /// Changes stop being detected once they become this old. -pub const MAX_TICK_DELTA: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); +pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// Types that implement reliable change detection. /// diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 45fc12d050049..5b7c949208014 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,7 +1,7 @@ //! Types for declaring and storing [`Component`]s. use crate::{ - change_detection::MAX_TICK_DELTA, + change_detection::MAX_CHANGE_AGE, storage::{SparseSetIndex, Storages}, system::Resource, }; @@ -346,6 +346,7 @@ impl Components { } } +/// Stores two [`World`](crate::world::World) "ticks" denoting when the component was added and when it was last changed (added or mutably-deferenced). #[derive(Copy, Clone, Debug)] pub struct ComponentTicks { pub(crate) added: u32, @@ -354,28 +355,30 @@ pub struct ComponentTicks { impl ComponentTicks { #[inline] + /// Returns `true` if the component was added since the system last ran, `false` otherwise. pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { // The comparison is relative to the world tick (`change_tick`) so that we can use // the full `u32` range. Comparing the system and component ticks directly would limit the - // maximum delta to `u32::MAX / 2` because of wraparound. + // maximum difference to `u32::MAX / 2` because of wraparound. let ticks_since_insert = change_tick .wrapping_sub(self.added) - .min(MAX_TICK_DELTA); + .min(MAX_CHANGE_AGE); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) - .min(MAX_TICK_DELTA); + .min(MAX_CHANGE_AGE); ticks_since_system > ticks_since_insert } #[inline] + /// Returns `true` if the component was added or mutably-dereferenced since the system last ran, `false` otherwise. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { let ticks_since_change = change_tick .wrapping_sub(self.changed) - .min(MAX_TICK_DELTA); + .min(MAX_CHANGE_AGE); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) - .min(MAX_TICK_DELTA); + .min(MAX_CHANGE_AGE); ticks_since_system > ticks_since_change } @@ -393,8 +396,10 @@ impl ComponentTicks { } /// Manually sets the change tick. - /// Usually, this is done automatically via the [`DerefMut`](std::ops::DerefMut) implementation - /// on [`Mut`](crate::world::Mut) or [`ResMut`](crate::system::ResMut) etc. + /// + /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation + /// on [`Mut`](crate::world::Mut), [`ResMut`](crate::system::ResMut), etc. + /// However, components and resources that make use of interior mutability might require manual updates. /// /// # Example /// ```rust,no_run @@ -411,10 +416,10 @@ impl ComponentTicks { } fn check_tick(last_change_tick: &mut u32, change_tick: u32) { - let delta = change_tick.wrapping_sub(*last_change_tick); - // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true + let age = change_tick.wrapping_sub(*last_change_tick); + // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true // so long as this check always runs before that can happen. - if delta > MAX_TICK_DELTA { - *last_change_tick = change_tick.wrapping_sub(MAX_TICK_DELTA); + if age > MAX_CHANGE_AGE { + *last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); } } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 457daf1ff660a..44b719e52617f 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,8 +1,8 @@ use bevy_utils::tracing::warn; use crate::{ - archetype::ArchetypeComponentId, change_detection::CHANGE_DETECTION_MAX_DELTA, - component::ComponentId, query::Access, schedule::SystemLabel, world::World, + archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId, + query::Access, schedule::SystemLabel, world::World, }; use std::borrow::Cow; @@ -69,15 +69,17 @@ pub(crate) fn check_system_change_tick( change_tick: u32, system_name: &str, ) { - let delta = change_tick.wrapping_sub(*last_change_tick); - // This comparison assumes that `delta` has not overflowed `u32::MAX` before, which will be true + let age = change_tick.wrapping_sub(*last_change_tick); + // This comparison assumes that `age` has not overflowed `u32::MAX` before, which will be true // so long as this check always runs before that can happen. - if delta > MAX_TICK_DELTA { + if age > MAX_CHANGE_AGE { warn!( "System '{}' has not run for {} ticks. \ Changes older than {} ticks will not be detected.", - system_name, delta, MAX_TICK_DELTA - 1, + system_name, + age, + MAX_CHANGE_AGE - 1, ); - *last_change_tick = change_tick.wrapping_sub(MAX_TICK_DELTA); + *last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); } } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 55cb02176a5ab..0a0ad91c84952 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -213,14 +213,12 @@ where } impl<'w, T: Resource> Res<'w, T> { - /// Returns true if (and only if) this resource been added since the last execution of this - /// system. + /// Returns `true` if the resource was added since the system last ran, `false` otherwise. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns true if (and only if) this resource been changed since the last execution of this - /// system. + /// Returns `true` if the resource was added or mutably-dereferenced since the system last ran, `false` otherwise. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) @@ -779,14 +777,12 @@ where } impl<'w, T: 'static> NonSend<'w, T> { - /// Returns true if (and only if) this resource been added since the last execution of this - /// system. + /// Returns `true` if the resource was added since the system last ran, `false` otherwise. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns true if (and only if) this resource been changed since the last execution of this - /// system. + /// Returns `true` if the resource was added or mutably-dereferenced since the system last ran, `false` otherwise. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) From fc28b10c5b58183b0499d80618c2e1e060b22711 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:45:52 -0800 Subject: [PATCH 10/23] >= instead of > for the correctness --- crates/bevy_ecs/src/schedule/stage.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 1efad0842d840..a37762e6a213d 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -572,7 +572,7 @@ impl SystemStage { let change_tick = world.change_tick(); let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); - if ticks_since_last_check > CHECK_TICK_THRESHOLD { + if ticks_since_last_check >= CHECK_TICK_THRESHOLD { // Check all system change ticks. for exclusive_system in &mut self.exclusive_at_start { exclusive_system.system_mut().check_change_tick(change_tick); From 5f022f6e7b1294cfe313d465c554b811c5c616f3 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Wed, 2 Mar 2022 20:57:01 -0800 Subject: [PATCH 11/23] word clarity people have asked before if systems detect their own changes, hopefully this helps --- crates/bevy_ecs/src/component.rs | 4 ++-- crates/bevy_ecs/src/system/system_param.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 5b7c949208014..121df5c600d8f 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -355,7 +355,7 @@ pub struct ComponentTicks { impl ComponentTicks { #[inline] - /// Returns `true` if the component was added since the system last ran, `false` otherwise. + /// Returns `true` if the component was added after the system last ran, `false` otherwise. pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { // The comparison is relative to the world tick (`change_tick`) so that we can use // the full `u32` range. Comparing the system and component ticks directly would limit the @@ -371,7 +371,7 @@ impl ComponentTicks { } #[inline] - /// Returns `true` if the component was added or mutably-dereferenced since the system last ran, `false` otherwise. + /// Returns `true` if the component was added or mutably-dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { let ticks_since_change = change_tick .wrapping_sub(self.changed) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 0a0ad91c84952..942de21c137b7 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -213,12 +213,12 @@ where } impl<'w, T: Resource> Res<'w, T> { - /// Returns `true` if the resource was added since the system last ran, `false` otherwise. + /// Returns `true` if the resource was added after the system last ran, `false` otherwise. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably-dereferenced since the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) @@ -777,12 +777,12 @@ where } impl<'w, T: 'static> NonSend<'w, T> { - /// Returns `true` if the resource was added since the system last ran, `false` otherwise. + /// Returns `true` if the resource was added after the system last ran, `false` otherwise. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably-dereferenced since the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) From 78183b8f770d98f732961dba14ace4f11a50f9d8 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 3 Mar 2022 09:42:02 -0800 Subject: [PATCH 12/23] move test cases to bevy_ecs::change_detection and initialize systems to have the world's last_change_tick --- crates/bevy_ecs/src/change_detection.rs | 102 ++++++++++++++++++ crates/bevy_ecs/src/component.rs | 13 ++- crates/bevy_ecs/src/schedule/stage.rs | 73 +------------ crates/bevy_ecs/src/system/function_system.rs | 2 + 4 files changed, 115 insertions(+), 75 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 0f92afdf59882..67d89ff5b65c1 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -228,3 +228,105 @@ pub struct ReflectMut<'a> { change_detection_impl!(ReflectMut<'a>, dyn Reflect,); #[cfg(feature = "bevy_reflect")] impl_into_inner!(ReflectMut<'a>, dyn Reflect,); + +#[cfg(test)] +mod tests { + use crate::{ + self as bevy_ecs, + change_detection::{CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE}, + component::Component, + query::ChangeTrackers, + system::{IntoSystem, Query, System}, + world::World, + }; + + #[derive(Component)] + struct C; + + #[test] + fn change_expiration() { + fn change_detected(query: Query>) -> bool { + query.single().is_changed() + } + + fn change_expired(query: Query>) -> bool { + query.single().is_changed() + } + + let mut world = World::new(); + + // component added: 1, changed: 1 + world.spawn().insert(C); + + let mut change_detected_system = IntoSystem::into_system(change_detected); + let mut change_expired_system = IntoSystem::into_system(change_expired); + change_detected_system.initialize(&mut world); + change_expired_system.initialize(&mut world); + + // world: 1, system last ran: 0, component changed: 1 + // The spawn will be detected since it happened after the system "last ran". + assert_eq!(change_detected_system.run((), &mut world), true); + + // world: 1 + MAX_CHANGE_AGE + let change_tick = world.change_tick.get_mut(); + *change_tick = change_tick.wrapping_add(MAX_CHANGE_AGE); + + // Both the system and component appeared `MAX_CHANGE_AGE` ticks ago. + // Since we clamp things to `MAX_CHANGE_AGE` for determinism, + // `ComponentTicks::is_changed` will now see `MAX_CHANGE_AGE > MAX_CHANGE_AGE` + // and return `false`. + assert_eq!(change_expired_system.run((), &mut world), false); + } + + #[test] + fn change_tick_wraparound() { + fn change_detected(query: Query>) -> bool { + query.single().is_changed() + } + + let mut world = World::new(); + world.last_change_tick = u32::MAX; + *world.change_tick.get_mut() = 0; + + // component added: 0, changed: 0 + world.spawn().insert(C); + + // system last ran: u32::MAX + let mut change_detected_system = IntoSystem::into_system(change_detected); + change_detected_system.initialize(&mut world); + + // Since the world is always ahead, as long as changes can't get older than `u32::MAX` (which we ensure), + // the wrapping difference will always be positive, so wraparound doesn't matter. + assert_eq!(change_detected_system.run((), &mut world), true); + } + + #[test] + fn change_tick_scan() { + let mut world = World::new(); + + // component added: 0, changed: 0 + world.spawn().insert(C); + + // a bunch of stuff happens, the component is now older than `MAX_CHANGE_AGE` + *world.change_tick.get_mut() += MAX_CHANGE_AGE + CHECK_TICK_THRESHOLD; + let change_tick = world.change_tick(); + + let mut query = world.query::>(); + for tracker in query.iter(&world) { + let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added); + let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed); + assert!(ticks_since_insert > MAX_CHANGE_AGE); + assert!(ticks_since_change > MAX_CHANGE_AGE); + } + + // scan change ticks and clamp those at risk of overflow + world.check_change_ticks(); + + for tracker in query.iter(&world) { + let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added); + let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed); + assert!(ticks_since_insert == MAX_CHANGE_AGE); + assert!(ticks_since_change == MAX_CHANGE_AGE); + } + } +} \ No newline at end of file diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 121df5c600d8f..ae21950fc114e 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -357,9 +357,11 @@ impl ComponentTicks { #[inline] /// Returns `true` if the component was added after the system last ran, `false` otherwise. pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { - // The comparison is relative to the world tick (`change_tick`) so that we can use - // the full `u32` range. Comparing the system and component ticks directly would limit the - // maximum difference to `u32::MAX / 2` because of wraparound. + // This works even with wraparound because the world tick (`change_tick`) is always "newer" than + // `last_change_tick` and `self.added`, and we scan periodically to clamp `ComponentTicks` values + // so they never get older than `u32::MAX` (the difference would overflow). + // + // The clamp here ensures determinism (since scans could differ between app runs). let ticks_since_insert = change_tick .wrapping_sub(self.added) .min(MAX_CHANGE_AGE); @@ -373,6 +375,11 @@ impl ComponentTicks { #[inline] /// Returns `true` if the component was added or mutably-dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { + // This works even with wraparound because the world tick (`change_tick`) is always "newer" than + // `last_change_tick` and `self.changed`, and we scan periodically to clamp `ComponentTicks` values + // so they never get older than `u32::MAX` (the difference would overflow). + // + // The clamp here ensures determinism (since scans could differ between app runs). let ticks_since_change = change_tick .wrapping_sub(self.changed) .min(MAX_CHANGE_AGE); diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index a37762e6a213d..2a27817a32ccb 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -566,7 +566,7 @@ impl SystemStage { /// counter has incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous `check_tick` scan. /// - /// During each scan, any change ticks older than [`MAX_TICK_DELTA`](crate::change_detection::MAX_TICK_DELTA) + /// During each scan, any change ticks older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE) /// are clamped to that difference, preventing potential false positives due to overflow. fn check_change_ticks(&mut self, world: &mut World) { let change_tick = world.change_tick(); @@ -941,8 +941,6 @@ impl Stage for SystemStage { #[cfg(test)] mod tests { use crate::{ - entity::Entity, - query::{ChangeTrackers, Changed}, schedule::{ BoxedSystemLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion, RunCriteria, RunCriteriaDescriptorCoercion, ShouldRun, SingleThreadedExecutor, Stage, @@ -2015,75 +2013,6 @@ mod tests { assert_eq!(*world.resource::(), 1); } - #[test] - fn change_ticks_wrapover() { - const MIN_TIME_SINCE_LAST_CHECK: u32 = u32::MAX / 8; - const MAX_DELTA: u32 = (u32::MAX / 4) * 3; - - let mut world = World::new(); - world.spawn().insert(W(0usize)); - *world.change_tick.get_mut() += MAX_DELTA + 1; - - let mut stage = SystemStage::parallel(); - fn work() {} - stage.add_system(work); - - // Overflow twice - for _ in 0..10 { - stage.run(&mut world); - for tracker in world.query::>>().iter(&world) { - let time_since_last_check = tracker - .change_tick - .wrapping_sub(tracker.component_ticks.added); - assert!(time_since_last_check <= MAX_DELTA); - let time_since_last_check = tracker - .change_tick - .wrapping_sub(tracker.component_ticks.changed); - assert!(time_since_last_check <= MAX_DELTA); - } - let change_tick = world.change_tick.get_mut(); - *change_tick = change_tick.wrapping_add(MIN_TIME_SINCE_LAST_CHECK + 1); - } - } - - #[test] - fn change_query_wrapover() { - use crate::{self as bevy_ecs, component::Component}; - - #[derive(Component)] - struct C; - let mut world = World::new(); - - // Spawn entities at various ticks - let component_ticks = [0, u32::MAX / 4, u32::MAX / 2, u32::MAX / 4 * 3, u32::MAX]; - let ids = component_ticks - .iter() - .map(|tick| { - *world.change_tick.get_mut() = *tick; - world.spawn().insert(C).id() - }) - .collect::>(); - - let test_cases = [ - // normal - (0, u32::MAX / 2, vec![ids[1], ids[2]]), - // just wrapped over - (u32::MAX / 2, 0, vec![ids[0], ids[3], ids[4]]), - ]; - for (last_change_tick, change_tick, changed_entities) in &test_cases { - *world.change_tick.get_mut() = *change_tick; - world.last_change_tick = *last_change_tick; - - assert_eq!( - world - .query_filtered::>() - .iter(&world) - .collect::>(), - *changed_entities - ); - } - } - #[test] fn run_criteria_with_query() { use crate::{self as bevy_ecs, component::Component}; diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 1d202223417e4..12394f04b5d6d 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -140,6 +140,7 @@ pub struct SystemState { impl SystemState { pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); + meta.last_change_tick = world.last_change_tick; let param_state = ::init(world, &mut meta); Self { meta, @@ -398,6 +399,7 @@ where #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); + self.system_meta.last_change_tick = world.last_change_tick; self.param_state = Some(::init( world, &mut self.system_meta, From 07e88fd82ec05ac3891318590d0cda9ae54436b3 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 3 Mar 2022 09:46:42 -0800 Subject: [PATCH 13/23] newline + rustfmt --- crates/bevy_ecs/src/change_detection.rs | 36 ++++++++++++------------- crates/bevy_ecs/src/component.rs | 16 +++++------ crates/bevy_ecs/src/schedule/stage.rs | 2 +- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 67d89ff5b65c1..ef675adb5a81b 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -6,17 +6,17 @@ use bevy_reflect::Reflect; use std::ops::{Deref, DerefMut}; /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. -/// -/// Change ticks can only be scanned when systems aren't running. Thus, if the threshold is `N`, +/// +/// Change ticks can only be scanned when systems aren't running. Thus, if the threshold is `N`, /// the maximum is `2 * N - 1` (i.e. the world ticks `N - 1` times, then `N` times). -/// +/// /// If no change is older than `u32::MAX - (2 * N - 1)` following a scan, none of their ages can /// overflow and cause false positives. // (518,400,000 = 1000 ticks per frame * 144 frames per second * 3600 seconds per hour) pub const CHECK_TICK_THRESHOLD: u32 = 518_400_000; /// The maximum change tick difference that won't overflow before the next `check_tick` scan. -/// +/// /// Changes stop being detected once they become this old. pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); @@ -244,57 +244,57 @@ mod tests { struct C; #[test] - fn change_expiration() { + fn change_expiration() { fn change_detected(query: Query>) -> bool { query.single().is_changed() } - + fn change_expired(query: Query>) -> bool { query.single().is_changed() } - + let mut world = World::new(); // component added: 1, changed: 1 world.spawn().insert(C); - + let mut change_detected_system = IntoSystem::into_system(change_detected); let mut change_expired_system = IntoSystem::into_system(change_expired); change_detected_system.initialize(&mut world); change_expired_system.initialize(&mut world); - + // world: 1, system last ran: 0, component changed: 1 // The spawn will be detected since it happened after the system "last ran". assert_eq!(change_detected_system.run((), &mut world), true); - + // world: 1 + MAX_CHANGE_AGE let change_tick = world.change_tick.get_mut(); *change_tick = change_tick.wrapping_add(MAX_CHANGE_AGE); - + // Both the system and component appeared `MAX_CHANGE_AGE` ticks ago. // Since we clamp things to `MAX_CHANGE_AGE` for determinism, // `ComponentTicks::is_changed` will now see `MAX_CHANGE_AGE > MAX_CHANGE_AGE` // and return `false`. assert_eq!(change_expired_system.run((), &mut world), false); } - + #[test] - fn change_tick_wraparound() { + fn change_tick_wraparound() { fn change_detected(query: Query>) -> bool { query.single().is_changed() } - + let mut world = World::new(); world.last_change_tick = u32::MAX; *world.change_tick.get_mut() = 0; - + // component added: 0, changed: 0 world.spawn().insert(C); - + // system last ran: u32::MAX let mut change_detected_system = IntoSystem::into_system(change_detected); change_detected_system.initialize(&mut world); - + // Since the world is always ahead, as long as changes can't get older than `u32::MAX` (which we ensure), // the wrapping difference will always be positive, so wraparound doesn't matter. assert_eq!(change_detected_system.run((), &mut world), true); @@ -329,4 +329,4 @@ mod tests { assert!(ticks_since_change == MAX_CHANGE_AGE); } } -} \ No newline at end of file +} diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index ae21950fc114e..d82051e3bfbf4 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -358,13 +358,11 @@ impl ComponentTicks { /// Returns `true` if the component was added after the system last ran, `false` otherwise. pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { // This works even with wraparound because the world tick (`change_tick`) is always "newer" than - // `last_change_tick` and `self.added`, and we scan periodically to clamp `ComponentTicks` values + // `last_change_tick` and `self.added`, and we scan periodically to clamp `ComponentTicks` values // so they never get older than `u32::MAX` (the difference would overflow). - // + // // The clamp here ensures determinism (since scans could differ between app runs). - let ticks_since_insert = change_tick - .wrapping_sub(self.added) - .min(MAX_CHANGE_AGE); + let ticks_since_insert = change_tick.wrapping_sub(self.added).min(MAX_CHANGE_AGE); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) .min(MAX_CHANGE_AGE); @@ -376,13 +374,11 @@ impl ComponentTicks { /// Returns `true` if the component was added or mutably-dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { // This works even with wraparound because the world tick (`change_tick`) is always "newer" than - // `last_change_tick` and `self.changed`, and we scan periodically to clamp `ComponentTicks` values + // `last_change_tick` and `self.changed`, and we scan periodically to clamp `ComponentTicks` values // so they never get older than `u32::MAX` (the difference would overflow). - // + // // The clamp here ensures determinism (since scans could differ between app runs). - let ticks_since_change = change_tick - .wrapping_sub(self.changed) - .min(MAX_CHANGE_AGE); + let ticks_since_change = change_tick.wrapping_sub(self.changed).min(MAX_CHANGE_AGE); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) .min(MAX_CHANGE_AGE); diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 2a27817a32ccb..27dea2f8193bb 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -564,7 +564,7 @@ impl SystemStage { /// All system and component change ticks are scanned for risk of delta overflow once the world /// counter has incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) - /// times since the previous `check_tick` scan. + /// times since the previous `check_tick` scan. /// /// During each scan, any change ticks older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE) /// are clamped to that difference, preventing potential false positives due to overflow. From a36782dab7e2afc46a5c955949dd6699de234f8e Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 3 Mar 2022 11:55:03 -0800 Subject: [PATCH 14/23] correction, new systems should detect everything as a change, not nothing new system (state) instances should detect everything as a change --- crates/bevy_ecs/src/system/function_system.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 12394f04b5d6d..6499aac9fb74f 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -8,7 +8,7 @@ use crate::{ check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch, SystemParamState, }, - world::{World, WorldId}, + world::{World, WorldId}, change_detection::MAX_CHANGE_AGE, }; use bevy_ecs_macros::all_tuples; use std::{borrow::Cow, fmt::Debug, hash::Hash, marker::PhantomData}; @@ -140,7 +140,7 @@ pub struct SystemState { impl SystemState { pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); - meta.last_change_tick = world.last_change_tick; + meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); let param_state = ::init(world, &mut meta); Self { meta, @@ -399,7 +399,7 @@ where #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); - self.system_meta.last_change_tick = world.last_change_tick; + self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); self.param_state = Some(::init( world, &mut self.system_meta, From 3f336806310e6a1850c4adfdfc6649b62a395b73 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 3 Mar 2022 11:58:34 -0800 Subject: [PATCH 15/23] rustfmt --- crates/bevy_ecs/src/system/function_system.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index 6499aac9fb74f..55ff0752f35e0 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1,5 +1,6 @@ use crate::{ archetype::{ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, + change_detection::MAX_CHANGE_AGE, component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, @@ -8,7 +9,7 @@ use crate::{ check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch, SystemParamState, }, - world::{World, WorldId}, change_detection::MAX_CHANGE_AGE, + world::{World, WorldId}, }; use bevy_ecs_macros::all_tuples; use std::{borrow::Cow, fmt::Debug, hash::Hash, marker::PhantomData}; From 8515a1f26e73a66fe68bdf7309d27f27603d8e69 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 3 Mar 2022 12:09:31 -0800 Subject: [PATCH 16/23] forgot about exclusive systems --- crates/bevy_ecs/src/system/exclusive_system.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/system/exclusive_system.rs b/crates/bevy_ecs/src/system/exclusive_system.rs index 7baacb24fed3d..46d2e9eb8c091 100644 --- a/crates/bevy_ecs/src/system/exclusive_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_system.rs @@ -1,4 +1,5 @@ use crate::{ + change_detection::MAX_CHANGE_AGE, system::{check_system_change_tick, BoxedSystem, IntoSystem}, world::World, }; @@ -43,7 +44,9 @@ where world.last_change_tick = saved_last_tick; } - fn initialize(&mut self, _: &mut World) {} + fn initialize(&mut self, world: &mut World) { + self.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + } fn check_change_tick(&mut self, change_tick: u32) { check_system_change_tick(&mut self.last_change_tick, change_tick, self.name.as_ref()); From 7a492c102fdb1f615ca96b125e797f05dabcd18e Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 3 Mar 2022 12:16:23 -0800 Subject: [PATCH 17/23] fix one last comment typo --- crates/bevy_ecs/src/change_detection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index ef675adb5a81b..1180aa05c413c 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -304,7 +304,7 @@ mod tests { fn change_tick_scan() { let mut world = World::new(); - // component added: 0, changed: 0 + // component added: 1, changed: 1 world.spawn().insert(C); // a bunch of stuff happens, the component is now older than `MAX_CHANGE_AGE` From e7bafd107d35e6857104363b6d5b3edc0963541f Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Sun, 6 Mar 2022 15:09:56 -0800 Subject: [PATCH 18/23] cargo fmt --- crates/bevy_ecs/src/change_detection.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 1180aa05c413c..8d8264dd97bc7 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -265,7 +265,7 @@ mod tests { // world: 1, system last ran: 0, component changed: 1 // The spawn will be detected since it happened after the system "last ran". - assert_eq!(change_detected_system.run((), &mut world), true); + assert!(change_detected_system.run((), &mut world)); // world: 1 + MAX_CHANGE_AGE let change_tick = world.change_tick.get_mut(); @@ -275,7 +275,7 @@ mod tests { // Since we clamp things to `MAX_CHANGE_AGE` for determinism, // `ComponentTicks::is_changed` will now see `MAX_CHANGE_AGE > MAX_CHANGE_AGE` // and return `false`. - assert_eq!(change_expired_system.run((), &mut world), false); + assert!(!change_expired_system.run((), &mut world)); } #[test] @@ -297,7 +297,7 @@ mod tests { // Since the world is always ahead, as long as changes can't get older than `u32::MAX` (which we ensure), // the wrapping difference will always be positive, so wraparound doesn't matter. - assert_eq!(change_detected_system.run((), &mut world), true); + assert!(change_detected_system.run((), &mut world)); } #[test] From 0beefc8aa730d486e19d4e64c821ae2c25c8ce49 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Tue, 8 Mar 2022 23:39:10 -0800 Subject: [PATCH 19/23] fix typos + consistency + update filter docs why is dereferenced so hard to spell --- crates/bevy_ecs/src/change_detection.rs | 15 +++++------ crates/bevy_ecs/src/component.rs | 4 +-- crates/bevy_ecs/src/query/filter.rs | 30 ++++++++-------------- crates/bevy_ecs/src/system/system_param.rs | 4 +-- 4 files changed, 22 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 8d8264dd97bc7..bf0c9c5c7aec0 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -43,19 +43,18 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// ``` /// pub trait DetectChanges { - /// Returns true if (and only if) this value been added since the last execution of this - /// system. + /// Returns `true` if this value was added after the system last ran, `false` otherwise. fn is_added(&self) -> bool; - /// Returns true if (and only if) this value been changed since the last execution of this - /// system. + /// Returns `true` if this value was added or mutably dereferenced after the system last ran, `false` otherwise. fn is_changed(&self) -> bool; - /// Manually flags this value as having been changed. This normally isn't - /// required because accessing this pointer mutably automatically flags this - /// value as "changed". + /// Flags this value as having been changed. + /// + /// Mutably accessing this smart pointer will automatically flag this value as having been changed. + /// However, mutation through interior mutability requires manual reporting. /// - /// **Note**: This operation is irreversible. + /// **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. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index d82051e3bfbf4..cf17694d4d07f 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -346,7 +346,7 @@ impl Components { } } -/// Stores two [`World`](crate::world::World) "ticks" denoting when the component was added and when it was last changed (added or mutably-deferenced). +/// Records when a component was added and when it was last mutably dereferenced (or added). #[derive(Copy, Clone, Debug)] pub struct ComponentTicks { pub(crate) added: u32, @@ -371,7 +371,7 @@ impl ComponentTicks { } #[inline] - /// Returns `true` if the component was added or mutably-dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the component was added or mutably dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { // This works even with wraparound because the world tick (`change_tick`) is always "newer" than // `last_change_tick` and `self.changed`, and we scan periodically to clamp `ComponentTicks` values diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 276b43459e64d..63c83f6350b1a 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -650,17 +650,12 @@ macro_rules! impl_tick_filter { } impl_tick_filter!( - /// Filter that retrieves components of type `T` that have been added since the last execution - /// of this system. + /// A filter on a component that only retains results added after the system last ran. /// - /// This filter is useful to do one-time post-processing on components. + /// A common use for this filter is one-time initialization. /// - /// Because the ordering of systems can change and this filter is only effective on changes - /// before the query executes you need to use explicit dependency ordering or ordered stages to - /// avoid frame delays. - /// - /// If instead behavior is meant to change on whether the component changed or not - /// [`ChangeTrackers`](crate::query::ChangeTrackers) may be used. + /// To retain all results without filtering but still check whether they were added after the + /// system last ran, use [`ChangeTrackers`](crate::query::ChangeTrackers). /// /// # Examples /// @@ -690,18 +685,15 @@ impl_tick_filter!( ); impl_tick_filter!( - /// Filter that retrieves components of type `T` that have been changed since the last - /// execution of this system. - /// - /// This filter is useful for synchronizing components, and as a performance optimization as it - /// means that the query contains fewer items for a system to iterate over. + /// A filter on a component that only retains results added or mutably dereferenced after the system last ran. + /// + /// A common use for this filter is avoiding redundant work when values have not changed. /// - /// Because the ordering of systems can change and this filter is only effective on changes - /// before the query executes you need to use explicit dependency ordering or ordered - /// stages to avoid frame delays. + /// **Note** that simply *mutably dereferencing* a component is considered a change ([`DerefMut`](std::ops::DerefMut)). + /// Bevy does not compare components to their previous values. /// - /// If instead behavior is meant to change on whether the component changed or not - /// [`ChangeTrackers`](crate::query::ChangeTrackers) may be used. + /// To retain all results without filtering but still check whether they were changed after the + /// system last ran, use [`ChangeTrackers`](crate::query::ChangeTrackers). /// /// # Examples /// diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 942de21c137b7..4e5cc1f26be6f 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -218,7 +218,7 @@ impl<'w, T: Resource> Res<'w, T> { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) @@ -782,7 +782,7 @@ impl<'w, T: 'static> NonSend<'w, T> { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably-dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably dereferenced after the system last ran, `false` otherwise. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) From e11a22945d6b09456048bb774fa06d4ed1f27cdc Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Wed, 9 Mar 2022 00:02:47 -0800 Subject: [PATCH 20/23] small nit --- crates/bevy_ecs/src/schedule/stage.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index 27dea2f8193bb..2cf14f0d0097b 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -562,12 +562,12 @@ impl SystemStage { } } - /// All system and component change ticks are scanned for risk of delta overflow once the world - /// counter has incremented at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) + /// All system and component change ticks are scanned once the world counter has incremented + /// at least [`CHECK_TICK_THRESHOLD`](crate::change_detection::CHECK_TICK_THRESHOLD) /// times since the previous `check_tick` scan. /// /// During each scan, any change ticks older than [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE) - /// are clamped to that difference, preventing potential false positives due to overflow. + /// are clamped to that age. This prevents false positives from appearing due to overflow. fn check_change_ticks(&mut self, world: &mut World) { let change_tick = world.change_tick(); let ticks_since_last_check = change_tick.wrapping_sub(self.last_tick_check); From 6e66a610ae5dc1fe85f16e538acbc5d38c1e9475 Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Wed, 9 Mar 2022 00:11:43 -0800 Subject: [PATCH 21/23] fmt --- crates/bevy_ecs/src/change_detection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index bf0c9c5c7aec0..d195a30012e50 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -50,7 +50,7 @@ pub trait DetectChanges { fn is_changed(&self) -> bool; /// Flags this value as having been changed. - /// + /// /// Mutably accessing this smart pointer will automatically flag this value as having been changed. /// However, mutation through interior mutability requires manual reporting. /// From 74f94cbfe687993d862b0726401ec26019b6336d Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 31 Mar 2022 09:43:28 -0700 Subject: [PATCH 22/23] simpler grammar --- crates/bevy_ecs/src/change_detection.rs | 4 ++-- crates/bevy_ecs/src/component.rs | 4 ++-- crates/bevy_ecs/src/system/system_param.rs | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index d195a30012e50..e3614f47afce0 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -43,10 +43,10 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// ``` /// pub trait DetectChanges { - /// Returns `true` if this value was added after the system last ran, `false` otherwise. + /// Returns `true` if this value was added after the system last ran. fn is_added(&self) -> bool; - /// Returns `true` if this value was added or mutably dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if this value was added or mutably dereferenced after the system last ran. fn is_changed(&self) -> bool; /// Flags this value as having been changed. diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index cf17694d4d07f..62dad1f735c86 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -355,7 +355,7 @@ pub struct ComponentTicks { impl ComponentTicks { #[inline] - /// Returns `true` if the component was added after the system last ran, `false` otherwise. + /// Returns `true` if the component was added after the system last ran. pub fn is_added(&self, last_change_tick: u32, change_tick: u32) -> bool { // This works even with wraparound because the world tick (`change_tick`) is always "newer" than // `last_change_tick` and `self.added`, and we scan periodically to clamp `ComponentTicks` values @@ -371,7 +371,7 @@ impl ComponentTicks { } #[inline] - /// Returns `true` if the component was added or mutably dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the component was added or mutably dereferenced after the system last ran. pub fn is_changed(&self, last_change_tick: u32, change_tick: u32) -> bool { // This works even with wraparound because the world tick (`change_tick`) is always "newer" than // `last_change_tick` and `self.changed`, and we scan periodically to clamp `ComponentTicks` values diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 4e5cc1f26be6f..c6cc0556f7cc4 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -213,12 +213,12 @@ where } impl<'w, T: Resource> Res<'w, T> { - /// Returns `true` if the resource was added after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added after the system last ran. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably dereferenced after the system last ran. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) @@ -777,12 +777,12 @@ where } impl<'w, T: 'static> NonSend<'w, T> { - /// Returns `true` if the resource was added after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added after the system last ran. pub fn is_added(&self) -> bool { self.ticks.is_added(self.last_change_tick, self.change_tick) } - /// Returns `true` if the resource was added or mutably dereferenced after the system last ran, `false` otherwise. + /// Returns `true` if the resource was added or mutably dereferenced after the system last ran. pub fn is_changed(&self) -> bool { self.ticks .is_changed(self.last_change_tick, self.change_tick) From a4151a59ecb9f5127d6e87ce201463082277e53e Mon Sep 17 00:00:00 2001 From: Joy <51241057+maniwani@users.noreply.github.com> Date: Thu, 31 Mar 2022 09:52:00 -0700 Subject: [PATCH 23/23] Update component.rs --- crates/bevy_ecs/src/component.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 62dad1f735c86..97adb2f5beb3e 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -401,7 +401,7 @@ impl ComponentTicks { /// Manually sets the change tick. /// /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation - /// on [`Mut`](crate::world::Mut), [`ResMut`](crate::system::ResMut), etc. + /// on [`Mut`](crate::change_detection::Mut), [`ResMut`](crate::change_detection::ResMut), etc. /// However, components and resources that make use of interior mutability might require manual updates. /// /// # Example