#ecs #world #archetypes #kaige #unique-id #entities #serialization

kaige_ecs

Legion ECS库的分支,对一些内容进行了修改,使其更适合在KaiGE中使用

1个不稳定版本

0.4.0 2023年12月12日

#651 in 游戏开发


kaige使用

MIT许可证

1MB
11K SLoC

入门指南

世界

世界是实体集合,其中每个实体都可以附加任意数量的组件

use legion::*;
let world = World::default();

实体可以通过push(用于单个实体)或extend(用于具有相同组件类型的实体集合)插入。世界在插入时会为每个实体创建一个唯一的ID,您可以稍后使用该ID引用该实体。

// a component is any type that is 'static, sized, send and sync
#[derive(Clone, Copy, Debug, PartialEq)]
struct Position {
    x: f32,
    y: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Velocity {
    dx: f32,
    dy: f32,
}

// push a component tuple into the world to create an entity
let entity: Entity = world.push((Position { x: 0.0, y: 0.0 }, Velocity { dx: 0.0, dy: 0.0 }));

// or extend via an IntoIterator of tuples to add many at once (this is faster)
let entities: &[Entity] = world.extend(vec![
    (Position { x: 0.0, y: 0.0 }, Velocity { dx: 0.0, dy: 0.0 }),
    (Position { x: 1.0, y: 1.0 }, Velocity { dx: 0.0, dy: 0.0 }),
    (Position { x: 2.0, y: 2.0 }, Velocity { dx: 0.0, dy: 0.0 }),
]);

您可以通过条目访问实体。条目允许您查询实体以找出附加到其实体的组件类型,获取组件引用或添加和删除组件。

// entries return `None` if the entity does not exist
if let Some(mut entry) = world.entry(entity) {
    // access information about the entity's archetype
    println!("{:?} has {:?}", entity, entry.archetype().layout().component_types());

    // add an extra component
    entry.add_component(12f32);

    // access the entity's components, returns `None` if the entity does not have the component
    assert_eq!(entry.get_component::<f32>().unwrap(), &12f32);
}

查询

条目不是搜索或批量访问世界最方便或性能最好的方式。 查询允许您以高性能和表达性迭代世界中的实体。

// you define a query be declaring what components you want to find, and how you will access them
let mut query = <&Position>::query();

// you can then iterate through the components found in the world
for position in query.iter(&world) {
    println!("{:?}", position);
}

您可以搜索具有一组组件的实体。

// construct a query from a "view tuple"
let mut query = <(&Velocity, &mut Position)>::query();

// this time we have &Velocity and &mut Position
for (velocity, position) in query.iter_mut(&mut world) {
    position.x += velocity.x;
    position.y += velocity.y;
}

您可以在基本查询中添加额外的过滤器。例如,您可以选择排除具有某些组件的实体,或者仅包括自上次查询运行以来某些组件已更改的实体(这种过滤是保守和粗粒度的)

// you can use boolean expressions when adding filters
let mut query = <(&Velocity, &mut Position)>::query()
    .filter(!component::<Ignore>() & maybe_changed::<Position>());

for (velocity, position) in query.iter_mut(&mut world) {
    position.x += velocity.dx;
    position.y += velocity.dy;
}

查询可以做更多的事情。有关更多信息,请参阅模块文档

系统

你可能已经注意到,当我们想要向组件写入时,我们需要使用 iter_mut 来遍历我们的查询。但也许你的应用程序想要能够处理不同实体上的不同组件,甚至可能同时在并行处理?虽然手动执行(参见 World::split)是可能的,但当应用程序的不同部分不知道彼此需要什么组件,或者可能甚至有冲突的访问需求时,这会非常困难。

系统调度 自动化了这个过程,甚至可以在你手动执行时更细粒度地安排工作。系统是工作单元。每个系统被定义为提供对查询和共享 资源 的访问的函数。这些系统可以被附加到调度上,调度是系统线性序列,按观察副作用(如对组件的写入)的顺序排列。调度将自动并行执行所有系统,同时保持每个系统执行视角的明显顺序。

// a system fn which loops through Position and Velocity components, and reads the Time shared resource
// the #[system] macro generates a fn called update_positions_system() which will construct our system
#[system(for_each)]
fn update_positions(pos: &mut Position, vel: &Velocity, #[resource] time: &Time) {
    pos.x += vel.dx * time.elapsed_seconds;
    pos.y += vel.dy * time.elapsed_seconds;
}

// construct a schedule (you should do this on init)
let mut schedule = Schedule::builder()
    .add_system(update_positions_system())
    .build();

// run our schedule (you should do this each update)
schedule.execute(&mut world, &mut resources);

有关更多信息,请参阅系统模块系统过程宏

功能标志

Legion 提供了一些功能标志

  • parallel - 通过 rayon 库启用并行迭代器和并行调度执行。默认启用。
  • extended-tuple-impls - 将视图和组件元组的最大大小从 8 增加到 24,但会增加编译时间。默认禁用。
  • serialize - 启用 serde 序列化模块和相关功能。默认启用。
  • crossbeam-events - 实现了 crossbeam Sender 通道的 EventSender 特性,允许它们用于事件订阅。默认启用。

WASM

Legion 默认启用并行性,但目前 Web Assembly 不支持并行,因为它以单线程运行。因此,为了构建 WASM,请确保在 Cargo.toml 中设置 default-features = false。此外,如果你想要使用 serialize 功能,你必须启用 stdwebwasm-bindgen 功能,这些功能将通过代理传递到 uuid crate。有关更多信息,请参阅 uuid crate

legion = { version = "*", default-features = false, features = ["wasm-bindgen"] }

依赖关系

~1.3–2.9MB
~55K SLoC