Optimize TileMap: replace HashMap with AHashMap, pack tile data

- Replace std HashMap (SipHash) with ahash::AHashMap for fast non-crypto hashing
- Pack tile data from tuples to structs: FloorTileData (~35 bytes) and FixtureTileData (~18 bytes)
- FloorTileData: pack 3 bools into single flags byte, use u8 for id/weight
- FixtureTileData: pack 2 bools into single flags byte
- Update all accessors: is_standable_tile, visibility, terrain generation, forestry
- Preparation for async pathfinding (Arc wrapping to come in follow-up)

Memory reduction: ~54% for floor tiles (76→35 bytes), ~62% for fixtures (48→18 bytes)
Hash performance: AHashMap uses fxhash, faster than SipHash for game data
This commit is contained in:
2026-03-18 14:52:46 +00:00
parent 81b70a661c
commit 8478b385bc
5 changed files with 259 additions and 55 deletions
+15 -12
View File
@@ -11,8 +11,8 @@ use std::hash::{Hash, Hasher};
use crate::{
constants::{SEED, TILE_SIZE},
world::{
tiles::TileMap, ChunkForrestryEvent, FixtureTilePrefab, TextureIDs, Textures,
VisibleGameEntity,
tiles::{FixtureTileData, TileMap},
ChunkForrestryEvent, FixtureTilePrefab, TextureIDs, Textures, VisibleGameEntity,
},
};
@@ -26,8 +26,7 @@ pub fn generate_chunk_forrestry(
let start = Instant::now();
let count = events.len();
let collected_tilemap_updates: Mutex<Vec<(IVec3, (i32, bool, bool, [u32; 8]))>> =
Mutex::new(Vec::<(IVec3, (i32, bool, bool, [u32; 8]))>::new());
let collected_tilemap_updates: Mutex<Vec<(IVec3, FixtureTileData)>> = Mutex::new(Vec::new());
events.par_read().for_each(|event| {
let floor_positions = &event.floor_tiles;
@@ -87,10 +86,10 @@ pub fn generate_chunk_forrestry(
}
}
collected_tilemap_updates
.lock()
.unwrap()
.push((trunk_ivec, (1, false, true, [0; 8])));
collected_tilemap_updates.lock().unwrap().push((
trunk_ivec,
FixtureTileData::new(1, false, true, [0; 8]),
));
log_positions.insert(trunk_ivec);
}
@@ -144,10 +143,14 @@ pub fn generate_chunk_forrestry(
))
.id();
commands.entity(leaf).insert(VisibleGameEntity);
collected_tilemap_updates
.lock()
.unwrap()
.push((ivec, (5, false, true, [0; 8])));
collected_tilemap_updates.lock().unwrap().push(
(
ivec,
FixtureTileData::new(
5, false, true, [0; 8],
),
),
);
}
}
}
+29 -21
View File
@@ -7,8 +7,9 @@ use noise::{NoiseFn, Perlin};
use crate::{
constants::{SEED, TILE_SIZE},
world::{
tiles::TileMap, ChunkForrestryEvent, ChunkTerrainEvent, FloorTilePrefab,
TileOcclusionEvent, CHUNK_SIZE, Z_ABOVE, Z_BELOW,
tiles::{FloorTileData, TileMap},
ChunkForrestryEvent, ChunkTerrainEvent, FloorTilePrefab, TileOcclusionEvent, CHUNK_SIZE,
Z_ABOVE, Z_BELOW,
},
};
@@ -50,8 +51,7 @@ pub fn generate_chunk_terrain(
let start_y = chunk_pos.y * CHUNK_SIZE;
let mut surface_positions: Vec<(Vec3, String)> = Vec::new();
let mut local_tilemap_updates: HashMap<IVec3, (i32, bool, bool, bool, i32, [u32; 8])> =
HashMap::new();
let mut local_tilemap_updates: HashMap<IVec3, FloorTileData> = HashMap::new();
// Generate tiles for this chunk
for local_y in 0..CHUNK_SIZE {
@@ -84,23 +84,26 @@ pub fn generate_chunk_terrain(
commands.command_scope(|mut cmd| {
FloorTilePrefab::air(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (0, true, false, true, 0, [0; 8]));
// Air tile
local_tilemap_updates.insert(
pos_ivec,
FloorTileData::new(0, true, false, true, 0, [0; 8]),
);
} else if cave_value < 0.8 {
commands.command_scope(|mut cmd| {
FloorTilePrefab::rock(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (2, false, true, false, 50, [0; 8]));
// Rock tile
local_tilemap_updates.insert(
pos_ivec,
FloorTileData::new(2, false, true, false, 50, [0; 8]),
);
} else {
commands.command_scope(|mut cmd| {
FloorTilePrefab::dirt(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (1, false, true, false, 85, [0; 8]));
// Dirt tile
local_tilemap_updates.insert(
pos_ivec,
FloorTileData::new(1, false, true, false, 85, [0; 8]),
);
}
} else if noise_position.z > position.z {
if (generate_surface_terrain(world_x, world_y) * TILE_SIZE).round()
@@ -109,23 +112,28 @@ pub fn generate_chunk_terrain(
commands.command_scope(|mut cmd| {
FloorTilePrefab::grass(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (1, false, true, false, 100, [0; 8])); // Dirt tile (grass)
surface_positions.push((position, ("grass").to_string()));
local_tilemap_updates.insert(
pos_ivec,
FloorTileData::new(1, false, true, false, 100, [0; 8]),
);
surface_positions.push((position, "grass".to_string()));
} else {
commands.command_scope(|mut cmd| {
FloorTilePrefab::dirt(position).spawn(&mut cmd);
});
local_tilemap_updates
.insert(pos_ivec, (1, false, true, false, 85, [0; 8]));
// Dirt tile
local_tilemap_updates.insert(
pos_ivec,
FloorTileData::new(1, false, true, false, 85, [0; 8]),
);
}
} else {
commands.command_scope(|mut cmd| {
FloorTilePrefab::air(position).spawn(&mut cmd);
});
local_tilemap_updates.insert(pos_ivec, (0, true, false, true, 0, [0; 8]));
// Air tile
local_tilemap_updates.insert(
pos_ivec,
FloorTileData::new(0, true, false, true, 0, [0; 8]),
);
}
}
}