【问题标题】:Collection with Traits that return Self具有返回 Self 的特征的集合
【发布时间】:2019-04-08 16:47:54
【问题描述】:

我正在尝试收集实现特定特征的对象。

如果我使用返回值的特征,则此方法有效

use std::collections::BTreeMap;

struct World {
    entities: Vec<usize>,
    database: BTreeMap<usize, Box<ReadValue>>,
    //database : BTreeMap<usize,Box<ReadEcs>>, // Doesn't work
}

struct SourceInputGateway {
    entity_id: usize,
}

trait ReadValue {
    fn read(&self) -> f32;
}

impl ReadValue for SourceInputGateway {
    fn read(&self) -> f32 {
        0.0
    }
}

但是,如果我想将 Self 作为值返回,那么无论是作为方法模板参数还是关联类型,这都行不通

trait ReadEcs {
    type T;
    fn read(&self) -> &Self::T;
}

impl ReadEcs for SourceInputGateway {
    type T = SourceInputGateway;
    fn read(&self) -> &Self::T {
        self
    }
}

我想做的是有一个实现ReadEcs的类型映射,具体类型并不重要。

进一步澄清编辑

如果我通过添加来扩展示例

// Different sized type
struct ComputeCalculator {
    entity_id : usize,
    name : String,
}

impl ReadValue for ComputeCalculator {
    fn read(&self) -> f32 {
        1230.0
    }
}

那我就可以了

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn read_write() {
        let mut world = World::new();
        world.database.insert(0,Box::new(SourceInputGateway{ entity_id : 1}));
        world.database.insert(2,Box::new(ComputeCalculator{ entity_id : 2 , name : "foo".into() }));

        for (k,ref v) in world.database {
            let item : &Box<ReadValue> = v;
            item.read();
        }

    }
}

但如果我更改或添加返回 Self 的 trait 方法,我就无法做到这一点。 我想了解一种在没有不安全指针的情况下绕过它的方法。

【问题讨论】:

  • Rust 是一种静态类型语言。每个表达式都有一个在编译时已知的类型。假设编译器允许Box&lt;dyn ReadEcs&gt; 类型的特征对象,并且您在变量x 中有这样一个特征对象。现在表达式x.read() 的类型是什么?试图回答这个问题应该清楚为什么这不起作用,并给出如何解决这个问题的提示。 (另请参阅definition of object safety in the language reference。)
  • @sven 我明白这一点。但是上面的例子是说只要所有的结构都有一个read() 方法,我们确实可以把它们放在一个集合中。测试代码运行良好,并按照您的预期分别返回 0,123。
  • 我有点困惑。说“所有结构都有read() 方法”没有多大意义。两个不同特征的read() 方法是完全不相关的。要问的重要问题是结构是否实现了特定的特征。您可以为对象安全的 trait 提供 trait 对象的集合,但不能为非对象安全的 trait 提供这样的集合。如果您的 read() 方法返回关联类型,则需要指定该类型才能创建 trait 对象——Box&lt;dyn ReadEcs&lt;T = f32&gt;&gt; 应该是有效的。
  • 您的约束直接相互矛盾(您承认这一点)。要找到解决方案,您需要解释约束的哪些部分可以是灵活的,或者退后一步从底层需求重新评估。

标签: rust traits


【解决方案1】:

我对此进行了更多思考,我认为可以在保留类型安全和连续存储的所有优势的同时解决这个问题。

使用指向存储的指针定义实体管理器

struct Entities {
    entities: Vec<usize>,
    containers: Vec<Box<Storage>>,
}

存储与行为相关的数据的组件本身

struct Position {
    entity_id: usize,
    position: f32,
}

struct Velocity {
    entity_id: usize,
    velocity: f32,
}

我们将有许多组件实例,因此我们需要一个连续的内存存储,通过索引访问。

struct PositionStore {
    storage: Vec<Position>,
}

struct VelocityStore {
    storage: Vec<Velocity>,
}



trait Storage {
    // Create and delete instances of the component
    fn allocate(&mut self, entity_id: usize) -> usize;

    // Interface methods that would correspond to a base class in C++
    fn do_this(&self);
    // fn do_that(&self);
}

store 的 trait 实现了 arena 风格的存储以及它将传递给组件的方法。这可能在 ECS 的“系统”部分,但留作以后练习。

我想知道如何将构造自定义对象的 Fn() 传递给 allocate() 方法。我还没有弄明白。

impl Storage for PositionStore {
    fn allocate(&mut self, entity_id: usize) -> usize {
        self.storage.push(Position {
            entity_id,
            position: 0.0,
        });
        self.storage.len() - 1
    }

    fn run(&self) {
        self.storage.iter().for_each(|item| { println!("{}",item.position); });
    }
}

impl Storage for VelocityStore {
    fn allocate(&mut self, entity_id: usize) -> usize {
        self.storage.push(Velocity {
            entity_id,
            velocity: 0.0,
        });
        self.storage.len() - 1
    }

    fn do_this(&self) {
        self.storage.iter().for_each(|item| { println!("{}",item.velocity); });
    }
}

一些样板。

impl Default for PositionStore {
    fn default() -> PositionStore {
        PositionStore {
            storage: Vec::new(),
        }
    }
}

impl Default for VelocityStore {
    fn default() -> VelocityStore {
        VelocityStore {
            storage: Vec::new(),
        }
    }
}

我认为这可以考虑更多。它存储了组件的存储和它们的位置之间的关系

您可能希望传递一个 lambda 函数,而不是 T::default(),该函数对您的每个组件都有特定的初始化

impl Entities {
    fn register<T>(&mut self) -> usize
    where
        T: Storage + Default + 'static,
    {
        self.containers.push(Box::new(T::default()));
        self.containers.len() - 1
    }

    fn create<T>(&mut self, entity_id: usize, id: usize) -> usize {
        self.containers[id].allocate(entity_id)
    }

    fn run_loop(&self) {
        self.containers.iter().for_each(|x| x.do_this());
    }
}

一个测试用例看看这是否有效

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn arbitary() {
        let mut entities = Entities {
            entities: Vec::new(),
            containers: Vec::new(),
        };
        let velocity_store_id = entities.register::<VelocityStore>();
        let position_store_id = entities.register::<PositionStore>();

        let _ = entities.create::<Velocity>(123, velocity_store_id);
        let _ = entities.create::<Velocity>(234, velocity_store_id);
        let _ = entities.create::<Position>(234, position_store_id);
        let _ = entities.create::<Position>(567, position_store_id);

        entities.run_loop();
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-10-07
    • 2020-02-29
    • 2020-12-20
    • 1970-01-01
    • 1970-01-01
    • 2019-01-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多