From 7ce9781f86c56f932dabc3bc84b8d21d7be8fecc Mon Sep 17 00:00:00 2001 From: Kai Stevenson Date: Tue, 11 Aug 2026 20:41:41 -0700 Subject: refactor sim loop --- src/sim/materials.rs | 41 ------------- src/sim/materials/mod.rs | 60 ++++++++++++++++++ src/sim/materials/sand.rs | 10 +++ src/sim/materials/water.rs | 78 ++++++++++++++++++++++++ src/sim/overlay.rs | 2 - src/sim/sim.rs | 149 ++++++++++++--------------------------------- 6 files changed, 187 insertions(+), 153 deletions(-) delete mode 100644 src/sim/materials.rs create mode 100644 src/sim/materials/mod.rs create mode 100644 src/sim/materials/sand.rs create mode 100644 src/sim/materials/water.rs (limited to 'src/sim') diff --git a/src/sim/materials.rs b/src/sim/materials.rs deleted file mode 100644 index 8b21618..0000000 --- a/src/sim/materials.rs +++ /dev/null @@ -1,41 +0,0 @@ -#[derive(Clone, Copy)] -pub struct Material<'a> { - pub name: &'a str, - pub r: u8, - pub g: u8, - pub b: u8, - - pub density: u8, -} - -#[repr(u8)] -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum MaterialId { - Void, - Sand, - Water, -} - -pub static MATERIALS: [Material; 3] = [ - Material { - name: "Void", - r: 0x00, - g: 0x00, - b: 0x00, - density: 0, - }, - Material { - name: "Sand", - r: 0xDE, - g: 0xCB, - b: 0x85, - density: 50, - }, - Material { - name: "Water", - r: 0x38, - g: 0xA9, - b: 0xFF, - density: 40, - }, -]; diff --git a/src/sim/materials/mod.rs b/src/sim/materials/mod.rs new file mode 100644 index 0000000..410236d --- /dev/null +++ b/src/sim/materials/mod.rs @@ -0,0 +1,60 @@ +use crate::sim::sim::UpdateCtx; + +mod sand; +mod water; + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum MaterialId { + Void = 0, + Sand, + Wood, + Water, +} + +pub struct MaterialDef { + pub name: &'static str, + pub color: [u8; 4], + pub density: u8, + pub sim_update: Option ()>, +} + +static MATERIALS: [MaterialDef; 4] = [ + MaterialDef { + name: "Void", + color: [0x00, 0x00, 0x00, 0xFF], + density: 0, + sim_update: None, + }, + MaterialDef { + name: "Sand", + color: [0xDE, 0xCB, 0x85, 0xFF], + density: 50, + sim_update: Some(sand::sim_update), + }, + MaterialDef { + name: "Wood", + color: [0x85, 0x56, 0x1D, 0xFF], + density: 50, + sim_update: None, + }, + MaterialDef { + name: "Water", + color: [0x38, 0xA9, 0xFF, 0xFF], + density: 40, + sim_update: Some(water::sim_update), + }, +]; + +impl MaterialId { + pub const ALL: [MaterialId; 4] = [ + MaterialId::Void, + MaterialId::Sand, + MaterialId::Wood, + MaterialId::Water, + ]; + #[inline] + pub fn def(self) -> &'static MaterialDef { + &MATERIALS[self as usize] + } +} diff --git a/src/sim/materials/sand.rs b/src/sim/materials/sand.rs new file mode 100644 index 0000000..6a780f3 --- /dev/null +++ b/src/sim/materials/sand.rs @@ -0,0 +1,10 @@ +use crate::sim::sim::UpdateCtx; + +#[inline] +pub fn sim_update(ctx: &mut UpdateCtx) { + ctx.candidates_swap(&[ + (ctx.self_x, ctx.self_y + 1), + (ctx.self_x - 1 + 2 * ctx.seqno_parity as i32, ctx.self_y + 1), + (ctx.self_x + 1 - 2 * ctx.seqno_parity as i32, ctx.self_y + 1), + ]); +} diff --git a/src/sim/materials/water.rs b/src/sim/materials/water.rs new file mode 100644 index 0000000..d875aee --- /dev/null +++ b/src/sim/materials/water.rs @@ -0,0 +1,78 @@ +use crate::sim::sim::UpdateCtx; + +#[inline] +pub fn sim_update(ctx: &mut UpdateCtx) { + // if the water can fall, do so + if ctx.candidates_swap(&[ + (ctx.self_x, ctx.self_y + 1), + (ctx.self_x - 1 + 2 * ctx.seqno_parity as i32, ctx.self_y + 1), + (ctx.self_x + 1 - 2 * ctx.seqno_parity as i32, ctx.self_y + 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.board.cell_at_position(ctx.self_x - 1, ctx.self_y); + let can_move_left = left_target + .is_some_and(|c| c.material.def().density < ctx.self_cell.material.def().density); + let right_target = ctx.board.cell_at_position(ctx.self_x + 1, ctx.self_y); + let can_move_right = right_target + .is_some_and(|c| c.material.def().density < ctx.self_cell.material.def().density); + + // we can't move down or to other side, so we're stuck + if !can_move_left && !can_move_right { + 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 + }; + if (side == 1 && !can_move_right) || (side == -1 && !can_move_left) { + continue; + } + + let offset = side * (1 + i / 2); + let hole_target = ctx + .board + .cell_at_position(ctx.self_x + offset, ctx.self_y + 1); + if let Some(target) = hole_target + && target.material.def().density < ctx.self_cell.material.def().density + { + // we identified a hole and we know that the space on this side is open + // move toward the hole + let move_target = if side == 1 { right_target } else { left_target }.clone(); + // new_target.flags = new_target.flags ^ 0b1; + // safe to unwrap + ctx.board + .set_cell_at_position(ctx.self_x, ctx.self_y, move_target.unwrap()); + ctx.board + .set_cell_at_position(ctx.self_x + side, ctx.self_y, ctx.self_cell); + return; + } + } + + // we didn't find a hole, so just move "randomly" on the same surface + // TODO when to settle? + let (target, target_x) = if !can_move_left { + (right_target, 1) + } else if !can_move_right { + (left_target, -1) + } else if ctx.seqno_parity % 2 == 1 { + (right_target, 1) + } else { + (left_target, -1) + }; + + ctx.board + .set_cell_at_position(ctx.self_x, ctx.self_y, target.unwrap()); + ctx.board + .set_cell_at_position(ctx.self_x + target_x, ctx.self_y, ctx.self_cell); +} diff --git a/src/sim/overlay.rs b/src/sim/overlay.rs index 7c5d5a4..e0560b5 100644 --- a/src/sim/overlay.rs +++ b/src/sim/overlay.rs @@ -1,5 +1,3 @@ -use core::range::Range; - use crate::{Config, Input, sim::board::Board}; pub fn create_compute_combined_overlay_offset( diff --git a/src/sim/sim.rs b/src/sim/sim.rs index 186483d..350b823 100644 --- a/src/sim/sim.rs +++ b/src/sim/sim.rs @@ -1,9 +1,32 @@ -use core::range::Range; +use crate::{Board, sim::board::Cell}; + +pub struct UpdateCtx<'a> { + pub self_x: i32, + pub self_y: i32, + pub self_cell: Cell, + pub delta_time: f32, + pub seqno_parity: u8, + pub board: &'a mut Board, +} -use crate::{ - Board, - sim::materials::{MATERIALS, MaterialId}, -}; +impl UpdateCtx<'_> { + pub fn candidates_swap(&mut self, candidates: &[(i32, i32)]) -> bool { + for candidate in candidates { + let target = self.board.cell_at_position(candidate.0, candidate.1); + if let Some(target) = target + && target.material.def().density < self.self_cell.material.def().density + { + // swap the cells + self.board + .set_cell_at_position(self.self_x, self.self_y, target); + self.board + .set_cell_at_position(candidate.0, candidate.1, self.self_cell); + return true; + } + } + false + } +} // TODO: chunks pub fn sim_tick(board: &mut Board, seqno: u64, delta_time: f32) { @@ -18,115 +41,21 @@ pub fn sim_tick(board: &mut Board, seqno: u64, delta_time: f32) { let x = if seqno_parity == 0 { col } else { -col }; let cell = board.cell_at_position(x, y); - if let Some(cell) = cell - && cell.flags & 0b1 == seqno_parity + if let Some(mut cur) = cell + && cur.flags & 0b1 == seqno_parity { - let mut cur = cell.clone(); // flip the parity bit cur.flags = cur.flags ^ 0b1; - let material = &MATERIALS[cur.material as usize]; - - match cur.material { - MaterialId::Void => {} - // TODO abstract density based movement - MaterialId::Sand => { - for candidate in [ - (x, y + 1), - (x - 1 + 2 * seqno_parity as i32, y + 1), - (x + 1 - 2 * seqno_parity as i32, y + 1), - ] { - let target = board.cell_at_position(candidate.0, candidate.1); - if let Some(target) = target - && MATERIALS[target.material as usize].density < material.density - { - // swap the cells - board.set_cell_at_position(x, y, target); - board.set_cell_at_position(candidate.0, candidate.1, cur); - break; - } - } - } - MaterialId::Water => 'water: { - // if the water can fall, do so - for candidate in [ - (x, y + 1), - (x - 1 + 2 * seqno_parity as i32, y + 1), - (x + 1 - 2 * seqno_parity as i32, y + 1), - ] { - let target = board.cell_at_position(candidate.0, candidate.1); - if let Some(target) = target - && MATERIALS[target.material as usize].density < material.density - { - // swap the cells - board.set_cell_at_position(x, y, target); - board.set_cell_at_position(candidate.0, candidate.1, cur); - break 'water; - } - } - // 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 = board.cell_at_position(x - 1, y); - let can_move_left = left_target.is_some_and(|c| { - MATERIALS[c.material as usize].density < material.density - }); - let right_target = board.cell_at_position(x + 1, y); - let can_move_right = right_target.is_some_and(|c| { - MATERIALS[c.material as usize].density < material.density - }); - - // we can't move down or to other side, so we're stuck - if !can_move_left && !can_move_right { - break 'water; - } - - // 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 seqno_parity == 0 { 1 } else { -1 }; - for i in 0..20 { - let side = if i % 2 == 0 { - starting_side - } else { - -starting_side - }; - if (side == 1 && !can_move_right) || (side == -1 && !can_move_left) { - continue; - } - - let offset = side * (1 + i / 2); - - let target = board.cell_at_position(x + offset, y + 1); - if let Some(target) = target - && MATERIALS[target.material as usize].density < material.density - { - // we identified a hole and we know that the space on this side is open - // move toward the hole - let mut new_target = - if side == 1 { right_target } else { left_target }.clone(); - // new_target.flags = new_target.flags ^ 0b1; - // safe to unwrap - board.set_cell_at_position(x, y, new_target.unwrap()); - board.set_cell_at_position(x + side, y, cur); - break 'water; - } - } - - // we didn't find a hole, so just move "randomly" on the same surface - // TODO when to settle? - let (target, target_x) = if !can_move_left { - (right_target, 1) - } else if !can_move_right { - (left_target, -1) - } else if seqno_parity % 2 == 1 { - (right_target, 1) - } else { - (left_target, -1) - }; - - board.set_cell_at_position(x, y, target.unwrap()); - board.set_cell_at_position(x + target_x, y, cur); - } + if let Some(update) = cur.material.def().sim_update { + update(&mut UpdateCtx { + self_x: x, + self_y: y, + self_cell: cur, + board, + delta_time, + seqno_parity, + }); } } } -- cgit v1.3.1