11 stable releases

1.7.0 Jun 27, 2023
1.6.3 Jun 22, 2023
1.6.1 Feb 16, 2023
1.5.1 Jan 19, 2023
0.1.0 Sep 28, 2022

#1377 in Procedural macros


Used in entity_data

Custom license

6KB
108 lines

entity_data

Build Status Crates.io Docs.rs

A container for entity component data.

Use Case

Suppose you need to store large number of objects, each of which can have multiple different fields. But you don't want to use Rust's dynamic-dispatch feature for the following reasons:

  1. Virtual dispatch induces indirection.
  2. You will have to store every object somewhere on the heap. This leads to cache-misses and hence slower iteration over the objects.

Data-oriented approach helps to overcome these issues.

The Architecture

An entity is an identifier for an object. Each entity can have multiple components ("fields") associated with it. The storage itself is based on ECS technique.

A unique set of components is called an Archetype. An archetype maintains a contiguous vector for each of its component types.

Examples

Simple usage:

use entity_data::{EntityStorage, Archetype};

struct Barks {
    bark_sound: String,
}

impl Barks {
    fn bark(&self) {
        println!("{}", self.bark_sound);
    }
}

struct Eats {
    favorite_food: String,
    eaten_food: Vec<String>,
}

impl Eats {
    fn eat(&mut self, food: String) {
        self.eaten_food.push(food);
    }
}

struct Animal {
    weight: f32,
    habitat: String,
}

#[derive(Archetype)]
struct Dog {
    animal: Animal,
    barks: Barks,
    eats: Eats,
}

#[derive(Archetype)]
struct Bird(Animal, Eats);

fn main() {
    let mut storage = EntityStorage::new();

    let super_dog = storage.add_entity(Dog {
        animal: Animal { weight: 30.0, habitat: "forest".to_string(), },
        barks: Barks { bark_sound: "bark.ogg".to_string(), },
        eats: Eats { favorite_food: "meat".to_string(), eaten_food: vec![] },
    });

    let hummingbird = storage.add_entity(Bird(
        Animal { weight: 5.0, habitat: "gardens".to_string() },
        Eats { favorite_food: "apples".to_string(), eaten_food: vec![] }
    ));

    let super_dog_barks = storage.get::<Barks>(&super_dog).unwrap();
    super_dog_barks.bark();

    let super_dog_eats = storage.get_mut::<Eats>(&super_dog).unwrap();
    super_dog_eats.favorite_food = "beans".to_string();

    let hummingbird_eats = storage.get_mut::<Eats>(&hummingbird).unwrap();
    hummingbird_eats.eat("seeds".to_string());
}

Processing multiple components simultaneously:

use entity_data::{EntityId, EntityStorage, System, SystemHandler};
use entity_data::system::SystemAccess;
use macros::Archetype;

#[derive(Default, Debug)]
struct Position {
    x: f32,
    y: f32,
}

#[derive(Debug)]
struct Name(String);

#[derive(Archetype)]
struct Dog {
    pos: Position,
    name: Name,
}

struct PositionsPrintSystem {}

#[derive(Default)]
struct ConcatAllNamesSystem {
    result: String
}

impl SystemHandler for PositionsPrintSystem {
    fn run(&mut self, data: SystemAccess) {
        let positions = data.component::<Position>();
        let names = data.component::<Name>();
        for (pos, name) in positions.iter().zip(names) {
            println!("{:?} - {:?}", pos, name);
        }
    }
}

impl SystemHandler for ConcatAllNamesSystem {
    fn run(&mut self, data: SystemAccess) {
        let names = data.component::<Name>();
        for name in names {
            self.result += &name.0;
        }
    }
}

fn main() {
    let mut storage = EntityStorage::new();

    let dog0 = storage.add(Dog {
        pos: Default::default(),
        name: Name("Bobby".to_owned())
    });
    let dog1 = storage.add(Dog {
        pos: Position { x: 3.0, y: 5.0 },
        name: Name("Jet".to_owned())
    });

    let mut positions_print_system = PositionsPrintSystem {};
    let mut concat_names_system = ConcatAllNamesSystem::default();

    let mut sys0 = System::new(&mut positions_print_system)
        .with::<Position>().with::<Name>();
    let mut sys1 = System::new(&mut concat_names_system)
        .with::<Name>();

    // or storage.dispatch_par() to run systems in parallel (requires `rayon` feature to be enabled).
    storage.dispatch(&mut [sys0, sys1]);

    println!("{}", concat_names_system.result);
}

Dependencies

~335–780KB
~19K SLoC