#ecs #dynamic-dispatch #interface #data

entity_data

实体组件数据的容器

19个稳定版本

1.11.1 2023年7月11日
1.10.3 2023年2月16日
1.7.0-rc22022年12月25日
1.7.0-rc12022年10月26日
0.2.1 2021年7月8日

241数据结构

Download history 6/week @ 2024-07-01 99/week @ 2024-07-29

每月下载量 105

自定义许可证

65KB
1.5K SLoC

entity_data

Build Status Crates.io Docs.rs

实体组件数据的容器。

用例

假设你需要存储大量的对象,每个对象可以具有多个不同的字段。但你不想使用Rust的动态调度功能,原因如下:

  1. 虚拟调度会引入间接引用。
  2. 你将不得不在堆上为每个对象存储某个位置。这会导致缓存未命中,从而导致对象迭代速度变慢。

面向数据的方法有助于解决这些问题。

架构

一个 实体 是一个对象的标识符。每个实体可以与多个组件(“字段”)相关联。存储本身基于 ECS 技术。

一组独特的组件称为 Archetype。一个archetype为它的每个组件类型维护一个连续的向量。

示例

简单使用

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());
}

同时处理多个组件

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);
}

依赖关系

~1–2MB
~36K SLoC