diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 91f29e4ac57e2..2e5bf03c10e8b 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -9,7 +9,7 @@ use crate::{ Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, SpawnBundleStatus, }, - component::{Component, ComponentId, ComponentTicks, Components, StorageType}, + component::{Component, ComponentId, Components, StorageType, Tick}, entity::{Entities, Entity, EntityLocation}, storage::{SparseSetIndex, SparseSets, Storages, Table}, }; @@ -394,11 +394,7 @@ impl BundleInfo { // SAFETY: bundle_component is a valid index for this bundle match bundle_component_status.get_status(bundle_component) { ComponentStatus::Added => { - column.initialize( - table_row, - component_ptr, - ComponentTicks::new(change_tick), - ); + column.initialize(table_row, component_ptr, Tick::new(change_tick)); } ComponentStatus::Mutated => { column.replace(table_row, component_ptr, change_tick); diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index b8d1f7c196d35..0831f316ef4f8 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -1,6 +1,11 @@ //! Types that detect when their internal data mutate. -use crate::{component::ComponentTicks, ptr::PtrMut, system::Resource}; +use crate::{ + component::{Tick, TickCells}, + ptr::PtrMut, + system::Resource, +}; +use bevy_ptr::UnsafeCellDeref; use std::ops::{Deref, DerefMut}; /// The (arbitrarily chosen) minimum number of world tick increments between `check_tick` scans. @@ -95,21 +100,21 @@ macro_rules! change_detection_impl { #[inline] fn is_added(&self) -> bool { self.ticks - .component_ticks - .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + .added + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn is_changed(&self) -> bool { self.ticks - .component_ticks - .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + .changed + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn set_changed(&mut self) { self.ticks - .component_ticks + .changed .set_changed(self.ticks.change_tick); } @@ -224,11 +229,30 @@ macro_rules! impl_debug { } pub(crate) struct Ticks<'a> { - pub(crate) component_ticks: &'a mut ComponentTicks, + pub(crate) added: &'a mut Tick, + pub(crate) changed: &'a mut Tick, pub(crate) last_change_tick: u32, pub(crate) change_tick: u32, } +impl<'a> Ticks<'a> { + /// # Safety + /// This should never alias the underlying ticks. All access must be unique. + #[inline] + pub(crate) unsafe fn from_tick_cells( + cells: TickCells<'a>, + last_change_tick: u32, + change_tick: u32, + ) -> Self { + Self { + added: cells.added.deref_mut(), + changed: cells.changed.deref_mut(), + last_change_tick, + change_tick, + } + } +} + /// Unique mutable borrow of a [`Resource`]. /// /// See the [`Resource`] documentation for usage. @@ -381,22 +405,20 @@ impl<'a> DetectChanges for MutUntyped<'a> { #[inline] fn is_added(&self) -> bool { self.ticks - .component_ticks - .is_added(self.ticks.last_change_tick, self.ticks.change_tick) + .added + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn is_changed(&self) -> bool { self.ticks - .component_ticks - .is_changed(self.ticks.last_change_tick, self.ticks.change_tick) + .changed + .is_older_than(self.ticks.last_change_tick, self.ticks.change_tick) } #[inline] fn set_changed(&mut self) { - self.ticks - .component_ticks - .set_changed(self.ticks.change_tick); + self.ticks.changed.set_changed(self.ticks.change_tick); } #[inline] @@ -429,10 +451,8 @@ mod tests { use crate::{ self as bevy_ecs, - change_detection::{ - ComponentTicks, Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE, - }, - component::Component, + change_detection::{Mut, NonSendMut, ResMut, Ticks, CHECK_TICK_THRESHOLD, MAX_CHANGE_AGE}, + component::{Component, ComponentTicks, Tick}, query::ChangeTrackers, system::{IntoSystem, Query, System}, world::World, @@ -514,8 +534,8 @@ mod tests { 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); + let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added.tick); + let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed.tick); assert!(ticks_since_insert > MAX_CHANGE_AGE); assert!(ticks_since_change > MAX_CHANGE_AGE); } @@ -524,8 +544,8 @@ mod tests { 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); + let ticks_since_insert = change_tick.wrapping_sub(tracker.component_ticks.added.tick); + let ticks_since_change = change_tick.wrapping_sub(tracker.component_ticks.changed.tick); assert!(ticks_since_insert == MAX_CHANGE_AGE); assert!(ticks_since_change == MAX_CHANGE_AGE); } @@ -534,11 +554,12 @@ mod tests { #[test] fn mut_from_res_mut() { let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, + added: Tick::new(1), + changed: Tick::new(2), }; let ticks = Ticks { - component_ticks: &mut component_ticks, + added: &mut component_ticks.added, + changed: &mut component_ticks.changed, last_change_tick: 3, change_tick: 4, }; @@ -549,8 +570,8 @@ mod tests { }; let into_mut: Mut = res_mut.into(); - assert_eq!(1, into_mut.ticks.component_ticks.added); - assert_eq!(2, into_mut.ticks.component_ticks.changed); + assert_eq!(1, into_mut.ticks.added.tick); + assert_eq!(2, into_mut.ticks.changed.tick); assert_eq!(3, into_mut.ticks.last_change_tick); assert_eq!(4, into_mut.ticks.change_tick); } @@ -558,11 +579,12 @@ mod tests { #[test] fn mut_from_non_send_mut() { let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, + added: Tick::new(1), + changed: Tick::new(2), }; let ticks = Ticks { - component_ticks: &mut component_ticks, + added: &mut component_ticks.added, + changed: &mut component_ticks.changed, last_change_tick: 3, change_tick: 4, }; @@ -573,8 +595,8 @@ mod tests { }; let into_mut: Mut = non_send_mut.into(); - assert_eq!(1, into_mut.ticks.component_ticks.added); - assert_eq!(2, into_mut.ticks.component_ticks.changed); + assert_eq!(1, into_mut.ticks.added.tick); + assert_eq!(2, into_mut.ticks.changed.tick); assert_eq!(3, into_mut.ticks.last_change_tick); assert_eq!(4, into_mut.ticks.change_tick); } @@ -584,13 +606,14 @@ mod tests { use super::*; struct Outer(i64); + let (last_change_tick, change_tick) = (2, 3); let mut component_ticks = ComponentTicks { - added: 1, - changed: 2, + added: Tick::new(1), + changed: Tick::new(2), }; - let (last_change_tick, change_tick) = (2, 3); let ticks = Ticks { - component_ticks: &mut component_ticks, + added: &mut component_ticks.added, + changed: &mut component_ticks.changed, last_change_tick, change_tick, }; diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c59a1e2028737..442fc60ae03a7 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -6,7 +6,8 @@ use crate::{ system::Resource, }; pub use bevy_ecs_macros::Component; -use bevy_ptr::OwningPtr; +use bevy_ptr::{OwningPtr, UnsafeCellDeref}; +use std::cell::UnsafeCell; use std::{ alloc::Layout, any::{Any, TypeId}, @@ -517,23 +518,26 @@ impl Components { } } -/// Records when a component was added and when it was last mutably dereferenced (or added). +/// Used to track changes in state between system runs, e.g. components being added or accessed mutably. #[derive(Copy, Clone, Debug)] -pub struct ComponentTicks { - pub(crate) added: u32, - pub(crate) changed: u32, +pub struct Tick { + pub(crate) tick: u32, } -impl ComponentTicks { +impl Tick { + pub const fn new(tick: u32) -> Self { + Self { tick } + } + #[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 { + /// Returns `true` if the tick is older than the system last's run. + pub fn is_older_than(&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.tick`, 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.tick).min(MAX_CHANGE_AGE); let ticks_since_system = change_tick .wrapping_sub(last_change_tick) .min(MAX_CHANGE_AGE); @@ -541,34 +545,81 @@ impl ComponentTicks { ticks_since_system > ticks_since_insert } + pub(crate) fn check_tick(&mut self, change_tick: u32) { + let age = change_tick.wrapping_sub(self.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 { + self.tick = change_tick.wrapping_sub(MAX_CHANGE_AGE); + } + } + + /// Manually sets the change tick. + /// + /// This is normally done automatically via the [`DerefMut`](std::ops::DerefMut) implementation + /// 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 + /// ```rust,no_run + /// # use bevy_ecs::{world::World, component::ComponentTicks}; + /// let world: World = unimplemented!(); + /// let component_ticks: ComponentTicks = unimplemented!(); + /// + /// component_ticks.set_changed(world.read_change_tick()); + /// ``` + #[inline] + pub fn set_changed(&mut self, change_tick: u32) { + self.tick = change_tick; + } +} + +/// Wrapper around [`Tick`]s for a single component +#[derive(Copy, Clone, Debug)] +pub struct TickCells<'a> { + pub added: &'a UnsafeCell, + pub changed: &'a UnsafeCell, +} + +impl<'a> TickCells<'a> { + /// # Safety + /// All cells contained within must uphold the safety invariants of [`UnsafeCellDeref::read`]. + #[inline] + pub(crate) unsafe fn read(&self) -> ComponentTicks { + ComponentTicks { + added: self.added.read(), + changed: self.changed.read(), + } + } +} + +/// 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: Tick, + pub(crate) changed: Tick, +} + +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 { + self.added.is_older_than(last_change_tick, 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 + self.changed.is_older_than(last_change_tick, change_tick) } pub(crate) fn new(change_tick: u32) -> Self { Self { - added: change_tick, - changed: change_tick, + added: Tick::new(change_tick), + changed: Tick::new(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 @@ -585,15 +636,6 @@ impl ComponentTicks { /// ``` #[inline] pub fn set_changed(&mut self, change_tick: u32) { - 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); + self.changed.set_changed(change_tick); } } diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index ad6598bcc6e0d..9e62594e3c44e 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,7 +1,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, change_detection::Ticks, - component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess}, storage::{ComponentSparseSet, Table}, @@ -654,7 +654,8 @@ pub struct WriteFetch<'w, T> { // T::Storage = TableStorage table_data: Option<( ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, + ThinSlicePtr<'w, UnsafeCell>, + ThinSlicePtr<'w, UnsafeCell>, )>, // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, @@ -733,7 +734,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { let column = table.get_column(component_id).debug_checked_unwrap(); fetch.table_data = Some(( column.get_data_slice().into(), - column.get_ticks_slice().into(), + column.get_added_ticks_slice().into(), + column.get_changed_ticks_slice().into(), )); } @@ -745,29 +747,27 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { ) -> Self::Item<'w> { match T::Storage::STORAGE_TYPE { StorageType::Table => { - let (table_components, table_ticks) = fetch.table_data.debug_checked_unwrap(); + let (table_components, added_ticks, changed_ticks) = + fetch.table_data.debug_checked_unwrap(); Mut { value: table_components.get(table_row).deref_mut(), ticks: Ticks { - component_ticks: table_ticks.get(table_row).deref_mut(), + added: added_ticks.get(table_row).deref_mut(), + changed: changed_ticks.get(table_row).deref_mut(), change_tick: fetch.change_tick, last_change_tick: fetch.last_change_tick, }, } } StorageType::SparseSet => { - let (component, component_ticks) = fetch + let (component, ticks) = fetch .sparse_set .debug_checked_unwrap() .get_with_ticks(entity) .debug_checked_unwrap(); Mut { value: component.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: component_ticks.deref_mut(), - change_tick: fetch.change_tick, - last_change_tick: fetch.last_change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, fetch.change_tick, fetch.last_change_tick), } } } @@ -992,7 +992,8 @@ impl ChangeTrackers { #[doc(hidden)] pub struct ChangeTrackersFetch<'w, T> { // T::Storage = TableStorage - table_ticks: Option>>, + table_added: Option>>, + table_changed: Option>>, // T::Storage = SparseStorage sparse_set: Option<&'w ComponentSparseSet>, @@ -1028,7 +1029,8 @@ unsafe impl WorldQuery for ChangeTrackers { change_tick: u32, ) -> ChangeTrackersFetch<'w, T> { ChangeTrackersFetch { - table_ticks: None, + table_added: None, + table_changed: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world .storages() @@ -1044,7 +1046,8 @@ unsafe impl WorldQuery for ChangeTrackers { unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { ChangeTrackersFetch { - table_ticks: fetch.table_ticks, + table_added: fetch.table_added, + table_changed: fetch.table_changed, sparse_set: fetch.sparse_set, marker: fetch.marker, last_change_tick: fetch.last_change_tick, @@ -1070,13 +1073,9 @@ unsafe impl WorldQuery for ChangeTrackers { &id: &ComponentId, table: &'w Table, ) { - fetch.table_ticks = Some( - table - .get_column(id) - .debug_checked_unwrap() - .get_ticks_slice() - .into(), - ); + let column = table.get_column(id).debug_checked_unwrap(); + fetch.table_added = Some(column.get_added_ticks_slice().into()); + fetch.table_changed = Some(column.get_changed_ticks_slice().into()); } #[inline(always)] @@ -1088,20 +1087,29 @@ unsafe impl WorldQuery for ChangeTrackers { match T::Storage::STORAGE_TYPE { StorageType::Table => ChangeTrackers { component_ticks: { - let table_ticks = fetch.table_ticks.debug_checked_unwrap(); - table_ticks.get(table_row).read() + ComponentTicks { + added: fetch + .table_added + .debug_checked_unwrap() + .get(table_row) + .read(), + changed: fetch + .table_changed + .debug_checked_unwrap() + .get(table_row) + .read(), + } }, marker: PhantomData, last_change_tick: fetch.last_change_tick, change_tick: fetch.change_tick, }, StorageType::SparseSet => ChangeTrackers { - component_ticks: *fetch + component_ticks: fetch .sparse_set .debug_checked_unwrap() .get_ticks(entity) - .debug_checked_unwrap() - .get(), + .debug_checked_unwrap(), marker: PhantomData, last_change_tick: fetch.last_change_tick, change_tick: fetch.change_tick, diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index 2cf2fc3e1c10b..a84d74f8075e3 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -1,9 +1,9 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId}, - component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType}, + component::{Component, ComponentId, ComponentStorage, StorageType, Tick}, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery}, - storage::{ComponentSparseSet, Table}, + storage::{Column, ComponentSparseSet, Table}, world::World, }; use bevy_ecs_macros::all_tuples; @@ -405,7 +405,8 @@ macro_rules! impl_tick_filter { $name: ident, $(#[$fetch_meta:meta])* $fetch_name: ident, - $is_detected: expr + $get_slice: expr, + $get_sparse_set: expr ) => { $(#[$meta])* pub struct $name(PhantomData); @@ -413,7 +414,7 @@ macro_rules! impl_tick_filter { #[doc(hidden)] $(#[$fetch_meta])* pub struct $fetch_name<'w, T> { - table_ticks: Option>>, + table_ticks: Option< ThinSlicePtr<'w, UnsafeCell>>, marker: PhantomData, sparse_set: Option<&'w ComponentSparseSet>, last_change_tick: u32, @@ -475,10 +476,11 @@ macro_rules! impl_tick_filter { table: &'w Table ) { fetch.table_ticks = Some( - table.get_column(component_id) - .debug_checked_unwrap() - .get_ticks_slice() - .into() + $get_slice( + &table + .get_column(component_id) + .debug_checked_unwrap() + ).into(), ); } @@ -502,23 +504,21 @@ macro_rules! impl_tick_filter { ) -> Self::Item<'w> { match T::Storage::STORAGE_TYPE { StorageType::Table => { - $is_detected(&*( - fetch.table_ticks + fetch + .table_ticks .debug_checked_unwrap() - .get(table_row)) - .deref(), - fetch.last_change_tick, - fetch.change_tick - ) + .get(table_row) + .deref() + .is_older_than(fetch.last_change_tick, fetch.change_tick) } StorageType::SparseSet => { - let ticks = &*fetch + let sparse_set = &fetch .sparse_set + .debug_checked_unwrap(); + $get_sparse_set(sparse_set, entity) .debug_checked_unwrap() - .get_ticks(entity) - .debug_checked_unwrap() - .get(); - $is_detected(ticks, fetch.last_change_tick, fetch.change_tick) + .deref() + .is_older_than(fetch.last_change_tick, fetch.change_tick) } } } @@ -595,7 +595,8 @@ impl_tick_filter!( /// ``` Added, AddedFetch, - ComponentTicks::is_added + Column::get_added_ticks_slice, + ComponentSparseSet::get_added_ticks ); impl_tick_filter!( @@ -632,7 +633,8 @@ impl_tick_filter!( /// ``` Changed, ChangedFetch, - ComponentTicks::is_changed + Column::get_changed_ticks_slice, + ComponentSparseSet::get_changed_ticks ); /// A marker trait to indicate that the filter works at an archetype level. diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index e047fb3c96bf8..2d852ba73e84c 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -1,8 +1,7 @@ use crate::archetype::ArchetypeComponentId; -use crate::component::{ComponentId, ComponentTicks, Components}; +use crate::component::{ComponentId, ComponentTicks, Components, TickCells}; use crate::storage::{Column, SparseSet}; use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; -use std::cell::UnsafeCell; /// The type-erased backing storage and metadata for a single resource within a [`World`]. /// @@ -33,18 +32,12 @@ impl ResourceData { /// Gets a read-only reference to the change ticks of the underlying resource, if available. #[inline] - pub fn get_ticks(&self) -> Option<&ComponentTicks> { - self.column - .get_ticks(0) - // SAFETY: - // - This borrow's lifetime is bounded by the lifetime on self. - // - A read-only borrow on self can only exist while a mutable borrow doesn't - // exist. - .map(|ticks| unsafe { ticks.deref() }) + pub fn get_ticks(&self) -> Option { + self.column.get_ticks(0) } #[inline] - pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, &UnsafeCell)> { + pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> { self.column.get(0) } @@ -85,7 +78,8 @@ impl ResourceData { ) { if self.is_present() { self.column.replace_untracked(0, value); - *self.column.get_ticks_unchecked(0).deref_mut() = change_ticks; + *self.column.get_added_ticks_unchecked(0).deref_mut() = change_ticks.added; + *self.column.get_changed_ticks_unchecked(0).deref_mut() = change_ticks.changed; } else { self.column.push(value, change_ticks); } diff --git a/crates/bevy_ecs/src/storage/sparse_set.rs b/crates/bevy_ecs/src/storage/sparse_set.rs index b935911ed2989..34844432504e0 100644 --- a/crates/bevy_ecs/src/storage/sparse_set.rs +++ b/crates/bevy_ecs/src/storage/sparse_set.rs @@ -1,5 +1,5 @@ use crate::{ - component::{ComponentId, ComponentInfo, ComponentTicks}, + component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells}, entity::Entity, storage::Column, }; @@ -189,7 +189,7 @@ impl ComponentSparseSet { } #[inline] - pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, &UnsafeCell)> { + pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> { let dense_index = *self.sparse.get(entity.index())? as usize; #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index]); @@ -197,13 +197,34 @@ impl ComponentSparseSet { unsafe { Some(( self.dense.get_data_unchecked(dense_index), - self.dense.get_ticks_unchecked(dense_index), + TickCells { + added: self.dense.get_added_ticks_unchecked(dense_index), + changed: self.dense.get_changed_ticks_unchecked(dense_index), + }, )) } } #[inline] - pub fn get_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { + pub fn get_added_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { + let dense_index = *self.sparse.get(entity.index())? as usize; + #[cfg(debug_assertions)] + assert_eq!(entity, self.entities[dense_index]); + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { Some(self.dense.get_added_ticks_unchecked(dense_index)) } + } + + #[inline] + pub fn get_changed_ticks(&self, entity: Entity) -> Option<&UnsafeCell> { + let dense_index = *self.sparse.get(entity.index())? as usize; + #[cfg(debug_assertions)] + assert_eq!(entity, self.entities[dense_index]); + // SAFETY: if the sparse index points to something in the dense vec, it exists + unsafe { Some(self.dense.get_changed_ticks_unchecked(dense_index)) } + } + + #[inline] + pub fn get_ticks(&self, entity: Entity) -> Option { let dense_index = *self.sparse.get(entity.index())? as usize; #[cfg(debug_assertions)] assert_eq!(entity, self.entities[dense_index]); diff --git a/crates/bevy_ecs/src/storage/table.rs b/crates/bevy_ecs/src/storage/table.rs index aa7bb32e535ec..9cd773af58d90 100644 --- a/crates/bevy_ecs/src/storage/table.rs +++ b/crates/bevy_ecs/src/storage/table.rs @@ -1,10 +1,10 @@ use crate::{ - component::{ComponentId, ComponentInfo, ComponentTicks, Components}, + component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells}, entity::Entity, query::DebugCheckedUnwrap, storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet}, }; -use bevy_ptr::{OwningPtr, Ptr, PtrMut}; +use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref}; use bevy_utils::HashMap; use std::alloc::Layout; use std::{ @@ -35,7 +35,8 @@ impl TableId { #[derive(Debug)] pub struct Column { data: BlobVec, - ticks: Vec>, + added_ticks: Vec>, + changed_ticks: Vec>, } impl Column { @@ -44,7 +45,8 @@ impl Column { Column { // SAFETY: component_info.drop() is valid for the types that will be inserted. data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) }, - ticks: Vec::with_capacity(capacity), + added_ticks: Vec::with_capacity(capacity), + changed_ticks: Vec::with_capacity(capacity), } } @@ -60,15 +62,11 @@ impl Column { /// # Safety /// Assumes data has already been allocated for the given row. #[inline] - pub(crate) unsafe fn initialize( - &mut self, - row: usize, - data: OwningPtr<'_>, - ticks: ComponentTicks, - ) { + pub(crate) unsafe fn initialize(&mut self, row: usize, data: OwningPtr<'_>, tick: Tick) { debug_assert!(row < self.len()); self.data.initialize_unchecked(row, data); - *self.ticks.get_unchecked_mut(row).get_mut() = ticks; + *self.added_ticks.get_unchecked_mut(row).get_mut() = tick; + *self.changed_ticks.get_unchecked_mut(row).get_mut() = tick; } /// Writes component data to the column at given row. @@ -80,7 +78,7 @@ impl Column { pub(crate) unsafe fn replace(&mut self, row: usize, data: OwningPtr<'_>, change_tick: u32) { debug_assert!(row < self.len()); self.data.replace_unchecked(row, data); - self.ticks + self.changed_ticks .get_unchecked_mut(row) .get_mut() .set_changed(change_tick); @@ -113,7 +111,8 @@ impl Column { #[inline] pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) { self.data.swap_remove_and_drop_unchecked(row); - self.ticks.swap_remove(row); + self.added_ticks.swap_remove(row); + self.changed_ticks.swap_remove(row); } #[inline] @@ -125,8 +124,9 @@ impl Column { (row < self.data.len()).then(|| { // SAFETY: The row was length checked before this. let data = unsafe { self.data.swap_remove_and_forget_unchecked(row) }; - let ticks = self.ticks.swap_remove(row).into_inner(); - (data, ticks) + let added = self.added_ticks.swap_remove(row).into_inner(); + let changed = self.changed_ticks.swap_remove(row).into_inner(); + (data, ComponentTicks { added, changed }) }) } @@ -139,8 +139,9 @@ impl Column { row: usize, ) -> (OwningPtr<'_>, ComponentTicks) { let data = self.data.swap_remove_and_forget_unchecked(row); - let ticks = self.ticks.swap_remove(row).into_inner(); - (data, ticks) + let added = self.added_ticks.swap_remove(row).into_inner(); + let changed = self.changed_ticks.swap_remove(row).into_inner(); + (data, ComponentTicks { added, changed }) } /// Removes the element from `other` at `src_row` and inserts it @@ -164,20 +165,23 @@ impl Column { debug_assert!(self.data.layout() == other.data.layout()); let ptr = self.data.get_unchecked_mut(dst_row); other.data.swap_remove_unchecked(src_row, ptr); - *self.ticks.get_unchecked_mut(dst_row) = other.ticks.swap_remove(src_row); + *self.added_ticks.get_unchecked_mut(dst_row) = other.added_ticks.swap_remove(src_row); + *self.changed_ticks.get_unchecked_mut(dst_row) = other.changed_ticks.swap_remove(src_row); } // # Safety // - ptr must point to valid data of this column's component type pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) { self.data.push(ptr); - self.ticks.push(UnsafeCell::new(ticks)); + self.added_ticks.push(UnsafeCell::new(ticks.added)); + self.changed_ticks.push(UnsafeCell::new(ticks.changed)); } #[inline] pub(crate) fn reserve_exact(&mut self, additional: usize) { self.data.reserve_exact(additional); - self.ticks.reserve_exact(additional); + self.added_ticks.reserve_exact(additional); + self.changed_ticks.reserve_exact(additional); } #[inline] @@ -192,16 +196,29 @@ impl Column { } #[inline] - pub fn get_ticks_slice(&self) -> &[UnsafeCell] { - &self.ticks + pub fn get_added_ticks_slice(&self) -> &[UnsafeCell] { + &self.added_ticks } #[inline] - pub fn get(&self, row: usize) -> Option<(Ptr<'_>, &UnsafeCell)> { + pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell] { + &self.changed_ticks + } + + #[inline] + pub fn get(&self, row: usize) -> Option<(Ptr<'_>, TickCells<'_>)> { (row < self.data.len()) // SAFETY: The row is length checked before fetching the pointer. This is being // accessed through a read-only reference to the column. - .then(|| unsafe { (self.data.get_unchecked(row), self.ticks.get_unchecked(row)) }) + .then(|| unsafe { + ( + self.data.get_unchecked(row), + TickCells { + added: self.added_ticks.get_unchecked(row), + changed: self.changed_ticks.get_unchecked(row), + }, + ) + }) } #[inline] @@ -237,27 +254,66 @@ impl Column { } #[inline] - pub fn get_ticks(&self, row: usize) -> Option<&UnsafeCell> { - self.ticks.get(row) + pub fn get_added_ticks(&self, row: usize) -> Option<&UnsafeCell> { + self.added_ticks.get(row) + } + + #[inline] + pub fn get_changed_ticks(&self, row: usize) -> Option<&UnsafeCell> { + self.changed_ticks.get(row) + } + + #[inline] + pub fn get_ticks(&self, row: usize) -> Option { + if row < self.data.len() { + // SAFETY: The size of the column has already been checked. + Some(unsafe { self.get_ticks_unchecked(row) }) + } else { + None + } } /// # Safety /// index must be in-bounds #[inline] - pub unsafe fn get_ticks_unchecked(&self, row: usize) -> &UnsafeCell { - debug_assert!(row < self.ticks.len()); - self.ticks.get_unchecked(row) + pub unsafe fn get_added_ticks_unchecked(&self, row: usize) -> &UnsafeCell { + debug_assert!(row < self.added_ticks.len()); + self.added_ticks.get_unchecked(row) + } + + /// # Safety + /// index must be in-bounds + #[inline] + pub unsafe fn get_changed_ticks_unchecked(&self, row: usize) -> &UnsafeCell { + debug_assert!(row < self.changed_ticks.len()); + self.changed_ticks.get_unchecked(row) + } + + /// # Safety + /// index must be in-bounds + #[inline] + pub unsafe fn get_ticks_unchecked(&self, row: usize) -> ComponentTicks { + debug_assert!(row < self.added_ticks.len()); + debug_assert!(row < self.changed_ticks.len()); + ComponentTicks { + added: self.added_ticks.get_unchecked(row).read(), + changed: self.changed_ticks.get_unchecked(row).read(), + } } pub fn clear(&mut self) { self.data.clear(); - self.ticks.clear(); + self.added_ticks.clear(); + self.changed_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); + for component_ticks in &mut self.added_ticks { + component_ticks.get_mut().check_tick(change_tick); + } + for component_ticks in &mut self.changed_ticks { + component_ticks.get_mut().check_tick(change_tick); } } } @@ -474,7 +530,8 @@ impl Table { self.entities.push(entity); for column in self.columns.values_mut() { column.data.set_len(self.entities.len()); - column.ticks.push(UnsafeCell::new(ComponentTicks::new(0))); + column.added_ticks.push(UnsafeCell::new(Tick::new(0))); + column.changed_ticks.push(UnsafeCell::new(Tick::new(0))); } index } @@ -631,7 +688,7 @@ mod tests { use crate::ptr::OwningPtr; use crate::storage::Storages; use crate::{ - component::{ComponentTicks, Components}, + component::{Components, Tick}, entity::Entity, storage::TableBuilder, }; @@ -657,7 +714,7 @@ mod tests { table.get_column_mut(component_id).unwrap().initialize( row, value_ptr, - ComponentTicks::new(0), + Tick::new(0), ); }); }; diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index acfdd8f7224e2..4e98e422879cb 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -3,7 +3,7 @@ use crate::{ archetype::{Archetype, Archetypes}, bundle::Bundles, change_detection::Ticks, - component::{Component, ComponentId, ComponentTicks, Components}, + component::{Component, ComponentId, ComponentTicks, Components, Tick}, entity::{Entities, Entity}, query::{ Access, FilteredAccess, FilteredAccessSet, QueryState, ReadOnlyWorldQuery, WorldQuery, @@ -273,7 +273,8 @@ pub trait Resource: Send + Sync + 'static {} /// Use `Option>` instead if the resource might not always exist. pub struct Res<'w, T: Resource> { value: &'w T, - ticks: &'w ComponentTicks, + added: &'w Tick, + changed: &'w Tick, last_change_tick: u32, change_tick: u32, } @@ -296,7 +297,8 @@ impl<'w, T: Resource> Res<'w, T> { pub fn clone(this: &Self) -> Self { Self { value: this.value, - ticks: this.ticks, + added: this.added, + changed: this.changed, last_change_tick: this.last_change_tick, change_tick: this.change_tick, } @@ -304,13 +306,14 @@ 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.added + .is_older_than(self.last_change_tick, self.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.changed + .is_older_than(self.last_change_tick, self.change_tick) } pub fn into_inner(self) -> &'w T { @@ -337,7 +340,8 @@ impl<'w, T: Resource> From> for Res<'w, T> { fn from(res: ResMut<'w, T>) -> Self { Self { value: res.value, - ticks: res.ticks.component_ticks, + added: res.ticks.added, + changed: res.ticks.changed, change_tick: res.ticks.change_tick, last_change_tick: res.ticks.last_change_tick, } @@ -417,7 +421,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResState { }); Res { value: ptr.deref(), - ticks: ticks.deref(), + added: ticks.added.deref(), + changed: ticks.changed.deref(), last_change_tick: system_meta.last_change_tick, change_tick, } @@ -458,7 +463,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResState { .get_resource_with_ticks(state.0.component_id) .map(|(ptr, ticks)| Res { value: ptr.deref(), - ticks: ticks.deref(), + added: ticks.added.deref(), + changed: ticks.changed.deref(), last_change_tick: system_meta.last_change_tick, change_tick, }) @@ -530,7 +536,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for ResMutState { ResMut { value: value.value, ticks: Ticks { - component_ticks: value.ticks.component_ticks, + added: value.ticks.added, + changed: value.ticks.changed, last_change_tick: system_meta.last_change_tick, change_tick, }, @@ -570,7 +577,8 @@ impl<'w, 's, T: Resource> SystemParamFetch<'w, 's> for OptionResMutState { .map(|value| ResMut { value: value.value, ticks: Ticks { - component_ticks: value.ticks.component_ticks, + added: value.ticks.added, + changed: value.ticks.changed, last_change_tick: system_meta.last_change_tick, change_tick, }, @@ -942,7 +950,10 @@ impl<'a, T> From> for NonSend<'a, T> { fn from(nsm: NonSendMut<'a, T>) -> Self { Self { value: nsm.value, - ticks: nsm.ticks.component_ticks.to_owned(), + ticks: ComponentTicks { + added: nsm.ticks.added.to_owned(), + changed: nsm.ticks.changed.to_owned(), + }, change_tick: nsm.ticks.change_tick, last_change_tick: nsm.ticks.last_change_tick, } @@ -1130,11 +1141,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for NonSendMutState { }); NonSendMut { value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), } } } @@ -1171,11 +1178,7 @@ impl<'w, 's, T: 'static> SystemParamFetch<'w, 's> for OptionNonSendMutState { .get_resource_with_ticks(state.0.component_id) .map(|(ptr, ticks)| NonSendMut { value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: system_meta.last_change_tick, - change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, system_meta.last_change_tick, change_tick), }) } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 66be80be0840c..37bf71adad1cd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2,14 +2,14 @@ use crate::{ archetype::{Archetype, ArchetypeId, Archetypes}, bundle::{Bundle, BundleInfo}, change_detection::{MutUntyped, Ticks}, - component::{Component, ComponentId, ComponentTicks, Components, StorageType}, + component::{Component, ComponentId, ComponentTicks, Components, StorageType, TickCells}, entity::{Entities, Entity, EntityLocation}, storage::{SparseSet, Storages}, world::{Mut, World}, }; -use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::debug; -use std::{any::TypeId, cell::UnsafeCell}; +use std::any::TypeId; /// A read-only reference to a particular [`Entity`] and all of its components #[derive(Copy, Clone)] @@ -77,12 +77,9 @@ impl<'w> EntityRef<'w> { /// Retrieves the change ticks for the given component. This can be useful for implementing change /// detection in custom runtimes. #[inline] - pub fn get_change_ticks(&self) -> Option<&'w ComponentTicks> { + pub fn get_change_ticks(&self) -> Option { // SAFETY: entity location is valid - unsafe { - get_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|ticks| ticks.deref()) - } + unsafe { get_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) } } /// Gets a mutable reference to the component of type `T` associated with @@ -104,11 +101,7 @@ impl<'w> EntityRef<'w> { get_component_and_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) .map(|(value, ticks)| Mut { value: value.assert_unique().deref_mut::(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick, - change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, last_change_tick, change_tick), }) } } @@ -208,12 +201,9 @@ impl<'w> EntityMut<'w> { /// Retrieves the change ticks for the given component. This can be useful for implementing change /// detection in custom runtimes. #[inline] - pub fn get_change_ticks(&self) -> Option<&ComponentTicks> { + pub fn get_change_ticks(&self) -> Option { // SAFETY: entity location is valid - unsafe { - get_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) - .map(|ticks| ticks.deref()) - } + unsafe { get_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) } } /// Gets a mutable reference to the component of type `T` associated with @@ -231,11 +221,11 @@ impl<'w> EntityMut<'w> { get_component_and_ticks_with_type(self.world, TypeId::of::(), self.entity, self.location) .map(|(value, ticks)| Mut { value: value.assert_unique().deref_mut::(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: self.world.last_change_tick(), - change_tick: self.world.read_change_tick(), - }, + ticks: Ticks::from_tick_cells( + ticks, + self.world.last_change_tick(), + self.world.read_change_tick(), + ), }) } @@ -635,7 +625,7 @@ unsafe fn get_component_and_ticks( component_id: ComponentId, entity: Entity, location: EntityLocation, -) -> Option<(Ptr<'_>, &UnsafeCell)> { +) -> Option<(Ptr<'_>, TickCells<'_>)> { let archetype = &world.archetypes[location.archetype_id]; let component_info = world.components.get_info_unchecked(component_id); match component_info.storage_type() { @@ -646,7 +636,10 @@ unsafe fn get_component_and_ticks( // SAFETY: archetypes only store valid table_rows and the stored component type is T Some(( components.get_data_unchecked(table_row), - components.get_ticks_unchecked(table_row), + TickCells { + added: components.get_added_ticks_unchecked(table_row), + changed: components.get_changed_ticks_unchecked(table_row), + }, )) } StorageType::SparseSet => world @@ -663,7 +656,7 @@ unsafe fn get_ticks( component_id: ComponentId, entity: Entity, location: EntityLocation, -) -> Option<&UnsafeCell> { +) -> Option { let archetype = &world.archetypes[location.archetype_id]; let component_info = world.components.get_info_unchecked(component_id); match component_info.storage_type() { @@ -747,7 +740,7 @@ pub(crate) unsafe fn get_component_and_ticks_with_type( type_id: TypeId, entity: Entity, location: EntityLocation, -) -> Option<(Ptr<'_>, &UnsafeCell)> { +) -> Option<(Ptr<'_>, TickCells<'_>)> { let component_id = world.components.get_id(type_id)?; get_component_and_ticks(world, component_id, entity, location) } @@ -759,7 +752,7 @@ pub(crate) unsafe fn get_ticks_with_type( type_id: TypeId, entity: Entity, location: EntityLocation, -) -> Option<&UnsafeCell> { +) -> Option { let component_id = world.components.get_id(type_id)?; get_ticks(world, component_id, entity, location) } @@ -911,11 +904,7 @@ pub(crate) unsafe fn get_mut( get_component_and_ticks_with_type(world, TypeId::of::(), entity, location).map( |(value, ticks)| Mut { value: value.assert_unique().deref_mut::(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick, - change_tick, - }, + ticks: Ticks::from_tick_cells(ticks, last_change_tick, change_tick), }, ) } @@ -932,11 +921,11 @@ pub(crate) unsafe fn get_mut_by_id( get_component_and_ticks(world, component_id, entity, location).map(|(value, ticks)| { MutUntyped { value: value.assert_unique(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: world.last_change_tick(), - change_tick: world.read_change_tick(), - }, + ticks: Ticks::from_tick_cells( + ticks, + world.last_change_tick(), + world.read_change_tick(), + ), } }) } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1aab56a9f4ae7..d42ae7744e127 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -12,18 +12,17 @@ use crate::{ bundle::{Bundle, BundleInserter, BundleSpawner, Bundles}, change_detection::{MutUntyped, Ticks}, component::{ - Component, ComponentDescriptor, ComponentId, ComponentInfo, ComponentTicks, Components, + Component, ComponentDescriptor, ComponentId, ComponentInfo, Components, TickCells, }, entity::{AllocAtWithoutReplacement, Entities, Entity}, query::{QueryState, ReadOnlyWorldQuery, WorldQuery}, storage::{ResourceData, SparseSet, Storages}, system::Resource, }; -use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::warn; use std::{ any::TypeId, - cell::UnsafeCell, fmt, sync::atomic::{AtomicU32, Ordering}, }; @@ -1001,7 +1000,7 @@ impl World { pub(crate) fn get_resource_with_ticks( &self, component_id: ComponentId, - ) -> Option<(Ptr<'_>, &UnsafeCell)> { + ) -> Option<(Ptr<'_>, TickCells<'_>)> { self.storages.resources.get(component_id)?.get_with_ticks() } @@ -1194,7 +1193,8 @@ impl World { let value_mut = Mut { value: &mut value, ticks: Ticks { - component_ticks: &mut ticks, + added: &mut ticks.added, + changed: &mut ticks.changed, last_change_tick, change_tick, }, @@ -1273,11 +1273,7 @@ impl World { let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; Some(Mut { value: ptr.assert_unique().deref_mut(), - ticks: Ticks { - component_ticks: ticks.deref_mut(), - last_change_tick: self.last_change_tick(), - change_tick: self.read_change_tick(), - }, + ticks: Ticks::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()), }) } @@ -1457,14 +1453,11 @@ impl World { let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; - // SAFE: This function has exclusive access to the world so nothing aliases `ticks`. - let ticks = Ticks { - // SAFETY: - // - index is in-bounds because the column is initialized and non-empty - // - no other reference to the ticks of the same row can exist at the same time - component_ticks: unsafe { ticks.deref_mut() }, - last_change_tick: self.last_change_tick(), - change_tick: self.read_change_tick(), + // SAFETY: This function has exclusive access to the world so nothing aliases `ticks`. + // - index is in-bounds because the column is initialized and non-empty + // - no other reference to the ticks of the same row can exist at the same time + let ticks = unsafe { + Ticks::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()) }; Some(MutUntyped {