简体   繁体   English

改变指向向量元素的不可变引用

[英]Mutate a immutable reference that points to vector element

I want to have a struct named Outcome, which holds references to entities.我想要一个名为 Outcome 的结构,它包含对实体的引用。 I then want to find the entity that it points to, borrow it mutably and change it according to an effect from Outcome.然后我想找到它指向的实体,可变地借用它并根据 Outcome 的效果改变它。 My code now looks like this我的代码现在看起来像这样

fn main() {
    let mut entities = vec![
        Entity {
            name: "George".to_string(),
            current_hp: 200.0,
            damage: 10.0,
        },
        Entity {
            name: "Jacob".to_string(),
            current_hp: 100.0,
            damage: 5.0,
        },
    ];

    let outcome = Outcome {
        caster: &entities[0],
        target: &entities[1],
        effect: Effect::Damage(entities[0].damage),
    };

    match outcome.effect {
        Effect::Damage(amount) => {
            outcome.target.current_hp -= amount;
        }
    }
}

This of course doesn't work, as I am trying to modify an immutable reference.这当然不起作用,因为我正在尝试修改不可变引用。 Can I somehow convert an immutable reference to a mutable reference when I have the mutable vector in scope?当我在 scope 中有可变向量时,我能否以某种方式将不可变引用转换为可变引用? Or is there perhaps a more rusty way to solve this issue?或者是否有更生疏的方法来解决这个问题?

(for info, Outcome is a struct returned by a function, which i pass the immutable references to, and it returns them back with an effect). (对于信息,Outcome 是一个由 function 返回的结构,我将不可变引用传递给它,并将它们返回并产生效果)。

The only viable solution I found would be changing the immutable reference in an unsafe block like this我发现唯一可行的解决方案是在这样的不安全块中更改不可变引用

match outcome.effect {
      Effect::Damage(amount) => unsafe {
          let target = outcome.target as *const Entity as *mut Entity;
          (*target).current_hp -= amount;
      },
}

You can use indices into the mutable vector:您可以在可变向量中使用索引:

fn main() {
    let mut entities = vec![
        Entity {
            name: "George".to_string(),
            current_hp: 200.0,
            damage: 10.0,
        },
        Entity {
            name: "Jacob".to_string(),
            current_hp: 100.0,
            damage: 5.0,
        },
    ];

    let outcome = Outcome {
        caster: 0,
        target: 1,
        effect: Effect::Damage(entities[0].damage),
    };

    match outcome.effect {
        Effect::Damage(amount) => {
            entities[outcome.target].current_hp -= amount;
        }
    }
}

This requires a certain discipline: you obviously can't reorder the vector, or the indices will also become invalid.这需要一定的纪律:您显然不能重新排序向量,否则索引也会变得无效。 To support despawning entities without shifting elements, you can wrap them in an Option, and despawning an entity sets its slot to None .为了在不移动元素的情况下支持消失实体,您可以将它们包装在 Option 中,并且使实体消失将其插槽设置为None Spawning an entity would entail searching the vector for a free slot (aka a None ), or appending a Some(entity) to the end of the vector to create a new slot if there are no free slots.生成实体将需要在向量中搜索空闲槽(又名None ),或者如果没有空闲槽,则将Some(entity)附加到向量的末尾以创建新槽。

This leads to a subtle issue: in between an index being generated and used, the entity it pointed to can die and the slot can be reused for a different entity, causing the effect to be applied to the wrong entity.这导致了一个微妙的问题:在生成和使用索引之间,它指向的实体可能会死掉,而插槽可以被不同的实体重用,从而导致效果应用于错误的实体。 This can be fixed using a generational index, where an entity index is actually a tuple (index, generation) .这可以使用分代索引来解决,其中实体索引实际上是一个元组(index, generation) Each slot in the vector becomes (usize /*generation*/, Option<Entity>) , and when an entity is spawned in a re-used slot it increments the generation of the slot.向量中的每个槽都变为(usize /*generation*/, Option<Entity>) ,当实体在重复使用的槽中生成时,它会增加槽的生成。 Accessing the entity with an index (now via a getter method) would check the generation of the index against the generation of the slot, and return None if the generations don't match.使用索引访问实体(现在通过 getter 方法)将检查索引的生成与插槽的生成,如果生成不匹配则返回None

Once you've implemented all that, you've gotten part of the way towards an Entity-Component System like Bevy's ECS, Hecs, Legion, or other libraries that are popular in the Rust gamedev ecosystem because they solve exactly these issues:-)一旦你实现了所有这些,你就已经成为了实体组件系统的一部分,比如 Bevy 的 ECS、Hecs、Legion 或其他在 Rust gamedev 生态系统中流行的库,因为它们正好解决了这些问题:-)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM