我正在尝试在 Rust 中实现一个基本的ECS。我想要一个数据结构,为每个组件存储该特定组件的存储。因为有些组件很常见,而有些则很少,所以我需要不同类型的存储策略,例如VecStorage<T>
和HashMapStorage<T>
。
由于游戏引擎的 ECS 不知道组件,我想出了:
trait AnyStorage: Debug {
fn new() -> Self
where
Self: Sized;
}
#[derive(Default, Debug)]
struct StorageMgr {
storages: HashMap<TypeId, Box<AnyStorage>>,
}
Run Code Online (Sandbox Code Playgroud)
使用VecStorage
和HashMapStorage<T>
实现AnyStorage
特性。既然AnyStorage
不知道T
,我添加了一个由两个具体存储实现的特性:ComponentStorage<T>
.
虽然我能够注册新组件(即Box<AnyStorage>
在StorageMgr
's 中storages
),但我没有找到插入组件的方法。
这是错误的代码:
pub fn add_component_to_storage<C: Component>(&mut self, component: C) {
let storage = self.storages.get_mut(&TypeId::of::<C>()).unwrap();
// storage is of type: &mut Box<AnyStorage + 'static>
println!("{:?}", storage); // Prints "VecStorage([])"
storage.insert(component); // This doesn't work
// This neither:
// let any_stor: &mut Any = storage;
// let storage = any_stor.downcast_ref::<ComponentStorage<C>>();
}
Run Code Online (Sandbox Code Playgroud)
我知道我的问题来自于它storage
的类型是&mut Box<AnyStorage>
; 我可以从中获得混凝土VecStorage
吗?
做这一切的重点是我希望组件在内存中是连续的,并且每个组件类型都有不同的存储。我无法解决自己使用Box<Component>
,或者我不知道如何使用。
我将我的问题简化为Rust Playground 上的最少代码。
我不确定这样的事情是否可能,但我终于想通了。关于您发布的示例失败的原因,有几件事需要注意。
AnyStorage
您的示例中的Trait没有实现ComponentStorage<T>
,因此因为您将“存储”存储在 a 中HashMap<TypeId, Box<AnyStorage>>
,Rust 无法保证每个存储类型都实现,ComponentStorage<T>::insert()
因为它只知道它们是AnyStorage
s。Storage<T>
并将它们存储在 a 中HashMap<TypeId, Box<Storage<T>>
,那么每个版本Storage
都必须存储相同的类型,因为单个T
. Rust 没有办法根据键的 TypeId 动态键入映射的值,因为这样的解决方案需要。此外,您不能替换T
withAny
因为Any
is not Sized
,这Vec
和所有其他存储类型都需要。我猜您知道所有这些,这就是您在原始示例中使用两个不同特征的原因。我结束了该解决方案使用存储在Storage<T>
S作为Any
S IN一个HashMap<TypeId, Box<Any>>
,然后我downcasted的Any
s转换Storage<T>
中的执行函数s StorageMgr
。我已经把一个小例子下面,和一个完整的版本是铁锈游乐场这里
。
trait Component: Debug + Sized + Any {
type Storage: Storage<Self>;
}
trait Storage<T: Debug>: Debug + Any {
fn new() -> Self
where
Self: Sized;
fn insert(&mut self, value: T);
}
struct StorageMgr {
storages: HashMap<TypeId, Box<Any>>,
}
impl StorageMgr {
pub fn new() -> Self {
Self {
storages: HashMap::new(),
}
}
pub fn get_storage_mut<C: Component>(&mut self) -> &mut <C as Component>::Storage {
let type_id = TypeId::of::<C>();
// Add a storage if it doesn't exist yet
if !self.storages.contains_key(&type_id) {
let new_storage = <C as Component>::Storage::new();
self.storages.insert(type_id, Box::new(new_storage));
}
// Get the storage for this type
match self.storages.get_mut(&type_id) {
Some(probably_storage) => {
// Turn the Any into the storage for that type
match probably_storage.downcast_mut::<<C as Component>::Storage>() {
Some(storage) => storage,
None => unreachable!(), // <- you may want to do something less explosive here
}
}
None => unreachable!(),
}
}
}
Run Code Online (Sandbox Code Playgroud)