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:
@@ -12,7 +12,8 @@ use crate::{
|
||||
constants::{SEED, TILE_SIZE},
|
||||
world::{
|
||||
tiles::{FixtureTileData, TileMap},
|
||||
ChunkForrestryEvent, FixtureTilePrefab, TextureIDs, Textures, VisibleGameEntity,
|
||||
ChunkForrestryEvent, ChunkMap, ChunkOwner, FixtureTilePrefab, TextureIDs, Textures,
|
||||
VisibleGameEntity,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -22,11 +23,14 @@ pub fn generate_chunk_forrestry(
|
||||
mut tilemap: ResMut<TileMap>,
|
||||
texture_ids: Res<TextureIDs>,
|
||||
textures: Res<Textures>,
|
||||
mut chunk_map: ResMut<ChunkMap>,
|
||||
) {
|
||||
let start = Instant::now();
|
||||
let count = events.len();
|
||||
|
||||
let collected_tilemap_updates: Mutex<Vec<(IVec3, FixtureTileData)>> = Mutex::new(Vec::new());
|
||||
// Collect (chunk_pos, entity) pairs from parallel section for chunk_entity_index
|
||||
let collected_entities: Mutex<Vec<(IVec2, Entity)>> = Mutex::new(Vec::new());
|
||||
|
||||
events.par_read().for_each(|event| {
|
||||
let floor_positions = &event.floor_tiles;
|
||||
@@ -71,6 +75,14 @@ pub fn generate_chunk_forrestry(
|
||||
let trunk_entity =
|
||||
FixtureTilePrefab::log(trunk_pos).spawn(&mut commands);
|
||||
tree_positions.push(trunk_pos);
|
||||
commands
|
||||
.entity(trunk_entity)
|
||||
.insert(ChunkOwner(event.chunk_position));
|
||||
collected_entities
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((event.chunk_position, trunk_entity));
|
||||
|
||||
// Add sprite component to the same entity if texture exists
|
||||
if let Some(texture_id) = texture_ids.refs.get(&500004) {
|
||||
if let Some(texture) = textures.handles.get(texture_id) {
|
||||
@@ -142,7 +154,14 @@ pub fn generate_chunk_forrestry(
|
||||
),
|
||||
))
|
||||
.id();
|
||||
commands.entity(leaf).insert(VisibleGameEntity);
|
||||
commands.entity(leaf).insert((
|
||||
VisibleGameEntity,
|
||||
ChunkOwner(event.chunk_position),
|
||||
));
|
||||
collected_entities
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((event.chunk_position, leaf));
|
||||
collected_tilemap_updates.lock().unwrap().push(
|
||||
(
|
||||
ivec,
|
||||
@@ -169,6 +188,16 @@ pub fn generate_chunk_forrestry(
|
||||
for (ivec, data) in collected_updates {
|
||||
tilemap.insert_fixture(ivec, data);
|
||||
}
|
||||
|
||||
// Populate chunk_entity_index from collected entities (after parallel section)
|
||||
let collected = collected_entities.into_inner().unwrap();
|
||||
for (chunk_pos, entity) in collected {
|
||||
chunk_map
|
||||
.chunk_entity_index
|
||||
.entry(chunk_pos)
|
||||
.or_default()
|
||||
.push(entity);
|
||||
}
|
||||
if count > 0 {
|
||||
println!(
|
||||
"Forrestry update for {:?} chunks in {:.2?}",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user