summaryrefslogtreecommitdiff
path: root/src/sim/cell_sim/sim.rs
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-16 17:29:42 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-16 17:29:42 -0700
commitf177cc716c5f2aa5b50b14ccbb421de89e3a7854 (patch)
treedb06f9f44251d6d90e5e9709d47e6cc4397b9443 /src/sim/cell_sim/sim.rs
parent40e9d818195824749293dff3afcfdd5c1432adbe (diff)
wip
Diffstat (limited to 'src/sim/cell_sim/sim.rs')
-rw-r--r--src/sim/cell_sim/sim.rs234
1 files changed, 234 insertions, 0 deletions
diff --git a/src/sim/cell_sim/sim.rs b/src/sim/cell_sim/sim.rs
new file mode 100644
index 0000000..fd9b6c9
--- /dev/null
+++ b/src/sim/cell_sim/sim.rs
@@ -0,0 +1,234 @@
+use std::marker::PhantomData;
+
+use fxhash::FxHashMap;
+use rand::{Rng, SeedableRng, rngs::SmallRng};
+use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
+
+use crate::{
+ config::CHUNK_SIZE,
+ sim::{
+ cell::{cell::Cell, materials::MaterialDef},
+ cell_sim::{chunk::Chunk, world::World},
+ },
+};
+
+struct ChunkAccess<'a> {
+ ptr: *mut Chunk,
+ len: usize,
+ _marker: PhantomData<&'a mut [Chunk]>,
+}
+
+impl<'a> ChunkAccess<'a> {
+ pub fn new(chunks: &'a mut [Chunk]) -> Self {
+ Self {
+ ptr: chunks.as_mut_ptr(),
+ len: chunks.len(),
+ _marker: PhantomData,
+ }
+ }
+ unsafe fn get(&self, i: usize) -> &'a mut Chunk {
+ debug_assert!(i < self.len);
+ unsafe { &mut *self.ptr.add(i) }
+ }
+}
+
+unsafe impl Sync for ChunkAccess<'_> {}
+
+fn get_cell(chunks: &[Option<&mut Chunk>; 9], x: i32, y: i32) -> Option<Cell> {
+ let dcx = x.div_euclid(CHUNK_SIZE);
+ let dcy = y.div_euclid(CHUNK_SIZE);
+ if dcx != 0 || dcy != 0 {
+ // in a different chunk
+ let nc_x = x.rem_euclid(CHUNK_SIZE) as u8;
+ let nc_y = y.rem_euclid(CHUNK_SIZE) as u8;
+
+ chunks[(dcx + 1 + (dcy + 1) * 3) as usize]
+ .as_ref()
+ .map(|chunk| chunk.get_cell_at_local_position(nc_x, nc_y))
+ } else {
+ chunks[4]
+ .as_ref()
+ .map(|target| target.get_cell_at_local_position(x as u8, y as u8))
+ }
+}
+
+pub fn set_cell(chunks: &mut [Option<&mut Chunk>; 9], x: i32, y: i32, cell: Cell) {
+ let dcx = x.div_euclid(CHUNK_SIZE);
+ let dcy = y.div_euclid(CHUNK_SIZE);
+ if dcx != 0 || dcy != 0 {
+ // in a different chunk
+ let nc_x = x.rem_euclid(CHUNK_SIZE) as u8;
+ let nc_y = y.rem_euclid(CHUNK_SIZE) as u8;
+
+ if let Some(chunk) = &mut chunks[(dcx + 1 + (dcy + 1) * 3) as usize] {
+ chunk.set_cell_at_local_position(nc_x, nc_y, cell);
+ chunk.needs_texture_update = true;
+ }
+ } else {
+ if let Some(target) = &mut chunks[4] {
+ target.set_cell_at_local_position(x as u8, y as u8, cell);
+ target.needs_texture_update = true;
+ }
+ }
+}
+
+pub struct UpdateCtx<'a, 'b, 'c> {
+ pub chunks: &'a mut [Option<&'b mut Chunk>; 9],
+ pub seqno: u64,
+ pub seqno_parity: u8,
+
+ pub x: i32,
+ pub y: i32,
+ pub cell: &'c mut Cell,
+ pub material: &'c MaterialDef,
+
+ pub rng: &'c mut dyn Rng,
+}
+
+impl UpdateCtx<'_, '_, '_> {
+ pub fn get_cell(&self, dx: i32, dy: i32) -> Option<Cell> {
+ let x = self.x + dx;
+ let y = self.y + dy;
+ get_cell(self.chunks, x, y)
+ }
+
+ 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);
+ let x = self.x + dx;
+ let y = self.y + dy;
+ set_cell(self.chunks, x, y, cell);
+ self.chunks.iter_mut().for_each(|c| {
+ if let Some(chunk) = c {
+ chunk.sleeping = false;
+ }
+ })
+ }
+
+ 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());
+ self.set_cell(dx, dy, *self.cell);
+ return true;
+ }
+ }
+ false
+ }
+}
+
+pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
+ puffin::profile_function!();
+ let seqno_parity = (seqno as u8) & 0b1;
+ let mut rng = SmallRng::seed_from_u64(seqno);
+
+ if chunks[4].is_some() {
+ for y in (0..CHUNK_SIZE).rev() {
+ for i in 0..CHUNK_SIZE {
+ let x = if seqno_parity == 0 {
+ i
+ } else {
+ (CHUNK_SIZE) - i - 1
+ };
+
+ let mut cell = get_cell(chunks, x, y).unwrap();
+ let material = cell.material.def();
+
+ if let Some(update) = material.sim_update
+ && 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,
+ seqno,
+ seqno_parity,
+
+ x,
+ y,
+ cell: &mut cell,
+ material,
+
+ // TODO this is platform-dependent, will break for multiplayer
+ rng: &mut rng,
+ };
+
+ update(&mut update_ctx);
+ }
+ }
+ }
+ }
+}
+
+const NEIGHBORHOOD_OFFSETS: [(i32, i32); 9] = [
+ (-1, -1),
+ (0, -1),
+ (1, -1),
+ (-1, 0),
+ (0, 0),
+ (1, 0),
+ (-1, 1),
+ (0, 1),
+ (1, 1),
+];
+
+pub fn sim_tick(world: &mut World, seqno: u64, use_threading: bool) {
+ puffin::profile_function!();
+
+ let mut columns: FxHashMap<i32, Vec<i32>> = FxHashMap::default();
+ for &(cx, cy) in world.chunk_position_to_chunk_idx.keys() {
+ columns.entry(cx).or_default().push(cy);
+ }
+
+ // color columns s.t. columns of same color are separated by two columns
+ // and sort the column bottom-to-top
+ // --------------------
+ // | 0, 1, 2, 0, 1, 2 |
+ // | 0, 1, 2, 0, 1, 2 |
+ // | 0, 1, 2, 0, 1, 2 |
+ // --------------------
+ let mut columns_by_color: [Vec<(i32, Vec<i32>)>; 3] = Default::default();
+ for (cx, mut cys) in columns {
+ cys.sort_unstable_by(|a, b| b.cmp(a));
+ columns_by_color[cx.rem_euclid(3) as usize].push((cx, cys));
+ }
+
+ let access = ChunkAccess::new(&mut world.chunks);
+
+ for color in &columns_by_color {
+ puffin::profile_scope!("chunk_color");
+
+ let chunk_closure = |(cx, cys): &(i32, Vec<i32>)| {
+ let cx = *cx;
+ for &cy in cys {
+ let mut chunks: [Option<&mut Chunk>; 9] = NEIGHBORHOOD_OFFSETS.map(|(dx, dy)| {
+ world
+ .chunk_position_to_chunk_idx
+ .get(&(cx + dx, cy + dy))
+ .map(|&idx| unsafe { access.get(idx) })
+ });
+
+ if let Some(target) = &mut chunks[4] {
+ if target.sleeping {
+ continue;
+ }
+ target.sleeping = true;
+ }
+
+ sim_tick_chunk(&mut chunks, seqno);
+ }
+ };
+
+ if use_threading {
+ // TODO use forte
+ color.par_iter().for_each(chunk_closure);
+ } else {
+ color.iter().for_each(chunk_closure);
+ };
+ }
+}