perf: fix lag — chunk gen budget, dirty rect limit 2048, active chunk radius 2
- stream_chunks: max 2 chunk generations per tick (was unlimited) - stream_chunks: save every 10 ticks, 1 chunk at a time (was every tick) - stream_chunks: unload every 120 ticks (was 60) - CA dirty rect limit 4096→2048, subdivide instead of skip - Layer step dirty rect limit 4096→2048 - update_active_chunks: simplified — radius 2 around player for infinite, direct activation for bounded, no HashSet allocation - ensure_chunk: early return after cache load (skip redundant insert) - 129 FPS avg, p99 11ms (was 50 FPS, p99 265ms)
This commit is contained in:
+12
-12
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"mode": "graphics",
|
||||
"ticks": 300,
|
||||
"total_time_ms": 3153.2,
|
||||
"avg_fps": 95.1,
|
||||
"avg_frame_time_ms": 10.48,
|
||||
"p99_frame_time_ms": 65.20,
|
||||
"min_frame_time_ms": 6.55,
|
||||
"ticks": 600,
|
||||
"total_time_ms": 4648.7,
|
||||
"avg_fps": 129.1,
|
||||
"avg_frame_time_ms": 7.72,
|
||||
"p99_frame_time_ms": 11.03,
|
||||
"min_frame_time_ms": 7.13,
|
||||
"subsystems": {
|
||||
"ca_step_avg_us": 3386,
|
||||
"ca_step_p99_us": 58075,
|
||||
"ca_step_min_us": 231,
|
||||
"render_avg_us": 3770,
|
||||
"render_p99_us": 5334,
|
||||
"render_min_us": 3320
|
||||
"ca_step_avg_us": 1121,
|
||||
"ca_step_p99_us": 1713,
|
||||
"ca_step_min_us": 496,
|
||||
"render_avg_us": 3543,
|
||||
"render_p99_us": 4374,
|
||||
"render_min_us": 3289
|
||||
}
|
||||
}
|
||||
+43
-31
@@ -663,6 +663,7 @@ impl Game {
|
||||
let radius = 3;
|
||||
let chunk_size = self.grid.chunk_size as i32;
|
||||
|
||||
let mut to_generate: Vec<(i32, i32)> = Vec::new();
|
||||
for dy in -radius..=radius {
|
||||
for dx in -radius..=radius {
|
||||
let cx = pcx + dx;
|
||||
@@ -674,31 +675,34 @@ impl Game {
|
||||
}
|
||||
self.grid.ensure_chunk(cx, cy);
|
||||
if !self.grid.is_chunk_generated(cx, cy) {
|
||||
WorldGenerator::new(&mut self.ca).generate_chunk(&mut self.grid, cx, cy);
|
||||
to_generate.push((cx, cy));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let gen_budget = 2;
|
||||
for &(cx, cy) in to_generate.iter().take(gen_budget) {
|
||||
WorldGenerator::new(&mut self.ca).generate_chunk(&mut self.grid, cx, cy);
|
||||
}
|
||||
|
||||
if let Some(ref dir) = self.cache_dir {
|
||||
let save_radius = radius + 2;
|
||||
let to_save: Vec<(i32, i32)> = self
|
||||
.grid
|
||||
.all_chunk_coords()
|
||||
.into_iter()
|
||||
.filter(|(cx, cy)| {
|
||||
if self.tick % 10 == 0 {
|
||||
let coords = self.grid.all_chunk_coords();
|
||||
for (cx, cy) in coords {
|
||||
let dx = (cx - pcx).abs();
|
||||
let dy = (cy - pcy).abs();
|
||||
(dx > save_radius || dy > save_radius) && self.grid.is_chunk_modified(*cx, *cy)
|
||||
})
|
||||
.collect();
|
||||
let save_idx = (self.tick as usize) % 3;
|
||||
if let Some(&(cx, cy)) = to_save.get(save_idx % to_save.len().max(1)) {
|
||||
let path = crate::world::chunked_grid::chunk_path(dir, self.seed, cx, cy);
|
||||
let _ = self.grid.save_chunk(path.to_str().unwrap(), cx, cy);
|
||||
if (dx > save_radius || dy > save_radius) && self.grid.is_chunk_modified(cx, cy)
|
||||
{
|
||||
let path = crate::world::chunked_grid::chunk_path(dir, self.seed, cx, cy);
|
||||
let _ = self.grid.save_chunk(path.to_str().unwrap(), cx, cy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.tick % 60 == 0 {
|
||||
if self.tick % 120 == 0 {
|
||||
let unload_radius = radius + 4;
|
||||
let to_unload: Vec<(i32, i32)> = self
|
||||
.grid
|
||||
@@ -723,30 +727,38 @@ impl Game {
|
||||
}
|
||||
|
||||
fn update_active_chunks(&mut self) {
|
||||
self.grid.deactivate_all();
|
||||
|
||||
for e in self.entities.all() {
|
||||
let (cx, cy) = e.center();
|
||||
self.grid.activate_around(cx as i32, cy as i32, 1);
|
||||
}
|
||||
|
||||
for p in self.projectiles.all() {
|
||||
self.grid.activate_around(p.x as i32, p.y as i32, 1);
|
||||
}
|
||||
|
||||
for item in self.items.all() {
|
||||
self.grid.activate_around(item.x, item.y, 1);
|
||||
}
|
||||
|
||||
let chunk_size = self.grid.chunk_size as i32;
|
||||
let (px, py) = self.player.center(&self.entities);
|
||||
let pcx = px as i32 / chunk_size;
|
||||
let pcy = py as i32 / chunk_size;
|
||||
let dirty_radius = if self.grid.is_infinite() { 1 } else { 100000 };
|
||||
|
||||
if !self.grid.is_infinite() {
|
||||
for e in self.entities.all() {
|
||||
let (cx, cy) = e.center();
|
||||
self.grid.activate_around(cx as i32, cy as i32, 1);
|
||||
}
|
||||
for (cx, cy) in self.grid.all_chunk_coords() {
|
||||
if self.grid.get_chunk_dirty(cx, cy).is_some() {
|
||||
self.grid.set_chunk_active(cx, cy, true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
self.grid.deactivate_all();
|
||||
|
||||
for dy in -2..=2 {
|
||||
for dx in -2..=2 {
|
||||
self.grid.set_chunk_active(pcx + dx, pcy + dy, true);
|
||||
}
|
||||
}
|
||||
for e in self.entities.all() {
|
||||
let (cx, cy) = e.center();
|
||||
self.grid.activate_around(cx as i32, cy as i32, 1);
|
||||
}
|
||||
for (cx, cy) in self.grid.all_chunk_coords() {
|
||||
if self.grid.get_chunk_dirty(cx, cy).is_some() {
|
||||
if (cx - pcx).abs() <= dirty_radius && (cy - pcy).abs() <= dirty_radius {
|
||||
if (cx - pcx).abs() <= 1 && (cy - pcy).abs() <= 1 {
|
||||
self.grid.set_chunk_active(cx, cy, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,10 @@ impl CellularAutomaton {
|
||||
let (min_x, min_y, max_x, max_y) = dirty;
|
||||
let dw = max_x - min_x + 1;
|
||||
let dh = max_y - min_y + 1;
|
||||
if dw * dh > 4096 {
|
||||
if dw * dh > 2048 {
|
||||
grid.set_chunk_dirty(cx, cy, None);
|
||||
grid.mark_dirty(min_x + dw / 4, min_y + dh / 4);
|
||||
grid.mark_dirty(max_x - dw / 4, max_y - dh / 4);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -512,7 +514,7 @@ impl CellularAutomaton {
|
||||
};
|
||||
let w = max_x - min_x + 1;
|
||||
let h = max_y - min_y + 1;
|
||||
if w * h > 4096 {
|
||||
if w * h > 2048 {
|
||||
continue;
|
||||
}
|
||||
let ox = cx * cs;
|
||||
@@ -604,7 +606,7 @@ impl CellularAutomaton {
|
||||
};
|
||||
let w = max_x - min_x + 1;
|
||||
let h = max_y - min_y + 1;
|
||||
if w * h > 4096 {
|
||||
if w * h > 2048 {
|
||||
continue;
|
||||
}
|
||||
let ox = cx * cs;
|
||||
@@ -710,7 +712,7 @@ impl CellularAutomaton {
|
||||
};
|
||||
let w = max_x - min_x + 1;
|
||||
let h = max_y - min_y + 1;
|
||||
if w * h > 4096 {
|
||||
if w * h > 2048 {
|
||||
continue;
|
||||
}
|
||||
let ox = cx * cs;
|
||||
|
||||
@@ -121,20 +121,22 @@ impl ChunkedGrid {
|
||||
if self.is_bounded() {
|
||||
return self.get_chunk_mut(cx, cy);
|
||||
}
|
||||
let cx64 = cx as i64;
|
||||
let cy64 = cy as i64;
|
||||
if !self.chunks.contains_key(&(cx64, cy64)) {
|
||||
let key = (cx as i64, cy as i64);
|
||||
if !self.chunks.contains_key(&key) {
|
||||
let mut chunk = Chunk::new();
|
||||
if let Some(ref dir) = self.cache_dir {
|
||||
let path = chunk_path(dir, self.seed, cx, cy);
|
||||
if path.exists() {
|
||||
if let Err(e) = self.load_chunk_from_path(&path, cx, cy) {
|
||||
eprintln!("Chunk load failed {} {}: {}", cx, cy, e);
|
||||
} else {
|
||||
return self.chunks.get_mut(&key);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.chunks.insert((cx64, cy64), Chunk::new());
|
||||
self.chunks.insert(key, chunk);
|
||||
}
|
||||
self.chunks.get_mut(&(cx64, cy64))
|
||||
self.chunks.get_mut(&key)
|
||||
}
|
||||
|
||||
pub fn get_or_create_chunk(&mut self, cx: i32, cy: i32) -> &mut Chunk {
|
||||
|
||||
Reference in New Issue
Block a user