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<C: Component>(
    world: &World,
    entity: Entity,
    last_run: Tick,
    this_run: Tick,
) -> Ref<C> {
    unsafe {
        let component_id = world
            .components()
            .get_id(TypeId::of::<C>())
            .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::<C>(),
                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.
This commit is contained in:
Periwink 2025-02-11 14:00:13 -05:00 committed by GitHub
parent 5eff6e80e1
commit d6725d3b1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -832,6 +832,15 @@ impl<'w, T: ?Sized> Ref<'w, T> {
changed_by: caller, 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> impl<'w, 'a, T> IntoIterator for &'a Ref<'w, T>
@ -949,6 +958,15 @@ impl<'w, T: ?Sized> Mut<'w, T> {
changed_by: caller, 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<Mut<'w, T>> for Ref<'w, T> { impl<'w, T: ?Sized> From<Mut<'w, T>> for Ref<'w, T> {