diff --git a/full.patch b/full.patch deleted file mode 100644 index 4f39466..0000000 --- a/full.patch +++ /dev/null @@ -1,201 +0,0 @@ -diff --git a/config.toml b/config.toml -index 9e6f89b..fbb1c42 100644 ---- a/config.toml -+++ b/config.toml -@@ -1,4 +1,4 @@ --initial_chunk_radius = 5 -+initial_chunk_radius = 15 - - [spawn_counts] - dorfs = 5 -diff --git a/src/world/mod.rs b/src/world/mod.rs -index 9698168..f597c34 100644 ---- a/src/world/mod.rs -+++ b/src/world/mod.rs -@@ -27,6 +27,7 @@ impl Plugin for WorldPlugin { - .init_resource::() - .init_resource::() - .init_resource::() -+ .init_resource::() - .add_message::() - .add_message::() - .add_message::() -diff --git a/src/world/tiles/benchmark.rs b/src/world/tiles/benchmark.rs -index bac3570..8541a71 100644 ---- a/src/world/tiles/benchmark.rs -+++ b/src/world/tiles/benchmark.rs -@@ -37,6 +37,11 @@ pub struct TilemapBenchmark { - pub dirty_keys_total: usize, - /// CPU memory estimate for tile data in MB. - pub tile_data_mb: f64, -+ -+ pub last_z_change_dirty_ms: f64, -+ pub last_z_change_dirty_count: usize, -+ pub last_z_change_populate_ms: f64, -+ pub z_change_history_ms: Vec, - } - - impl Default for TilemapBenchmark { -@@ -53,6 +58,10 @@ impl Default for TilemapBenchmark { - dirty_keys_last: 0, - dirty_keys_total: 0, - tile_data_mb: 0.0, -+ last_z_change_dirty_ms: 0.0, -+ last_z_change_dirty_count: 0, -+ last_z_change_populate_ms: 0.0, -+ z_change_history_ms: Vec::with_capacity(100), - } - } - } -@@ -69,7 +78,7 @@ impl TilemapBenchmark { - let p50_idx = (self.frame_times.len() as f32 * 0.50) as usize; - let p99_idx = (self.frame_times.len() as f32 * 0.99) as usize; - let mut sorted = self.frame_times.clone(); -- sorted.sort(); -+ sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); - let p50 = sorted - .get(p50_idx.min(sorted.len().saturating_sub(1))) - .copied() -@@ -107,6 +116,35 @@ impl TilemapBenchmark { - ); - println!("║ Tile data mem: {:.2} MB", self.tile_data_mb); - println!("║ Cumulative: {:.2}s", self.populate_ms / 1000.0); -+ println!("╠══════════════════════════════════════════════════════════════╣"); -+ let z_p50 = if !self.z_change_history_ms.is_empty() { -+ let mut sorted = self.z_change_history_ms.clone(); -+ sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); -+ sorted[sorted.len() / 2] -+ } else { -+ 0.0 -+ }; -+ let z_p95 = if !self.z_change_history_ms.is_empty() { -+ let mut sorted = self.z_change_history_ms.clone(); -+ sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); -+ let idx = ((sorted.len() as f32) * 0.95) as usize; -+ sorted[idx.min(sorted.len().saturating_sub(1))] -+ } else { -+ 0.0 -+ }; -+ println!("║ Z-scroll spike:"); -+ println!( -+ "║ Last dirty marking: {:.2}ms ({} keys)", -+ self.last_z_change_dirty_ms, self.last_z_change_dirty_count -+ ); -+ println!( -+ "║ Last z-change populate: {:.2}ms", -+ self.last_z_change_populate_ms -+ ); -+ println!( -+ "║ Z-change populate p50: {:.2}ms p95: {:.2}ms", -+ z_p50, z_p95 -+ ); - println!("╚══════════════════════════════════════════════════════════════╝"); - println!(); - } -diff --git a/src/world/tiles/tilemap_chunk.rs b/src/world/tiles/tilemap_chunk.rs -index 6074dc5..bf8f459 100644 ---- a/src/world/tiles/tilemap_chunk.rs -+++ b/src/world/tiles/tilemap_chunk.rs -@@ -50,6 +50,17 @@ impl Default for TilemapChunkSpawner { - } - } - -+pub const MAX_POPULATE_PER_FRAME: usize = 512; -+ -+#[derive(Resource)] -+pub struct PreviousZIndex(pub i32); -+ -+impl Default for PreviousZIndex { -+ fn default() -> Self { -+ Self(0) -+ } -+} -+ - impl TilemapChunkSpawner { - pub fn queue_chunk(&mut self, chunk_pos: IVec2) { - for z in 0..=(Z_TOTAL as usize) { -@@ -284,18 +295,27 @@ pub fn populate_tilemap_chunk_data( - if registry.dirty_keys.is_empty() { - return; - } -+ -+ let was_z_change = bench.last_z_change_dirty_count > 0; - let now = Instant::now(); - let camera_z = z_index.0 as i32; -- let current_z = (camera_z as f32 + Z_BELOW) as usize; -+ let current_z_index = (camera_z as f32 + Z_BELOW) as usize; -+ -+ let mut to_process: Vec = registry.dirty_keys.iter().cloned().collect(); -+ to_process.sort_by_key(|k| (k.z_index as i32 - current_z_index as i32).unsigned_abs()); -+ to_process.truncate(MAX_POPULATE_PER_FRAME); -+ -+ for key in &to_process { -+ registry.dirty_keys.remove(key); -+ } - -- let dirty: Vec = registry.dirty_keys.drain().collect(); - let mut processed = 0usize; - -- for key in dirty { -+ for key in to_process { - let Some(&entity) = registry.entities.get(&key) else { - continue; - }; -- let Ok((mut tile_data, chunk_key)) = chunk_data_query.get_mut(entity) else { -+ let Ok((mut tile_data, _)) = chunk_data_query.get_mut(entity) else { - continue; - }; - -@@ -320,15 +340,48 @@ pub fn populate_tilemap_chunk_data( - bench.dirty_keys_total += processed; - bench.tile_data_mb = (registry.entities.len() as f64 * (CHUNK_SIZE * CHUNK_SIZE) as f64 * 4.0) - / (1024.0 * 1024.0); -+ -+ if was_z_change { -+ bench.last_z_change_populate_ms = elapsed; -+ bench.z_change_history_ms.push(elapsed); -+ if bench.z_change_history_ms.len() > 100 { -+ bench.z_change_history_ms.remove(0); -+ } -+ } - } - --pub fn on_camera_z_changed(z_index: Res, mut registry: ResMut) { -- if z_index.is_changed() { -- let keys: Vec = registry.entities.keys().cloned().collect(); -- for key in keys { -+pub fn on_camera_z_changed( -+ z_index: Res, -+ mut registry: ResMut, -+ mut prev_z: ResMut, -+ mut bench: ResMut, -+) { -+ if !z_index.is_changed() { -+ return; -+ } -+ -+ let old_camera_z = prev_z.0; -+ let new_camera_z = z_index.0 as i32; -+ prev_z.0 = new_camera_z; -+ -+ let now = Instant::now(); -+ let mut count = 0; -+ -+ let keys: Vec = registry.entities.keys().cloned().collect(); -+ for key in keys { -+ let tile_z = camera_z_for_z_index(key.z_index); -+ let old_diff = old_camera_z - tile_z; -+ let new_diff = new_camera_z - tile_z; -+ let was_in_range = old_diff >= 0 && old_diff <= 8; -+ let is_in_range = new_diff >= 0 && new_diff <= 8; -+ if was_in_range || is_in_range { - registry.dirty_keys.insert(key); -+ count += 1; - } - } -+ -+ bench.last_z_change_dirty_ms = now.elapsed().as_secs_f64() * 1000.0; -+ bench.last_z_change_dirty_count = count; - } - - pub fn despawn_tilemap_chunks( diff --git a/src/world/generation/terrain.rs b/src/world/generation/terrain.rs index ae02503..ed91ba2 100644 --- a/src/world/generation/terrain.rs +++ b/src/world/generation/terrain.rs @@ -9,8 +9,8 @@ use crate::{ constants::{SEED, TILE_SIZE}, world::{ tiles::{ChunkData, FloorTileData, TileMap}, - ChunkForrestryEvent, ChunkTerrainEvent, FloorTilePrefab, TileOcclusionEvent, CHUNK_SIZE, - Z_ABOVE, Z_BELOW, ChunkMap, + ChunkForrestryEvent, ChunkMap, ChunkTerrainEvent, FloorTilePrefab, TileOcclusionEvent, + CHUNK_SIZE, Z_ABOVE, Z_BELOW, }, }; @@ -280,10 +280,12 @@ pub fn spawn_terrain_tasks( for event in events.read() { let chunk_pos = event.chunk_position; let blobs_clone = blobs.clone(); - task_pool.spawn(async move { - let blob = generate_terrain_blob(chunk_pos); - blobs_clone.lock().unwrap().push(blob); - }).detach(); + task_pool + .spawn(async move { + let blob = generate_terrain_blob(chunk_pos); + blobs_clone.lock().unwrap().push(blob); + }) + .detach(); } println!("{} terrain tasks spawned in {:.2?}", count, start.elapsed()); @@ -331,7 +333,11 @@ pub fn apply_terrain_blobs( } if applied_count > 0 { - println!("{} terrain blobs applied in {:.2?}", applied_count, start.elapsed()); + println!( + "{} terrain blobs applied in {:.2?}", + applied_count, + start.elapsed() + ); } }