# Unified EntityPtr Types # TL;DR We can reduce code duplication for the following sets of types because they differ only in semantics: - `EntityRef`, `EntityMut`, and `EntityWorldMut` - `FilteredEntityRef`, `FilteredEntityMut` - `EntityRefExcept`, `EntityMutExcept` What they look like currently: ```rust pub struct EntityRef<'w>(UnsafeEntityCell<'w>); pub struct EntityMut<'w>(UnsafeEntityCell<'w>); // This isn't *exactly* what it is in-engine, but it's equivalent. pub struct EntityWorldMut<'w>(UnsafeEntityCell<'w>); ``` What they will look like afterwards: ```rust pub struct EntityPtr<'w, L: ReadAccess>(UnsafeEntityCell<'w>, PhantomData<L>); pub type EntityRef<'w> = EntityPtr<'w, ReadOnly>; pub type EntityMut<'w> = EntityPtr<'w, Mutable>; pub type EnttiyWorldMut<'w> = EntityPtr<'w, WorldMutable>; ``` # Access Levels These types do not differ memory-wise, but they do semantically. If we take a look at the three aforementioned types, we can see that there's three different "access levels" for safely accessing an `UnsafeEntityCell`: 1. Read only access 2. Mutable access 3. Structurally mutable access (can insert/remove components, and despawn the entity) We can define some marker traits and structs to put these into rust: ```rust pub trait ReadAccess /* sealed */ {} pub struct ReadOnly; impl ReadAccess for ReadOnly {} // Since we also have two different levels of mutability pub trait MutableAccess: ReadAccess {} pub struct Mutable; impl ReadAccess for Mutable {} impl MutableAccess for Mutable {} pub struct WorldMutable; impl ReadAccess for WorldMutable {} impl MutableAccess for WorldMutable {} ``` The above set of traits and types should cover all the different kinds of access bounds: - `L: ReadAccess` - for when *some* kind of read access is needed - `L = ReadOnly` - for when *precisely* read-only access is needed - `L: MutableAccess` - for when *some* kind of mutable access is needed - `L = Mutable` - for when *precisely* non-world-mutable access is needed - `L = WorldMutable` - for when *precisely* world-mutable access is needed ## Downgrading access Because each higher level of access implies the levels below it, we can safely allow downgrading higher access level pointers to lower access level: ```rust // Mutable access implies we have read-only access. impl<'w> From<EntityPtr<'w, Mutable>> for EntityPtr<'w, ReadOnly> { // ... } // World-mutable access implies we have mutable access. impl<'w> From<EntityPtr<'w, WorldMutable>> for EntityPtr<'w, Mutable> { // ... } ``` ## Minimum access level When working generically with *some pointer* we are only given read access: ```rust // All operations within this `impl` block must only read. impl<'w, L: ReadAccess> EntityPtr<'w, L> { // Because we can safely read the entity ID at any access level, // we write the function once rather than for every specific access level. pub fn id(&self) -> Entity { // ... } // Same with the entity's location pub fn location(&self) -> EntityLocation { // ... } // ... } ``` ## Downgrading arbitrary access levels We can combine the two previous concepts for implementing conversions from `&EntityPtr<ANY>` -> `EntityPtr<ReadOnly>` with only one `From` implementation: ```rust // A read-only reference to an EntityPtr of arbitrary access level // can be downgraded into an EntityPtr with read-only access. impl<'w, L: ReadAccess> From<&'w EntityPtr<'_, L>> for EntityPtr<'w, ReadOnly> { // ... } // A mutable reference to an EntityPtr of arbitrary *mutable* access level // can be downgraded into an EntityPtr with mutable access. // This is equivalent to reborrowing. impl<'w, L: MutableAccess> From<&'w mut EntityPtr<'_, L>> for EntityPtr<'w, Mutable> { // ... } ``` ## Amendment: Consider adding `Immutable` access type + `Deref`/`DerefMut` impls for downgrading Proposed by @bushRAT on the Bevy Discord, revised based on discussion with @quartermeister and @Diddykonga. Originally proposed was to add `Deref`/`DerefMut` implementations for `WorldMutable` -> `Mutable` -> `ReadOnly`. However this would be unsound as `ReadOnly` is (currently) `Copy` + `Clone`, so someone could do: ```rust fn duplicate<'w>(ptr: &mut EntityPtr<'w, Mutable>) -> (&mut EntityPtr<'w, Mutable>, EntityPtr<'w, ReadOnly>) { (*ptr).clone() } // oops just caused aliased mutability! let (ptr, dup) = duplicate(ptr); ``` To combat this we could introduce a 4th access type, `Immutable`, alongside our three other access types (`ReadOnly`, `Mutable`, and `WorldMutable`). This enables the removal of the `ReadAccess` and `MutableAccess` traits and we would instead provide `Deref`/`DerefMut` impls, likely leading to a cleaner implementation overall. First a look at the types in code: ```rust // Access types; no traits needed as we'll use them directly pub struct ReadOnly; pub struct Immutable; pub struct Mutable; pub struct WorldMutable; pub struct EntityPtr<'w, Access = ReadOnly>(UnsafeEntityCell<'w>, PhantomData<Access>); pub type EntityView<'w> = EntityPtr<'w, ReadOnly>; pub type EntityRef<'w> = EntityPtr<'w, Immutable>; pub type EntityMut<'w> = EntityPtr<'w, Mutable>; pub type EntityWorldMut<'w> = EntityPtr<'w, WorldMutable>; ``` The first thing We should be able to safely implement the following: ```rust impl<'w> Deref for EntityPtr<'w, WorldMutable> { type Target = EntityPtr<'w, Mutable>; fn deref(&self) -> &Self::Target { // EntityPtrs are layout-compatible and ideally #[repr(transparent)], // so this transmute should be safe. std::mem::transmute(self) } } impl<'w> DerefMut for EntityPtr<'w, WorldMutable> { fn deref_mut(&mut self) -> &mut Self::Target { // Likewise as above std::mem::transmute(self) } } ``` # Tackling this change systematically This changeset in its entirety will likely involve >3000 line changes when including file re-organization. Therefore it's prudent to make sure we push these changes incrementally in as small portions as reasonably possible. ## Re-organization of [entity_ref.rs](https://github.com/bevyengine/bevy/blob/d3e9ecbb8c523c9632720463dea6b46393e840bf/crates/bevy_ecs/src/world/entity_ref.rs) This is a big file (presently 4485 lines), and is a bit slow for Rust-Analyzer to process after an edit even on my (doot) well-specced machine, so we should take the following steps: 1. Move `DynamicComponentFetch` to `entity_fetch.rs` - We will likely unify this trait with `WorldEntityFetch` at some point, anyways. 2. Move `FilteredEntityRef`/`FilteredEntityMut`, `TryFromFilteredError` and associated tests into a new file `entity_ref_filtered.rs` 3. Move `EntityRefExcept`/`EntityMutExcept`, `fn bundle_contains_component`, and associated tests into a new file `entity_ref_except.rs` With all that done `entity_ref.rs` will be down to ~3180 lines. ## Change [EntityWorldMut](https://github.com/bevyengine/bevy/blob/d3e9ecbb8c523c9632720463dea6b46393e840bf/crates/bevy_ecs/src/world/entity_ref.rs#L856-L860) to hold just an `UnsafeEntityCell` ```diff pub struct EntityWorldMut<'w> { - world: &'w mut World, - entity: Entity, - location: EntityLocation, + cell: UnsafeEntityCell<'w>, } // then fix up all uses of the now missing fields... ``` In isolation this might seem like a downgrade as we'll go from holding an explicit `&mut World` to an `UnsafeWorldCell` (contained in the `UnsafeEntityCell`), but it's required for the next step / the overall plan. ## Finally, implement this document 1. Add new `EntityPtr<L: ReadAccess>` type and change `EntityRef` from `struct` to `type` alias. 2. Change `EntityMut` into a `type` alias. 3. Change `EntityWorldMut` into a `type` alias. 4. Add new `FilteredEntityPtr<L: ReadAccess>` type and change `FilteredEntityRef`/`FilteredEntityMut` into type aliases. 5. Add new `EntityPtrExcept<L: ReadAccess>` type and change `EntityRefExcept`/`EntityMutExcept` into type aliases. These can be done as separate PRs, but TBD which changes should be grouped together into individual PRs.