从子方法更改HashMap的键 [英] Changing key of HashMap from child method

查看:52
本文介绍了从子方法更改HashMap的键的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在开发一款在活塞中实现瓷砖的 2D 沙盒游戏.它的基本结构是一个 App 结构体,它存储了一个 world: World 结构体,该结构体具有 tiles: HashMap 属性Boxs,关键是瓷砖所在位置的XY位置.

I'm working on a 2D sandbox game that implements tiles in piston. The basic structure of this is an App struct, which stores a world: World struct, which has a tiles: HashMap property of Box<Tile>s, with the key being an X-Y position of where the tile is.

为了渲染这个,我遍历屏幕上每个图块空间的坐标,如果哈希图中有一个带有这些坐标的图块,我调用 tile.render(&context, &mutgl);.

To render this, I loop through the coordinates of every tile space on the screen and if there is a tile in the hashmap with those co-ordinates, I call tile.render(&context, &mut gl);.

我现在需要实现一个 Tile.update() 方法,该方法将在每次更新时调用,但是该方法需要能够修改块的坐标,这意味着修改哈希图中磁贴本身的键,但我无法将哈希图传递给 tile.update() 函数,因为访问磁贴会借用哈希图.

I now need to implement a Tile.update() method which will be called each update, however this method would need to have the ability to modify the co-ordinates of the block which means modifying the key of the tile itself in the hashmap, but I can't pass the hashmap to the tile.update() function since accessing the tile would borrow the hashmap.

我如何以最不笨拙/最优雅的方式实现这一点?有没有更好的方法我应该用来存储允许它工作的瓷砖?无法从 Tile.update() 方法外部更改坐标.

How do I accomplish this in the least hacky/most elegant way possible? Is there a better method I should be using to store the tiles that will allow this to work? Changing the co-ordinates from outside the Tile.update() method is not possible.

这几乎就是我想要做的.我知道为什么它不起作用,我只是不知道如何解决它:

This is pretty much what I'm trying to do. I know why it won't work, I'm just not sure how to get around it:

use std::collections::HashMap;
use std::boxed::Box;

pub trait Tile {
    fn new(x: i32, y: i32) -> Self where Self: Sized;
    fn render(&self, camera_x: i32, camera_y: i32);
    fn update(&mut self, app: &mut App);
}

pub struct DirtTile {
    pub x: i32,
    pub y: i32,
}

impl Tile for DirtTile {
    fn new(x: i32, y: i32) -> DirtTile {
        return DirtTile { x: x, y: y };
    }

    fn render(&self, camera_x: i32, camera_y: i32) {
        // render
    }

    fn update(&mut self, app: &mut App) {
        let world = app.world;

        // update pos
        let new_pos = [self.x + 1, self.y];
        if world.tiles.get(&new_pos).is_none() {
            world.tiles.remove(&[self.x, self.y]);
            world.tiles.insert(new_pos, Box::new(*self));
            self.x = new_pos[0];
            self.y = new_pos[1];
        }
    }
}

pub struct World {
    tiles: HashMap<[i32; 2], Box<Tile>>,
}

pub struct App {
    world: World,
}

fn main() {
    let mut app = App { world: World { tiles: HashMap::new() } };

    app.world.tiles.insert([0, 0], Box::new(DirtTile::new(0, 0)));

    for (xy, tile) in &app.world.tiles {
        tile.update(&mut app);
    }
}

游乐场

推荐答案

不能直接修改 HashMap 的键.来自 HashMap 文档:

You can't modify the key of a HashMap directly. From the HashMap documentation:

如果键的哈希值由 Hash trait 确定,或者它的相等性由 Eq<确定,那么修改键是一个逻辑错误/code> trait,在地图中时会发生变化.

It is a logic error for a key to be modified in such a way that the key's hash, as determined by the Hash trait, or its equality, as determined by the Eq trait, changes while it is in the map.

在那里您必须删除并重新插入 Tile,您已经尝试这样做了.但是,您不能在仍借用对象时移除和重新插入.在 update 方法中,Tile 对象是通过 &self 借用的.所以你必须在这个方法之外做它.

There you have to remove and reinsert the Tile, which you already try to do. You can't, however, remove and reinsert while you still borrow the object. In the update method the Tile object is borrowed via &self. So you have to do it outside of this method.

遗憾的是,您也无法在遍历 HashMap 时删除和重新插入,因为它会使迭代器无效.一种可能性是在遍历地图时收集 Vec 中所有修改过的对象,然后将它们插入到地图中.

Sadly, you also can't remove and reinsert while iterating through the HashMap since it would invalidate the iterator. One possibility would be to collect all modified objects in a Vec while iterating through the map and insert those into the map afterwards.

但是,请考虑使用不同的数据结构.如果不知道您将拥有多少块瓷砖,它们改变位置的频率等等,就很难对此进行推理.您可以将您的世界分成多个块,例如Minecraft 确实如此,每个块都跨越一些大小为 N x N 的二次区域.然后每个块都可以将其瓷砖保存在大小为 N^2Vec 中.但同样:无法根据给定的信息说出哪种数据结构最适合您.另外:这样的问题不适合 SO.

However, consider using a different data structure. It's difficult to reason about that without knowing how many tiles you will have, how often they change position and so on. You could separate your world into chunks, like e.g. Minecraft does, with each chunk spanning some quadratic area of size N x N. Each chunk could save it's Tiles in a Vec of size N^2 then. But again: impossible to say what data structure would suit you best with the information given. Also: such question wouldn't fit SO.

一些补充说明:

  • 您可以使用元组 (i32, i32) 而不是 [i32;2] 可以让你像这样解构它:for ((x, y), tile) in &app.world.tiles.
  • 您不需要将每个Tile 放入Box:HashMap 已经拥有这些对象;将它们装箱只会引入一个额外的间接层. 考虑使用不同的设计,允许您内联"存储许多对象.Box 引入了另一层间接——现代 CPU 根本不喜欢的东西.游戏编程模式书籍的这一部分是有关此主题的绝佳读物.
  • You could use a tuple (i32, i32) instead of [i32; 2] which would let you destructure it like so: for ((x, y), tile) in &app.world.tiles.
  • You don't need to put every Tile into a Box: the HashMap already owns the objects; boxing them would only introduce one additional layer of indirection. Consider using a different design that allows you to store many objects "inline". The Box introduces another layer of indirection -- something moderns CPUs don't like at all. A great read about this topic is this section of the game-programming-patterns book.

这篇关于从子方法更改HashMap的键的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆