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
+9 -2
View File
@@ -10,7 +10,7 @@ use crate::{
world::{
tiles::{ChunkData, FloorTileData, TileMap, TerrainSpriteState, CurrentWorldSpriteState},
ChunkForrestryEvent, ChunkTerrainEvent, FloorTilePrefab, TileOcclusionEvent, CHUNK_SIZE,
Z_ABOVE, Z_BELOW,
Z_ABOVE, Z_BELOW, ChunkMap, ChunkOwner,
},
};
@@ -297,6 +297,7 @@ pub fn apply_terrain_blobs(
mut commands: Commands,
blob_storage: Res<TerrainBlobStorage>,
mut tilemap: ResMut<TileMap>,
mut chunk_map: ResMut<ChunkMap>,
mut forrestry_event_writer: MessageWriter<ChunkForrestryEvent>,
mut occlusion_event_writer: MessageWriter<TileOcclusionEvent>,
) {
@@ -318,7 +319,13 @@ pub fn apply_terrain_blobs(
tilemap.chunks.insert(blob.chunk_pos, blob.chunk_data);
for (_position, prefab) in blob.tile_spawns {
prefab.spawn(&mut commands);
let entity = prefab.spawn(&mut commands);
commands.entity(entity).insert(ChunkOwner(blob.chunk_pos));
chunk_map
.chunk_entity_index
.entry(blob.chunk_pos)
.or_default()
.push(entity);
}
for pos in new_positions {