Wrap TileMap HashMaps in Arc for async pathfinding access

- Add Arc<AHashMap> wrapper around floor_tiles, fixture_tiles, item_tiles
- Copy-on-write semantics: Arc::make_mut clones only if other Arcs exist
- Add insert_floor, insert_fixture, insert_item, remove_item methods
- Add get_floor_mut, get_fixture_mut for visibility updates
- Update all mutation sites to use new TileMap methods
- Enables cheap Arc::clone for async pathfinding workers
- Single-threaded pathfinding: no clone, direct access
- Multi-threaded pathfinding: clone Arc, read without locks
This commit is contained in:
2026-03-18 14:56:57 +00:00
parent 8478b385bc
commit 466a20700a
6 changed files with 49 additions and 30 deletions
+2 -18
View File
@@ -41,15 +41,7 @@ pub fn spawn_prefab(
}, },
}) })
.id(); .id();
let mut items = tilemap tilemap.insert_item(position.as_ivec3(), meat.index_u32());
.item_tiles
.get(&position.as_ivec3())
.unwrap_or(&Vec::new())
.clone();
items.push(meat.index_u32());
tilemap
.item_tiles
.insert(position.as_ivec3(), items.clone());
return commands return commands
.entity(meat) .entity(meat)
.insert(VisibleGameEntity) .insert(VisibleGameEntity)
@@ -76,15 +68,7 @@ pub fn spawn_prefab(
}, },
}) })
.id(); .id();
let mut items = tilemap tilemap.insert_item(position.as_ivec3(), coin.index_u32());
.item_tiles
.get(&position.as_ivec3())
.unwrap_or(&Vec::new())
.clone();
items.push(coin.index_u32());
tilemap
.item_tiles
.insert(position.as_ivec3(), items.clone());
commands commands
.entity(coin) .entity(coin)
.insert(VisibleGameEntity) .insert(VisibleGameEntity)
+4 -4
View File
@@ -116,13 +116,13 @@ pub fn item_tile_management_system(
for position in positions { for position in positions {
let items = match tilemap.item_tiles.get(&position) { let items = match tilemap.item_tiles.get(&position) {
Some(i) => i, Some(i) => i.clone(),
None => continue, None => continue,
}; };
// Clean up empty tiles // Clean up empty tiles
if items.is_empty() { if items.is_empty() {
tilemap.item_tiles.remove(&position); tilemap.remove_item(&position);
rotation_timer.current_indices.remove(&position); rotation_timer.current_indices.remove(&position);
continue; continue;
} }
@@ -146,8 +146,8 @@ pub fn item_tile_management_system(
.copied() .copied()
.unwrap_or(0); .unwrap_or(0);
for &entity_id in items { for entity_id in &items {
if let Some(entity) = Entity::from_raw_u32(entity_id) { if let Some(entity) = Entity::from_raw_u32(*entity_id) {
if let Ok(mut rotation_state) = item_query.get_mut(entity) { if let Ok(mut rotation_state) = item_query.get_mut(entity) {
rotation_state.should_be_visible = false; rotation_state.should_be_visible = false;
} }
+1 -1
View File
@@ -167,7 +167,7 @@ pub fn generate_chunk_forrestry(
let collected_updates = collected_tilemap_updates.into_inner().unwrap(); let collected_updates = collected_tilemap_updates.into_inner().unwrap();
for (ivec, data) in collected_updates { for (ivec, data) in collected_updates {
tilemap.fixture_tiles.insert(ivec, data); tilemap.insert_fixture(ivec, data);
} }
if count > 0 { if count > 0 {
println!( println!(
+1 -1
View File
@@ -159,7 +159,7 @@ pub fn generate_chunk_terrain(
.unwrap() .unwrap()
.into_iter() .into_iter()
.map(|(pos, data)| { .map(|(pos, data)| {
tilemap.floor_tiles.insert(pos, data); tilemap.insert_floor(pos, data);
pos pos
}) })
.collect(); .collect();
+40 -5
View File
@@ -1,5 +1,6 @@
use ahash::AHashMap; use ahash::AHashMap;
use bevy::prelude::*; use bevy::prelude::*;
use std::sync::Arc;
/// Packed floor tile data for efficient storage. ~35 bytes vs 76 bytes tuple. /// Packed floor tile data for efficient storage. ~35 bytes vs 76 bytes tuple.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
@@ -170,12 +171,13 @@ impl FixtureTileData {
} }
} }
/// Tile map with fast AHashMap for pathfinding lookups. /// Tile map with Arc-wrapped HashMaps for async pathfinding access.
#[derive(Resource, Default)] /// Uses copy-on-write: Arc::make_mut clones only if other Arcs exist.
#[derive(Resource, Clone, Default)]
pub struct TileMap { pub struct TileMap {
pub floor_tiles: AHashMap<IVec3, FloorTileData>, pub floor_tiles: Arc<AHashMap<IVec3, FloorTileData>>,
pub fixture_tiles: AHashMap<IVec3, FixtureTileData>, pub fixture_tiles: Arc<AHashMap<IVec3, FixtureTileData>>,
pub item_tiles: AHashMap<IVec3, Vec<u32>>, pub item_tiles: Arc<AHashMap<IVec3, Vec<u32>>>,
} }
impl TileMap { impl TileMap {
@@ -202,4 +204,37 @@ impl TileMap {
pub fn has_fixture(&self, pos: &IVec3) -> bool { pub fn has_fixture(&self, pos: &IVec3) -> bool {
self.fixture_tiles.contains_key(pos) self.fixture_tiles.contains_key(pos)
} }
#[inline]
pub fn insert_floor(&mut self, pos: IVec3, tile: FloorTileData) {
Arc::make_mut(&mut self.floor_tiles).insert(pos, tile);
}
#[inline]
pub fn insert_fixture(&mut self, pos: IVec3, tile: FixtureTileData) {
Arc::make_mut(&mut self.fixture_tiles).insert(pos, tile);
}
#[inline]
pub fn insert_item(&mut self, pos: IVec3, entity_id: u32) {
Arc::make_mut(&mut self.item_tiles)
.entry(pos)
.or_default()
.push(entity_id);
}
#[inline]
pub fn remove_item(&mut self, pos: &IVec3) -> Option<Vec<u32>> {
Arc::make_mut(&mut self.item_tiles).remove(pos)
}
#[inline]
pub fn get_floor_mut(&mut self, pos: &IVec3) -> Option<&mut FloorTileData> {
Arc::make_mut(&mut self.floor_tiles).get_mut(pos)
}
#[inline]
pub fn get_fixture_mut(&mut self, pos: &IVec3) -> Option<&mut FixtureTileData> {
Arc::make_mut(&mut self.fixture_tiles).get_mut(pos)
}
} }
+1 -1
View File
@@ -100,7 +100,7 @@ pub fn handle_tile_occlusion_updates(
for (_, mut tile, pos) in floor_tiles.iter_mut() { for (_, mut tile, pos) in floor_tiles.iter_mut() {
if let Some(visibility) = update_map.get(&pos.translation.as_ivec3()) { if let Some(visibility) = update_map.get(&pos.translation.as_ivec3()) {
tile.visible_range = *visibility; tile.visible_range = *visibility;
if let Some(tile_data) = tilemap.floor_tiles.get_mut(&pos.translation.as_ivec3()) { if let Some(tile_data) = tilemap.get_floor_mut(&pos.translation.as_ivec3()) {
tile_data.visible_range = *visibility; tile_data.visible_range = *visibility;
} }
} }