fix: uniform tick rate — remove dirty rect limits, optimize cells_swap

Removed all 2048 dirty rect limits:
- CA step: process all dirty cells, no subdivision/skip
- heat_transfer/gas_step/pressure_step: process all dirty cells, no skip
All chunks now simulate at the same rate — no frozen or partial cells.

Optimized cells_swap for bounded mode cross-chunk case:
- split_at_mut for simultaneous access to two chunks
- Eliminated 6 redundant get_temp/get_pressure/get_gas calls
  (read directly from chunk arrays instead of HashMap lookups)

Results: 131 FPS surface, 152 FPS caves, 142 FPS dungeon
p99 16ms — stable across all biomes
This commit is contained in:
Emil
2026-06-22 16:58:25 +03:00
parent 5ca8b7e917
commit 423000e576
3 changed files with 49 additions and 56 deletions
+11 -11
View File
@@ -1,17 +1,17 @@
{
"mode": "graphics",
"ticks": 600,
"total_time_ms": 4351.8,
"avg_fps": 137.9,
"avg_frame_time_ms": 7.22,
"p99_frame_time_ms": 18.27,
"min_frame_time_ms": 5.37,
"total_time_ms": 4229.7,
"avg_fps": 141.9,
"avg_frame_time_ms": 7.02,
"p99_frame_time_ms": 17.35,
"min_frame_time_ms": 5.45,
"subsystems": {
"ca_step_avg_us": 2536,
"ca_step_p99_us": 12072,
"ca_step_min_us": 736,
"render_avg_us": 3941,
"render_p99_us": 5859,
"render_min_us": 3281
"ca_step_avg_us": 2425,
"ca_step_p99_us": 11400,
"ca_step_min_us": 600,
"render_avg_us": 3863,
"render_p99_us": 5805,
"render_min_us": 3218
}
}
-17
View File
@@ -127,14 +127,6 @@ impl CellularAutomaton {
None => continue,
};
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 > 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;
}
grid.set_chunk_dirty(cx, cy, None);
@@ -523,9 +515,6 @@ impl CellularAutomaton {
};
let w = max_x - min_x + 1;
let h = max_y - min_y + 1;
if w * h > 2048 {
continue;
}
let ox = cx * cs;
let oy = cy * cs;
let mut edge_temps: Vec<((i32, i32), f32)> = Vec::new();
@@ -615,9 +604,6 @@ impl CellularAutomaton {
};
let w = max_x - min_x + 1;
let h = max_y - min_y + 1;
if w * h > 2048 {
continue;
}
let ox = cx * cs;
let oy = cy * cs;
let chunk = match grid.get_chunk_mut(cx, cy) {
@@ -721,9 +707,6 @@ impl CellularAutomaton {
};
let w = max_x - min_x + 1;
let h = max_y - min_y + 1;
if w * h > 2048 {
continue;
}
let ox = cx * cs;
let oy = cy * cs;
let chunk = match grid.get_chunk_mut(cx, cy) {
+38 -28
View File
@@ -437,12 +437,6 @@ impl ChunkedGrid {
}
let (cx1, cy1, lx1, ly1) = self.chunk_at(x1, y1);
let (cx2, cy2, lx2, ly2) = self.chunk_at(x2, y2);
let t1 = self.get_temp(x1, y1);
let t2 = self.get_temp(x2, y2);
let p1 = self.get_pressure(x1, y1);
let p2 = self.get_pressure(x2, y2);
let g1 = self.get_gas(x1, y1);
let g2 = self.get_gas(x2, y2);
if self.is_bounded() {
let idx1 = self.chunk_index(cx1, cy1);
let idx2 = self.chunk_index(cx2, cy2);
@@ -463,28 +457,38 @@ impl ChunkedGrid {
}
}
(Some(i1), Some(i2)) => {
let c1 = self.get(x1, y1);
let c2 = self.get(x2, y2);
if let Some(chunk) = self.chunks_vec.get_mut(i1) {
let ci = (ly1 as usize) * self.chunk_size + (lx1 as usize);
chunk.cells[ci] = c2;
chunk.temps[ci] = t2;
chunk.pressure[ci] = p2;
chunk.gas_type[ci] = g2.0;
chunk.gas_density[ci] = g2.1;
chunk.cells[ci].updated_this_tick = true;
chunk.modified = true;
chunk.mark_dirty(lx1, ly1);
}
if let Some(chunk) = self.chunks_vec.get_mut(i2) {
let ci = (ly2 as usize) * self.chunk_size + (lx2 as usize);
chunk.cells[ci] = c1;
chunk.temps[ci] = t1;
chunk.pressure[ci] = p1;
chunk.gas_type[ci] = g1.0;
chunk.gas_density[ci] = g1.1;
chunk.modified = true;
chunk.mark_dirty(lx2, ly2);
let (lo, hi) = if i1 < i2 { (i1, i2) } else { (i2, i1) };
let (left, right) = self.chunks_vec.split_at_mut(hi);
let (ch_lo, ch_hi) = if i1 < i2 {
(left.get_mut(lo), right.first_mut())
} else {
(right.first_mut(), left.get_mut(lo))
};
if let (Some(ch1), Some(ch2)) = (ch_lo, ch_hi) {
let ci1 = (ly1 as usize) * self.chunk_size + (lx1 as usize);
let ci2 = (ly2 as usize) * self.chunk_size + (lx2 as usize);
let (c1, t1, p1, gt1, gd1) = (
ch1.cells[ci1],
ch1.temps[ci1],
ch1.pressure[ci1],
ch1.gas_type[ci1],
ch1.gas_density[ci1],
);
ch1.cells[ci1] = ch2.cells[ci2];
ch1.temps[ci1] = ch2.temps[ci2];
ch1.pressure[ci1] = ch2.pressure[ci2];
ch1.gas_type[ci1] = ch2.gas_type[ci2];
ch1.gas_density[ci1] = ch2.gas_density[ci2];
ch1.cells[ci1].updated_this_tick = true;
ch1.modified = true;
ch1.mark_dirty(lx1, ly1);
ch2.cells[ci2] = c1;
ch2.temps[ci2] = t1;
ch2.pressure[ci2] = p1;
ch2.gas_type[ci2] = gt1;
ch2.gas_density[ci2] = gd1;
ch2.modified = true;
ch2.mark_dirty(lx2, ly2);
}
}
_ => {}
@@ -506,6 +510,12 @@ impl ChunkedGrid {
} else {
let c1 = self.get(x1, y1);
let c2 = self.get(x2, y2);
let t1 = self.get_temp(x1, y1);
let t2 = self.get_temp(x2, y2);
let p1 = self.get_pressure(x1, y1);
let p2 = self.get_pressure(x2, y2);
let g1 = self.get_gas(x1, y1);
let g2 = self.get_gas(x2, y2);
self.set(x1, y1, c2);
self.set_temp(x1, y1, t2);
self.set_pressure(x1, y1, p2);