Tracking Issue: Entity API Deduplication
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 171
Description
This is a tracking issue for the [Entity API Deduplication Working Group](https://discord.com/channels/691052431525675048/1308513055000498226).
# Progress
- [ ] #18820
- [ ] Cleanup `EntityWorldMut` in preparation for changing its layout.
- [ ] Swap `EntityWorldMut` to storing an `UnsafeEntityCell`.
- [ ] Make `EntityWorldMut` into an alias for `EntityMut` and add the `Global` access scope. `EntityWorldRef` will also be added at this point.
- [ ] Make `World::entity` and `World::get_entity` return `EntityWorldRef`.
- [ ] Add `EntityRefOnly`, `EntityMutOnly` and the `Only` access scope, as well as the `split` functions detailed in part 2.
# Plan (Part 1)
> This plan is based on [this design document](https://hackmd.io/@bevy/By4NcOYzkg).
We have a lot of types for accessing individual entities:
- `EntityRef`: read access to all components
- `FilteredEntityRef`: read access to some components, determined at runtime
- `EntityRefExcept`: read access to some components, determined at compile-time
- `EntityMut`: write access to all components
- `FilteredEntityMut`: write access to some components, determined at runtime
- `EntityMutExcept`: write access to some components, determined at compile-time
- `EntityWorldMut`: write access to all components, and to the entire world including structural changes
We can observe two sets of types here: those with read access, and those with read/write access. This pairs nicely with how normal Rust references work, so lets group them up.
- read access
- `EntityRef`: all components
- `FilteredEntityRef`: some components, determined at runtime
- `EntityRefExcept`: some components, determined at compile-time
- write access
- `EntityMut`: all components
- `FilteredEntityMut`: some components, determined at runtime
- `EntityMutExcept`: some components, determined at compile-time
- `EntityWorldMut`: all components and the entire world, including structural changes
Now within each group, we see that they all have the same *kind* of access (reading or writing), but differ in *what* they can access. Therefore, we can collapse each group down into a single generic type, `EntityRef` and `EntityMut` which use a "scope" type parameter to determine *what* the reference has access to:
```rust
pub struct EntityRef<'w, S: AccessScope = Full> { /* ... */ }
pub type FilteredEntityRef<'w> = EntityRef<'w, Filtered>;
pub type EntityRefExcept<'w, B> = EntityRef<'w, Except>;
pub struct EntityMut<'w, S: AccessScope = Full> { /* ... */ }
pub type FilteredEntityMut<'w> = EntityMut<'w, Filtered>;
pub type EntityMutExcept<'w, B> = EntityMut<'w, Except>;
pub type EntityWorldMut<'w> = EntityMut<'w, Global>;
pub unsafe trait AccessScope {
fn can_read(&self, components: &Components, id: ComponentId) -> bool;
fn can_write(&self, components: &Components, id: ComponentId) -> bool;
}
```
At this point, we've reduced our need for duplicating functions across all of the entity reference types from 7 down to 2!
# Plan (Part 2)
At this stage, we can add some new features.
## `EntityWorldRef`
This is an `EntityRef` counterpart to `EntityWorldMut`. We can return this type from `World::entity` and `World::get_entity` (non-mut) to provide simultaneous immutable access to the whole world.
## `EntityRefOnly` and `EntityMutOnly`
These are the inverse of `EntityRefExcept` and `EntityMutExcept`: they provide access to *only* the components contained in the `Bundle`. We can make use of the `XOnly` and `XExcept` reference types together to provide a low-overhead alternative to a proposed `EntityMut::get_components_mut`:
```rust
impl<'w> EntityMut<'w, Full> {
pub fn split(&mut self) -> (EntityMutOnly<'_, B>, EntityMutExcept<'_, B>);
pub fn split_by(&mut self, access: Access) -> (FilteredEntityMut<'_>, FilteredEntityMut<'_>);
pub fn into_split(self) -> (EntityMutOnly<'w, B>, EntityMutExcept<'w, B>);
pub fn into_split_by(self, access: Access) -> (FilteredEntityMut<'w>, FilteredEntityMut<'w>);
}
```
# Plan (Part 3)
WIP.
# Design documents
- Original: https://hackmd.io/@bevy/Hyj8Kjy1ye
- Deref-ladder based alternative: https://hackmd.io/@bevy/B1vgzGdf1g
- Original v2 (what we've gone with): https://hackmd.io/@bevy/By4NcOYzkg
# Additional context
- [Working Group discussion thread on Discord](https://discord.com/channels/691052431525675048/1308513055000498226)
- [Original discussion thread on Discord](https://discord.com/channels/691052431525675048/1308513055000498226)
Contributor guide
Research direction
Start by reading the linked design document and the progress item for #18820, then review the EntityRef, EntityMut, and EntityWorldMut API types described here. The work is complete when the listed deduplication, access-scope, alias, world-reference, and split-function milestones are implemented and the tracking checklist is resolved.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- backend-api-design, game-dev
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 20/100