Skip to content

Commit

Permalink
Unified identifer for entities & relations (bevyengine#9797)
Browse files Browse the repository at this point in the history
# Objective

The purpose of this PR is to begin putting together a unified identifier
structure that can be used by entities and later components (as
entities) as well as relationship pairs for relations, to enable all of
these to be able to use the same storages. For the moment, to keep
things small and focused, only `Entity` is being changed to make use of
the new `Identifier` type, keeping `Entity`'s API and
serialization/deserialization the same. Further changes are for
follow-up PRs.

## Solution

`Identifier` is a wrapper around `u64` split into two `u32` segments
with the idea of being generalised to not impose restrictions on
variants. That is for `Entity` to do. Instead, it is a general API for
taking bits to then merge and map into a `u64` integer. It exposes
low/high methods to return the two value portions as `u32` integers,
with then the MSB masked for usage as a type flag, enabling entity kind
discrimination and future activation/deactivation semantics.

The layout in this PR for `Identifier` is described as below, going from
MSB -> LSB.

```
|F| High value                    | Low value                      |
|_|_______________________________|________________________________|
|1| 31                            | 32                             |

F = Bit Flags
```

The high component in this implementation has only 31 bits, but that
still leaves 2^31 or 2,147,483,648 values that can be stored still, more
than enough for any generation/relation kinds/etc usage. The low part is
a full 32-bit index. The flags allow for 1 bit to be used for
entity/pair discrimination, as these have different usages for the
low/high portions of the `Identifier`. More bits can be reserved for
more variants or activation/deactivation purposes, but this currently
has no use in bevy.

More bits could be reserved for future features at the cost of bits for
the high component, so how much to reserve is up for discussion. Also,
naming of the struct and methods are also subject to further
bikeshedding and feedback.

Also, because IDs can have different variants, I wonder if
`Entity::from_bits` needs to return a `Result` instead of potentially
panicking on receiving an invalid ID.

PR is provided as an early WIP to obtain feedback and notes on whether
this approach is viable.

---

## Changelog

### Added

New `Identifier` struct for unifying IDs.

### Changed

`Entity` changed to use new `Identifier`/`IdentifierMask` as the
underlying ID logic.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: vero <email@atlasdostal.com>
  • Loading branch information
3 people authored Jan 13, 2024
1 parent 5c6b7d5 commit e6a324a
Show file tree
Hide file tree
Showing 7 changed files with 672 additions and 149 deletions.
22 changes: 13 additions & 9 deletions crates/bevy_ecs/src/entity/map_entities.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{entity::Entity, world::World};
use crate::{
entity::Entity,
identifier::masks::{IdentifierMask, HIGH_MASK},
world::World,
};
use bevy_utils::EntityHashMap;

use super::inc_generation_by;

/// Operation to map all contained [`Entity`] fields in a type to new values.
///
/// As entity IDs are valid only for the [`World`] they're sourced from, using [`Entity`]
Expand Down Expand Up @@ -70,11 +72,13 @@ impl<'m> EntityMapper<'m> {
}

// this new entity reference is specifically designed to never represent any living entity
let new = Entity {
generation: inc_generation_by(self.dead_start.generation, self.generations),
index: self.dead_start.index,
};
self.generations += 1;
let new = Entity::from_raw_and_generation(
self.dead_start.index(),
IdentifierMask::inc_masked_high_by(self.dead_start.generation, self.generations),
);

// Prevent generations counter from being a greater value than HIGH_MASK.
self.generations = (self.generations + 1) & HIGH_MASK;

self.map.insert(entity, new);

Expand Down Expand Up @@ -109,7 +113,7 @@ impl<'m> EntityMapper<'m> {
// SAFETY: Entities data is kept in a valid state via `EntityMap::world_scope`
let entities = unsafe { world.entities_mut() };
assert!(entities.free(self.dead_start).is_some());
assert!(entities.reserve_generations(self.dead_start.index, self.generations));
assert!(entities.reserve_generations(self.dead_start.index(), self.generations));
}

/// Creates an [`EntityMapper`] from a provided [`World`] and [`EntityHashMap<Entity, Entity>`], then calls the
Expand Down
Loading

0 comments on commit e6a324a

Please sign in to comment.