diff --git a/crates/bevy_ecs/macros/src/fetch.rs b/crates/bevy_ecs/macros/src/fetch.rs index 6d0e3d509428c..5ddb3c9a05551 100644 --- a/crates/bevy_ecs/macros/src/fetch.rs +++ b/crates/bevy_ecs/macros/src/fetch.rs @@ -223,8 +223,8 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { unsafe fn init_fetch<'__w>( _world: &'__w #path::world::World, state: &Self::State, - _last_change_tick: u32, - _change_tick: u32 + _last_change_tick: u64, + _change_tick: u64 ) -> >::Fetch { #fetch_struct_name { #(#field_idents: diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index ed4391d7e0041..1aa9011e84cc7 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -240,7 +240,7 @@ pub fn impl_param_set(_input: TokenStream) -> TokenStream { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { ParamSet { param_states: &mut state.0, @@ -385,7 +385,7 @@ pub fn derive_system_param(input: TokenStream) -> TokenStream { state: &'s mut Self, system_meta: &#path::system::SystemMeta, world: &'w #path::world::World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { #struct_name { #(#fields: <<#field_types as #path::system::SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)* diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index d9124f2493b84..992d983c4b4ed 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -267,7 +267,7 @@ impl BundleInfo { components: &mut Components, storages: &'a mut Storages, archetype_id: ArchetypeId, - change_tick: u32, + change_tick: u64, ) -> BundleInserter<'a, 'b> { let new_archetype_id = self.add_bundle_to_archetype(archetypes, storages, components, archetype_id); @@ -326,7 +326,7 @@ impl BundleInfo { archetypes: &'a mut Archetypes, components: &mut Components, storages: &'a mut Storages, - change_tick: u32, + change_tick: u64, ) -> BundleSpawner<'a, 'b> { let new_archetype_id = self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); @@ -357,7 +357,7 @@ impl BundleInfo { add_bundle: &AddBundle, entity: Entity, table_row: usize, - change_tick: u32, + change_tick: u64, bundle: T, ) { // NOTE: get_components calls this closure on each component in "bundle order". @@ -482,7 +482,7 @@ pub(crate) struct BundleInserter<'a, 'b> { sparse_sets: &'a mut SparseSets, result: InsertBundleResult<'a>, archetypes_ptr: *mut Archetype, - change_tick: u32, + change_tick: u64, } pub(crate) enum InsertBundleResult<'a> { @@ -618,7 +618,7 @@ pub(crate) struct BundleSpawner<'a, 'b> { bundle_info: &'b BundleInfo, table: &'a mut Table, sparse_sets: &'a mut SparseSets, - change_tick: u32, + change_tick: u64, } impl<'a, 'b> BundleSpawner<'a, 'b> { diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 914b1951376bf..a8cf6576e38c9 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -4,21 +4,6 @@ use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource}; #[cfg(feature = "bevy_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`, -/// 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); - /// Types that implement reliable change detection. /// /// ## Example @@ -31,7 +16,7 @@ pub const MAX_CHANGE_AGE: u32 = u32::MAX - (2 * CHECK_TICK_THRESHOLD - 1); /// use bevy_ecs::prelude::*; /// /// #[derive(Resource)] -/// struct MyResource(u32); +/// struct MyResource(u64); /// /// fn my_system(mut resource: ResMut) { /// if resource.is_changed() { @@ -69,7 +54,7 @@ pub trait DetectChanges { /// 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; + fn last_changed(&self) -> u64; /// Manually sets the change tick recording the previous time this data was mutated. /// @@ -77,7 +62,7 @@ pub trait DetectChanges { /// 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); + fn set_last_changed(&mut self, last_change_tick: u64); /// Manually bypasses change detection, allowing you to mutate the underlying value without updating the change tick. /// @@ -97,14 +82,14 @@ macro_rules! change_detection_impl { fn is_added(&self) -> bool { self.ticks .component_ticks - .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + .is_added(self.ticks.last_change_tick) } #[inline] fn is_changed(&self) -> bool { self.ticks .component_ticks - .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + .is_changed(self.ticks.last_change_tick) } #[inline] @@ -115,12 +100,12 @@ macro_rules! change_detection_impl { } #[inline] - fn last_changed(&self) -> u32 { + fn last_changed(&self) -> u64 { self.ticks.last_change_tick } #[inline] - fn set_last_changed(&mut self, last_change_tick: u32) { + fn set_last_changed(&mut self, last_change_tick: u64) { self.ticks.last_change_tick = last_change_tick } @@ -226,8 +211,8 @@ macro_rules! impl_debug { pub(crate) struct Ticks<'a> { pub(crate) component_ticks: &'a mut ComponentTicks, - pub(crate) last_change_tick: u32, - pub(crate) change_tick: u32, + pub(crate) last_change_tick: u64, + pub(crate) change_tick: u64, } /// Unique mutable borrow of a [`Resource`]. @@ -333,14 +318,14 @@ impl<'a> DetectChanges for MutUntyped<'a> { fn is_added(&self) -> bool { self.ticks .component_ticks - .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + .is_added(self.ticks.last_change_tick) } #[inline] fn is_changed(&self) -> bool { self.ticks .component_ticks - .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + .is_changed(self.ticks.last_change_tick) } #[inline] @@ -351,12 +336,12 @@ impl<'a> DetectChanges for MutUntyped<'a> { } #[inline] - fn last_changed(&self) -> u32 { + fn last_changed(&self) -> u64 { self.ticks.last_change_tick } #[inline] - fn set_last_changed(&mut self, last_change_tick: u32) { + fn set_last_changed(&mut self, last_change_tick: u64) { self.ticks.last_change_tick = last_change_tick; } @@ -380,13 +365,8 @@ mod tests { use crate::{ self as bevy_ecs, - change_detection::{ - ComponentTicks, Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, - }, + change_detection::{ComponentTicks, Mut, NonSendMut, ResMut, Ticks}, component::Component, - query::ChangeTrackers, - system::{IntoSystem, Query, System}, - world::World, }; #[derive(Component)] @@ -395,93 +375,6 @@ mod tests { #[derive(Resource)] struct R; - #[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(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!(change_detected_system.run((), &mut world)); - - // 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!(!change_expired_system.run((), &mut world)); - } - - #[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(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!(change_detected_system.run((), &mut world)); - } - - #[test] - fn change_tick_scan() { - let mut world = World::new(); - - // component added: 1, changed: 1 - world.spawn(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); - } - } - #[test] fn mut_from_res_mut() { let mut component_ticks = ComponentTicks { @@ -561,6 +454,6 @@ mod tests { *inner = 64; assert!(inner.is_changed()); // Modifying one field of a component should flag a change for the entire component. - assert!(component_ticks.is_changed(last_change_tick, change_tick)); + assert!(component_ticks.is_changed(last_change_tick)); } } diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c59a1e2028737..c1e181d590183 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,7 +1,6 @@ //! Types for declaring and storing [`Component`]s. use crate::{ - change_detection::MAX_CHANGE_AGE, storage::{SparseSetIndex, Storages}, system::Resource, }; @@ -520,55 +519,30 @@ impl Components { /// 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, - pub(crate) changed: u32, + pub(crate) added: u64, + pub(crate) changed: u64, } impl ComponentTicks { #[inline] /// 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 - // 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_system = change_tick - .wrapping_sub(last_change_tick) - .min(MAX_CHANGE_AGE); - - ticks_since_system > ticks_since_insert + pub fn is_added(&self, last_change_tick: u64) -> bool { + self.added > last_change_tick } #[inline] /// 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 - // 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_system = change_tick - .wrapping_sub(last_change_tick) - .min(MAX_CHANGE_AGE); - - ticks_since_system > ticks_since_change + pub fn is_changed(&self, last_change_tick: u64) -> bool { + self.changed > last_change_tick } - pub(crate) fn new(change_tick: u32) -> Self { + pub(crate) fn new(change_tick: u64) -> Self { Self { added: change_tick, changed: change_tick, } } - pub(crate) fn check_ticks(&mut self, change_tick: u32) { - check_tick(&mut self.added, change_tick); - check_tick(&mut self.changed, change_tick); - } - /// Manually sets the change tick. /// /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation @@ -584,16 +558,7 @@ impl ComponentTicks { /// component_ticks.set_changed(world.read_change_tick()); /// ``` #[inline] - pub fn set_changed(&mut self, change_tick: u32) { + pub fn set_changed(&mut self, change_tick: u64) { self.changed = change_tick; } } - -fn check_tick(last_change_tick: &mut u32, change_tick: u32) { - 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 age > MAX_CHANGE_AGE { - *last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); - } -} diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index a4670944807f8..8a996ea268b35 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -323,8 +323,8 @@ pub unsafe trait WorldQuery: for<'w> WorldQueryGats<'w> { unsafe fn init_fetch<'w>( world: &'w World, state: &Self::State, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> >::Fetch; /// Returns true if (and only if) every table of every archetype matched by this fetch contains @@ -482,8 +482,8 @@ unsafe impl WorldQuery for Entity { unsafe fn init_fetch<'w>( _world: &'w World, _state: &(), - _last_change_tick: u32, - _change_tick: u32, + _last_change_tick: u64, + _change_tick: u64, ) -> EntityFetch<'w> { EntityFetch { entities: None } } @@ -583,8 +583,8 @@ unsafe impl WorldQuery for &T { unsafe fn init_fetch<'w>( world: &'w World, &component_id: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, + _last_change_tick: u64, + _change_tick: u64, ) -> ReadFetch<'w, T> { ReadFetch { table_components: None, @@ -721,8 +721,8 @@ pub struct WriteFetch<'w, T> { entities: Option>, sparse_set: Option<&'w ComponentSparseSet>, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, } /// SAFETY: access of `&T` is a subset of `&mut T` @@ -746,8 +746,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { unsafe fn init_fetch<'w>( world: &'w World, &component_id: &ComponentId, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> WriteFetch<'w, T> { WriteFetch { table_components: None, @@ -939,8 +939,8 @@ unsafe impl WorldQuery for Option { unsafe fn init_fetch<'w>( world: &'w World, state: &T::State, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> OptionFetch<'w, T> { OptionFetch { fetch: T::init_fetch(world, state, last_change_tick, change_tick), @@ -1068,8 +1068,7 @@ impl<'w, T: WorldQuery> WorldQueryGats<'w> for Option { #[derive(Clone)] pub struct ChangeTrackers { pub(crate) component_ticks: ComponentTicks, - pub(crate) last_change_tick: u32, - pub(crate) change_tick: u32, + pub(crate) last_change_tick: u64, marker: PhantomData, } @@ -1078,7 +1077,6 @@ impl std::fmt::Debug for ChangeTrackers { f.debug_struct("ChangeTrackers") .field("component_ticks", &self.component_ticks) .field("last_change_tick", &self.last_change_tick) - .field("change_tick", &self.change_tick) .finish() } } @@ -1086,14 +1084,12 @@ impl std::fmt::Debug for ChangeTrackers { impl ChangeTrackers { /// Returns true if this component has been added since the last execution of this system. pub fn is_added(&self) -> bool { - self.component_ticks - .is_added(self.last_change_tick, self.change_tick) + self.component_ticks.is_added(self.last_change_tick) } /// Returns true if this component has been changed since the last execution of this system. pub fn is_changed(&self) -> bool { - self.component_ticks - .is_changed(self.last_change_tick, self.change_tick) + self.component_ticks.is_changed(self.last_change_tick) } } @@ -1107,8 +1103,7 @@ pub struct ChangeTrackersFetch<'w, T> { sparse_set: Option<&'w ComponentSparseSet>, marker: PhantomData, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, } impl Clone for ChangeTrackersFetch<'_, T> { @@ -1120,7 +1115,6 @@ impl Clone for ChangeTrackersFetch<'_, T> { sparse_set: self.sparse_set, marker: self.marker, last_change_tick: self.last_change_tick, - change_tick: self.change_tick, } } } @@ -1146,8 +1140,8 @@ unsafe impl WorldQuery for ChangeTrackers { unsafe fn init_fetch<'w>( world: &'w World, &id: &ComponentId, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + _change_tick: u64, ) -> ChangeTrackersFetch<'w, T> { ChangeTrackersFetch { table_ticks: None, @@ -1157,7 +1151,6 @@ unsafe impl WorldQuery for ChangeTrackers { .then(|| world.storages().sparse_sets.get(id).unwrap()), marker: PhantomData, last_change_tick, - change_tick, } } @@ -1207,7 +1200,6 @@ unsafe impl WorldQuery for ChangeTrackers { }, marker: PhantomData, last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, } } StorageType::SparseSet => { @@ -1225,7 +1217,6 @@ unsafe impl WorldQuery for ChangeTrackers { .unwrap_or_else(|| debug_checked_unreachable()), marker: PhantomData, last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, } } } @@ -1245,7 +1236,6 @@ unsafe impl WorldQuery for ChangeTrackers { }, marker: PhantomData, last_change_tick: fetch.last_change_tick, - change_tick: fetch.change_tick, } } @@ -1312,7 +1302,7 @@ macro_rules! impl_tuple_fetch { } #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> >::Fetch { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u64, _change_tick: u64) -> >::Fetch { let ($($name,)*) = state; ($($name::init_fetch(_world, $name, _last_change_tick, _change_tick),)*) } @@ -1422,7 +1412,7 @@ macro_rules! impl_anytuple_fetch { } #[allow(clippy::unused_unit)] - unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> >::Fetch { + unsafe fn init_fetch<'w>(_world: &'w World, state: &Self::State, _last_change_tick: u64, _change_tick: u64) -> >::Fetch { let ($($name,)*) = state; ($(($name::init_fetch(_world, $name, _last_change_tick, _change_tick), false),)*) } @@ -1554,8 +1544,8 @@ unsafe impl WorldQuery for NopWorldQuery { unsafe fn init_fetch( _world: &World, _state: &Q::State, - _last_change_tick: u32, - _change_tick: u32, + _last_change_tick: u64, + _change_tick: u64, ) { } diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 960c6d4bcd7f5..2984d98e02ab4 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -61,8 +61,8 @@ unsafe impl WorldQuery for With { unsafe fn init_fetch( _world: &World, _state: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, + _last_change_tick: u64, + _change_tick: u64, ) { } @@ -168,8 +168,8 @@ unsafe impl WorldQuery for Without { unsafe fn init_fetch( _world: &World, _state: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, + _last_change_tick: u64, + _change_tick: u64, ) { } @@ -318,7 +318,7 @@ macro_rules! impl_query_filter_tuple { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; - unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> >::Fetch { + unsafe fn init_fetch<'w>(world: &'w World, state: &Self::State, last_change_tick: u64, change_tick: u64) -> >::Fetch { let ($($filter,)*) = state; ($(OrFetch { fetch: $filter::init_fetch(world, $filter, last_change_tick, change_tick), @@ -445,8 +445,7 @@ macro_rules! impl_tick_filter { marker: PhantomData, entities: Option>, sparse_set: Option<&'w ComponentSparseSet>, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, } // SAFETY: `ROQueryFetch` is the same as `QueryFetch` @@ -458,7 +457,7 @@ macro_rules! impl_tick_filter { item } - unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_change_tick: u32, change_tick: u32) -> >::Fetch { + unsafe fn init_fetch<'w>(world: &'w World, &id: &ComponentId, last_change_tick: u64, _change_tick: u64) -> >::Fetch { QueryFetch::<'w, Self> { table_ticks: None, entities: None, @@ -467,7 +466,6 @@ macro_rules! impl_tick_filter { .then(|| world.storages().sparse_sets.get(id).unwrap()), marker: PhantomData, last_change_tick, - change_tick, } } @@ -496,14 +494,14 @@ macro_rules! impl_tick_filter { } unsafe fn table_fetch<'w>(fetch: &mut >::Fetch, table_row: usize) -> >::Item { - $is_detected(&*(fetch.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), fetch.last_change_tick, fetch.change_tick) + $is_detected(&*(fetch.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), fetch.last_change_tick) } unsafe fn archetype_fetch<'w>(fetch: &mut >::Fetch, archetype_index: usize) -> >::Item { match T::Storage::STORAGE_TYPE { StorageType::Table => { let table_row = *fetch.entity_table_rows.unwrap_or_else(|| debug_checked_unreachable()).get(archetype_index); - $is_detected(&*(fetch.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), fetch.last_change_tick, fetch.change_tick) + $is_detected(&*(fetch.table_ticks.unwrap_or_else(|| debug_checked_unreachable()).get(table_row)).deref(), fetch.last_change_tick) } StorageType::SparseSet => { let entity = *fetch.entities.unwrap_or_else(|| debug_checked_unreachable()).get(archetype_index); @@ -514,7 +512,7 @@ macro_rules! impl_tick_filter { .map(|ticks| &*ticks.get()) .cloned() .unwrap(); - $is_detected(&ticks, fetch.last_change_tick, fetch.change_tick) + $is_detected(&ticks, fetch.last_change_tick) } } } @@ -575,7 +573,6 @@ macro_rules! impl_tick_filter { entities: self.entities.clone(), sparse_set: self.sparse_set.clone(), last_change_tick: self.last_change_tick.clone(), - change_tick: self.change_tick.clone(), } } } diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 9cda2013cd979..9c56c197e083d 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -29,8 +29,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIter<'w, 's, Q, F> { pub(crate) unsafe fn new( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Self { QueryIter { query_state, @@ -101,8 +101,8 @@ where world: &'w World, query_state: &'s QueryState, entity_list: EntityList, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> QueryManyIter<'w, 's, Q, F, I> { let fetch = Q::init_fetch( world, @@ -285,8 +285,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery, const K: usize> pub(crate) unsafe fn new( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Self { // Initialize array with cursors. // There is no FromIterator on arrays, so instead initialize it manually with MaybeUninit @@ -511,8 +511,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, unsafe fn init_empty( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Self { QueryIterationCursor { table_id_iter: [].iter(), @@ -524,8 +524,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's, unsafe fn init( world: &'w World, query_state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Self { let fetch = Q::init_fetch( world, diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 586df9de05d5d..37cbe8dc4b177 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -127,7 +127,7 @@ impl QueryState { /// Checks if the query is empty for the given [`World`], where the last change and current tick are given. #[inline] - pub fn is_empty(&self, world: &World, last_change_tick: u32, change_tick: u32) -> bool { + pub fn is_empty(&self, world: &World, last_change_tick: u64, change_tick: u64) -> bool { // SAFETY: NopFetch does not access any members while &self ensures no one has exclusive access unsafe { self.as_nop() @@ -396,8 +396,8 @@ impl QueryState { &self, world: &'w World, entity: Entity, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Result, QueryEntityError> { let location = world .entities @@ -443,8 +443,8 @@ impl QueryState { &self, world: &'w World, entities: [Entity; N], - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Result<[ROQueryItem<'w, Q>; N], QueryEntityError> { // SAFETY: fetch is read-only // and world must be validated @@ -480,8 +480,8 @@ impl QueryState { &self, world: &'w World, entities: [Entity; N], - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Result<[QueryItem<'w, Q>; N], QueryEntityError> { // Verify that all entities are unique for i in 0..N { @@ -702,8 +702,8 @@ impl QueryState { pub(crate) unsafe fn iter_unchecked_manual<'w, 's>( &'s self, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> QueryIter<'w, 's, Q, F> { QueryIter::new(world, self, last_change_tick, change_tick) } @@ -723,8 +723,8 @@ impl QueryState { &'s self, entities: EntityList, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> QueryManyIter<'w, 's, Q, F, EntityList::IntoIter> where EntityList::Item: Borrow, @@ -746,8 +746,8 @@ impl QueryState { pub(crate) unsafe fn iter_combinations_unchecked_manual<'w, 's, const K: usize>( &'s self, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> QueryCombinationIter<'w, 's, Q, F, K> { QueryCombinationIter::new(world, self, last_change_tick, change_tick) } @@ -910,8 +910,8 @@ impl QueryState { &self, world: &'w World, mut func: FN, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual @@ -973,8 +973,8 @@ impl QueryState { world: &'w World, batch_size: usize, func: FN, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) { // NOTE: If you are changing query iteration code, remember to update the following places, where relevant: // QueryIter, QueryIterationCursor, QueryManyIter, QueryCombinationIter, QueryState::for_each_unchecked_manual, QueryState::par_for_each_unchecked_manual @@ -1186,8 +1186,8 @@ impl QueryState { pub unsafe fn get_single_unchecked_manual<'w>( &self, world: &'w World, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Result, QuerySingleError> { let mut query = self.iter_unchecked_manual(world, last_change_tick, change_tick); let first = query.next(); diff --git a/crates/bevy_ecs/src/schedule/stage.rs b/crates/bevy_ecs/src/schedule/stage.rs index bb14e63e05057..686deddfe1997 100644 --- a/crates/bevy_ecs/src/schedule/stage.rs +++ b/crates/bevy_ecs/src/schedule/stage.rs @@ -1,6 +1,5 @@ use crate::{ self as bevy_ecs, - change_detection::CHECK_TICK_THRESHOLD, component::ComponentId, prelude::IntoSystem, schedule::{ @@ -429,16 +428,6 @@ 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 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() - < (CHECK_TICK_THRESHOLD as usize) - ); debug_assert!( self.uninitialized_run_criteria.is_empty() && self.uninitialized_parallel.is_empty() @@ -511,37 +500,6 @@ impl SystemStage { } } - /// 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 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); - - 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); - } - for exclusive_system in &mut self.exclusive_before_commands { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for exclusive_system in &mut self.exclusive_at_end { - exclusive_system.system_mut().check_change_tick(change_tick); - } - for parallel_system in &mut self.parallel { - parallel_system.system_mut().check_change_tick(change_tick); - } - - // Check all component change ticks. - world.check_change_ticks(); - self.last_tick_check = change_tick; - } - } - /// Sorts run criteria and populates resolved input-criteria for piping. /// Returns a map of run criteria labels to their indices. fn process_run_criteria( @@ -864,9 +822,6 @@ impl Stage for SystemStage { } } - // Check for old component and system change ticks - self.check_change_ticks(world); - // Evaluate run criteria. let run_criteria = &mut self.run_criteria; for index in 0..run_criteria.len() { diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index 3ca81979dcf1e..226a7a7f5f77b 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -119,7 +119,7 @@ impl ComponentSparseSet { /// # Safety /// The `value` pointer must point to a valid address that matches the [`Layout`](std::alloc::Layout) /// inside the [`ComponentInfo`] given when constructing this sparse set. - pub(crate) unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u32) { + pub(crate) unsafe fn insert(&mut self, entity: Entity, value: OwningPtr<'_>, change_tick: u64) { if let Some(&dense_index) = self.sparse.get(entity.id()) { #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index as usize]); @@ -233,10 +233,6 @@ impl ComponentSparseSet { false } } - - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - self.dense.check_change_ticks(change_tick); - } } /// A data structure that blends dense and sparse storage @@ -424,12 +420,6 @@ impl SparseSets { set.clear(); } } - - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for set in self.sets.values_mut() { - set.check_change_ticks(change_tick); - } - } } #[cfg(test)] diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index b0c84d898236a..057af4b67d767 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -77,7 +77,7 @@ impl Column { /// # Safety /// Assumes data has already been allocated for the given row. #[inline] - pub(crate) unsafe fn replace(&mut self, row: usize, data: OwningPtr<'_>, change_tick: u32) { + pub(crate) unsafe fn replace(&mut self, row: usize, data: OwningPtr<'_>, change_tick: u64) { debug_assert!(row < self.len()); self.data.replace_unchecked(row, data); self.ticks @@ -198,13 +198,6 @@ impl Column { self.data.clear(); self.ticks.clear(); } - - #[inline] - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for component_ticks in &mut self.ticks { - component_ticks.get_mut().check_ticks(change_tick); - } - } } pub struct Table { @@ -412,12 +405,6 @@ impl Table { self.entities.is_empty() } - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for column in self.columns.values_mut() { - column.check_change_ticks(change_tick); - } - } - pub fn iter(&self) -> impl Iterator { self.columns.values() } @@ -513,12 +500,6 @@ impl Tables { table.clear(); } } - - pub(crate) fn check_change_ticks(&mut self, change_tick: u32) { - for table in &mut self.tables { - table.check_change_ticks(change_tick); - } - } } impl Index for Tables { diff --git a/crates/bevy_ecs/src/system/commands/parallel_scope.rs b/crates/bevy_ecs/src/system/commands/parallel_scope.rs index 573afa70aae51..5a7e676944361 100644 --- a/crates/bevy_ecs/src/system/commands/parallel_scope.rs +++ b/crates/bevy_ecs/src/system/commands/parallel_scope.rs @@ -59,7 +59,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for ParallelCommandsState { state: &'s mut Self, _: &crate::system::SystemMeta, world: &'w World, - _: u32, + _: u64, ) -> Self::Item { ParallelCommands { state, diff --git a/crates/bevy_ecs/src/system/exclusive_function_system.rs b/crates/bevy_ecs/src/system/exclusive_function_system.rs index 662d031daa2e3..aab4fde28d115 100644 --- a/crates/bevy_ecs/src/system/exclusive_function_system.rs +++ b/crates/bevy_ecs/src/system/exclusive_function_system.rs @@ -1,13 +1,11 @@ use crate::{ archetype::ArchetypeComponentId, - change_detection::MAX_CHANGE_AGE, component::ComponentId, query::Access, schedule::{SystemLabel, SystemLabelId}, system::{ - check_system_change_tick, AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamFetch, - ExclusiveSystemParamItem, ExclusiveSystemParamState, IntoSystem, System, SystemMeta, - SystemTypeIdLabel, + AsSystemLabel, ExclusiveSystemParam, ExclusiveSystemParamFetch, ExclusiveSystemParamItem, + ExclusiveSystemParamState, IntoSystem, System, SystemMeta, SystemTypeIdLabel, }, world::{World, WorldId}, }; @@ -112,11 +110,11 @@ where true } - fn get_last_change_tick(&self) -> u32 { + fn get_last_change_tick(&self) -> u64 { self.system_meta.last_change_tick } - fn set_last_change_tick(&mut self, last_change_tick: u32) { + fn set_last_change_tick(&mut self, last_change_tick: u64) { self.system_meta.last_change_tick = last_change_tick; } @@ -129,7 +127,7 @@ where #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); - self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + self.system_meta.last_change_tick = world.change_tick(); self.param_state = Some(::init( world, &mut self.system_meta, @@ -138,14 +136,6 @@ where fn update_archetype_component_access(&mut self, _world: &World) {} - #[inline] - fn check_change_tick(&mut self, change_tick: u32) { - check_system_change_tick( - &mut self.system_meta.last_change_tick, - change_tick, - self.system_meta.name.as_ref(), - ); - } fn default_labels(&self) -> Vec { vec![self.func.as_system_label().as_label()] } diff --git a/crates/bevy_ecs/src/system/function_system.rs b/crates/bevy_ecs/src/system/function_system.rs index c3f672e718734..f914398b1344d 100644 --- a/crates/bevy_ecs/src/system/function_system.rs +++ b/crates/bevy_ecs/src/system/function_system.rs @@ -1,13 +1,12 @@ use crate::{ archetype::{ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, - change_detection::MAX_CHANGE_AGE, component::ComponentId, prelude::FromWorld, query::{Access, FilteredAccessSet}, schedule::{SystemLabel, SystemLabelId}, system::{ - check_system_change_tick, ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch, - SystemParamItem, SystemParamState, + ReadOnlySystemParamFetch, System, SystemParam, SystemParamFetch, SystemParamItem, + SystemParamState, }, world::{World, WorldId}, }; @@ -23,7 +22,7 @@ pub struct SystemMeta { // NOTE: this must be kept private. making a SystemMeta non-send is irreversible to prevent // SystemParams from overriding each other is_send: bool, - pub(crate) last_change_tick: u32, + pub(crate) last_change_tick: u64, } impl SystemMeta { @@ -143,7 +142,7 @@ pub struct SystemState { impl SystemState { pub fn new(world: &mut World) -> Self { let mut meta = SystemMeta::new::(); - meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + meta.last_change_tick = world.last_change_tick(); let param_state = ::init(world, &mut meta); Self { meta, @@ -411,11 +410,11 @@ where out } - fn get_last_change_tick(&self) -> u32 { + fn get_last_change_tick(&self) -> u64 { self.system_meta.last_change_tick } - fn set_last_change_tick(&mut self, last_change_tick: u32) { + fn set_last_change_tick(&mut self, last_change_tick: u64) { self.system_meta.last_change_tick = last_change_tick; } @@ -428,7 +427,7 @@ where #[inline] fn initialize(&mut self, world: &mut World) { self.world_id = Some(world.id()); - self.system_meta.last_change_tick = world.change_tick().wrapping_sub(MAX_CHANGE_AGE); + self.system_meta.last_change_tick = world.last_change_tick(); self.param_state = Some(::init( world, &mut self.system_meta, @@ -450,14 +449,6 @@ where } } - #[inline] - fn check_change_tick(&mut self, change_tick: u32) { - check_system_change_tick( - &mut self.system_meta.last_change_tick, - change_tick, - self.system_meta.name.as_ref(), - ); - } fn default_labels(&self) -> Vec { vec![self.func.as_system_label().as_label()] } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 43542e0f114a7..ebbd45b0826b0 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -276,8 +276,8 @@ use std::{any::TypeId, borrow::Borrow, fmt::Debug}; pub struct Query<'world, 'state, Q: WorldQuery, F: ReadOnlyWorldQuery = ()> { pub(crate) world: &'world World, pub(crate) state: &'state QueryState, - pub(crate) last_change_tick: u32, - pub(crate) change_tick: u32, + pub(crate) last_change_tick: u64, + pub(crate) change_tick: u64, } impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> std::fmt::Debug for Query<'w, 's, Q, F> { @@ -297,8 +297,8 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> Query<'w, 's, Q, F> { pub(crate) unsafe fn new( world: &'w World, state: &'s QueryState, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Self { Self { world, diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 43fcdb41dd3cb..16adf3cf80923 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -1,9 +1,8 @@ -use bevy_utils::tracing::warn; use core::fmt::Debug; use crate::{ - archetype::ArchetypeComponentId, change_detection::MAX_CHANGE_AGE, component::ComponentId, - query::Access, schedule::SystemLabelId, world::World, + archetype::ArchetypeComponentId, component::ComponentId, query::Access, + schedule::SystemLabelId, world::World, }; use std::borrow::Cow; @@ -59,44 +58,24 @@ pub trait System: Send + Sync + 'static { fn initialize(&mut self, _world: &mut World); /// Update the system's archetype component [`Access`]. fn update_archetype_component_access(&mut self, world: &World); - fn check_change_tick(&mut self, change_tick: u32); + /// The default labels for the system fn default_labels(&self) -> Vec { Vec::new() } /// Gets the system's last change tick - fn get_last_change_tick(&self) -> u32; + fn get_last_change_tick(&self) -> u64; /// Sets the system's last change tick /// # Warning /// This is a complex and error-prone 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 set_last_change_tick(&mut self, last_change_tick: u32); + fn set_last_change_tick(&mut self, last_change_tick: u64); } /// A convenience type alias for a boxed [`System`] trait object. pub type BoxedSystem = Box>; -pub(crate) fn check_system_change_tick( - last_change_tick: &mut u32, - change_tick: u32, - system_name: &str, -) { - 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 age > MAX_CHANGE_AGE { - warn!( - "System '{}' has not run for {} ticks. \ - Changes older than {} ticks will not be detected.", - system_name, - age, - MAX_CHANGE_AGE - 1, - ); - *last_change_tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); - } -} - impl Debug for dyn System { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "System {}: {{{}}}", self.name(), { diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 6f57b2dd0e4a6..12ce30320dfa1 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -131,7 +131,7 @@ pub trait SystemParamFetch<'world, 'state>: SystemParamState { state: &'state mut Self, system_meta: &SystemMeta, world: &'world World, - change_tick: u32, + change_tick: u64, ) -> Self::Item; } @@ -189,7 +189,7 @@ impl<'w, 's, Q: WorldQuery + 'static, F: ReadOnlyWorldQuery + 'static> SystemPar state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { Query::new(world, state, system_meta.last_change_tick, change_tick) } @@ -220,7 +220,7 @@ pub struct ParamSet<'w, 's, T: SystemParam> { param_states: &'s mut T::Fetch, world: &'w World, system_meta: SystemMeta, - change_tick: u32, + change_tick: u64, } /// The [`SystemParamState`] of [`ParamSet`]. pub struct ParamSetState SystemParamFetch<'w, 's>>(T); @@ -274,8 +274,8 @@ pub trait Resource: Send + Sync + 'static {} pub struct Res<'w, T: Resource> { value: &'w T, ticks: &'w ComponentTicks, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, } // SAFETY: Res only reads a single World resource @@ -304,13 +304,12 @@ impl<'w, T: Resource> Res<'w, T> { /// 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) + self.ticks.is_added(self.last_change_tick) } /// 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) + self.ticks.is_changed(self.last_change_tick) } pub fn into_inner(self) -> &'w T { @@ -391,7 +390,7 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { let column = world .get_populated_resource_column(state.component_id) @@ -439,7 +438,7 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { world .get_populated_resource_column(state.0.component_id) @@ -502,7 +501,7 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { let value = world .get_resource_unchecked_mut_with_id(state.component_id) @@ -549,7 +548,7 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResMutState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { world .get_resource_unchecked_mut_with_id(state.0.component_id) @@ -590,7 +589,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for CommandQueue { state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { Commands::new(state, world) } @@ -642,7 +641,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for WorldState { _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { world } @@ -744,7 +743,7 @@ impl<'w, 's, T: FromWorld + Send + 'static> SystemParamFetch<'w, 's> for LocalSt state: &'s mut Self, _system_meta: &SystemMeta, _world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { Local(state.0.get()) } @@ -839,7 +838,7 @@ impl<'w, 's, T: Component> SystemParamFetch<'w, 's> for RemovedComponentsState Self::Item { RemovedComponents { world, @@ -864,8 +863,7 @@ impl<'w, 's, T: Component> SystemParamFetch<'w, 's> for RemovedComponentsState { pub(crate) value: &'w T, ticks: ComponentTicks, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, } // SAFETY: Only reads a single World non-send resource @@ -883,13 +881,12 @@ where impl<'w, T: 'static> NonSend<'w, T> { /// 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) + self.ticks.is_added(self.last_change_tick) } /// 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) + self.ticks.is_changed(self.last_change_tick) } } @@ -905,7 +902,6 @@ impl<'a, T> From> for NonSend<'a, T> { Self { value: nsm.value, ticks: nsm.ticks.component_ticks.to_owned(), - change_tick: nsm.ticks.change_tick, last_change_tick: nsm.ticks.last_change_tick, } } @@ -960,7 +956,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + _change_tick: u64, ) -> Self::Item { world.validate_non_send_access::(); let column = world @@ -977,7 +973,6 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendState { value: column.get_data_ptr().deref::(), ticks: column.get_ticks_unchecked(0).read(), last_change_tick: system_meta.last_change_tick, - change_tick, } } } @@ -1010,7 +1005,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + _change_tick: u64, ) -> Self::Item { world.validate_non_send_access::(); world @@ -1019,7 +1014,6 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendState { value: column.get_data_ptr().deref::(), ticks: column.get_ticks_unchecked(0).read(), last_change_tick: system_meta.last_change_tick, - change_tick, }) } } @@ -1076,7 +1070,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { world.validate_non_send_access::(); let column = world @@ -1124,7 +1118,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { world.validate_non_send_access::(); world @@ -1166,7 +1160,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for ArchetypesState { _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { world.archetypes() } @@ -1198,7 +1192,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for ComponentsState { _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { world.components() } @@ -1230,7 +1224,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for EntitiesState { _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { world.entities() } @@ -1262,7 +1256,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for BundlesState { _state: &'s mut Self, _system_meta: &SystemMeta, world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { world.bundles() } @@ -1279,20 +1273,20 @@ impl<'w, 's> SystemParamFetch<'w, 's> for BundlesState { /// on a [`Mut`](crate::change_detection::Mut) or [`ResMut`](crate::change_detection::ResMut). #[derive(Debug)] pub struct SystemChangeTick { - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, } impl SystemChangeTick { /// Returns the current [`World`] change tick seen by the system. #[inline] - pub fn change_tick(&self) -> u32 { + pub fn change_tick(&self) -> u64 { self.change_tick } /// Returns the [`World`] change tick seen by the system the previous time it ran. #[inline] - pub fn last_change_tick(&self) -> u32 { + pub fn last_change_tick(&self) -> u64 { self.last_change_tick } } @@ -1322,7 +1316,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for SystemChangeTickState { _state: &'s mut Self, system_meta: &SystemMeta, _world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { SystemChangeTick { last_change_tick: system_meta.last_change_tick, @@ -1408,7 +1402,7 @@ impl<'w, 's> SystemParamFetch<'w, 's> for SystemNameState { state: &'s mut Self, _system_meta: &SystemMeta, _world: &'w World, - _change_tick: u32, + _change_tick: u64, ) -> Self::Item { SystemName { name: state.name.as_ref(), @@ -1436,7 +1430,7 @@ macro_rules! impl_system_param_tuple { state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { let ($($param,)*) = state; @@ -1582,7 +1576,7 @@ where state: &'state mut Self, system_meta: &SystemMeta, world: &'world World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { // SAFETY: We properly delegate SystemParamState StaticSystemParam(S::get_param(&mut state.0, system_meta, world, change_tick)) diff --git a/crates/bevy_ecs/src/system/system_piping.rs b/crates/bevy_ecs/src/system/system_piping.rs index 4f4a192ff535d..b51350fea7bf7 100644 --- a/crates/bevy_ecs/src/system/system_piping.rs +++ b/crates/bevy_ecs/src/system/system_piping.rs @@ -107,16 +107,11 @@ impl> System for PipeSystem< .extend(self.system_b.archetype_component_access()); } - fn check_change_tick(&mut self, change_tick: u32) { - self.system_a.check_change_tick(change_tick); - self.system_b.check_change_tick(change_tick); - } - - fn get_last_change_tick(&self) -> u32 { + fn get_last_change_tick(&self) -> u64 { self.system_a.get_last_change_tick() } - fn set_last_change_tick(&mut self, last_change_tick: u32) { + fn set_last_change_tick(&mut self, last_change_tick: u64) { self.system_a.set_last_change_tick(last_change_tick); self.system_b.set_last_change_tick(last_change_tick); } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 5816696f30b64..cae46b63f4d52 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -97,8 +97,8 @@ impl<'w> EntityRef<'w> { #[inline] pub unsafe fn get_unchecked_mut( &self, - last_change_tick: u32, - change_tick: u32, + last_change_tick: u64, + change_tick: u64, ) -> Option> { get_component_and_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) .map(|(value, ticks)| Mut { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 3e45e8c100fea..9c16c8d4d671f 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -25,7 +25,7 @@ use bevy_utils::tracing::debug; use std::{ any::TypeId, fmt, - sync::atomic::{AtomicU32, Ordering}, + sync::atomic::{AtomicU64, Ordering}, }; mod identifier; @@ -61,8 +61,8 @@ pub struct World { /// Access cache used by [WorldCell]. pub(crate) archetype_component_access: ArchetypeComponentAccess, main_thread_validator: MainThreadValidator, - pub(crate) change_tick: AtomicU32, - pub(crate) last_change_tick: u32, + pub(crate) change_tick: AtomicU64, + pub(crate) last_change_tick: u64, } impl Default for World { @@ -79,7 +79,7 @@ impl Default for World { main_thread_validator: Default::default(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes // are detected on first system runs and for direct world queries. - change_tick: AtomicU32::new(1), + change_tick: AtomicU64::new(1), last_change_tick: 0, } } @@ -835,7 +835,7 @@ impl World { }; // SAFETY: resources table always have row 0 let ticks = unsafe { column.get_ticks_unchecked(0).deref() }; - ticks.is_added(self.last_change_tick(), self.read_change_tick()) + ticks.is_added(self.last_change_tick()) } pub fn is_resource_changed(&self) -> bool { @@ -852,7 +852,7 @@ impl World { }; // SAFETY: resources table always have row 0 let ticks = unsafe { column.get_ticks_unchecked(0).deref() }; - ticks.is_changed(self.last_change_tick(), self.read_change_tick()) + ticks.is_changed(self.last_change_tick()) } /// Gets a reference to the resource of the given type @@ -1424,37 +1424,25 @@ impl World { } #[inline] - pub fn increment_change_tick(&self) -> u32 { + pub fn increment_change_tick(&self) -> u64 { self.change_tick.fetch_add(1, Ordering::AcqRel) } #[inline] - pub fn read_change_tick(&self) -> u32 { + pub fn read_change_tick(&self) -> u64 { self.change_tick.load(Ordering::Acquire) } #[inline] - pub fn change_tick(&mut self) -> u32 { + pub fn change_tick(&mut self) -> u64 { *self.change_tick.get_mut() } #[inline] - pub fn last_change_tick(&self) -> u32 { + pub fn last_change_tick(&self) -> u64 { self.last_change_tick } - pub fn check_change_ticks(&mut self) { - // Iterate over all component change ticks, clamping their age to max age - // PERF: parallelize - let change_tick = self.change_tick(); - self.storages.tables.check_change_ticks(change_tick); - self.storages.sparse_sets.check_change_ticks(change_tick); - let resource_archetype = self.archetypes.resource_mut(); - for column in resource_archetype.unique_components.values_mut() { - column.check_change_ticks(change_tick); - } - } - pub fn clear_entities(&mut self) { self.storages.tables.clear(); self.storages.sparse_sets.clear(); diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index cdfb462c2324b..10a53eca18518 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -84,7 +84,7 @@ where state: &'s mut Self, system_meta: &SystemMeta, world: &'w World, - change_tick: u32, + change_tick: u64, ) -> Self::Item { let main_world = ResState::::get_param( &mut state.main_world_state, diff --git a/crates/bevy_time/src/fixed_timestep.rs b/crates/bevy_time/src/fixed_timestep.rs index 8a63f67f92bb6..62e1dc7f17748 100644 --- a/crates/bevy_time/src/fixed_timestep.rs +++ b/crates/bevy_time/src/fixed_timestep.rs @@ -225,15 +225,11 @@ impl System for FixedTimestep { .update_archetype_component_access(world); } - fn check_change_tick(&mut self, change_tick: u32) { - self.internal_system.check_change_tick(change_tick); - } - - fn get_last_change_tick(&self) -> u32 { + fn get_last_change_tick(&self) -> u64 { self.internal_system.get_last_change_tick() } - fn set_last_change_tick(&mut self, last_change_tick: u32) { + fn set_last_change_tick(&mut self, last_change_tick: u64) { self.internal_system.set_last_change_tick(last_change_tick); } }