diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 458564beb4cfe..501b548b3ce72 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -189,6 +189,32 @@ impl<'w> EntityRef<'w> { ) } } + + /// Gets the component of the given [`ComponentId`] from the entity. + /// + /// **You should prefer to use the typed API where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// Unlike [`EntityRef::get`], this returns a raw pointer to the component, + /// instead of a reference. + /// + /// # Safety + /// + /// - The returned reference must never alias another reference to this component + /// - The returned reference must not be used after this component is moved which + /// may happen from **any** `insert_component`, `remove_component` or `despawn` + /// operation on this world (non-exhaustive list). + #[inline] + pub unsafe fn get_unchecked_mut_by_id( + &self, + component_id: ComponentId, + ) -> Option> { + self.world.components().get_info(component_id)?; + // SAFETY: entity_location is valid, component_id is valid as checked by the line above, + // the caller promises that they can uniquely access this component + get_mut_by_id(self.world, self.entity, self.location, component_id) + } } impl<'w> From> for EntityRef<'w> { @@ -692,7 +718,7 @@ impl<'w> EntityMut<'w> { #[inline] pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option> { self.world.components().get_info(component_id)?; - // SAFETY: entity_location is valid, component_id is valid as checked by the line above + // SAFETY: entity_location is valid, component_id is valid as checked by the line above, world access is unique unsafe { get_mut_by_id(self.world, self.entity, self.location, component_id) } } } @@ -1064,21 +1090,26 @@ pub(crate) unsafe fn get_mut( }) } -// SAFETY: EntityLocation must be valid, component_id must be valid +// SAFETY: +// - EntityLocation must be valid, component_id must be valid +// - world access to the component must be valid, either because the caller has a `&mut` world or it synchronizes access like systems do #[inline] pub(crate) unsafe fn get_mut_by_id( - world: &mut World, + world: &World, entity: Entity, location: EntityLocation, component_id: ComponentId, ) -> Option { - let change_tick = world.change_tick(); let info = world.components.get_info_unchecked(component_id); - // SAFETY: world access is unique, entity location and component_id required to be valid + // SAFETY: world access promised by the caller, entity location and component_id required to be valid get_component_and_ticks(world, component_id, info.storage_type(), entity, location).map( |(value, ticks)| MutUntyped { value: value.assert_unique(), - ticks: Ticks::from_tick_cells(ticks, world.last_change_tick(), 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 0e8009916715c..56edf477ea1b4 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1499,23 +1499,43 @@ impl World { /// use this in cases where the actual types are not known at compile time.** #[inline] pub fn get_resource_mut_by_id(&mut self, component_id: ComponentId) -> Option> { + // SAFETY: unique world access + unsafe { self.get_resource_unchecked_mut_by_id(component_id) } + } + + /// Gets a resource to the resource with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// of the [`World`] is still valid. + /// + /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// use this in cases where the actual types are not known at compile time.** + /// + /// # Safety + /// This will allow aliased mutable access to the given resource type. The caller must ensure + /// that there is either only one mutable access or multiple immutable accesses at a time. + #[inline] + pub unsafe fn get_resource_unchecked_mut_by_id( + &self, + component_id: ComponentId, + ) -> Option> { let info = self.components.get_info(component_id)?; if !info.is_send_and_sync() { self.validate_non_send_access_untyped(info.name()); } - let change_tick = self.change_tick(); - let (ptr, ticks) = self.get_resource_with_ticks(component_id)?; - // SAFETY: This function has exclusive access to the world so nothing aliases `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 - let ticks = unsafe { Ticks::from_tick_cells(ticks, self.last_change_tick(), change_tick) }; + // - no other reference to the ticks, because the caller promises it + let ticks = unsafe { + Ticks::from_tick_cells(ticks, self.last_change_tick(), self.read_change_tick()) + }; Some(MutUntyped { - // SAFETY: This function has exclusive access to the world so nothing aliases `ptr`. - value: unsafe { ptr.assert_unique() }, + // SAFETY: the caller of this function has to ensure that they can mutably access this + // component for the duration of the `'_` lifetime + value: ptr.assert_unique(), ticks, }) } @@ -1574,7 +1594,7 @@ impl World { component_id: ComponentId, ) -> Option> { self.components().get_info(component_id)?; - // SAFETY: entity_location is valid, component_id is valid as checked by the line above + // SAFETY: entity_location is valid, component_id is valid as checked by the line above, world access is unique unsafe { get_mut_by_id( self,