12 个版本 (7 个破坏性更新)
0.9.1 | 2023 年 8 月 10 日 |
---|---|
0.9.0 | 2023 年 4 月 22 日 |
0.8.2 | 2023 年 4 月 3 日 |
0.7.0 | 2023 年 3 月 28 日 |
0.3.0 |
|
#49 in 游戏开发
每月 82 次下载
1MB
21K SLoC
brood
快速灵活的 实体组件系统 库。
brood
从头开始构建,旨在使用户感觉舒适,同时速度尽可能快,甚至可能比其他流行的实体组件系统(通常缩写为 ECS)库更快。 brood
使用异构列表来允许任意数量的组件集合,这意味着您的实体大小或系统视图范围没有限制。 所有您期望从标准 ECS 库中获得的功能都存在,包括与 serde
和 rayon
库的互操作性,分别用于序列化和并行处理。
主要功能
用法
使用 brood
有两个主要方面:存储实体和操作实体。
存储实体
在存储实体之前,应建立以下定义
- 组件:单个数据块。在本文库中,它是指实现了
Any
特质的任何类型。 - 实体:一组组件。这些是通过
entity!()
宏定义的。 - 世界:实体的容器。
通过简单地定义其类型来定义组件。例如,以下 struct
是组件
struct Position {
x: f32,
y: f32,
}
struct Velocity {
x: f32,
y: f32,
}
为了在 World
容器中使用这些组件,它们需要被包含在一个 Registry
中,并在创建时提供给 World
。可以使用 Registry!()
宏来创建一个 Registry
。
use brood::Registry;
type Registry = Registry!(Position, Velocity);
然后可以使用这个 Registry
创建一个 World
,并将实体存储在其中。
use brood::{entity, World};
let mut world = World::<Registry>::new();
// Store an entity inside the newly created World.
let position = Position {
x: 3.5,
y: 6.2,
};
let velocity = Velocity {
x: 1.0,
y: 2.5,
};
world.insert(entity!(position, velocity));
请注意,上面存储在 world
中的实体可以由 Registry
的任何子集组成,并且可以以任何顺序提供。
操作实体
要操作存储在 World
中的实体,必须使用 System
。System
被定义为操作包含指定组件集的任何实体,读取和修改这些组件。以下是如何定义和运行一个示例系统的示例
use brood::{query::{filter, result, Views}, registry, system::System};
struct UpdatePosition;
impl System for UpdatePosition {
type Filter: filter::None;
type Views<'a>: Views!(&'a mut Position, &'a Velocity);
type ResourceViews: Views!();
type EntryViews: Views!();
fn run<'a, R, S, I, E>(
&mut self,
query_results: Result<R, S, I, Self::ResourceViews<'a>, Self::EntryViews<'a>, E>,
) where
R: registry::Registry,
I: Iterator<Item = Self::Views<'a>>,
{
for result!(position, velocity) in query_results.iter {
position.x += velocity.x;
position.y += velocity.y;
}
}
}
world.run_system(&mut UpdatePosition);
此系统将操作包含 Position
和 Velocity
组件的每个实体(无论它们可能包含哪些其他组件),使用 Velocity
组件中的值就地更新 Position
组件。
对于更复杂的 System
,有许多选项,包括可选组件、自定义过滤器以及后处理逻辑。请参阅文档以获取更多信息。
序列化/反序列化
brood
提供了对使用 serde
的序列化和反序列化的第一级支持。通过启用 serde
软件包功能,可以使用 serde
的 Serializer
和 Deserializer
将 World
容器和它们包含的实体进行序列化和反序列化。请注意,只要 World
的 Registry
中的每个组件都是可序列化和反序列化的,World
就是可序列化和反序列化的。
例如,可以将 World
序列化为 bincode
(并从同一文件反序列化)如下所示
use brood::{entity, Registry, World};
#[derive(Deserialize, Serialize)]
struct Position {
x: f32,
y: f32,
}
#[derive(Deserialize, Serialize)]
struct Velocity {
x: f32,
y: f32,
}
type Registry = Registry!(Position, Velocity);
let mut world = World::<Registry>::new();
// Insert several entities made of different components.
world.insert(entity!(Position {
x: 1.0,
y: 1.1,
});
world.insert(entity!(Velocity {
x: 0.0,
y: 5.0,
});
world.insert(entity!(Position {
x: 4.2,
y: 0.1,
}, Velocity {
x: 1.1,
y: 0.4,
});
let encoded = bincode::serialize(&world).unwrap();
let decoded_world = bincode::deserialize(&encoded).unwrap();
请注意,根据序列化器和解序列化器是否是 可读的,存在两种序列化模式。可读的序列化将按行序列化实体,这较慢但更容易被人阅读。不可读的序列化将按列序列化实体,这要快得多,但手动阅读要困难得多。
并行处理
brood
通过 rayon
支持并行处理。通过启用 rayon
软件包功能,可以并行化对 World
的操作。
并行操作实体
为了并行化系统对实体的操作(通常称为内部并行化),可以使用 ParSystem
而不是标准的 System
。这将允许将 ParSystem
的操作分散到多个 CPU 上。例如,可以定义如下 ParSystem
use brood::{entity, query::{filter, result, Views}, Registry, registry, World, system::ParSystem};
use rayon::iter::ParallelIterator;
struct Position {
x: f32,
y: f32,
}
struct Velocity {
x: f32,
y: f32,
}
type Registry = Registry!(Position, Velocity);
let mut world = World::<Registry>::new();
// Store an entity inside the newly created World.
let position = Position {
x: 3.5,
y: 6.2,
};
let velocity = Velocity {
x: 1.0,
y: 2.5,
};
world.insert(entity!(position, velocity));
struct UpdatePosition;
impl ParSystem for UpdatePosition {
type Filter: filter::None;
type Views<'a>: Views!(&'a mut Position, &'a Velocity);
type ResourceViews: Views!();
type EntryViews: Views!();
fn run<'a, R, S, I, E>(
&mut self,
query_results: Result<R, S, I, Self::ResourceViews<'a>, Self::EntryViews<'a>, E>,
) where
R: registry::Registry,
I: ParallelIterator<Item = Self::Views<'a>>,
{
query_results.iter.for_each(|result!(position, velocity)| {
position.x += velocity.x;
position.y += velocity.y;
});
}
}
world.run_par_system(&mut UpdatePosition);
定义 ParSystem
与定义 System
非常相似。请参阅文档以获取更多定义选项。
并行运行系统
通过定义一个 Schedule
,可以并行运行多个 System
和 ParSystem
。一个 Schedule
将自动将 System
划分为阶段,每个阶段可以同时运行。这些阶段旨在确保它们不会违反 Rust 的借用和可变性规则,并且完全安全使用。
定义并运行一个包含多个 System
的 Schedule
,如下所示
use brood::{entity, query::{filter, result, Views}, Registry, registry, World, system::{schedule, schedule::task, System}};
struct Position {
x: f32,
y: f32,
}
struct Velocity {
x: f32,
y: f32,
}
struct IsMoving(bool);
type Registry = Registry!(Position, Velocity, IsMoving);
let mut world = World::<Registry>::new();
// Store an entity inside the newly created World.
let position = Position {
x: 3.5,
y: 6.2,
};
let velocity = Velocity {
x: 1.0,
y: 2.5,
};
world.insert(entity!(position, velocity, IsMoving(false)));
struct UpdatePosition;
impl System for UpdatePosition {
type Filter: filter::None;
type Views<'a>: Views!(&'a mut Position, &'a Velocity);
type ResourceViews: Views!();
type EntryViews: Views!();
fn run<'a, R, S, I, E>(
&mut self,
query_results: Result<R, S, I, Self::ResourceViews<'a>, Self::EntryViews<'a>, E>,
) where
R: registry::Registry,
I: Iterator<Item = Self::Views<'a>>,
{
for result!(position, velocity) in query_results.iter {
position.x += velocity.x;
position.y += velocity.y;
}
}
}
struct UpdateIsMoving;
impl System for UpdateIsMoving {
type Filter: filter::None;
type Views<'a>: Views!(&'a Velocity, &'a mut IsMoving);
type ResourceViews: Views!();
type EntryViews: Views!();
fn run<'a, R, S, I, E>(
&mut self,
query_results: Result<R, S, I, Self::ResourceViews<'a>, Self::EntryViews<'a>, E>,
) where
R: registry::Registry,
I: Iterator<Item = Self::Views<'a>>,
{
for result!(velocity, is_moving) in query_results.iter {
is_moving.0 = velocity.x != 0.0 || velocity.y != 0.0;
}
}
}
let mut schedule = schedule!(task::System(UpdatePosition), task::System(UpdateIsMoving));
world.run_schedule(&mut schedule);
请注意,阶段是由每个 System
的 Views
决定的。那些 Views
不包含组件的冲突可变借用 System
被分组到单个阶段中。
最低支持的 Rust 版本
本软件包保证在稳定版本的 rustc 1.65.0
及更高版本上编译。
许可证
本项目许可协议为以下之一
- Apache License, Version 2.0 (LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
任选其一。
贡献
除非您明确说明,否则根据 Apache-2.0 许可证定义的,您有意提交的任何贡献,包括在本工作中使用的内容,都将按上述方式双重许可,不附加任何额外条款或条件。
依赖关系
~0.5–1.1MB
~19K SLoC