我正在从头开始重新设计我的ECS,这次我选择了原型。
原型ECS的核心思想是,我有一个Map,将原型(组件类型的元组)id绑定到包含该原型的所有实体(以及组件数据)的桶。
我想要的是:
我认为我的实体在它们的原型上是通用的,以便在编译时了解每个实体有什么组件,并保证get_component
方法的结果。
pub struct Entity<Archetype> {
archetype_marker: PhantomData<Archetype>,
index: usize,
}
对于Rust类型,我认为原型应该是直接的类型元组,因为这是最有意义的。例如,(String, usize, u32)
是一个原型。id可以用TypeId来计算。
我已经有了一个基本的结构:
struct ComponentTable {
// for each archetype, store the entities components.
components: ArchetypeMap,
}
struct ComponentBucket<Archetype> {
entities: HashMap<usize, Archetype>,
last_index: usize,
}
ArchetypeMap
是一个类似于the AnyMap crate的Map,其中我将Archetype
Map到ComponentBucket<Archetype>
,除了我在mapp中强制实现AbstractBucket
trait的值:
pub trait AbstractBucket {
fn query<Archetype>(&self) -> Option<impl Iterator<Type = Archetype>>;
}
这里的目标是能够覆盖所有的桶进行迭代,即使使用原型Map提供的类型保证。
现在,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)
}
}
现在这是我卡住的地方,试图为我的bucket实现AbstractBucket
trait:
impl<BucketArchetype> AbstractBucket for ComponentBucket<BucketArchetype> {
fn query<Archetype>(&self) -> Option<()> {
// ????
}
}
我怎么能有办法把类型解包成类型的元组呢?这看起来没有意义,因为我的类型不一定是元组,但如果它们不是,我们可以将它们视为一个元素的元组。
这一职能应:如果Archetype
是BucketArchetype
的子原型,则返回原型上的迭代器(具有从桶原型到请求原型的Map)。否则,返回None
。
有了Rust强大的类型系统,我觉得我试图实现的是可行的,但我既没有构建它,也没有找到编译器无法理解的一些例子,证明我的设计无法工作。
我看过现有原型ECS的例子,但它们似乎都在运行时保持原型的价值,而我试图在编译时完成大部分工作。
我还研究了variadic泛型,因为这看起来很适合,但它们在Rust中不可用。
最后,我尝试在编译时对类型执行操作,比如将类型添加到类型元组,并以这种方式构建新类型。这看起来像是在编译时可以执行的操作,但我正在努力解决它。也许Rust不够强大?也许我需要一些更强大的架构为我的原型系统?
1条答案
按热度按时间x33g5p2x1#
如果Archetype是BucketArchetype的子原型,则返回原型上的迭代器(具有从bucket原型到asked原型的Map)。否则,返回None。
这听起来像是你可能想表达一个创建原型的能力的界限:从,也许像:
ecs_contract.rs:
ecs_impls.rs:
要保存多种原型,您可能希望创建一个嵌套结构,其中的conceptTable保存一个hashmap的hashmap,每种原型一个hashmap,您可能需要ArchetypeMap行为的更宽松的函数定义。你能提供更多关于你想如何实现多类型Map的细节吗?
一个简单的方法来改善这一切是更多地关注的特点,使特点类似于你想要使用的东西的形式,然后只是使结构,以适应他们之后;然后你可以很容易地在不同的结构上重新实现traits。即原型,实体,组件,这些都是特征,然后TupleArchetype是一个结构,MapArchetype是一个结构,等等......功能抽象接口总是允许多个结构具体实现。
一个想法是使原型Map的函数透镜化成为原型类上的原型方法。然后,不是组件表处理所有类型的原型,原型将从原型树的分支中插入和删除自己。对不起,我试图解决这个问题,但我对Map上的类型感到困惑。希望这能帮上一点忙!