Tainted \\ Coders

Bevy Entities

Last updated:

At a high level an Entity exclusively owns zero or more Component instances.

Each entity can only have a single component of each type and these types can be added or removed dynamically over the course of the entity’s lifetime.

Entities are identifiers

The Entity type is a lightweight identifier that’s valid only for the world it is sourced from.

// https://github.com/bevyengine/bevy/blob/c50561035833943876672a6f15e3cda2752ce11a/crates/bevy_ecs/src/entity/mod.rs#L118C1-L122C2
#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
pub struct Entity {
    generation: u32,
    index: u32,
}

The type itself is a simple holder of both a index and a generation property.

The two form a generational index. This allows fast insertion after data removal in an array while maintaining the contiguous memory layout that makes ECS more performant.

Entities are local to the world

Each World keeps a list of Entities which contains 3 sets of entity IDs:

  1. freelist: IDs, previously freed
  2. reserved: list of IDs that were once in the freelist, but got reserved
  3. pending: count of new IDs which do not exist yet

The generational index ensures that two simultaneously-live entities will never share the same index.

Generations are incremented each time an entity with a given index is despawned. This serves as a “count” of the number of times a given index has been reused.

These unique identifiers enable Bevy to allocate them in a lazy way. It first reserves an ID and then can allocate it later.

This test illustrates the concept:

#[cfg(test)]
mod tests {

    #[derive(Component)]
    struct Health(i32);

    #[test]
    fn reserve_and_spawn() {
        let mut world = World::default();
        // We reserve an entity id
        let id = world.entities().reserve_entity();

        // Then we lazily spawn the entity using
        // the reserved id
        world.flush();

        let mut entity = world.entity_mut(id);
        entity.insert(Health(0));
        assert_eq!(
            entity.get::<Health>().unwrap(),
            &Health(0)
        );
    }
}

In an actual application we don’t actually manage any of this ourselves. We use the commands system parameter instead:

fn spawn_health(
    mut commands: Commands
) {
    commands.spawn(Health(0));
}

Entities are stored in tables

Entities is a type held by your World and holds for metadata of all the entities in the World. Each piece of metadata contains:

  • The generation of every entity.
  • The alive/dead status of a particular entity. (i.e. “has entity 3 been despawned?”)
  • The location of the entity’s components in memory (via [EntityLocation])

The EntityLocation contains information about the Table the entity’s components are stored in.

Each Table has a Column for each component type it stores:

// https://github.com/bevyengine/bevy/blob/bf4f4e42da3da07b0e84a4d6e40077f7398aea8b/crates/bevy_ecs/src/storage/table.rs#L559
pub struct Table {
    columns: ImmutableSparseSet<ComponentId, Column>,
    entities: Vec<Entity>,
}

The ImmutableSparseSet can be understood as a simple HashMap.

And a Column is a type-erased Vec<T: Component>.

To get a row out of our table we use the Entity as an index on each Column.

So if we had a table with 3 columns:

Health column: [_, _, 50]
Player column: [_, _, X]
Enemy column:  [_, _, _]

We could get the components for an entity with an ID of 2 which would be:

Health(50)
Player

This is a simplified illustration. Bevy will actually create a type TableRow representing this ID, and we can use the Entity identifier to get the TableRow.

Entities enable structure of arrays

This kind of storage concept is called Structure of Arrays (SoA) instead of Arrays of Structures (AoS).

In an AoS program we could imagine a more traditional object oriented game engine like Godot. Our structures hold all of our components. So one object with many properties, each one being a component:

struct Player {
    health: u32,
    speed: u32,
    name: String,
    team: u32
}

We could think about our game loop iterating over each player and performing its required logic:

fn movement_system(mut players: Query<&mut Player>) {}
fn attacking_system(mut players: Query<&mut Player>) {}

One problem is that we cannot split these mutable references up anymore. Each system that does anything to player will have to wait its turn to perform. The more we centralize god objects like this the harder the problem gets.

Each query would also require more memory, one system might use only the name, but loads all the rest of its components all the same.

Instead in Bevy we use Structure of Arrays to do the same thing:

struct Player;
struct Health(u32);
struct Name(String);
struct TeamId(u32);

When we want to create a system that decrements our health under some condition, we do not also need to mutably borrow the other components.

Bevy will work hard to try and schedule your systems to run in parallel if they don’t need mutable access to the same data.

Archetypes group components by entities

So which Table does an entity’s components go into? That’s where the archetype comes in.

Every component has an ArchetypeId based on the combination of components their entity has.

A world has only one Archetype for each unique combination of components on your entities. Their ArchetypeId is locally unique to a world, not globally unique between worlds.

Archetypes point to a particular table, but multiple archetypes may store their table components in the same table.

Both Archetypes and Tables are created but never cleaned up. They are not removed and persist until the world is dropped.

Archetypes are useful when used by the scheduler:

fn system_a(query: Query<&mut Health, With<Player>>) {}
fn system_b(query: Query<&mut Health, Without<Player>>) {}

system_b will in parallel with system_a, even though the two use a mutable reference to the same component type.

Even though both components share the same ComponentId, they actually have different ArchetypeComponentIds which lets the scheduler identify these disjoint queries.

Read more