从子方法更改HashMap的键 [英] Changing key of HashMap from child method
问题描述
我正在开发一款在活塞中实现瓷砖的 2D 沙盒游戏.它的基本结构是一个 App
结构体,它存储了一个 world: World
结构体,该结构体具有 的
s,关键是瓷砖所在位置的XY位置.tiles: HashMap
属性Box
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 theEq
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^2
的 Vec
中.但同样:无法根据给定的信息说出哪种数据结构最适合您.另外:这样的问题不适合 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 everyConsider using a different design that allows you to store many objects "inline". TheTile
into aBox
: theHashMap
already owns the objects; boxing them would only introduce one additional layer of indirection.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屋!