Reduce memory usage in component fetches and change detection filters (#15283)

## Objective

- Adopted #6396

## Solution

Same as #6396, we use a compile-time checked `StorageSwitch` union type
to select the fetch data based on the component's storage type, saving
>= 8 bytes per component fetch in a given query.

Note: We forego the Query iteration change as it exists in a slightly
different form now on main.

## Testing

- All current tests pass locally.

---------

Co-authored-by: james7132 <contact@jamessliu.com>
Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
This commit is contained in:
Christian Hughes 2024-09-27 07:06:40 -07:00 committed by GitHub
parent d70595b667
commit a0c722ff4c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 268 additions and 159 deletions

View file

@ -1058,19 +1058,22 @@ unsafe impl QueryData for &Archetype {
unsafe impl ReadOnlyQueryData for &Archetype {} unsafe impl ReadOnlyQueryData for &Archetype {}
#[doc(hidden)] #[doc(hidden)]
pub struct ReadFetch<'w, T> { pub struct ReadFetch<'w, T: Component> {
// T::STORAGE_TYPE = StorageType::Table components: StorageSwitch<
table_components: Option<ThinSlicePtr<'w, UnsafeCell<T>>>, T,
// T::STORAGE_TYPE = StorageType::SparseSet // T::STORAGE_TYPE = StorageType::Table
sparse_set: Option<&'w ComponentSparseSet>, Option<ThinSlicePtr<'w, UnsafeCell<T>>>,
// T::STORAGE_TYPE = StorageType::SparseSet
&'w ComponentSparseSet,
>,
} }
impl<T> Clone for ReadFetch<'_, T> { impl<T: Component> Clone for ReadFetch<'_, T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<T> Copy for ReadFetch<'_, T> {} impl<T: Component> Copy for ReadFetch<'_, T> {}
/// SAFETY: /// SAFETY:
/// `fetch` accesses a single component in a readonly way. /// `fetch` accesses a single component in a readonly way.
@ -1098,20 +1101,22 @@ unsafe impl<T: Component> WorldQuery for &T {
_this_run: Tick, _this_run: Tick,
) -> ReadFetch<'w, T> { ) -> ReadFetch<'w, T> {
ReadFetch { ReadFetch {
table_components: None, components: StorageSwitch::new(
sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet).then(|| { || None,
// SAFETY: The underlying type associated with `component_id` is `T`, || {
// which we are allowed to access since we registered it in `update_archetype_component_access`. // SAFETY: The underlying type associated with `component_id` is `T`,
// Note that we do not actually access any components in this function, we just get a shared // which we are allowed to access since we registered it in `update_archetype_component_access`.
// reference to the sparse set, which is used to access the components in `Self::fetch`. // Note that we do not actually access any components in this function, we just get a shared
unsafe { // reference to the sparse set, which is used to access the components in `Self::fetch`.
world unsafe {
.storages() world
.sparse_sets .storages()
.get(component_id) .sparse_sets
.debug_checked_unwrap() .get(component_id)
} .debug_checked_unwrap()
}), }
},
),
} }
} }
@ -1143,12 +1148,14 @@ unsafe impl<T: Component> WorldQuery for &T {
&component_id: &ComponentId, &component_id: &ComponentId,
table: &'w Table, table: &'w Table,
) { ) {
fetch.table_components = Some( let table_data = Some(
table table
.get_data_slice_for(component_id) .get_data_slice_for(component_id)
.debug_checked_unwrap() .debug_checked_unwrap()
.into(), .into(),
); );
// SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table
unsafe { fetch.components.set_table(table_data) };
} }
#[inline(always)] #[inline(always)]
@ -1157,22 +1164,20 @@ unsafe impl<T: Component> WorldQuery for &T {
entity: Entity, entity: Entity,
table_row: TableRow, table_row: TableRow,
) -> Self::Item<'w> { ) -> Self::Item<'w> {
match T::STORAGE_TYPE { fetch.components.extract(
StorageType::Table => { |table| {
// SAFETY: STORAGE_TYPE = Table // SAFETY: set_table was previously called
let table = unsafe { fetch.table_components.debug_checked_unwrap() }; let table = unsafe { table.debug_checked_unwrap() };
// SAFETY: Caller ensures `table_row` is in range. // SAFETY: Caller ensures `table_row` is in range.
let item = unsafe { table.get(table_row.as_usize()) }; let item = unsafe { table.get(table_row.as_usize()) };
item.deref() item.deref()
} },
StorageType::SparseSet => { |sparse_set| {
// SAFETY: STORAGE_TYPE = SparseSet
let sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
// SAFETY: Caller ensures `entity` is in range. // SAFETY: Caller ensures `entity` is in range.
let item = unsafe { sparse_set.get(entity).debug_checked_unwrap() }; let item = unsafe { sparse_set.get(entity).debug_checked_unwrap() };
item.deref() item.deref()
} },
} )
} }
fn update_component_access( fn update_component_access(
@ -1212,27 +1217,29 @@ unsafe impl<T: Component> QueryData for &T {
unsafe impl<T: Component> ReadOnlyQueryData for &T {} unsafe impl<T: Component> ReadOnlyQueryData for &T {}
#[doc(hidden)] #[doc(hidden)]
pub struct RefFetch<'w, T> { pub struct RefFetch<'w, T: Component> {
// T::STORAGE_TYPE = StorageType::Table components: StorageSwitch<
table_data: Option<( T,
ThinSlicePtr<'w, UnsafeCell<T>>, // T::STORAGE_TYPE = StorageType::Table
ThinSlicePtr<'w, UnsafeCell<Tick>>, Option<(
ThinSlicePtr<'w, UnsafeCell<Tick>>, ThinSlicePtr<'w, UnsafeCell<T>>,
MaybeThinSlicePtrLocation<'w>, ThinSlicePtr<'w, UnsafeCell<Tick>>,
)>, ThinSlicePtr<'w, UnsafeCell<Tick>>,
// T::STORAGE_TYPE = StorageType::SparseSet MaybeThinSlicePtrLocation<'w>,
sparse_set: Option<&'w ComponentSparseSet>, )>,
// T::STORAGE_TYPE = StorageType::SparseSet
&'w ComponentSparseSet,
>,
last_run: Tick, last_run: Tick,
this_run: Tick, this_run: Tick,
} }
impl<T> Clone for RefFetch<'_, T> { impl<T: Component> Clone for RefFetch<'_, T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<T> Copy for RefFetch<'_, T> {} impl<T: Component> Copy for RefFetch<'_, T> {}
/// SAFETY: /// SAFETY:
/// `fetch` accesses a single component in a readonly way. /// `fetch` accesses a single component in a readonly way.
@ -1260,20 +1267,22 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
this_run: Tick, this_run: Tick,
) -> RefFetch<'w, T> { ) -> RefFetch<'w, T> {
RefFetch { RefFetch {
table_data: None, components: StorageSwitch::new(
sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet).then(|| { || None,
// SAFETY: The underlying type associated with `component_id` is `T`, || {
// which we are allowed to access since we registered it in `update_archetype_component_access`. // SAFETY: The underlying type associated with `component_id` is `T`,
// Note that we do not actually access any components in this function, we just get a shared // which we are allowed to access since we registered it in `update_archetype_component_access`.
// reference to the sparse set, which is used to access the components in `Self::fetch`. // Note that we do not actually access any components in this function, we just get a shared
unsafe { // reference to the sparse set, which is used to access the components in `Self::fetch`.
world unsafe {
.storages() world
.sparse_sets .storages()
.get(component_id) .sparse_sets
.debug_checked_unwrap() .get(component_id)
} .debug_checked_unwrap()
}), }
},
),
last_run, last_run,
this_run, this_run,
} }
@ -1308,7 +1317,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
table: &'w Table, table: &'w Table,
) { ) {
let column = table.get_column(component_id).debug_checked_unwrap(); let column = table.get_column(component_id).debug_checked_unwrap();
fetch.table_data = Some(( let table_data = Some((
column.get_data_slice(table.entity_count()).into(), column.get_data_slice(table.entity_count()).into(),
column.get_added_ticks_slice(table.entity_count()).into(), column.get_added_ticks_slice(table.entity_count()).into(),
column.get_changed_ticks_slice(table.entity_count()).into(), column.get_changed_ticks_slice(table.entity_count()).into(),
@ -1317,6 +1326,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
#[cfg(not(feature = "track_change_detection"))] #[cfg(not(feature = "track_change_detection"))]
(), (),
)); ));
// SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table
unsafe { fetch.components.set_table(table_data) };
} }
#[inline(always)] #[inline(always)]
@ -1325,11 +1336,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
entity: Entity, entity: Entity,
table_row: TableRow, table_row: TableRow,
) -> Self::Item<'w> { ) -> Self::Item<'w> {
match T::STORAGE_TYPE { fetch.components.extract(
StorageType::Table => { |table| {
// SAFETY: STORAGE_TYPE = Table // SAFETY: set_table was previously called
let (table_components, added_ticks, changed_ticks, _callers) = let (table_components, added_ticks, changed_ticks, _callers) =
unsafe { fetch.table_data.debug_checked_unwrap() }; unsafe { table.debug_checked_unwrap() };
// SAFETY: The caller ensures `table_row` is in range. // SAFETY: The caller ensures `table_row` is in range.
let component = unsafe { table_components.get(table_row.as_usize()) }; let component = unsafe { table_components.get(table_row.as_usize()) };
@ -1352,17 +1363,11 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
#[cfg(feature = "track_change_detection")] #[cfg(feature = "track_change_detection")]
changed_by: caller.deref(), changed_by: caller.deref(),
} }
} },
StorageType::SparseSet => { |sparse_set| {
// SAFETY: STORAGE_TYPE = SparseSet
let component_sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
// SAFETY: The caller ensures `entity` is in range. // SAFETY: The caller ensures `entity` is in range.
let (component, ticks, _caller) = unsafe { let (component, ticks, _caller) =
component_sparse_set unsafe { sparse_set.get_with_ticks(entity).debug_checked_unwrap() };
.get_with_ticks(entity)
.debug_checked_unwrap()
};
Ref { Ref {
value: component.deref(), value: component.deref(),
@ -1370,8 +1375,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
#[cfg(feature = "track_change_detection")] #[cfg(feature = "track_change_detection")]
changed_by: _caller.deref(), changed_by: _caller.deref(),
} }
} },
} )
} }
fn update_component_access( fn update_component_access(
@ -1411,27 +1416,29 @@ unsafe impl<'__w, T: Component> QueryData for Ref<'__w, T> {
unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {} unsafe impl<'__w, T: Component> ReadOnlyQueryData for Ref<'__w, T> {}
#[doc(hidden)] #[doc(hidden)]
pub struct WriteFetch<'w, T> { pub struct WriteFetch<'w, T: Component> {
// T::STORAGE_TYPE = StorageType::Table components: StorageSwitch<
table_data: Option<( T,
ThinSlicePtr<'w, UnsafeCell<T>>, // T::STORAGE_TYPE = StorageType::Table
ThinSlicePtr<'w, UnsafeCell<Tick>>, Option<(
ThinSlicePtr<'w, UnsafeCell<Tick>>, ThinSlicePtr<'w, UnsafeCell<T>>,
MaybeThinSlicePtrLocation<'w>, ThinSlicePtr<'w, UnsafeCell<Tick>>,
)>, ThinSlicePtr<'w, UnsafeCell<Tick>>,
// T::STORAGE_TYPE = StorageType::SparseSet MaybeThinSlicePtrLocation<'w>,
sparse_set: Option<&'w ComponentSparseSet>, )>,
// T::STORAGE_TYPE = StorageType::SparseSet
&'w ComponentSparseSet,
>,
last_run: Tick, last_run: Tick,
this_run: Tick, this_run: Tick,
} }
impl<T> Clone for WriteFetch<'_, T> { impl<T: Component> Clone for WriteFetch<'_, T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
*self *self
} }
} }
impl<T> Copy for WriteFetch<'_, T> {} impl<T: Component> Copy for WriteFetch<'_, T> {}
/// SAFETY: /// SAFETY:
/// `fetch` accesses a single component mutably. /// `fetch` accesses a single component mutably.
@ -1459,20 +1466,22 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
this_run: Tick, this_run: Tick,
) -> WriteFetch<'w, T> { ) -> WriteFetch<'w, T> {
WriteFetch { WriteFetch {
table_data: None, components: StorageSwitch::new(
sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet).then(|| { || None,
// SAFETY: The underlying type associated with `component_id` is `T`, || {
// which we are allowed to access since we registered it in `update_archetype_component_access`. // SAFETY: The underlying type associated with `component_id` is `T`,
// Note that we do not actually access any components in this function, we just get a shared // which we are allowed to access since we registered it in `update_archetype_component_access`.
// reference to the sparse set, which is used to access the components in `Self::fetch`. // Note that we do not actually access any components in this function, we just get a shared
unsafe { // reference to the sparse set, which is used to access the components in `Self::fetch`.
world unsafe {
.storages() world
.sparse_sets .storages()
.get(component_id) .sparse_sets
.debug_checked_unwrap() .get(component_id)
} .debug_checked_unwrap()
}), }
},
),
last_run, last_run,
this_run, this_run,
} }
@ -1507,7 +1516,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
table: &'w Table, table: &'w Table,
) { ) {
let column = table.get_column(component_id).debug_checked_unwrap(); let column = table.get_column(component_id).debug_checked_unwrap();
fetch.table_data = Some(( let table_data = Some((
column.get_data_slice(table.entity_count()).into(), column.get_data_slice(table.entity_count()).into(),
column.get_added_ticks_slice(table.entity_count()).into(), column.get_added_ticks_slice(table.entity_count()).into(),
column.get_changed_ticks_slice(table.entity_count()).into(), column.get_changed_ticks_slice(table.entity_count()).into(),
@ -1516,6 +1525,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
#[cfg(not(feature = "track_change_detection"))] #[cfg(not(feature = "track_change_detection"))]
(), (),
)); ));
// SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table
unsafe { fetch.components.set_table(table_data) };
} }
#[inline(always)] #[inline(always)]
@ -1524,11 +1535,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
entity: Entity, entity: Entity,
table_row: TableRow, table_row: TableRow,
) -> Self::Item<'w> { ) -> Self::Item<'w> {
match T::STORAGE_TYPE { fetch.components.extract(
StorageType::Table => { |table| {
// SAFETY: STORAGE_TYPE = Table // SAFETY: set_table was previously called
let (table_components, added_ticks, changed_ticks, _callers) = let (table_components, added_ticks, changed_ticks, _callers) =
unsafe { fetch.table_data.debug_checked_unwrap() }; unsafe { table.debug_checked_unwrap() };
// SAFETY: The caller ensures `table_row` is in range. // SAFETY: The caller ensures `table_row` is in range.
let component = unsafe { table_components.get(table_row.as_usize()) }; let component = unsafe { table_components.get(table_row.as_usize()) };
@ -1551,17 +1562,11 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
#[cfg(feature = "track_change_detection")] #[cfg(feature = "track_change_detection")]
changed_by: caller.deref_mut(), changed_by: caller.deref_mut(),
} }
} },
StorageType::SparseSet => { |sparse_set| {
// SAFETY: STORAGE_TYPE = SparseSet
let component_sparse_set = unsafe { fetch.sparse_set.debug_checked_unwrap() };
// SAFETY: The caller ensures `entity` is in range. // SAFETY: The caller ensures `entity` is in range.
let (component, ticks, _caller) = unsafe { let (component, ticks, _caller) =
component_sparse_set unsafe { sparse_set.get_with_ticks(entity).debug_checked_unwrap() };
.get_with_ticks(entity)
.debug_checked_unwrap()
};
Mut { Mut {
value: component.assert_unique().deref_mut(), value: component.assert_unique().deref_mut(),
@ -1569,8 +1574,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
#[cfg(feature = "track_change_detection")] #[cfg(feature = "track_change_detection")]
changed_by: _caller.deref_mut(), changed_by: _caller.deref_mut(),
} }
} },
} )
} }
fn update_component_access( fn update_component_access(
@ -2312,6 +2317,74 @@ unsafe impl<T: ?Sized> QueryData for PhantomData<T> {
/// SAFETY: `PhantomData` never accesses any world data. /// SAFETY: `PhantomData` never accesses any world data.
unsafe impl<T: ?Sized> ReadOnlyQueryData for PhantomData<T> {} unsafe impl<T: ?Sized> ReadOnlyQueryData for PhantomData<T> {}
/// A compile-time checked union of two different types that differs based on the
/// [`StorageType`] of a given component.
pub(super) union StorageSwitch<C: Component, T: Copy, S: Copy> {
/// The table variant. Requires the component to be a table component.
table: T,
/// The sparse set variant. Requires the component to be a sparse set component.
sparse_set: S,
_marker: PhantomData<C>,
}
impl<C: Component, T: Copy, S: Copy> StorageSwitch<C, T, S> {
/// Creates a new [`StorageSwitch`] using the given closures to initialize
/// the variant corresponding to the component's [`StorageType`].
pub fn new(table: impl FnOnce() -> T, sparse_set: impl FnOnce() -> S) -> Self {
match C::STORAGE_TYPE {
StorageType::Table => Self { table: table() },
StorageType::SparseSet => Self {
sparse_set: sparse_set(),
},
}
}
/// Creates a new [`StorageSwitch`] using a table variant.
///
/// # Panics
///
/// This will panic on debug builds if `C` is not a table component.
///
/// # Safety
///
/// `C` must be a table component.
#[inline]
pub unsafe fn set_table(&mut self, table: T) {
match C::STORAGE_TYPE {
StorageType::Table => self.table = table,
_ => {
#[cfg(debug_assertions)]
unreachable!();
#[cfg(not(debug_assertions))]
std::hint::unreachable_unchecked()
}
}
}
/// Fetches the internal value from the variant that corresponds to the
/// component's [`StorageType`].
pub fn extract<R>(&self, table: impl FnOnce(T) -> R, sparse_set: impl FnOnce(S) -> R) -> R {
match C::STORAGE_TYPE {
StorageType::Table => table(
// SAFETY: C::STORAGE_TYPE == StorageType::Table
unsafe { self.table },
),
StorageType::SparseSet => sparse_set(
// SAFETY: C::STORAGE_TYPE == StorageType::SparseSet
unsafe { self.sparse_set },
),
}
}
}
impl<C: Component, T: Copy, S: Copy> Clone for StorageSwitch<C, T, S> {
fn clone(&self) -> Self {
*self
}
}
impl<C: Component, T: Copy, S: Copy> Copy for StorageSwitch<C, T, S> {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use bevy_ecs_macros::QueryData; use bevy_ecs_macros::QueryData;

View file

@ -2,7 +2,7 @@ use crate::{
archetype::Archetype, archetype::Archetype,
component::{Component, ComponentId, Components, StorageType, Tick}, component::{Component, ComponentId, Components, StorageType, Tick},
entity::Entity, entity::Entity,
query::{DebugCheckedUnwrap, FilteredAccess, WorldQuery}, query::{DebugCheckedUnwrap, FilteredAccess, StorageSwitch, WorldQuery},
storage::{ComponentSparseSet, Table, TableRow}, storage::{ComponentSparseSet, Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, World}, world::{unsafe_world_cell::UnsafeWorldCell, World},
}; };
@ -615,14 +615,28 @@ all_tuples!(impl_or_query_filter, 0, 15, F, S);
pub struct Added<T>(PhantomData<T>); pub struct Added<T>(PhantomData<T>);
#[doc(hidden)] #[doc(hidden)]
#[derive(Clone)] pub struct AddedFetch<'w, T: Component> {
pub struct AddedFetch<'w> { ticks: StorageSwitch<
table_ticks: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>, T,
sparse_set: Option<&'w ComponentSparseSet>, // T::STORAGE_TYPE = StorageType::Table
Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
// T::STORAGE_TYPE = StorageType::SparseSet
&'w ComponentSparseSet,
>,
last_run: Tick, last_run: Tick,
this_run: Tick, this_run: Tick,
} }
impl<T: Component> Clone for AddedFetch<'_, T> {
fn clone(&self) -> Self {
Self {
ticks: self.ticks,
last_run: self.last_run,
this_run: self.this_run,
}
}
}
/// SAFETY: /// SAFETY:
/// `fetch` accesses a single component in a readonly way. /// `fetch` accesses a single component in a readonly way.
/// This is sound because `update_component_access` adds read access for that component and panics when appropriate. /// This is sound because `update_component_access` adds read access for that component and panics when appropriate.
@ -630,7 +644,7 @@ pub struct AddedFetch<'w> {
/// This is sound because `matches_component_set` returns whether the set contains that component. /// This is sound because `matches_component_set` returns whether the set contains that component.
unsafe impl<T: Component> WorldQuery for Added<T> { unsafe impl<T: Component> WorldQuery for Added<T> {
type Item<'w> = bool; type Item<'w> = bool;
type Fetch<'w> = AddedFetch<'w>; type Fetch<'w> = AddedFetch<'w, T>;
type State = ComponentId; type State = ComponentId;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
@ -649,9 +663,16 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
this_run: Tick, this_run: Tick,
) -> Self::Fetch<'w> { ) -> Self::Fetch<'w> {
Self::Fetch::<'w> { Self::Fetch::<'w> {
table_ticks: None, ticks: StorageSwitch::new(
sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet) || None,
.then(|| world.storages().sparse_sets.get(id).debug_checked_unwrap()), || {
// SAFETY: The underlying type associated with `component_id` is `T`,
// which we are allowed to access since we registered it in `update_archetype_component_access`.
// Note that we do not actually access any components' ticks in this function, we just get a shared
// reference to the sparse set, which is used to access the components' ticks in `Self::fetch`.
unsafe { world.storages().sparse_sets.get(id).debug_checked_unwrap() }
},
),
last_run, last_run,
this_run, this_run,
} }
@ -685,12 +706,14 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
&component_id: &ComponentId, &component_id: &ComponentId,
table: &'w Table, table: &'w Table,
) { ) {
fetch.table_ticks = Some( let table_ticks = Some(
table table
.get_added_ticks_slice_for(component_id) .get_added_ticks_slice_for(component_id)
.debug_checked_unwrap() .debug_checked_unwrap()
.into(), .into(),
); );
// SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table
unsafe { fetch.ticks.set_table(table_ticks) };
} }
#[inline(always)] #[inline(always)]
@ -699,26 +722,24 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
entity: Entity, entity: Entity,
table_row: TableRow, table_row: TableRow,
) -> Self::Item<'w> { ) -> Self::Item<'w> {
match T::STORAGE_TYPE { fetch.ticks.extract(
StorageType::Table => { |table| {
// SAFETY: STORAGE_TYPE = Table // SAFETY: set_table was previously called
let table = unsafe { fetch.table_ticks.debug_checked_unwrap() }; let table = unsafe { table.debug_checked_unwrap() };
// SAFETY: The caller ensures `table_row` is in range. // SAFETY: The caller ensures `table_row` is in range.
let tick = unsafe { table.get(table_row.as_usize()) }; let tick = unsafe { table.get(table_row.as_usize()) };
tick.deref().is_newer_than(fetch.last_run, fetch.this_run) tick.deref().is_newer_than(fetch.last_run, fetch.this_run)
} },
StorageType::SparseSet => { |sparse_set| {
// SAFETY: STORAGE_TYPE = SparseSet
let sparse_set = unsafe { &fetch.sparse_set.debug_checked_unwrap() };
// SAFETY: The caller ensures `entity` is in range. // SAFETY: The caller ensures `entity` is in range.
let tick = unsafe { let tick = unsafe {
ComponentSparseSet::get_added_tick(sparse_set, entity).debug_checked_unwrap() ComponentSparseSet::get_added_tick(sparse_set, entity).debug_checked_unwrap()
}; };
tick.deref().is_newer_than(fetch.last_run, fetch.this_run) tick.deref().is_newer_than(fetch.last_run, fetch.this_run)
} },
} )
} }
#[inline] #[inline]
@ -833,14 +854,22 @@ unsafe impl<T: Component> QueryFilter for Added<T> {
pub struct Changed<T>(PhantomData<T>); pub struct Changed<T>(PhantomData<T>);
#[doc(hidden)] #[doc(hidden)]
#[derive(Clone)] pub struct ChangedFetch<'w, T: Component> {
pub struct ChangedFetch<'w> { ticks: StorageSwitch<T, Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>, &'w ComponentSparseSet>,
table_ticks: Option<ThinSlicePtr<'w, UnsafeCell<Tick>>>,
sparse_set: Option<&'w ComponentSparseSet>,
last_run: Tick, last_run: Tick,
this_run: Tick, this_run: Tick,
} }
impl<T: Component> Clone for ChangedFetch<'_, T> {
fn clone(&self) -> Self {
Self {
ticks: self.ticks,
last_run: self.last_run,
this_run: self.this_run,
}
}
}
/// SAFETY: /// SAFETY:
/// `fetch` accesses a single component in a readonly way. /// `fetch` accesses a single component in a readonly way.
/// This is sound because `update_component_access` add read access for that component and panics when appropriate. /// This is sound because `update_component_access` add read access for that component and panics when appropriate.
@ -848,7 +877,7 @@ pub struct ChangedFetch<'w> {
/// This is sound because `matches_component_set` returns whether the set contains that component. /// This is sound because `matches_component_set` returns whether the set contains that component.
unsafe impl<T: Component> WorldQuery for Changed<T> { unsafe impl<T: Component> WorldQuery for Changed<T> {
type Item<'w> = bool; type Item<'w> = bool;
type Fetch<'w> = ChangedFetch<'w>; type Fetch<'w> = ChangedFetch<'w, T>;
type State = ComponentId; type State = ComponentId;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
@ -867,9 +896,16 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
this_run: Tick, this_run: Tick,
) -> Self::Fetch<'w> { ) -> Self::Fetch<'w> {
Self::Fetch::<'w> { Self::Fetch::<'w> {
table_ticks: None, ticks: StorageSwitch::new(
sparse_set: (T::STORAGE_TYPE == StorageType::SparseSet) || None,
.then(|| world.storages().sparse_sets.get(id).debug_checked_unwrap()), || {
// SAFETY: The underlying type associated with `component_id` is `T`,
// which we are allowed to access since we registered it in `update_archetype_component_access`.
// Note that we do not actually access any components' ticks in this function, we just get a shared
// reference to the sparse set, which is used to access the components' ticks in `Self::fetch`.
unsafe { world.storages().sparse_sets.get(id).debug_checked_unwrap() }
},
),
last_run, last_run,
this_run, this_run,
} }
@ -903,12 +939,14 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
&component_id: &ComponentId, &component_id: &ComponentId,
table: &'w Table, table: &'w Table,
) { ) {
fetch.table_ticks = Some( let table_ticks = Some(
table table
.get_changed_ticks_slice_for(component_id) .get_changed_ticks_slice_for(component_id)
.debug_checked_unwrap() .debug_checked_unwrap()
.into(), .into(),
); );
// SAFETY: set_table is only called when T::STORAGE_TYPE = StorageType::Table
unsafe { fetch.ticks.set_table(table_ticks) };
} }
#[inline(always)] #[inline(always)]
@ -917,26 +955,24 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
entity: Entity, entity: Entity,
table_row: TableRow, table_row: TableRow,
) -> Self::Item<'w> { ) -> Self::Item<'w> {
match T::STORAGE_TYPE { fetch.ticks.extract(
StorageType::Table => { |table| {
// SAFETY: STORAGE_TYPE = Table // SAFETY: set_table was previously called
let table = unsafe { fetch.table_ticks.debug_checked_unwrap() }; let table = unsafe { table.debug_checked_unwrap() };
// SAFETY: The caller ensures `table_row` is in range. // SAFETY: The caller ensures `table_row` is in range.
let tick = unsafe { table.get(table_row.as_usize()) }; let tick = unsafe { table.get(table_row.as_usize()) };
tick.deref().is_newer_than(fetch.last_run, fetch.this_run) tick.deref().is_newer_than(fetch.last_run, fetch.this_run)
} },
StorageType::SparseSet => { |sparse_set| {
// SAFETY: STORAGE_TYPE = SparseSet
let sparse_set = unsafe { &fetch.sparse_set.debug_checked_unwrap() };
// SAFETY: The caller ensures `entity` is in range. // SAFETY: The caller ensures `entity` is in range.
let tick = unsafe { let tick = unsafe {
ComponentSparseSet::get_changed_tick(sparse_set, entity).debug_checked_unwrap() ComponentSparseSet::get_changed_tick(sparse_set, entity).debug_checked_unwrap()
}; };
tick.deref().is_newer_than(fetch.last_run, fetch.this_run) tick.deref().is_newer_than(fetch.last_run, fetch.this_run)
} },
} )
} }
#[inline] #[inline]