From d6725d3b1baa6a730fa6bfab1bb8f2a9e5f4716e Mon Sep 17 00:00:00 2001 From: Periwink Date: Tue, 11 Feb 2025 14:00:13 -0500 Subject: [PATCH] Expose method to update the internal ticks of Ref and Mut (#17716) ## What problem does this solve or what need does it fill? There are some situations (https://github.com/bevyengine/bevy/issues/13735) where the ticks that are present inside `Ref` are incorrect, for example if `Ref` is created outside of a `SystemParam`. I still want to use `Ref` because it has convenient `is_added` and `is_changed` methods. My current solution is to build my own `Ref` by copy-pasting most the bevy code to do that via something like ```rust /// This method is necessary because there is no easy way to pub(crate) fn get_ref( world: &World, entity: Entity, last_run: Tick, this_run: Tick, ) -> Ref { unsafe { let component_id = world .components() .get_id(TypeId::of::()) .unwrap_unchecked(); let world = world.as_unsafe_world_cell_readonly(); let entity_cell = world.get_entity(entity).unwrap_unchecked(); get_component_and_ticks( world, component_id, C::STORAGE_TYPE, entity, entity_cell.location(), ) .map(|(value, cells, _caller)| { Ref::new( value.deref::(), cells.added.deref(), cells.changed.deref(), last_run, this_run, #[cfg(feature = "track_location")] _caller.deref(), ) }) .unwrap_unchecked() } } // Utility function to return #[inline] unsafe fn get_component_and_ticks( world: UnsafeWorldCell<'_>, component_id: ComponentId, storage_type: StorageType, entity: Entity, location: EntityLocation, ) -> Option<(Ptr<'_>, TickCells<'_>, MaybeUnsafeCellLocation<'_>)> { match storage_type { StorageType::Table => { let table = unsafe { world.storages().tables.get(location.table_id) }?; // SAFETY: archetypes only store valid table_rows and caller ensure aliasing rules Some(( table.get_component(component_id, location.table_row)?, TickCells { added: table .get_added_tick(component_id, location.table_row) .unwrap_unchecked(), changed: table .get_changed_tick(component_id, location.table_row) .unwrap_unchecked(), }, #[cfg(feature = "track_location")] table .get_changed_by(component_id, location.table_row) .unwrap_unchecked(), #[cfg(not(feature = "track_location"))] (), )) } StorageType::SparseSet => { let storage = unsafe { world.storages() }.sparse_sets.get(component_id)?; storage.get_with_ticks(entity) } } } ``` It would be very convenient if instead bevy exposed a way to create a `Ref` object with custom `last_run` and `this_run` ticks. This PR does this by exposing a function to overwrite the `last_run` and `this_run` ticks. (Same with `Mut`) I am ok with marking the method unsafe or risky if it's deemed to risky for end-users. --- crates/bevy_ecs/src/change_detection.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 256ac89547..8731fb8eb7 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -832,6 +832,15 @@ impl<'w, T: ?Sized> Ref<'w, T> { changed_by: caller, } } + + /// Overwrite the `last_run` and `this_run` tick that are used for change detection. + /// + /// This is an advanced feature. `Ref`s are usually _created_ by engine-internal code and + /// _consumed_ by end-user code. + pub fn set_ticks(&mut self, last_run: Tick, this_run: Tick) { + self.ticks.last_run = last_run; + self.ticks.this_run = this_run; + } } impl<'w, 'a, T> IntoIterator for &'a Ref<'w, T> @@ -949,6 +958,15 @@ impl<'w, T: ?Sized> Mut<'w, T> { changed_by: caller, } } + + /// Overwrite the `last_run` and `this_run` tick that are used for change detection. + /// + /// This is an advanced feature. `Mut`s are usually _created_ by engine-internal code and + /// _consumed_ by end-user code. + pub fn set_ticks(&mut self, last_run: Tick, this_run: Tick) { + self.ticks.last_run = last_run; + self.ticks.this_run = this_run; + } } impl<'w, T: ?Sized> From> for Ref<'w, T> {