feat: implement chunk unloading infrastructure

Chunk unloading system with cleanly abstracted decision layer.

- ChunkMap: replace (bool, i32) timer tuple with () presence marker;
  remove FIFO cycle fields pending_unload/unload_cycle/unload_cursor/
  unload_countdown. loaded_chunks is now HashMap<IVec2, ()>.
- handle_chunk_events: simplify re-entrancy check to contains_key;
  remove parallel Mutex dedup machinery — load detection is now trivial.
- is_standable: single HashMap get; return false for unloaded chunks
  instead of falling back to is_standable_slow (pathfinding correctness).
- remove_chunk_data: bounds-iteration tile cleanup for chunk unload.
- ChunkOwner component + chunk_entity_index: static terrain entities
  (floor tiles, fixtures, trees) tagged at spawn and despawned by
  chunk. Mobile entities (dorfs, pigs, rabbits) not tracked — safe
  by design since they were never indexed.
- unload_chunk: canonical single-chunk unload function; cleans
  loaded_chunks, despawns entities, removes tilemap data, marks render
  chunk dirty via ChunkZKey::from_world, updates connectivity, fires
  rebake. Full docstring with step-by-step state map, working example
  for dynamic unload, and "what it does NOT handle" section.
- dynamic_chunk_unloading_system: no-op stub. Re-enable by writing
  a system that diffs wanted vs loaded chunks and calls unload_chunk.
  Wired out of FixedUpdate during foundation work.
- render chunk fix: ChunkZKey::from_world now pub(crate) so unload
  can derive the correct render chunk key (spans 4x4 world chunks)
  matching what build_quilted_terrain_sprites uses at spawn.
- FloorTilePrefab::spawn: pre-existing bug — return Entity not ().
This commit is contained in:
2026-03-20 10:30:33 +00:00
parent a0eb608d73
commit d7cbbaa04b
7 changed files with 237 additions and 63 deletions
+4 -2
View File
@@ -107,8 +107,10 @@ impl FloorTilePrefab {
}
}
pub fn spawn(self, commands: &mut Commands) {
commands.spawn((self.tile, self.transform, self.tile_state, self.visibility));
pub fn spawn(self, commands: &mut Commands) -> Entity {
commands
.spawn((self.tile, self.transform, self.tile_state, self.visibility))
.id()
}
}
+1 -1
View File
@@ -44,7 +44,7 @@ pub struct ChunkZKey {
impl ChunkZKey {
#[inline]
fn from_world(world_x: f32, world_y: f32, z_index: usize) -> Self {
pub(crate) fn from_world(world_x: f32, world_y: f32, z_index: usize) -> Self {
Self {
chunk_x: (world_x / (TILE_SIZE * CHUNK_TILES as f32)).floor() as i32,
chunk_y: (world_y / (TILE_SIZE * CHUNK_TILES as f32)).floor() as i32,
+30 -6
View File
@@ -232,14 +232,14 @@ impl TileMap {
}
/// O(1) standability check using bit-packed chunk data.
/// Falls back to HashMap lookups if chunk data is not available.
/// Returns false if chunk is not loaded (unloaded chunks have no valid tiles).
pub fn is_standable(&self, world_pos: IVec3) -> bool {
let chunk_pos = world_to_chunk(world_pos);
if let Some(chunk) = self.chunks.get(&chunk_pos) {
let (local_x, local_y, z) = ChunkData::world_to_local(world_pos);
return chunk.is_standable(local_x, local_y, z);
}
self.is_standable_slow(world_pos)
let Some(chunk) = self.chunks.get(&chunk_pos) else {
return false;
};
let (local_x, local_y, z) = ChunkData::world_to_local(world_pos);
chunk.is_standable(local_x, local_y, z)
}
/// Fallback standability check using HashMap lookups.
@@ -283,4 +283,28 @@ impl TileMap {
.map(|t| t.astar_weight)
.unwrap_or(100)
}
/// Remove all tile data for a specific chunk from the TileMap.
/// Iterates all positions in the chunk volume and removes from HashMaps.
/// Used during chunk unloading to clean up tile data.
pub fn remove_chunk_data(&mut self, chunk_pos: IVec2) {
use crate::constants::ITILE_SIZE;
use crate::world::chunks::{CHUNK_SIZE, Z_ABOVE, Z_BELOW};
for local_x in 0..CHUNK_SIZE {
for local_y in 0..CHUNK_SIZE {
for z in -Z_BELOW as i32..=Z_ABOVE as i32 {
let pos = IVec3::new(
chunk_pos.x * CHUNK_SIZE + local_x,
chunk_pos.y * CHUNK_SIZE + local_y,
z,
) * ITILE_SIZE;
self.floor_tiles.remove(&pos);
self.fixture_tiles.remove(&pos);
self.item_tiles.remove(&pos);
}
}
}
self.chunks.remove(&chunk_pos);
}
}