在 Rust 中存储具有泛型类型参数的异构类型的集合

tot*_*olo 4 rust

我正在尝试在 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)

使用VecStorageHashMapStorage<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 上的最少代码。

Sab*_*nim 6

我不确定这样的事情是否可能,但我终于想通了。关于您发布的示例失败的原因,有几件事需要注意。

  1. AnyStorage您的示例中的Trait没有实现ComponentStorage<T>,因此因为您将“存储”存储在 a 中HashMap<TypeId, Box<AnyStorage>>,Rust 无法保证每个存储类型都实现,ComponentStorage<T>::insert()因为它只知道它们是AnyStorages。
  2. 如果你确实将这两个特征组合成一个简单的调用Storage<T>并将它们存储在 a 中HashMap<TypeId, Box<Storage<T>>,那么每个版本Storage都必须存储相同的类型,因为单个T. Rust 没有办法根据键的 TypeId 动态键入映射的值,因为这样的解决方案需要。此外,您不能替换TwithAny因为Anyis not Sized,这Vec和所有其他存储类型都需要。我猜您知道所有这些,这就是您在原始示例中使用两个不同特征的原因。

我结束了该解决方案使用存储在Storage<T>S作为AnyS IN一个HashMap<TypeId, Box<Any>>,然后我downcasted的Anys转换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)