From 466a20700a9604daa6bbe0978138cc4ed8a2ce62 Mon Sep 17 00:00:00 2001 From: popertots Date: Wed, 18 Mar 2026 14:56:57 +0000 Subject: [PATCH] Wrap TileMap HashMaps in Arc for async pathfinding access - Add Arc 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 --- .../item/prefabs/misc/misc_prefabs.rs | 20 +-------- src/entities/item/systems.rs | 8 ++-- src/world/generation/forestry.rs | 2 +- src/world/generation/terrain.rs | 2 +- src/world/tiles/tilemap.rs | 45 ++++++++++++++++--- src/world/tiles/visibility.rs | 2 +- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/src/entities/item/prefabs/misc/misc_prefabs.rs b/src/entities/item/prefabs/misc/misc_prefabs.rs index 39d9e16..f5d9706 100644 --- a/src/entities/item/prefabs/misc/misc_prefabs.rs +++ b/src/entities/item/prefabs/misc/misc_prefabs.rs @@ -41,15 +41,7 @@ pub fn spawn_prefab( }, }) .id(); - let mut items = tilemap - .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()); + tilemap.insert_item(position.as_ivec3(), meat.index_u32()); return commands .entity(meat) .insert(VisibleGameEntity) @@ -76,15 +68,7 @@ pub fn spawn_prefab( }, }) .id(); - let mut items = tilemap - .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()); + tilemap.insert_item(position.as_ivec3(), coin.index_u32()); commands .entity(coin) .insert(VisibleGameEntity) diff --git a/src/entities/item/systems.rs b/src/entities/item/systems.rs index 5bc0eef..fc3f536 100644 --- a/src/entities/item/systems.rs +++ b/src/entities/item/systems.rs @@ -116,13 +116,13 @@ pub fn item_tile_management_system( for position in positions { let items = match tilemap.item_tiles.get(&position) { - Some(i) => i, + Some(i) => i.clone(), None => continue, }; // Clean up empty tiles if items.is_empty() { - tilemap.item_tiles.remove(&position); + tilemap.remove_item(&position); rotation_timer.current_indices.remove(&position); continue; } @@ -146,8 +146,8 @@ pub fn item_tile_management_system( .copied() .unwrap_or(0); - for &entity_id in items { - if let Some(entity) = Entity::from_raw_u32(entity_id) { + for entity_id in &items { + if let Some(entity) = Entity::from_raw_u32(*entity_id) { if let Ok(mut rotation_state) = item_query.get_mut(entity) { rotation_state.should_be_visible = false; } diff --git a/src/world/generation/forestry.rs b/src/world/generation/forestry.rs index 8cdd321..4decbb3 100644 --- a/src/world/generation/forestry.rs +++ b/src/world/generation/forestry.rs @@ -167,7 +167,7 @@ pub fn generate_chunk_forrestry( let collected_updates = collected_tilemap_updates.into_inner().unwrap(); for (ivec, data) in collected_updates { - tilemap.fixture_tiles.insert(ivec, data); + tilemap.insert_fixture(ivec, data); } if count > 0 { println!( diff --git a/src/world/generation/terrain.rs b/src/world/generation/terrain.rs index f1c0cae..673ee7e 100644 --- a/src/world/generation/terrain.rs +++ b/src/world/generation/terrain.rs @@ -159,7 +159,7 @@ pub fn generate_chunk_terrain( .unwrap() .into_iter() .map(|(pos, data)| { - tilemap.floor_tiles.insert(pos, data); + tilemap.insert_floor(pos, data); pos }) .collect(); diff --git a/src/world/tiles/tilemap.rs b/src/world/tiles/tilemap.rs index d37a3ab..f3fd9ef 100644 --- a/src/world/tiles/tilemap.rs +++ b/src/world/tiles/tilemap.rs @@ -1,5 +1,6 @@ use ahash::AHashMap; use bevy::prelude::*; +use std::sync::Arc; /// Packed floor tile data for efficient storage. ~35 bytes vs 76 bytes tuple. #[derive(Clone, Copy, Debug)] @@ -170,12 +171,13 @@ impl FixtureTileData { } } -/// Tile map with fast AHashMap for pathfinding lookups. -#[derive(Resource, Default)] +/// Tile map with Arc-wrapped HashMaps for async pathfinding access. +/// Uses copy-on-write: Arc::make_mut clones only if other Arcs exist. +#[derive(Resource, Clone, Default)] pub struct TileMap { - pub floor_tiles: AHashMap, - pub fixture_tiles: AHashMap, - pub item_tiles: AHashMap>, + pub floor_tiles: Arc>, + pub fixture_tiles: Arc>, + pub item_tiles: Arc>>, } impl TileMap { @@ -202,4 +204,37 @@ impl TileMap { pub fn has_fixture(&self, pos: &IVec3) -> bool { 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> { + 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) + } } diff --git a/src/world/tiles/visibility.rs b/src/world/tiles/visibility.rs index b7386d0..c56ce8b 100644 --- a/src/world/tiles/visibility.rs +++ b/src/world/tiles/visibility.rs @@ -100,7 +100,7 @@ pub fn handle_tile_occlusion_updates( for (_, mut tile, pos) in floor_tiles.iter_mut() { if let Some(visibility) = update_map.get(&pos.translation.as_ivec3()) { 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; } }