summaryrefslogtreecommitdiff
path: root/src/sim
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-18 22:22:27 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-18 22:22:27 -0700
commit2640f91e36558650bb988dcc82b5148ee71f3eb0 (patch)
tree5f67c648569109c84736b11b39cb6d2cd6b67e4d /src/sim
parent92dd02d7cc4d9fc930760d26d81edbe2197a3fc0 (diff)
optimize with settlement counter
Diffstat (limited to 'src/sim')
-rw-r--r--src/sim/cell/cell.rs24
-rw-r--r--src/sim/cell/materials/fire.rs1
-rw-r--r--src/sim/cell/materials/gas.rs78
-rw-r--r--src/sim/cell/materials/liquid.rs (renamed from src/sim/cell/materials/water.rs)0
-rw-r--r--src/sim/cell/materials/mod.rs42
-rw-r--r--src/sim/cell/materials/powder.rs (renamed from src/sim/cell/materials/sand.rs)0
-rw-r--r--src/sim/cell/materials/smoke.rs20
-rw-r--r--src/sim/cell_sim/chunk.rs9
-rw-r--r--src/sim/cell_sim/sim.rs32
-rw-r--r--src/sim/rb_sim/mod.rs35
10 files changed, 112 insertions, 129 deletions
diff --git a/src/sim/cell/cell.rs b/src/sim/cell/cell.rs
index d567893..edb2402 100644
--- a/src/sim/cell/cell.rs
+++ b/src/sim/cell/cell.rs
@@ -10,6 +10,7 @@ pub struct Cell {
impl Cell {
const FLAG_PARITY: u8 = 0b0000_0001;
const FLAG_RB: u8 = 0b0000_0010;
+ const MASK_SETTLED: u8 = 0b0001_1100;
#[inline]
pub fn parity(self) -> u8 {
@@ -33,6 +34,7 @@ impl Cell {
pub fn rb(self) -> bool {
(self.flags & Self::FLAG_RB) == Self::FLAG_RB
}
+ #[inline]
pub fn set_rb(&mut self, rb: bool) {
if rb {
self.flags |= Self::FLAG_RB
@@ -40,6 +42,28 @@ impl Cell {
self.flags = self.flags & !Self::FLAG_RB
}
}
+
+ #[inline]
+ pub fn settled(self) -> u8 {
+ (self.flags & Self::MASK_SETTLED) >> 2
+ }
+ #[inline]
+ fn set_settled(&mut self, settled: u8) {
+ debug_assert!(settled <= 7);
+ self.flags = (settled << 2) | (self.flags & !Self::MASK_SETTLED);
+ }
+ #[inline]
+ pub fn reset_settled(&mut self) {
+ self.flags &= !Self::MASK_SETTLED;
+ }
+
+ #[inline]
+ pub fn increment_settled(&mut self) {
+ let s = self.settled();
+ if s < 7 {
+ self.set_settled(s + 1);
+ }
+ }
}
impl Cell {
diff --git a/src/sim/cell/materials/fire.rs b/src/sim/cell/materials/fire.rs
index e0416b0..dd1ff8c 100644
--- a/src/sim/cell/materials/fire.rs
+++ b/src/sim/cell/materials/fire.rs
@@ -25,6 +25,7 @@ impl FireCellView for Cell {
}
// TODO optimize number of rng calls?
+// TODO wtf is fire
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) {
let ticks_lived = ctx.cell.get_ticks_lived();
diff --git a/src/sim/cell/materials/gas.rs b/src/sim/cell/materials/gas.rs
index c0cd9f9..2ee7c00 100644
--- a/src/sim/cell/materials/gas.rs
+++ b/src/sim/cell/materials/gas.rs
@@ -1,76 +1,20 @@
+use rand::RngExt;
+
use crate::sim::cell_sim::sim::UpdateCtx;
#[inline]
pub fn sim_update(ctx: &mut UpdateCtx) {
- // if the water can fall, do so
- if ctx.candidates_swap(&[
- (0, -1),
- (-1 + 2 * ctx.seqno_parity as i32, -1),
- (1 - 2 * ctx.seqno_parity as i32, -1),
- ]) {
+ // only allow upward movement some of the time to limit movement speed
+ if ctx.rng.random_range(0.0..1.0) > 0.8 && ctx.candidates_swap(&[(0, -1)]) {
return;
}
-
- // if the water can't fall, check if we can move left or right
- // these are inverted on parity so that we don't preference a direction
- let left_target = ctx.get_cell(-1, 0);
- let can_move_left =
- left_target.is_some_and(|c| c.material.def().density < ctx.material.density);
- let right_target = ctx.get_cell(1, 0);
- let can_move_right =
- right_target.is_some_and(|c| c.material.def().density < ctx.material.density);
-
- // we can't move down or to other side, so we're stuck
- if !can_move_left && !can_move_right {
+ // same for each horizontal direction
+ if ctx.rng.random_range(0.0..1.0) > 0.8
+ && ctx.candidates_swap(&[(1 - ctx.seqno_parity as i32 * 2, 0)])
+ {
return;
}
-
- // if we can't move left, just move right
- if !can_move_left {
- ctx.candidates_swap(&[(1, 0)]);
- return;
- }
- // and vice versa
- if !can_move_right {
- ctx.candidates_swap(&[(-1, 0)]);
- return;
- }
-
- // find the closest hole within 20 pixels (TODO optimize)
- // a hole is any space below us with a lesser density
- // prevents equidistance stuck state
- let starting_side = if ctx.seqno_parity == 0 { 1 } else { -1 };
- for i in 0..20 {
- let side = if i % 2 == 0 {
- starting_side
- } else {
- -starting_side
- };
-
- let offset = side * (1 + i / 2);
- let hole_target = ctx.get_cell(offset, -1);
- if let Some(target) = hole_target
- && target.material.def().density < ctx.material.density
- {
- // we identified a hole and we know that the space on this side is open
- // move toward the hole
- // new_target.flags = new_target.flags ^ 0b1;
- ctx.candidates_swap(&[(side, 0)]);
- return;
- }
- }
-
- // we didn't find a hole, so just move "randomly" on the same surface
- // TODO when to settle?
- let target_x = if !can_move_left {
- 1
- } else if !can_move_right {
- -1
- } else if ctx.seqno_parity % 2 == 1 {
- 1
- } else {
- -1
- };
-
- ctx.candidates_swap(&[(target_x, 0)]);
+ if ctx.rng.random_range(0.0..1.0) > 0.8
+ && ctx.candidates_swap(&[(-1 + ctx.seqno_parity as i32 * 2, 0)])
+ {}
}
diff --git a/src/sim/cell/materials/water.rs b/src/sim/cell/materials/liquid.rs
index 4b848fb..4b848fb 100644
--- a/src/sim/cell/materials/water.rs
+++ b/src/sim/cell/materials/liquid.rs
diff --git a/src/sim/cell/materials/mod.rs b/src/sim/cell/materials/mod.rs
index 0d19696..9bcf5e4 100644
--- a/src/sim/cell/materials/mod.rs
+++ b/src/sim/cell/materials/mod.rs
@@ -2,9 +2,8 @@ use crate::sim::cell_sim::sim::UpdateCtx;
mod fire;
mod gas;
-mod sand;
-mod smoke;
-mod water;
+mod liquid;
+mod powder;
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
@@ -13,71 +12,82 @@ pub enum MaterialId {
Sand,
Wood,
Water,
- Gas,
Fire,
Smoke,
}
+#[repr(u8)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug)]
+pub enum MaterialForm {
+ Void,
+ Gas,
+ Liquid,
+ Powder,
+ Solid,
+}
+
pub struct MaterialDef {
pub name: &'static str,
pub color: (u8, u8, u8, u8),
pub density: u8,
+ pub form: MaterialForm,
pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> ()>,
}
-static MATERIALS: [MaterialDef; 7] = [
+static MATERIALS: [MaterialDef; 6] = [
MaterialDef {
name: "Void",
color: (0x00, 0x00, 0x00, 0x00),
density: 0,
+ // form usually but not always determines the update fn
+ form: MaterialForm::Void,
sim_update: None,
},
MaterialDef {
name: "Sand",
color: (0xDE, 0xCB, 0x85, 0xFF),
density: 50,
- sim_update: Some(sand::sim_update),
+ form: MaterialForm::Powder,
+ sim_update: Some(powder::sim_update),
},
MaterialDef {
name: "Wood",
color: (0x85, 0x56, 0x1D, 0xFF),
density: 50,
+ form: MaterialForm::Solid,
sim_update: None,
},
MaterialDef {
name: "Water",
color: (0x38, 0xA9, 0xFF, 0xFF),
density: 40,
- sim_update: Some(water::sim_update),
- },
- MaterialDef {
- name: "Gas",
- color: (0xBD, 0xFF, 0xE4, 0xAA),
- density: 10,
- sim_update: Some(gas::sim_update),
+ form: MaterialForm::Liquid,
+ sim_update: Some(liquid::sim_update),
},
+ // TODO this is not a material
MaterialDef {
name: "Fire",
color: (0xFC, 0x66, 0x00, 0xAA),
// for now this matches wood
density: 50,
+ form: MaterialForm::Gas,
sim_update: Some(fire::sim_update),
},
MaterialDef {
name: "Smoke",
color: (0x32, 0x35, 0x36, 0xAA),
density: 15,
- sim_update: Some(smoke::sim_update),
+ form: MaterialForm::Gas,
+ sim_update: Some(gas::sim_update),
},
];
impl MaterialId {
- pub const ALL: [MaterialId; 7] = [
+ pub const ALL: [MaterialId; 6] = [
MaterialId::Void,
MaterialId::Sand,
MaterialId::Wood,
MaterialId::Water,
- MaterialId::Gas,
MaterialId::Fire,
MaterialId::Smoke,
];
diff --git a/src/sim/cell/materials/sand.rs b/src/sim/cell/materials/powder.rs
index d2d1265..d2d1265 100644
--- a/src/sim/cell/materials/sand.rs
+++ b/src/sim/cell/materials/powder.rs
diff --git a/src/sim/cell/materials/smoke.rs b/src/sim/cell/materials/smoke.rs
deleted file mode 100644
index 2ee7c00..0000000
--- a/src/sim/cell/materials/smoke.rs
+++ /dev/null
@@ -1,20 +0,0 @@
-use rand::RngExt;
-
-use crate::sim::cell_sim::sim::UpdateCtx;
-
-#[inline]
-pub fn sim_update(ctx: &mut UpdateCtx) {
- // only allow upward movement some of the time to limit movement speed
- if ctx.rng.random_range(0.0..1.0) > 0.8 && ctx.candidates_swap(&[(0, -1)]) {
- return;
- }
- // same for each horizontal direction
- if ctx.rng.random_range(0.0..1.0) > 0.8
- && ctx.candidates_swap(&[(1 - ctx.seqno_parity as i32 * 2, 0)])
- {
- return;
- }
- if ctx.rng.random_range(0.0..1.0) > 0.8
- && ctx.candidates_swap(&[(-1 + ctx.seqno_parity as i32 * 2, 0)])
- {}
-}
diff --git a/src/sim/cell_sim/chunk.rs b/src/sim/cell_sim/chunk.rs
index c7d619b..85d2c09 100644
--- a/src/sim/cell_sim/chunk.rs
+++ b/src/sim/cell_sim/chunk.rs
@@ -1,7 +1,10 @@
use crate::{
config::{CELLS_IN_CHUNK, CHUNK_SIZE},
sim::{
- cell::{cell::Cell, materials::MaterialId},
+ cell::{
+ cell::Cell,
+ materials::{MaterialForm, MaterialId},
+ },
lib::marching_squares::Marchable,
},
};
@@ -36,7 +39,9 @@ impl Marchable for Chunk {
if x < 0 || x >= CHUNK_SIZE || y < 0 || y >= CHUNK_SIZE {
false
} else {
- self.get_cell_at_local_position(x as u8, y as u8).material != MaterialId::Void
+ // we only build a path for solid cells or settled powder cells
+ let cell = self.get_cell_at_local_position(x as u8, y as u8);
+ cell.material.def().form == MaterialForm::Solid || cell.settled() > 4
}
}
fn size(&self) -> (i32, i32) {
diff --git a/src/sim/cell_sim/sim.rs b/src/sim/cell_sim/sim.rs
index 8831bb4..4b9f05f 100644
--- a/src/sim/cell_sim/sim.rs
+++ b/src/sim/cell_sim/sim.rs
@@ -5,14 +5,10 @@ use rand::{Rng, SeedableRng, rngs::SmallRng};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use crate::{
- config::{CELLS_IN_CHUNK, CHUNK_SIZE},
+ config::CHUNK_SIZE,
sim::{
- cell::{
- cell::Cell,
- materials::{MaterialDef, MaterialId},
- },
+ cell::{cell::Cell, materials::MaterialDef},
cell_sim::{chunk::Chunk, world::World},
- rb_sim::{RbSimManager, rb_entity::RbEntity},
},
};
@@ -87,6 +83,7 @@ pub struct UpdateCtx<'a, 'b, 'c> {
pub material: &'c MaterialDef,
pub rng: &'c mut dyn Rng,
+ swapped: bool,
}
impl UpdateCtx<'_, '_, '_> {
@@ -99,8 +96,8 @@ impl UpdateCtx<'_, '_, '_> {
pub fn set_cell(&mut self, dx: i32, dy: i32, cell: Cell) {
// cannot move out of the neighbourhood, but also cannot move to the edge of the neighbourhood
// as this would wake a chunk outside of the neighbourhood
- debug_assert!(dx > -15 && dx < 15);
- debug_assert!(dy > -15 && dy < 15);
+ debug_assert!(dx > -CHUNK_SIZE + 1 && dx < CHUNK_SIZE - 1);
+ debug_assert!(dy > -CHUNK_SIZE + 1 && dy < CHUNK_SIZE - 1);
let x = self.x + dx;
let y = self.y + dy;
set_cell(self.chunks, x, y, cell);
@@ -113,10 +110,14 @@ impl UpdateCtx<'_, '_, '_> {
pub fn candidates_swap(&mut self, candidates: &[(i32, i32)]) -> bool {
for &(dx, dy) in candidates {
- let candidate_cell = self.get_cell(dx, dy);
- if candidate_cell.is_some_and(|c| c.material.def().density < self.material.density) {
- self.set_cell(0, 0, candidate_cell.unwrap());
+ if let Some(mut candidate_cell) = self.get_cell(dx, dy)
+ && candidate_cell.material.def().density < self.material.density
+ {
+ candidate_cell.reset_settled();
+ self.cell.reset_settled();
+ self.set_cell(0, 0, candidate_cell);
self.set_cell(dx, dy, *self.cell);
+ self.swapped = true;
return true;
}
}
@@ -145,8 +146,6 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
&& cell.parity() == seqno_parity
{
cell.flip_parity();
- // apply the flipped parity in case the sim target doesn't
- set_cell(chunks, x, y, cell);
let mut update_ctx = UpdateCtx {
chunks,
@@ -160,9 +159,16 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
// TODO this is platform-dependent, will break for multiplayer
rng: &mut rng,
+ swapped: false,
};
update(&mut update_ctx);
+
+ if !update_ctx.swapped {
+ // the cell didn't move, so it's more settled, and we also need to update its state for parity
+ cell.increment_settled();
+ set_cell(chunks, x, y, cell);
+ }
}
}
}
diff --git a/src/sim/rb_sim/mod.rs b/src/sim/rb_sim/mod.rs
index d986962..17f605e 100644
--- a/src/sim/rb_sim/mod.rs
+++ b/src/sim/rb_sim/mod.rs
@@ -129,7 +129,9 @@ impl RbSimManager {
height: h,
};
- let collider = self.convex_hull_collider_from_marchable(&entity).build();
+ let collider = self
+ .convex_hull_collider_from_marchable(&entity, None)
+ .build();
self.rb_entities.insert(id, entity);
self.physics_manager.collider_set.insert_with_parent(
@@ -175,16 +177,22 @@ impl RbSimManager {
}
}
- fn polyline_from_marchable(&self, marchable: &impl Marchable) -> (Vec<Vec2>, Vec<[u32; 2]>) {
+ fn polyline_from_marchable(
+ &self,
+ marchable: &impl Marchable,
+ minimum_verts: Option<u32>,
+ ) -> (Vec<Vec2>, Vec<[u32; 2]>) {
let (w, h) = marchable.size();
let polys = marching_squares_vertex_trace(marchable, w, h);
let mut vertices = Vec::new();
let mut indices = Vec::new();
+ let minimum_verts = minimum_verts.unwrap_or(3);
+
for poly in polys {
let v = vertices.len() as u32;
let p = poly.len() as u32;
- if p < 3 {
+ if p < minimum_verts {
continue;
}
for i in 0..p {
@@ -202,8 +210,9 @@ impl RbSimManager {
fn polyline_collider_from_marchable(
&self,
marchable: &impl Marchable,
+ minimum_verts: Option<u32>,
) -> geometry::ColliderBuilder {
- let (vertices, indices) = self.polyline_from_marchable(marchable);
+ let (vertices, indices) = self.polyline_from_marchable(marchable, minimum_verts);
geometry::ColliderBuilder::polyline(vertices, Some(indices))
}
@@ -211,18 +220,22 @@ impl RbSimManager {
fn convex_hull_collider_from_marchable(
&self,
marchable: &impl Marchable,
+ minimum_verts: Option<u32>,
) -> geometry::ColliderBuilder {
- let (vertices, indices) = self.polyline_from_marchable(marchable);
+ let (vertices, indices) = self.polyline_from_marchable(marchable, minimum_verts);
geometry::ColliderBuilder::convex_decomposition(&vertices, &indices)
}
pub fn upsert_chunk_collider(&mut self, cx: i32, cy: i32, chunk: &Chunk) {
- let collider = self.polyline_collider_from_marchable(chunk).translation(
- vec2(
- (cx as f32 + 0.5) * CHUNK_SIZE as f32,
- (cy as f32 + 0.5) * CHUNK_SIZE as f32,
- ) / PIXELS_TO_METRES,
- );
+ puffin::profile_function!();
+ let collider = self
+ .polyline_collider_from_marchable(chunk, Some(20))
+ .translation(
+ vec2(
+ (cx as f32 + 0.5) * CHUNK_SIZE as f32,
+ (cy as f32 + 0.5) * CHUNK_SIZE as f32,
+ ) / PIXELS_TO_METRES,
+ );
if let Some(handle) = self.chunk_colliders.remove(&(cx, cy)) {
self.physics_manager.collider_set.remove(