#ecs #component #systems #system #entity #parameters #thread

thinkofname/think_ecs

Univercity游戏源代码:https://store.steampowered.com/app/808160/UniverCity/

1个不稳定版本

0.1.0 2020年1月26日

#762 in 并发

157 星标 & 8 关注者

GPL-3.0-or-later

97KB
2.5K SLoC

一个不带组件锁的多线程实体组件系统

特性

  • 快速,旨在具有低开销
  • 线程化,尽可能自动地线程化系统
  • 简单,在API使用方面没有复杂之处

工作原理

系统通过使用参数声明它们希望访问的组件。一个 Read<T> 参数声明系统只希望以只读方式访问组件,而 Write<T> 声明系统可以修改组件(添加/删除/编辑)。

内部调度器将使用固定数量的线程执行所有系统,只允许可以在任何给定时间安全运行的系统运行。该系统的规则很简单:只要没有写入者,组件可以同时拥有任意数量的读者。只有一个系统可以写入组件,并且在系统持有对组件的写入访问权时,不允许读者。

使用方法

首先,所有用作组件的结构体都必须实现 Component 特性。这可以通过 component! 宏来完成。 (有关不同类型,请参阅 component! 的文档)。然后必须将组件注册到 Container 中。

struct Position {
    x: i32,
    y: i32,
}
component!(Position => Vec);
let mut c = Container::new();
c.register_component::<Position>();

现在可以使用容器创建实体,添加组件并访问它们。

let entity = c.new_entity();
c.add_component(entity, Position { x: 5, y: 10});
// Mutable access to the component
{
    let pos = c.get_component_mut::<Position>(entity).unwrap();
    pos.x += 4;
}
// Immutable access to the component
assert_eq!(c.get_component::<Position>(entity), Some(&Position { x: 9, y: 10}));

实体通常通过系统进行处理。系统仅仅是函数。您可以通过Systems类型注册要运行的一组函数。当运行Systems时,系统将根据其参数以及当前正在运行的其他系统自动决定何时运行。系统的运行顺序没有定义。

函数至少需要一个参数,即一个EntityManager引用。这提供了一个接口来创建和遍历系统中的所有实体。其他参数必须是Read<T>Write<T>引用,其中T是组件类型。Read提供对组件的不可变访问,而Write提供可变访问(包括添加/删除组件)。ReadWrite类型都提供了一个mask方法,可以与EntityManageriter_mask方法一起使用来遍历实体的子集。掩码可以像以下这样链接,以遍历多个类型的交集:pos.mask().and(vel)

let mut sys = Systems::new();
closure_system!(fn example(em: EntityManager, mut pos: Write<Position>) {
    let mask = pos.mask();
    for e in em.iter_mask(&mask) {
        let pos = pos.get_component_mut(e).unwrap();
        pos.y -= 3;
    }
});
sys.add(example);
sys.run(&mut c);

问题/问题

  • 在系统中移除实体实际上不会在所有系统执行完毕之前将其移除。

依赖关系

~1.5MB
~25K SLoC