bevyengine / bevyengine/bevy

Tracking Issue: Entity API Deduplication

Open
#18,837 0 comments 0 reactions 0 assignees View on GitHub
A-ECS C-Code-Quality C-Feature C-Tracking-Issue
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.