diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index b051e630ebc44..6c6c4b0e20e41 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -32,6 +32,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream { }; let storage = storage_path(&bevy_ecs_path, attrs.storage); + let refs = attrs.refs; ast.generics .make_where_clause() @@ -44,15 +45,21 @@ pub fn derive_component(input: TokenStream) -> TokenStream { TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { type Storage = #storage; + // TODO: better error than `this trait takes 0 generic arguments but 1 generic argument was supplied` + type Refs = #refs; + // type Refs = #bevy_ecs_path::component_refs::ChangeDetectionRefs; + // type Refs = #bevy_ecs_path::component_refs::UnwrappedRefs; } }) } pub const COMPONENT: Symbol = Symbol("component"); pub const STORAGE: Symbol = Symbol("storage"); +pub const REFS: Symbol = Symbol("refs"); struct Attrs { storage: StorageTy, + refs: syn::Type, } #[derive(Clone, Copy)] @@ -70,6 +77,11 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { let mut attrs = Attrs { storage: StorageTy::Table, + refs: syn::Type::Path(syn::TypePath { + path: crate::BevyManifest::default() + .get_path("bevy_ecs::component_refs::ChangeDetectionRefs"), + qself: None, + }), }; for meta in meta_items { @@ -92,6 +104,9 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { } }; } + Meta(NameValue(m)) if m.path == REFS => { + attrs.refs = syn::parse_str(get_lit_str(STORAGE, &m.lit)?.value().as_str())?; + } Meta(meta_item) => { return Err(Error::new_spanned( meta_item.path(), diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index 6e8097584317a..b4cadf7b79f2b 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1,5 +1,6 @@ //! Types for declaring and storing [`Component`]s. +use crate::component_refs::ComponentRefs; use crate::{ change_detection::MAX_CHANGE_AGE, storage::{SparseSetIndex, Storages}, @@ -85,6 +86,26 @@ use std::{ /// [`Table`]: crate::storage::Table /// [`SparseSet`]: crate::storage::SparseSet /// +/// # Choosing `Query` reference types +/// +/// When used in [queries], components can return types other then the reference type specified. For example, +/// requesting `&mut Transform` in a query will result in a query that returns `Mut`, a wrapper +/// that updates change detection information when it is mutably de-referenced. The reference types returned +/// can be overridden by specifying a [`ComponentRefs`] type as part of your `Component` derive. +/// +/// For example, to disable change detection, add #[component(refs = UnwrappedRefs)]: +/// +/// ``` +/// # use bevy_ecs::component::Component; +/// # +/// #[derive(Component)] +/// #[component(storage = "SparseSet", refs = UnwrappedRefs)] +/// struct ComponentA; +/// ``` +/// +/// [`queries`]: crate::system::Query +/// [`ComponentRefs`] crate::component_refs::ComponentRefs +/// /// # Implementing the trait for foreign types /// /// As a consequence of the [orphan rule], it is not possible to separate into two different crates the implementation of `Component` from the definition of a type. @@ -142,8 +163,9 @@ use std::{ /// /// [`SyncCell`]: bevy_utils::synccell::SyncCell /// [`Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html -pub trait Component: Send + Sync + 'static { +pub trait Component: Send + Sync + Sized + 'static { type Storage: ComponentStorage; + type Refs: ComponentRefs; } pub struct TableStorage; diff --git a/crates/bevy_ecs/src/component_refs.rs b/crates/bevy_ecs/src/component_refs.rs new file mode 100644 index 0000000000000..ff3552e806f1e --- /dev/null +++ b/crates/bevy_ecs/src/component_refs.rs @@ -0,0 +1,247 @@ +use crate::change_detection::{Mut, Ref, Ticks, TicksMut}; +use crate::component::{Component, ComponentStorage, StorageType}; +use crate::entity::Entity; +use crate::query::{ComponentFetch, DebugCheckedUnwrap}; +use crate::storage::TableRow; +use bevy_ptr::UnsafeCellDeref; +use std::marker::PhantomData; + +/// Trait for defining the types returned by &T and &mut T component queries. +pub trait ComponentRefs { + type Ref<'w>: ComponentRef<'w, T>; + type MutRef<'w>: ComponentRef<'w, T>; + + fn shrink_ref<'wlong: 'wshort, 'wshort>(item: Self::Ref<'wlong>) -> Self::Ref<'wshort>; + fn shrink_mut<'wlong: 'wshort, 'wshort>(item: Self::MutRef<'wlong>) -> Self::MutRef<'wshort>; +} + +pub trait ComponentRef<'w, T> { + unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self; +} + +/// Pass this to your component derives to override the default type returned by &T +/// and &mut T component queries. This can be useful if you don't need change detection for +/// a component and want to avoid the overhead. +/// +/// Example: +/// ```rust +/// // TODO +/// ``` +/// +/// TODO expand on change detection features lost by using this +/// Use this with caution, because Changed queries will not work +/// +/// Todo: Is it possible to disable verify for engine components like Transform? (probably not) +pub struct UnwrappedRefs { + phantom: PhantomData, +} +impl ComponentRefs for UnwrappedRefs +where + T: Sized + Component, +{ + type Ref<'w> = &'w T; + type MutRef<'w> = &'w mut T; + + fn shrink_ref<'wlong: 'wshort, 'wshort>(item: Self::Ref<'wlong>) -> Self::Ref<'wshort> { + item + } + + fn shrink_mut<'wlong: 'wshort, 'wshort>(item: Self::MutRef<'wlong>) -> Self::MutRef<'wshort> { + item + } +} + +pub struct ChangeDetectionRefs { + phantom: PhantomData, +} +impl ComponentRefs for ChangeDetectionRefs +where + T: Sized + Component, +{ + type Ref<'w> = &'w T; + // type Ref<'w> = Ref<'w, T>; + type MutRef<'w> = Mut<'w, T>; + + fn shrink_ref<'wlong: 'wshort, 'wshort>(item: Self::Ref<'wlong>) -> Self::Ref<'wshort> { + item + } + + fn shrink_mut<'wlong: 'wshort, 'wshort>(item: Self::MutRef<'wlong>) -> Self::MutRef<'wshort> { + item + } +} + +impl<'w, T> ComponentRef<'w, T> for &'w T +where + T: Component, +{ + unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self { + match T::Storage::STORAGE_TYPE { + StorageType::Table => fetch + .table_data + .debug_checked_unwrap() + .0 + .get(table_row.index()) + .deref(), + StorageType::SparseSet => fetch + .sparse_set + .debug_checked_unwrap() + .get(entity) + .debug_checked_unwrap() + .deref(), + } + } +} +impl<'w, T> ComponentRef<'w, T> for Ref<'w, T> +where + T: Component, +{ + unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self { + match T::Storage::STORAGE_TYPE { + StorageType::Table => { + let (table_components, added_ticks, changed_ticks) = + fetch.table_data.debug_checked_unwrap(); + Ref { + value: table_components.get(table_row.index()).deref(), + ticks: Ticks { + added: added_ticks.get(table_row.index()).deref(), + changed: changed_ticks.get(table_row.index()).deref(), + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, + }, + } + } + StorageType::SparseSet => { + let (component, ticks) = fetch + .sparse_set + .debug_checked_unwrap() + .get_with_ticks(entity) + .debug_checked_unwrap(); + Ref { + value: component.deref(), + ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick), + } + } + } + } +} +impl<'w, T> ComponentRef<'w, T> for &'w mut T +where + T: Component, +{ + unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self { + match T::Storage::STORAGE_TYPE { + StorageType::Table => fetch + .table_data + .debug_checked_unwrap() + .0 + .get(table_row.index()) + .deref_mut(), + StorageType::SparseSet => fetch + .sparse_set + .debug_checked_unwrap() + .get_with_ticks(entity) + .debug_checked_unwrap() + .0 + .assert_unique() + .deref_mut(), + } + } +} + +impl<'w, T> ComponentRef<'w, T> for Mut<'w, T> +where + T: Component, +{ + unsafe fn new(fetch: &ComponentFetch<'w, T>, entity: Entity, table_row: TableRow) -> Self { + match T::Storage::STORAGE_TYPE { + StorageType::Table => { + let (table_components, added_ticks, changed_ticks) = + fetch.table_data.debug_checked_unwrap(); + Mut { + value: table_components.get(table_row.index()).deref_mut(), + ticks: TicksMut { + added: added_ticks.get(table_row.index()).deref_mut(), + changed: changed_ticks.get(table_row.index()).deref_mut(), + change_tick: fetch.change_tick, + last_change_tick: fetch.last_change_tick, + }, + } + } + StorageType::SparseSet => { + let (component, ticks) = fetch + .sparse_set + .debug_checked_unwrap() + .get_with_ticks(entity) + .debug_checked_unwrap(); + Mut { + value: component.assert_unique().deref_mut(), + ticks: TicksMut::from_tick_cells( + ticks, + fetch.last_change_tick, + fetch.change_tick, + ), + } + } + } + } +} + +mod test { + use crate::{self as bevy_ecs, prelude::*}; + + #[test] + fn test_explicit_change_detection_derive() { + #[derive(Component)] + #[component(refs = "ChangeDetectionRefs")] + struct Number(usize); + + let mut world = World::new(); + world.spawn(Number(1)); + + fn detect_changed(query: Query>) -> usize { + return query.iter().count(); + } + let mut detect_changed_system = IntoSystem::into_system(detect_changed); + detect_changed_system.initialize(&mut world); + + fn update_number(mut query: Query<&mut Number>) { + query.single_mut().0 = 3; + } + let mut update_number_system = IntoSystem::into_system(update_number); + update_number_system.initialize(&mut world); + + // initialize last_run tick + detect_changed_system.run((), &mut world); + update_number_system.run((), &mut world); + assert_eq!(detect_changed_system.run((), &mut world), 1); + } + + #[test] + fn test_unwrapped_refs() { + #[derive(Component, Debug)] + #[component(refs = "UnwrappedRefs")] + struct Number(usize); + + let mut world = World::new(); + world.spawn(Number(1)); + + fn detect_changed(query: Query>) -> usize { + return query.iter().count(); + } + let mut detect_changed_system = IntoSystem::into_system(detect_changed); + detect_changed_system.initialize(&mut world); + + fn update_number(mut query: Query<&mut Number>) { + query.single_mut().0 = 3; + dbg!(query.single_mut()); + } + let mut update_number_system = IntoSystem::into_system(update_number); + update_number_system.initialize(&mut world); + + // initialize last_run tick + detect_changed_system.run((), &mut world); + update_number_system.run((), &mut world); + assert_eq!(detect_changed_system.run((), &mut world), 0); + } +} diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8ccffe54293a6..d3b33c3a8a0ea 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -8,6 +8,7 @@ pub mod archetype; pub mod bundle; pub mod change_detection; pub mod component; +pub mod component_refs; pub mod entity; pub mod event; pub mod query; @@ -31,6 +32,7 @@ pub mod prelude { bundle::Bundle, change_detection::{DetectChanges, DetectChangesMut, Mut, Ref}, component::Component, + component_refs::{ChangeDetectionRefs, UnwrappedRefs}, entity::Entity, event::{EventReader, EventWriter, Events}, query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without}, diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index 5733ffd262829..c72713c0d416c 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1,11 +1,12 @@ +use crate::component_refs::ComponentRefs; use crate::{ archetype::{Archetype, ArchetypeComponentId}, - change_detection::{Ticks, TicksMut}, component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick}, + component_refs::ComponentRef, entity::Entity, query::{Access, DebugCheckedUnwrap, FilteredAccess}, storage::{ComponentSparseSet, Table, TableRow}, - world::{Mut, Ref, World}, + world::World, }; use bevy_ecs_macros::all_tuples; pub use bevy_ecs_macros::WorldQuery; @@ -516,167 +517,29 @@ unsafe impl WorldQuery for Entity { unsafe impl ReadOnlyWorldQuery for Entity {} #[doc(hidden)] -pub struct ReadFetch<'w, T> { +pub struct ComponentFetch<'w, T> { // T::Storage = TableStorage - table_components: Option>>, - // T::Storage = SparseStorage - sparse_set: Option<&'w ComponentSparseSet>, -} - -/// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl WorldQuery for &T { - type Fetch<'w> = ReadFetch<'w, T>; - type Item<'w> = &'w T; - type ReadOnly = Self; - type State = ComponentId; - - fn shrink<'wlong: 'wshort, 'wshort>(item: &'wlong T) -> &'wshort T { - item - } - - const IS_DENSE: bool = { - match T::Storage::STORAGE_TYPE { - StorageType::Table => true, - StorageType::SparseSet => false, - } - }; - - const IS_ARCHETYPAL: bool = true; - - unsafe fn init_fetch<'w>( - world: &'w World, - &component_id: &ComponentId, - _last_change_tick: u32, - _change_tick: u32, - ) -> ReadFetch<'w, T> { - ReadFetch { - table_components: None, - sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { - world - .storages() - .sparse_sets - .get(component_id) - .debug_checked_unwrap() - }), - } - } - - unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - ReadFetch { - table_components: fetch.table_components, - sparse_set: fetch.sparse_set, - } - } - - #[inline] - unsafe fn set_archetype<'w>( - fetch: &mut ReadFetch<'w, T>, - component_id: &ComponentId, - _archetype: &'w Archetype, - table: &'w Table, - ) { - if Self::IS_DENSE { - Self::set_table(fetch, component_id, table); - } - } - - #[inline] - unsafe fn set_table<'w>( - fetch: &mut ReadFetch<'w, T>, - &component_id: &ComponentId, - table: &'w Table, - ) { - fetch.table_components = Some( - table - .get_column(component_id) - .debug_checked_unwrap() - .get_data_slice() - .into(), - ); - } - - #[inline(always)] - unsafe fn fetch<'w>( - fetch: &mut Self::Fetch<'w>, - entity: Entity, - table_row: TableRow, - ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { - StorageType::Table => fetch - .table_components - .debug_checked_unwrap() - .get(table_row.index()) - .deref(), - StorageType::SparseSet => fetch - .sparse_set - .debug_checked_unwrap() - .get(entity) - .debug_checked_unwrap() - .deref(), - } - } - - fn update_component_access( - &component_id: &ComponentId, - access: &mut FilteredAccess, - ) { - assert!( - !access.access().has_write(component_id), - "&{} conflicts with a previous access in this query. Shared access cannot coincide with exclusive access.", - std::any::type_name::(), - ); - access.add_read(component_id); - } - - fn update_archetype_component_access( - &component_id: &ComponentId, - archetype: &Archetype, - access: &mut Access, - ) { - if let Some(archetype_component_id) = archetype.get_archetype_component_id(component_id) { - access.add_read(archetype_component_id); - } - } - - fn init_state(world: &mut World) -> ComponentId { - world.init_component::() - } - - fn matches_component_set( - &state: &ComponentId, - set_contains_id: &impl Fn(ComponentId) -> bool, - ) -> bool { - set_contains_id(state) - } -} - -/// SAFETY: access is read only -unsafe impl ReadOnlyWorldQuery for &T {} - -#[doc(hidden)] -pub struct RefFetch<'w, T> { - // T::Storage = TableStorage - table_data: Option<( + pub(crate) table_data: Option<( ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, ThinSlicePtr<'w, UnsafeCell>, )>, // T::Storage = SparseStorage - sparse_set: Option<&'w ComponentSparseSet>, + pub(crate) sparse_set: Option<&'w ComponentSparseSet>, - last_change_tick: u32, - change_tick: u32, + pub(crate) last_change_tick: u32, + pub(crate) change_tick: u32, } /// SAFETY: `Self` is the same as `Self::ReadOnly` -unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { - type Fetch<'w> = RefFetch<'w, T>; - type Item<'w> = Ref<'w, T>; +unsafe impl WorldQuery for &T { + type Fetch<'w> = ComponentFetch<'w, T>; + type Item<'w> = <::Refs as ComponentRefs>::Ref<'w>; type ReadOnly = Self; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Ref<'wlong, T>) -> Ref<'wshort, T> { - item + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + <::Refs as ComponentRefs>::shrink_ref(item) } const IS_DENSE: bool = { @@ -693,8 +556,8 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { &component_id: &ComponentId, last_change_tick: u32, change_tick: u32, - ) -> RefFetch<'w, T> { - RefFetch { + ) -> ComponentFetch<'w, T> { + ComponentFetch { table_data: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world @@ -709,7 +572,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - RefFetch { + ComponentFetch { table_data: fetch.table_data, sparse_set: fetch.sparse_set, last_change_tick: fetch.last_change_tick, @@ -719,7 +582,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { #[inline] unsafe fn set_archetype<'w>( - fetch: &mut RefFetch<'w, T>, + fetch: &mut ComponentFetch<'w, T>, component_id: &ComponentId, _archetype: &'w Archetype, table: &'w Table, @@ -731,7 +594,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { #[inline] unsafe fn set_table<'w>( - fetch: &mut RefFetch<'w, T>, + fetch: &mut ComponentFetch<'w, T>, &component_id: &ComponentId, table: &'w Table, ) { @@ -749,32 +612,7 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { - StorageType::Table => { - let (table_components, added_ticks, changed_ticks) = - fetch.table_data.debug_checked_unwrap(); - Ref { - value: table_components.get(table_row.index()).deref(), - ticks: Ticks { - added: added_ticks.get(table_row.index()).deref(), - changed: changed_ticks.get(table_row.index()).deref(), - change_tick: fetch.change_tick, - last_change_tick: fetch.last_change_tick, - }, - } - } - StorageType::SparseSet => { - let (component, ticks) = fetch - .sparse_set - .debug_checked_unwrap() - .get_with_ticks(entity) - .debug_checked_unwrap(); - Ref { - value: component.deref(), - ticks: Ticks::from_tick_cells(ticks, fetch.last_change_tick, fetch.change_tick), - } - } - } + Self::Item::new(fetch, entity, table_row) } fn update_component_access( @@ -812,32 +650,17 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> { } /// SAFETY: access is read only -unsafe impl<'__w, T: Component> ReadOnlyWorldQuery for Ref<'__w, T> {} - -#[doc(hidden)] -pub struct WriteFetch<'w, T> { - // T::Storage = TableStorage - table_data: Option<( - ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, - ThinSlicePtr<'w, UnsafeCell>, - )>, - // T::Storage = SparseStorage - sparse_set: Option<&'w ComponentSparseSet>, - - last_change_tick: u32, - change_tick: u32, -} +unsafe impl<'__w, T: Component> ReadOnlyWorldQuery for &T {} /// SAFETY: access of `&T` is a subset of `&mut T` unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { - type Fetch<'w> = WriteFetch<'w, T>; - type Item<'w> = Mut<'w, T>; + type Fetch<'w> = ComponentFetch<'w, T>; + type Item<'w> = <::Refs as ComponentRefs>::MutRef<'w>; type ReadOnly = &'__w T; type State = ComponentId; - fn shrink<'wlong: 'wshort, 'wshort>(item: Mut<'wlong, T>) -> Mut<'wshort, T> { - item + fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> { + <::Refs as ComponentRefs>::shrink_mut(item) } const IS_DENSE: bool = { @@ -854,8 +677,8 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { &component_id: &ComponentId, last_change_tick: u32, change_tick: u32, - ) -> WriteFetch<'w, T> { - WriteFetch { + ) -> ComponentFetch<'w, T> { + ComponentFetch { table_data: None, sparse_set: (T::Storage::STORAGE_TYPE == StorageType::SparseSet).then(|| { world @@ -870,7 +693,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { } unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> { - WriteFetch { + ComponentFetch { table_data: fetch.table_data, sparse_set: fetch.sparse_set, last_change_tick: fetch.last_change_tick, @@ -880,7 +703,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { #[inline] unsafe fn set_archetype<'w>( - fetch: &mut WriteFetch<'w, T>, + fetch: &mut ComponentFetch<'w, T>, component_id: &ComponentId, _archetype: &'w Archetype, table: &'w Table, @@ -892,7 +715,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { #[inline] unsafe fn set_table<'w>( - fetch: &mut WriteFetch<'w, T>, + fetch: &mut ComponentFetch<'w, T>, &component_id: &ComponentId, table: &'w Table, ) { @@ -910,36 +733,7 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T { entity: Entity, table_row: TableRow, ) -> Self::Item<'w> { - match T::Storage::STORAGE_TYPE { - StorageType::Table => { - let (table_components, added_ticks, changed_ticks) = - fetch.table_data.debug_checked_unwrap(); - Mut { - value: table_components.get(table_row.index()).deref_mut(), - ticks: TicksMut { - added: added_ticks.get(table_row.index()).deref_mut(), - changed: changed_ticks.get(table_row.index()).deref_mut(), - change_tick: fetch.change_tick, - last_change_tick: fetch.last_change_tick, - }, - } - } - StorageType::SparseSet => { - let (component, ticks) = fetch - .sparse_set - .debug_checked_unwrap() - .get_with_ticks(entity) - .debug_checked_unwrap(); - Mut { - value: component.assert_unique().deref_mut(), - ticks: TicksMut::from_tick_cells( - ticks, - fetch.last_change_tick, - fetch.change_tick, - ), - } - } - } + Self::Item::new(fetch, entity, table_row) } fn update_component_access(