Make EntityRef::new unsafe (#7222)

# Objective

- We rely on the construction of `EntityRef` to be valid elsewhere in unsafe code. This construction is not checked (for performance reasons), and thus this private method must be unsafe.
- Fixes #7218.

## Solution

- Make the method unsafe.
- Add safety docs.
- Improve safety docs slightly for the sibling `EntityMut::new`.
- Add debug asserts to start to verify these assumptions in debug mode.


## Context for reviewers

I attempted to verify the `EntityLocation` more thoroughly, but this turned out to be more work than expected. I've spun that off into #7221 as a result.
This commit is contained in:
Alice Cecile 2023-01-16 22:10:51 +00:00
parent e44990a48d
commit 39e14a4a40
3 changed files with 30 additions and 6 deletions

View file

@ -747,7 +747,7 @@ impl EntityMeta {
// SAFETY:
// This type must not contain any pointers at any level, and be safe to fully fill with u8::MAX.
/// A location of an entity in an archetype.
#[derive(Copy, Clone, Debug)]
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct EntityLocation {
/// The ID of the [`Archetype`] the [`Entity`] belongs to.

View file

@ -22,8 +22,17 @@ pub struct EntityRef<'w> {
}
impl<'w> EntityRef<'w> {
/// # Safety
///
/// - `entity` must be valid for `world`: the generation should match that of the entity at the same index.
/// - `location` must be sourced from `world`'s `Entities` and must exactly match the location for `entity`
///
/// The above is trivially satisfied if `location` was sourced from `world.entities().get(entity)`.
#[inline]
pub(crate) fn new(world: &'w World, entity: Entity, location: EntityLocation) -> Self {
pub(crate) unsafe fn new(world: &'w World, entity: Entity, location: EntityLocation) -> Self {
debug_assert!(world.entities().contains(entity));
debug_assert_eq!(world.entities().get(entity), Some(location));
Self {
world,
entity,
@ -193,7 +202,9 @@ impl<'w> EntityRef<'w> {
impl<'w> From<EntityMut<'w>> for EntityRef<'w> {
fn from(entity_mut: EntityMut<'w>) -> EntityRef<'w> {
EntityRef::new(entity_mut.world, entity_mut.entity, entity_mut.location)
// SAFETY: the safety invariants on EntityMut and EntityRef are identical
// and EntityMut is promised to be valid by construction.
unsafe { EntityRef::new(entity_mut.world, entity_mut.entity, entity_mut.location) }
}
}
@ -206,13 +217,20 @@ pub struct EntityMut<'w> {
impl<'w> EntityMut<'w> {
/// # Safety
/// entity and location _must_ be valid
///
/// - `entity` must be valid for `world`: the generation should match that of the entity at the same index.
/// - `location` must be sourced from `world`'s `Entities` and must exactly match the location for `entity`
///
/// The above is trivially satisfied if `location` was sourced from `world.entities().get(entity)`.
#[inline]
pub(crate) unsafe fn new(
world: &'w mut World,
entity: Entity,
location: EntityLocation,
) -> Self {
debug_assert!(world.entities().contains(entity));
debug_assert_eq!(world.entities().get(entity), Some(location));
EntityMut {
world,
entity,

View file

@ -317,7 +317,10 @@ impl World {
#[inline]
pub fn get_entity(&self, entity: Entity) -> Option<EntityRef> {
let location = self.entities.get(entity)?;
Some(EntityRef::new(self, entity, location))
// SAFETY: if the Entity is invalid, the function returns early.
// Additionally, Entities::get(entity) returns the correct EntityLocation if the entity exists.
let entity_ref = unsafe { EntityRef::new(self, entity, location) };
Some(entity_ref)
}
/// Returns an [`Entity`] iterator of current entities.
@ -331,13 +334,16 @@ impl World {
.iter()
.enumerate()
.map(|(archetype_row, archetype_entity)| {
let entity = archetype_entity.entity();
let location = EntityLocation {
archetype_id: archetype.id(),
archetype_row: ArchetypeRow::new(archetype_row),
table_id: archetype.table_id(),
table_row: archetype_entity.table_row(),
};
EntityRef::new(self, archetype_entity.entity(), location)
// SAFETY: entity exists and location accurately specifies the archetype where the entity is stored
unsafe { EntityRef::new(self, entity, location) }
})
})
}