提问人:LucioleMaléfique 提问时间:10/16/2023 更新时间:10/17/2023 访问量:85
对类型进行编译时操作
Compile time operations on types
问:
我正在从头开始重新设计我的 ECS,这次选择了原型。
原型 ECS 的核心思想是,我有一个映射,该映射将原型(组件类型的元组)ID 绑定到包含该原型的所有实体(以及组件数据)的存储桶。
我想要什么:
我虽然认为我的实体是通用的,而不是它们的原型,但要对每个实体具有哪些组件有编译时间知识,并保证方法的结果。get_component
pub struct Entity<Archetype> {
archetype_marker: PhantomData<Archetype>,
index: usize,
}
对于 Rust 类型,我认为原型应该是类型的直接元组,因为它是最有意义的。例如,是一个原型。可以使用 TypeId 计算 ID。(String, usize, u32)
我已经有一个基本结构:
struct ComponentTable {
// for each archetype, store the entities components.
components: ArchetypeMap,
}
struct ComponentBucket<Archetype> {
entities: HashMap<usize, Archetype>,
last_index: usize,
}
这是一个类似于AnyMap板条箱的地图,除了我在mapp中强制执行值之外,我映射到该位置:ArchetypeMap
Archetype
ComponentBucket<Archetype>
AbstractBucket
pub trait AbstractBucket {
fn query<Archetype>(&self) -> Option<impl Iterator<Type = Archetype>>;
}
这里的目标是能够遍历所有桶进行迭代,即使使用原型映射提供的类型 ereasure。
现在,ECS 需要能够创建/删除实体,这对于这种方法来说是微不足道的。
impl ComponentTable {
pub fn create_entity<Archetype: 'static>(&mut self, components: Archetype) -> Entity<Archetype> {
match self.components.get_mut::<ComponentBucket<Archetype>>() {
Some(bucket) => bucket.create_entity(components),
None => {
let mut bucket = ComponentBucket::default();
let entity = bucket.create_entity(components);
self.components.insert::<ComponentBucket<Archetype>>(bucket);
entity
}
}
}
pub fn remove_entity<Archetype: 'static>(&mut self, entity: Entity<Archetype>) -> Option<Archetype> {
self.components.get_mut::<ComponentBucket<Archetype>>()?.remove_entity(entity)
}
}
impl<Archetype> ComponentBucket<Archetype> {
pub fn create_entity(&mut self, components: Archetype) -> Entity<Archetype> {
let index = self.last_index;
self.last_index += 1;
self.entities.insert(index, components);
Entity::new(index)
}
pub fn remove_entity(&mut self, entity: Entity<Archetype>) -> Option<Archetype> {
self.entities.remove(&entity.index())
}
}
添加/删除组件可以通过明确完整的原型元组来完成,但我宁愿有一个适用于每个原型的方法,因为这个实现将需要根据实体的原型大小使用许多不同的名称:
impl ComponentTable {
pub fn add_component<T1: 'static, T2: 'static, C: 'static>(&mut self, entity: Entity<(T1, T2)>, component: C) -> Option<Entity<(T1, T2, C)>> {
let archetype = self.remove_entity(entity)?;
let new_entity = self.create_entity((archetype.0, archetype.1, component));
Some(new_entity)
}
pub fn remove_component<T1: 'static, T2: 'static, C: 'static>(&mut self, entity: Entity<(T1, T2, C)>) -> Option<Entity<(T1, T2)>> {
let archetype = self.remove_entity(entity)?;
let new_entity = self.create_entity((archetype.0, archetype.1));
Some(new_entity)
}
}
现在这就是我卡住的地方,试图为我的桶实现特征:AbstractBucket
impl<BucketArchetype> AbstractBucket for ComponentBucket<BucketArchetype> {
fn query<Archetype>(&self) -> Option<()> {
// ????
}
}
我怎么能有办法将类型“解包”为类型的元组?看起来它没有意义,因为我的类型不一定是元组,但如果它们不是,我们可以将它们视为一个元素的元组。
此函数应:
如果 是 的子原型,则返回原型的迭代器(具有从存储桶原型到请求原型的映射)。否则,返回 .Archetype
BucketArchetype
None
有了 Rust 强大的类型系统,我觉得我想要实现的都是可行的,但我要么没有构建它,要么找不到编译器无法理解的示例并证明我的设计无法工作。
我查看了现有原型 ECS 的示例,但它们似乎都在运行时保留了原型值,我试图在编译时完成大部分工作。
我还研究了可泛型,因为这看起来很合适,但它们在 Rust 中不可用。
最后,我尝试在编译时对类型执行操作,例如将类型添加到类型元组并以这种方式构建新类型。这看起来像是在编译时可行的操作,但我正在努力。也许 Rust 不够强大?也许我需要一些更强大的架构来构建我的原型系统?
答:
如果 Archetype 是 BucketArchetype 的子原型,则返回 原型上的迭代器(使用来自存储桶原型的映射 到询问的原型)。否则,返回 None。
这听起来像是您可能想表达对创建原型的能力的限制:From,也许是这样的:
ecs_contract.rs :
pub struct Entity<Archetype> {
archetype_marker: PhantomData<Archetype>,
index: usize,
}
struct ComponentTable {
// for each archetype, store the entities components.
components: ArchetypeMap, // you must clarify this type!
}
struct ComponentBucket<Archetype> {
entities: HashMap<usize, Archetype>,
last_index: usize,
}
// functionalize the entities to enable more sensible default impl of AbstractBucket
pub trait Entities<A> {
fn entities(&self) -> HashMap<usize, A>;
}
// note: you probably want to use lifetimes to avoid cloning your underlying bucket data to make this iterator (assuming sub archetypes are subsets of archetypes)
pub trait AbstractBucket<BucketArchetype>: Entities<BucketArchetype> {
fn query<Archetype>(&self) -> Option<impl Iterator<Type = Archetype>>
where Archetype: From<BucketArchetype> {
Some(self.entities().values().map(Archetype::from))
}
}
ecs_impls.rs :
impl ComponentTable {
pub fn create_entity<Archetype: 'static>(&mut self, components: Archetype) -> Entity<Archetype> {
match self.components.get_mut::<ComponentBucket<Archetype>>() {
Some(bucket) => bucket.create_entity(components),
None => {
let mut bucket = ComponentBucket::default();
let entity = bucket.create_entity(components);
self.components.insert::<ComponentBucket<Archetype>>(bucket);
entity
}
}
}
pub fn remove_entity<Archetype: 'static>(&mut self, entity: Entity<Archetype>) -> Option<Archetype> {
self.components.get_mut::<ComponentBucket<Archetype>>()?.remove_entity(entity)
}
}
impl<Archetype> ComponentBucket<Archetype> {
pub fn create_entity(&mut self, components: Archetype) -> Entity<Archetype> {
let index = self.last_index;
self.last_index += 1;
self.entities.insert(index, components);
Entity::new(index)
}
pub fn remove_entity(&mut self, entity: Entity<Archetype>) -> Option<Archetype> {
self.entities.remove(&entity.index())
}
}
// default implementation
impl<BucketArchetype> AbstractBucket for ComponentBucket<BucketArchetype> {}
为了保存多种原型,你可能想要创建一个嵌套结构,其中 ComponentTable 保存一个哈希图的哈希图,每种 Archetype 一个哈希图,你可能需要对 ArchetypeMap 的行为进行更宽松的功能定义。您能否提供有关如何实现多种类型的映射的更多详细信息?
改善这一切的一个简单方法是更多地关注特征,使特征类似于您想要使用的事物的形式,然后制作适合它们的结构;然后,您可以轻松地在以后的不同结构上重新实现这些特征。即原型、实体、组件,这些是特征,那么 TupleArchetype 是一个结构,MapArchetype 是一个结构,等等......函数抽象接口总是允许多个结构具体实现。
一个想法是使原型映射的功能镜头化成为原型类的原型方法。然后,原型将插入和删除它们自己在原型树的分支中,而不是处理各种原型的组件表。对不起,我试图解决这个问题,但我对地图上的类型感到困惑。希望这对您有所帮助!
评论
HashMap<TypeId, Vec<u8>>
T
ArchtypeBucket<A>
评论