13个版本 (4个破坏性更新)
1.0.0-rc6 | 2024年7月16日 |
---|---|
1.0.0-rc2 | 2024年6月21日 |
0.6.0 | 2024年6月7日 |
0.5.0 | 2023年5月10日 |
0.2.0-rc.3 | 2022年9月7日 |
#2291 in 游戏开发
每月102次下载
在 2 个包中使用 (通过 edict)
71KB
592 行
Edict 是一个快速、强大且高效的ECS包,它扩展了传统的ECS功能集。由你的同僚 🦀用Rust编写
基本用法 🌱
use edict::prelude::*;
// Create world instance.
let mut world = World::new();
// Declare some components.
#[derive(Component)]
struct Pos(f32, f32);
// Declare some more.
#[derive(Component)]
struct Vel(f32, f32);
// Spawn entity with components.
world.spawn((Pos(0.0, 0.0), Vel(1.0, 1.0)));
// Query components and iterate over views.
for (pos, vel) in world.view::<(&mut Pos, &Vel)>() {
pos.0 += vel.0;
pos.1 += vel.1;
}
// Define functions that will be used as systems.
#[edict::system::system] // This attribute is optional, but it catches if function is not a system.
fn move_system(pos_vel: View<(&mut Pos, &Vel)>) {
for (pos, vel) in pos_vel {
pos.0 += vel.0;
pos.1 += vel.1;
}
}
// Create scheduler to run systems. Requires "scheduler" feature.
use edict::scheduler::Scheduler;
let mut scheduler = Scheduler::new();
scheduler.add_system(move_system);
// Run systems without parallelism.
scheduler.run_sequential(&mut world);
// Run systems using threads. Requires "std" feature.
scheduler.run_threaded(&mut world);
// Or use custom thread pool.
特性
实体 🧩
简单ID
在 Entity 组件系统中,我们创建实体并将它们用于获取相关数据。Edict提供了 EntityId
类型来标识实体。
EntityId
是一个实体的世界唯一标识符。Edict使用ID而不生成和回收,为此它采用 u64
作为底层类型,并有一些预留空间。只需不断创建ID数百年,就可以避免耗尽。这极大地简化了 World
状态的序列化,因为它不需要对实体ID进行处理。
默认情况下,实体ID仅在单个 World
中是唯一的。对于多世界场景,Edict提供了一种方法,可以在任何所需组合的世界之间使实体ID唯一。
标识符按顺序从由 IdRange
分配的标识符范围中分配,这些范围由 IdRangeAllocator
分配。默认情况下,使用从 1 到 u64::MAX - 1
的 IdRange
,这使得默认的标识符分配非常快速。可以为 WorldBuilder
提供自定义的 IdRangeAllocator
来使用自定义的标识符范围。
例如,在客户端-服务器架构中,服务器和客户端可能使用不重叠的标识符范围。这样可以允许在服务器上序列化的状态无标识符映射地传输到客户端,这在组件引用实体时可能很麻烦。
在多服务器或 P2P 架构中,IdRangeAllocator
需要与每个服务器通信以分配不重叠的标识符范围。
人体工学实体类型
使用 ECS 可能会导致许多 .unwrap()
调用或过多的错误处理。有许多情况下可以保证实体存在(例如,它刚刚从视图中返回)。为了避免处理在无法到达时返回的 NoSuchEntity
错误,Edict 提供了扩展 Entity
特性的 AliveEntity
特性。各种方法需要 AliveEntity
处理并跳过存在性检查。
为多个实体类型实现了 Entity
和 AliveEntity
特性。
EntityId
仅实现 Entity
,因为它不提供任何保证。
EntityBound
保证是活动的,允许在不需要处理实体缺失的方法中使用它。它保持 World
借用的生命周期,使得无法从世界中取消任何实体的存在。使用错误的 World
可能会导致恐慌。可以从关系查询中获取 EntityBound
。
EntityLoc
不仅保证实体存在,还包含实体在构体中的位置,允许在访问其组件时跳过查找步骤。与 EntityBound
类似,它保持 World
借用的生命周期,使得无法从世界中取消任何实体的存在。使用错误的 World
可能会导致恐慌。可以从 Entities
查询中获取 EntityLoc
。
EntityRef
是特殊的。它没有实现 Entity
或 AliveEntity
特性,因为它不应该在全局方法中使用。相反,它提供了对实体数据的直接访问,并允许插入/删除组件等变异操作。
组件 🛠️
非线程安全类型
支持带有一些限制的 !Send
和 !Sync
组件和资源。
World
本身不是可发送的,但可以在线程之间共享。拥有 World
的线程在文档中被称为 "主" 线程。
可发送的组件和资源只能从 "主" 线程获取可变引用。可同步的组件和资源只能从 "主" 线程获取不可变引用。由于 World
的引用可能存在于 "主" 线程之外,应该使用 WorldLocal
引用,它可以使用对 World
的可变引用来创建。
具有特性和不具有特性的组件
可选的 Component
特性允许在组件首次插入时隐式注册组件类型。隐式注册使用 Component
实现定义的行为。当需要时,可以使用 WorldBuilder
来显式注册并覆盖组件行为。
非 Component
类型需要显式注册,并且使用具有 _external
后缀的少数方法代替正常方法。当 World
已经构建时,只能进行默认注册。当需要时,可以使用 WorldBuilder
来显式注册并覆盖组件行为。
实体关系 🔗
可以向一对实体添加关系,将它们绑定在一起。查询可以获取关系并通过实体与其他实体之间的关系来过滤实体。当两个实体中的任何一个被销毁时,关系就会被删除。Relation
类型可以进一步配置边界实体的行为。
查询 🔍
强大的 Query
机制,可以按组件、关系和其他标准过滤实体并获取实体数据。查询可以是可变的或不可变的,可发送的或不可发送的,有状态的或无状态的。
在 World
上使用查询会创建视图。视图可以用于遍历匹配查询的实体,并产生查询项。或者获取单个实体的数据。
ViewRef
和 ViewMut
是方便的类型别名,用于从 World
方法返回的视图类型。
运行时和编译时检查
运行时检查可用于查询可变别名避免。
ViewRef
和 ViewCell
进行运行时检查,允许存在具有别名访问的多个视图,将检查推迟到运行时,以防止无效的别名发生。
当不需要时,应使用带有编译时检查的 ViewMut
和 View
。
当期望 View
时,ViewRef
和 ViewCell
可以被锁定以创建一个 View
。
借用
组件类型可以定义从它借用的操作,借用类型可能不是有固定大小的,允许借用切片和 dyn 特性。提供了一个宏来帮助定义借用操作。提供了尝试从适合的组件中借用类型的查询。
BorrowAll
从实现请求类型借用的所有组件中借用。返回一个包含借用值的Vec
,因为实体的多个组件可能提供它。如果没有任何组件提供请求的类型,则跳过实体。BorrowAny
从实现请求类型借用的第一个合适的组件中借用。返回一个单一值。如果没有任何组件提供请求的类型,则跳过实体。BorrowOne
配置为从它应该借用的请求类型的组件中借用TypeId
。如果组件不提供请求的类型,则引发恐慌。跳过没有组件的实体。
资源 📦
名为“资源”的内置类型映射,用于单例值。资源可以插入到/从 World
中获取。资源与实体及其组件分开存在。
动作 🏃♂️
使用 ActionEncoder
来记录动作,并稍后以可变访问 World
运行它们。或者当动作不是 Send
时,使用 LocalActionEncoder
。或者方便的 WorldLocal::defer*
方法将动作推迟到内部 LocalActionEncoder
。
自动更改跟踪 🤖
每个组件实例都配备了一个纪元计数器,用于跟踪组件的最后潜在突变。查询可以读取和更新组件的纪元以跟踪变化。提供Modified
类型的查询来过滤最近更改的组件。可以通过World::epoch
方法获取最后纪元。
系统 ⚙️
系统是构建在World
上操作逻辑的一种方便方式。Edict定义了System
特质来在World
上运行逻辑。并且定义了IntoSystem
特质,用于可转换为System
的类型。
函数可以自动实现IntoSystem
- 它需要返回()
并接受实现FnArg
特质的参数。存在FnArg
实现
View
和ViewCell
来遍历实体及其组件。除非需要处理系统内视图冲突,否则请使用View
。Res
和ResMut
用于访问资源。ResLocal
和ResMutLocal
用于访问非线程安全资源。这将使系统不可发送,并强制它在主线程上运行。ActionEncoder
用于记录突变World
状态的行动,例如实体生成、插入和删除组件或资源。State
用于存储系统在运行之间的本地状态。
轻松调度 📅
Scheduler
提供用于运行System
。尽可能并行运行添加到Scheduler
的系统,但它们的行为就像按照添加顺序顺序执行一样。
如果系统不冲突,它们可以并行执行。
如果系统冲突,首先添加的系统将在添加较晚的系统开始之前执行。
可以使用std
线程或rayon
作为执行器。用户可以通过实现ScopedExecutor
特质来提供自定义执行器。
需要启用默认启用的"scheduler"
功能。
钩子 🎣
组件替换/删除钩子会在组件被替换或删除时自动调用。
当组件注册时,可以为其添加钩子,以便在组件值被替换或删除时调用。隐式注册 Component
类型将注册在特定义义上的钩子。
删除钩子在通过 World::drop
或实体被销毁且未调用时被调用。
替换钩子在组件被替换时调用,例如组件被插入到实体中且实体已经具有相同类型的组件。替换钩子返回一个布尔值,表示是否应调用替换组件的删除钩子。
钩子可以将操作记录到提供的 LocalActionEncoder
中,该操作将在导致钩子被调用的 World
方法返回之前执行。
当组件实现 Component
特性时,特定义义上定义的钩子将自动注册以调用 Component::on_drop
和 Component::on_replace
方法。它们可以使用 WorldBuilder
重写自定义钩子。对于非 Component
类型,只能通过 WorldBuilder
注册钩子。默认使用 World
的注册不会注册任何钩子。
异步-等待 ⏳
用于运行需要等待特定条件或事件或跨越多个时钟周期的逻辑的异步执行器。
需要等待的逻辑可能很难使用系统实现。系统在循环中运行,通常处理具有某些组件的每个实体。实现等待逻辑需要向现有或新组件添加等待状态,逻辑将分散在多个系统运行或多个系统之间。
异步可以用来等待某些条件或事件。可以访问 ECS 数据的异步称为 "flows"。
可以使用 World
的 World::spawn_flow
或 FlowWorld::spawn_flow
方法在 Flows
中启动 "flows"。该类型用作执行器来运行启动的 "flows"。
流可以被绑定到实体,并通过使用World::spawn_flow_for
、FlowWorld::spawn_flow_for
、EntityRef::spawn_flow
或FlowEntity::spawn_flow
方法进行生成。当实体被销毁时,此类流将被取消。
返回future的函数可以作为流。对于World::spawn_flow
,使用签名FnOnce(FlowWorld) -> Future
的函数或闭包。对于World::spawn_flow_for
,使用签名FnOnce(FlowEntity) -> Future
的函数或闭包。
用户可以使用FlowWorld
和FlowEntity
的poll
方法实现低级别的future,以访问任务Context
。Edict只提供了一两个低级别的future,用于执行等待操作。
yield_now!
将控制权一次性地交给执行器,并在下一次执行时恢复。FlowEntity::wait_despawned
等待实体被销毁。FlowEntity::wait_has_component
等待实体获得一个组件。
WakeOnDrop
组件可以在销毁实体时唤醒一个任务。
建议使用流处理跨多个tick的高级逻辑,使用系统处理每个tick运行的底层逻辑。流可以通过向实体添加特殊组件来请求系统执行操作。系统也可以通过生成流来执行长时间运行的操作。
需要flow
功能,默认启用。
no_std支持
Edict可以在no_std
环境中使用,但需要alloc
crate。默认启用std
功能。
如果未启用"std"功能,错误类型将不会实现std::error::Error
。
当启用"flow"功能且未启用"std"时,使用外部函数实现TLS。应用程序必须提供这些函数的实现,否则链接将失败。
当启用"scheduler"功能且未启用"std"时,使用外部函数实现线程挂起。应用程序必须提供这些函数的实现,否则链接将失败。
许可证
根据以下任一许可证授权:
- Apache许可证,版本2.0(license/APACHE 或 https://apache.ac.cn/licenses/LICENSE2.0)
- MIT许可证(license/MIT 或 http://opensource.org/licenses/MIT)
由你选择。
贡献
除非你明确声明,否则根据Apache-2.0许可证定义,你有意提交以包含在作品中的任何贡献,应按上述方式双许可,不附加任何额外条款或条件。
依赖
~340–790KB
~19K SLoC