perf: eliminate HashMap lookups on z-scroll with fast recolor + targeted boundary dirty
Three optimizations for z-scroll performance: Option 1 — Fast in-place recolor (no HashMap lookups): - TilemapChunkStates stores solid_bits + underground_bits per chunk - recolor_chunk_for_depth iterates flat Vec<Option<TileData>> — pure cache-friendly sequential reads - tile_fade_color computes color from tileset_idx + z_diff without any HashMap access - vs repopulate_chunk_tiles: 32K Vec reads vs 32K HashMap lookups per frame Option 2 — Targeted boundary dirty (from 9×chunks to 2×chunks): - on_camera_z_changed now only marks the 4 boundary z-levels: old_entered, old_exited, new_entered, new_exited - Registry.z_change_keys tracks z-changed keys separately from occlusion dirty_keys - populate_tilemap_chunk_data uses fast recolor for z_change_keys, full repopulate for dirty_keys (occlusion — rare) Option 3 — Separate budgets: - z_change_keys processed with MAX_POPULATE_PER_FRAME budget (fast recolor) - dirty_keys processed with MAX_POPULATE_PER_FRAME budget (full repopulate) - Both pipelines tracked separately in benchmark Also: - populate_chunk_tiles now returns underground_bits for state storage - compute_solid_bits helper extracts solid tiles from TileData Vec - spawn_tilemap_chunks computes and stores both bitmasks on spawn - despawn_tilemap_chunks clears all state for despawned chunks
This commit is contained in:
+1
-1
@@ -1,4 +1,4 @@
|
||||
initial_chunk_radius = 5
|
||||
initial_chunk_radius = 15
|
||||
|
||||
[spawn_counts]
|
||||
dorfs = 5
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
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::<tiles::TilemapBenchmark>()
|
||||
.init_resource::<tiles::TilemapChunkRegistry>()
|
||||
.init_resource::<tiles::TilemapChunkSpawner>()
|
||||
+ .init_resource::<tiles::PreviousZIndex>()
|
||||
.add_message::<GenerateChunkEvent>()
|
||||
.add_message::<ChunkTerrainEvent>()
|
||||
.add_message::<ChunkWeatheringAndPrecipitationEvent>()
|
||||
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<f64>,
|
||||
}
|
||||
|
||||
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<ChunkLayerKey> = 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<ChunkLayerKey> = 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<ZIndex>, mut registry: ResMut<TilemapChunkRegistry>) {
|
||||
- if z_index.is_changed() {
|
||||
- let keys: Vec<ChunkLayerKey> = registry.entities.keys().cloned().collect();
|
||||
- for key in keys {
|
||||
+pub fn on_camera_z_changed(
|
||||
+ z_index: Res<ZIndex>,
|
||||
+ mut registry: ResMut<TilemapChunkRegistry>,
|
||||
+ mut prev_z: ResMut<PreviousZIndex>,
|
||||
+ mut bench: ResMut<super::TilemapBenchmark>,
|
||||
+) {
|
||||
+ 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<ChunkLayerKey> = 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(
|
||||
@@ -26,6 +26,7 @@ impl Plugin for WorldPlugin {
|
||||
.init_resource::<TerrainBlobStorage>()
|
||||
.init_resource::<tiles::TilemapBenchmark>()
|
||||
.init_resource::<tiles::TilemapChunkRegistry>()
|
||||
.init_resource::<tiles::TilemapChunkStates>()
|
||||
.init_resource::<tiles::TilemapChunkSpawner>()
|
||||
.init_resource::<tiles::PreviousZIndex>()
|
||||
.add_message::<GenerateChunkEvent>()
|
||||
|
||||
@@ -24,6 +24,7 @@ pub struct ChunkLayerKey {
|
||||
pub struct TilemapChunkRegistry {
|
||||
pub entities: HashMap<ChunkLayerKey, Entity>,
|
||||
pub dirty_keys: HashSet<ChunkLayerKey>,
|
||||
pub z_change_keys: HashSet<ChunkLayerKey>,
|
||||
}
|
||||
|
||||
impl Default for TilemapChunkRegistry {
|
||||
@@ -31,10 +32,17 @@ impl Default for TilemapChunkRegistry {
|
||||
Self {
|
||||
entities: Default::default(),
|
||||
dirty_keys: Default::default(),
|
||||
z_change_keys: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct TilemapChunkStates {
|
||||
pub solid_bits: HashMap<ChunkLayerKey, u64>,
|
||||
pub underground_bits: HashMap<ChunkLayerKey, u64>,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct TilemapChunkSpawner {
|
||||
pub pending: Vec<ChunkLayerKey>,
|
||||
@@ -146,15 +154,14 @@ fn populate_chunk_tiles(
|
||||
chunk_pos: IVec2,
|
||||
z_index: usize,
|
||||
camera_z: i32,
|
||||
) -> Vec<Option<TileData>> {
|
||||
) -> (Vec<Option<TileData>>, u64) {
|
||||
let z_diff = i32::max(camera_z - camera_z_for_z_index(z_index), 0);
|
||||
if z_diff > 8 {
|
||||
return vec![None; (CHUNK_SIZE * CHUNK_SIZE) as usize];
|
||||
}
|
||||
|
||||
let mut tiles = Vec::with_capacity((CHUNK_SIZE * CHUNK_SIZE) as usize);
|
||||
let mut underground_bits: u64 = 0;
|
||||
|
||||
for local_y in 0..CHUNK_SIZE {
|
||||
for local_x in 0..CHUNK_SIZE {
|
||||
let slot_idx = local_y * CHUNK_SIZE + local_x;
|
||||
let world_pos = IVec3::new(
|
||||
chunk_pos.x * CHUNK_SIZE_TILE + local_x * ITILE_SIZE,
|
||||
chunk_pos.y * CHUNK_SIZE_TILE + local_y * ITILE_SIZE,
|
||||
@@ -166,6 +173,10 @@ fn populate_chunk_tiles(
|
||||
let idx = tile_id_to_tileset_index(t.id as u32);
|
||||
if let Some(tileset_idx) = idx {
|
||||
let is_visible = is_tile_visible_at_z(t, z_index);
|
||||
let is_underground = !is_visible && t.id != 0;
|
||||
if is_underground {
|
||||
underground_bits |= 1u64 << slot_idx;
|
||||
}
|
||||
let tile_data = if is_visible {
|
||||
tile_for_depth(tileset_idx, z_diff)
|
||||
} else if t.id == 0 {
|
||||
@@ -186,7 +197,42 @@ fn populate_chunk_tiles(
|
||||
}
|
||||
}
|
||||
}
|
||||
tiles
|
||||
(tiles, underground_bits)
|
||||
}
|
||||
|
||||
fn recolor_chunk_for_depth(
|
||||
tile_data: &mut TilemapChunkTileData,
|
||||
z_diff: i32,
|
||||
solid_bits: u64,
|
||||
underground_bits: u64,
|
||||
) {
|
||||
for (i, slot) in tile_data.0.iter_mut().enumerate() {
|
||||
let Some(td) = slot else { continue };
|
||||
let is_underground = (underground_bits >> i) & 1 == 1;
|
||||
td.color = if is_underground {
|
||||
Color::BLACK
|
||||
} else {
|
||||
tile_fade_color(td.tileset_index, z_diff)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn tile_fade_color(tileset_idx: u16, z_diff: i32) -> Color {
|
||||
if z_diff <= 0 {
|
||||
return Color::WHITE;
|
||||
}
|
||||
let n = (z_diff as f32).min(8_f32);
|
||||
let t = 1_f32 - (1_f32 - LAYER_ALPHA).powf(n);
|
||||
let sky_r = 133_f32 / 255_f32;
|
||||
let sky_g = 167_f32 / 255_f32;
|
||||
let sky_b = 178_f32 / 255_f32;
|
||||
if t >= 0.98 || tileset_idx == 0 {
|
||||
return Color::srgb(sky_r, sky_g, sky_b);
|
||||
}
|
||||
let r = (1_f32 - t) + sky_r * t;
|
||||
let g = (1_f32 - t) + sky_g * t;
|
||||
let b = (1_f32 - t) + sky_b * t;
|
||||
Color::srgb(r, g, b)
|
||||
}
|
||||
|
||||
pub fn spawn_tilemap_chunks(
|
||||
@@ -194,6 +240,7 @@ pub fn spawn_tilemap_chunks(
|
||||
tilemap: Res<TileMap>,
|
||||
tileset: Res<TilemapTileset>,
|
||||
mut registry: ResMut<TilemapChunkRegistry>,
|
||||
mut states: ResMut<TilemapChunkStates>,
|
||||
mut spawner: ResMut<TilemapChunkSpawner>,
|
||||
z_index: Res<ZIndex>,
|
||||
mut bench: ResMut<super::TilemapBenchmark>,
|
||||
@@ -202,6 +249,9 @@ pub fn spawn_tilemap_chunks(
|
||||
if !spawner.started && !registry.entities.is_empty() {
|
||||
registry.entities.clear();
|
||||
registry.dirty_keys.clear();
|
||||
registry.z_change_keys.clear();
|
||||
states.solid_bits.clear();
|
||||
states.underground_bits.clear();
|
||||
spawner.started = true;
|
||||
}
|
||||
return;
|
||||
@@ -217,13 +267,16 @@ pub fn spawn_tilemap_chunks(
|
||||
for key in spawner.pending.drain(..) {
|
||||
let z_diff = i32::max(camera_z - camera_z_for_z_index(key.z_index), 0);
|
||||
|
||||
let tile_data = if key.layer == LAYER_FLOOR {
|
||||
populate_chunk_tiles(&tilemap, key.chunk_pos, key.z_index, camera_z)
|
||||
let (tile_data, underground_bits) = if key.layer == LAYER_FLOOR {
|
||||
let (tiles, ub) = populate_chunk_tiles(&tilemap, key.chunk_pos, key.z_index, camera_z);
|
||||
let solid_bits = compute_solid_bits(&tiles);
|
||||
states.solid_bits.insert(key, solid_bits);
|
||||
states.underground_bits.insert(key, ub);
|
||||
(tiles, ub)
|
||||
} else {
|
||||
vec![None; (CHUNK_SIZE * CHUNK_SIZE) as usize]
|
||||
(vec![None; (CHUNK_SIZE * CHUNK_SIZE) as usize], 0u64)
|
||||
};
|
||||
|
||||
// Fixed
|
||||
let half_chunk = (CHUNK_SIZE_TILE as f32) / 2.0 - TILE_SIZE / 2.0;
|
||||
let world_x = (key.chunk_pos.x as f32) * (CHUNK_SIZE_TILE as f32) + half_chunk;
|
||||
let world_y = (key.chunk_pos.y as f32) * (CHUNK_SIZE_TILE as f32) + half_chunk;
|
||||
@@ -264,6 +317,18 @@ pub fn spawn_tilemap_chunks(
|
||||
bench.populate_ms += now.elapsed().as_secs_f64() * 1000.0;
|
||||
}
|
||||
|
||||
fn compute_solid_bits(tiles: &[Option<TileData>]) -> u64 {
|
||||
let mut bits: u64 = 0;
|
||||
for (i, slot) in tiles.iter().enumerate() {
|
||||
if let Some(td) = slot {
|
||||
if td.tileset_index != 0 {
|
||||
bits |= 1u64 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
bits
|
||||
}
|
||||
|
||||
pub fn update_tilemap_chunk_visibility(
|
||||
z_index: Res<ZIndex>,
|
||||
mut query: Query<(&ChunkLayerKey, &mut Visibility)>,
|
||||
@@ -287,38 +352,35 @@ pub fn update_tilemap_chunk_visibility(
|
||||
|
||||
pub fn populate_tilemap_chunk_data(
|
||||
mut registry: ResMut<TilemapChunkRegistry>,
|
||||
mut states: ResMut<TilemapChunkStates>,
|
||||
tilemap: Res<TileMap>,
|
||||
z_index: Res<ZIndex>,
|
||||
mut chunk_data_query: Query<(&mut TilemapChunkTileData, &ChunkLayerKey)>,
|
||||
mut bench: ResMut<super::TilemapBenchmark>,
|
||||
) {
|
||||
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_index = (camera_z as f32 + Z_BELOW) as usize;
|
||||
|
||||
let mut to_process: Vec<ChunkLayerKey> = 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 mut processed = 0usize;
|
||||
|
||||
for key in to_process {
|
||||
if !registry.z_change_keys.is_empty() {
|
||||
let current_z_index = (camera_z as f32 + Z_BELOW) as usize;
|
||||
let mut z_keys: Vec<ChunkLayerKey> = registry.z_change_keys.drain().collect();
|
||||
z_keys.sort_by_key(|k| (k.z_index as i32 - current_z_index as i32).unsigned_abs());
|
||||
z_keys.truncate(MAX_POPULATE_PER_FRAME);
|
||||
|
||||
for key in &z_keys {
|
||||
registry.z_change_keys.remove(key);
|
||||
}
|
||||
|
||||
bench.last_z_change_dirty_count = registry.entities.len();
|
||||
|
||||
for key in z_keys {
|
||||
let Some(&entity) = registry.entities.get(&key) else {
|
||||
continue;
|
||||
};
|
||||
let Ok((mut tile_data, _)) = chunk_data_query.get_mut(entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if key.layer != LAYER_FLOOR {
|
||||
continue;
|
||||
}
|
||||
@@ -328,10 +390,55 @@ pub fn populate_tilemap_chunk_data(
|
||||
continue;
|
||||
}
|
||||
|
||||
let new_tiles = populate_chunk_tiles(&tilemap, key.chunk_pos, key.z_index, camera_z);
|
||||
let solid_bits = states.solid_bits.get(&key).copied().unwrap_or(0);
|
||||
let underground_bits = states.underground_bits.get(&key).copied().unwrap_or(0);
|
||||
recolor_chunk_for_depth(&mut tile_data, z_diff, solid_bits, underground_bits);
|
||||
processed += 1;
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_secs_f64() * 1000.0;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if !registry.dirty_keys.is_empty() {
|
||||
let current_z_index = (camera_z as f32 + Z_BELOW) as usize;
|
||||
let mut to_process: Vec<ChunkLayerKey> = 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);
|
||||
}
|
||||
|
||||
for key in to_process {
|
||||
let Some(&entity) = registry.entities.get(&key) else {
|
||||
continue;
|
||||
};
|
||||
let Ok((mut tile_data, _)) = chunk_data_query.get_mut(entity) else {
|
||||
continue;
|
||||
};
|
||||
if key.layer != LAYER_FLOOR {
|
||||
continue;
|
||||
}
|
||||
|
||||
let z_diff = i32::max(camera_z - camera_z_for_z_index(key.z_index), 0);
|
||||
if z_diff > 8 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let (new_tiles, underground_bits) =
|
||||
populate_chunk_tiles(&tilemap, key.chunk_pos, key.z_index, camera_z);
|
||||
let solid_bits = compute_solid_bits(&new_tiles);
|
||||
states.solid_bits.insert(key, solid_bits);
|
||||
states.underground_bits.insert(key, underground_bits);
|
||||
tile_data.0 = new_tiles;
|
||||
processed += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let elapsed = now.elapsed().as_secs_f64() * 1000.0;
|
||||
bench.last_populate_ms = elapsed;
|
||||
@@ -340,14 +447,6 @@ 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(
|
||||
@@ -365,21 +464,34 @@ pub fn on_camera_z_changed(
|
||||
prev_z.0 = new_camera_z;
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
let old_entered_idx = (old_camera_z as f32 + Z_BELOW) as usize;
|
||||
let old_exited_idx = ((old_camera_z - 8) as f32 + Z_BELOW) as usize;
|
||||
let new_entered_idx = (new_camera_z as f32 + Z_BELOW) as usize;
|
||||
let new_exited_idx = ((new_camera_z - 8) as f32 + Z_BELOW) as usize;
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
let keys: Vec<ChunkLayerKey> = 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);
|
||||
let mut new_keys: Vec<ChunkLayerKey> = Vec::new();
|
||||
|
||||
for key in registry.entities.keys() {
|
||||
if key.z_index == old_entered_idx
|
||||
|| key.z_index == new_entered_idx
|
||||
|| key.z_index == old_exited_idx
|
||||
|| key.z_index == new_exited_idx
|
||||
{
|
||||
new_keys.push(*key);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if !new_keys.is_empty() {
|
||||
registry.z_change_keys.clear();
|
||||
for key in new_keys {
|
||||
registry.z_change_keys.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
bench.last_z_change_dirty_ms = now.elapsed().as_secs_f64() * 1000.0;
|
||||
bench.last_z_change_dirty_count = count;
|
||||
}
|
||||
@@ -388,6 +500,7 @@ pub fn despawn_tilemap_chunks(
|
||||
mut commands: Commands,
|
||||
chunk_map: Res<super::super::chunks::ChunkMap>,
|
||||
mut registry: ResMut<TilemapChunkRegistry>,
|
||||
mut states: ResMut<TilemapChunkStates>,
|
||||
query: Query<(Entity, &ChunkLayerKey)>,
|
||||
) {
|
||||
let loaded: HashSet<IVec2> = chunk_map.loaded_chunks.keys().cloned().collect();
|
||||
@@ -396,6 +509,10 @@ pub fn despawn_tilemap_chunks(
|
||||
if !loaded.contains(&key.chunk_pos) {
|
||||
commands.entity(entity).despawn();
|
||||
registry.entities.remove(key);
|
||||
registry.dirty_keys.remove(key);
|
||||
registry.z_change_keys.remove(key);
|
||||
states.solid_bits.remove(key);
|
||||
states.underground_bits.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user