7个版本

0.2.5 2019年1月6日
0.2.4 2018年10月30日
0.1.0 2018年10月19日

#125 in #linear

每月下载 29次

MIT 许可证

415KB
919

Pyro

线性实体组件系统

LICENSE LICENSE Documentation Crates.io Version

基准测试

ecs_bench

bench defense


lib.rs:

什么是实体组件系统?

实体组件系统(ECS)与关系数据库(如SQL)非常相似。`World` 是数据存储,其中游戏对象(也称为 `Entity`)生存。`Entity` 包含数据或 `Component`。ECS 可以高效地查询这些组件。

给我所有具有位置和速度组件的实体,然后根据速度更新位置。

type PosVelQuery = (Write<Pos>, Read<Vel>);
//                  ^^^^^       ^^^^
//                  Mutable     Immutable
world.matcher::<All<PosVelQuery>>().for_each(|(pos, vel)|{
    pos += vel;
})

内部

概述

  • 迭代始终是 线性的
  • 不同的组件组合存在于单独的存储中
  • 移除实体不会创建空洞。
  • 所有操作都设计为批量使用。
  • 运行时强制执行借用规则。见 RuntimeBorrow
  • Entity 使用包装的代际索引。见 Entity::version
// A Storage that contains `Pos`, `Vel`, `Health`.
(
   [Pos1, Pos2, Pos3, .., PosN],
   [Vel1, Vel2, Vel3, .., VelN],
   [Health1, Health2, Health3, .., HealthN],
)

// A Storage that contains `Pos`, `Vel`.
(
   [Pos1, Pos2, Pos3, .., PosM]
   [Vel1, Vel2, Vel3, .., VelM]
)

迭代完全线性,除了跳转到不同的存储。

上述查询的迭代模式将是

positions:  [Pos1, Pos2, Pos3, .., PosN], [Pos1, Pos2, Pos3, .., PosM]
velocities: [Vel1, Vel2, Vel3, .., VelN], [Vel1, Vel2, Vel3, .., VelM]
                                        ^
                                        Jump occurs here

跳跃类似于两个迭代器的链。我们查看所有匹配特定查询的存储。如果查询是 `Write`,则我们会查找包含位置数组的所有存储,提取迭代器并将它们链接起来

每个组件组合都将存在于单独的存储中。这保证了迭代始终是线性的。

基准测试

入门

extern crate pyro;
use pyro::{ World, Entity, Read, Write, All, SoaStorage };
struct Position;
struct Velocity;


// By default creates a world backed by a [`SoaStorage`]
let mut world: World<SoaStorage> = World::new();
let add_pos_vel = (0..99).map(|_| (Position{}, Velocity{}));
//                                 ^^^^^^^^^^^^^^^^^^^^^^^^
//                                 A tuple of (Position, Velocity),
//                                 Note: Order does *not* matter

// Appends 99 entities with a Position and Velocity component.
world.append_components(add_pos_vel);

// Appends a single entity
world.append_components(Some((Position{}, Velocity{})));

// Requests a mutable borrow to Position, and an immutable borrow to Velocity.
// Common queries can be reused with a typedef like this but it is not necessary.
type PosVelQuery = (Write<Position>, Read<Velocity>);

// Retrieves all entities that have a Position and Velocity component as an iterator.
world.matcher::<All<PosVelQuery>>().for_each(|(pos, vel)|{
   // ...
});

// The same query as above but also retrieves the entities and collects the entities into a
// `Vec<Entity>`.
let entities: Vec<Entity> =
    world.matcher_with_entities::<All<PosVelQuery>>()
    .filter_map(|(entity, (pos, vel))|{
        Some(entity)
    }).collect();

// Removes all the entities
world.remove_entities(entities);
let count = world.matcher::<All<PosVelQuery>>().count();
assert_eq!(count, 0);

依赖

~3MB
~56K SLoC