diff options
Diffstat (limited to 'src/sim/cell_sim')
| -rw-r--r-- | src/sim/cell_sim/chunk.rs | 29 | ||||
| -rw-r--r-- | src/sim/cell_sim/mod.rs | 4 | ||||
| -rw-r--r-- | src/sim/cell_sim/overlay.rs | 50 | ||||
| -rw-r--r-- | src/sim/cell_sim/sim.rs | 234 | ||||
| -rw-r--r-- | src/sim/cell_sim/world.rs | 69 |
5 files changed, 386 insertions, 0 deletions
diff --git a/src/sim/cell_sim/chunk.rs b/src/sim/cell_sim/chunk.rs new file mode 100644 index 0000000..116a38a --- /dev/null +++ b/src/sim/cell_sim/chunk.rs @@ -0,0 +1,29 @@ +use crate::{ + config::{CELLS_IN_CHUNK, CHUNK_SIZE}, + sim::cell::cell::Cell, +}; + +pub struct Chunk { + pub cells: Box<[Cell; CELLS_IN_CHUNK]>, + pub sleeping: bool, + pub needs_texture_update: bool, +} + +impl Chunk { + #[inline] + pub fn get_cell_at_local_position(&self, x: u8, y: u8) -> Cell { + self.cells[x as usize + y as usize * CHUNK_SIZE as usize] + } + #[inline] + pub fn set_cell_at_local_position(&mut self, x: u8, y: u8, cell: Cell) { + self.cells[x as usize + y as usize * CHUNK_SIZE as usize] = cell; + } + + pub fn void() -> Self { + Chunk { + cells: Box::new([Cell::void(); CELLS_IN_CHUNK]), + sleeping: true, + needs_texture_update: true, + } + } +} diff --git a/src/sim/cell_sim/mod.rs b/src/sim/cell_sim/mod.rs new file mode 100644 index 0000000..a1db2a1 --- /dev/null +++ b/src/sim/cell_sim/mod.rs @@ -0,0 +1,4 @@ +pub mod chunk; +pub mod overlay; +pub mod sim; +pub mod world; diff --git a/src/sim/cell_sim/overlay.rs b/src/sim/cell_sim/overlay.rs new file mode 100644 index 0000000..ee494ae --- /dev/null +++ b/src/sim/cell_sim/overlay.rs @@ -0,0 +1,50 @@ +use crate::{Config, Input, sim::cell_sim::world::World}; + +pub fn create_compute_combined_overlay_offset( + world: &World, + config: &Config, + input: &Input, +) -> impl Fn(i32, i32) -> (u8, u8, u8, u8) { + puffin::profile_function!(); + // TODO fix this move? + move |pixel_x: i32, pixel_y: i32| { + // could allow negative offsets too + let mut offset: (u8, u8, u8, u8) = (0x00, 0x00, 0x00, 0x00); + + // // bounds + // // left + // let xl = -((board.get_game_width() / 2 + 1) as i32); + // // right + // let xu = (board.get_game_width() / 2 + 1) as i32; + // // bottom + // let yl = -((board.get_game_height() / 2 + 1) as i32); + // // top + // let yu = (board.get_game_height() / 2 + 1) as i32; + + // if ((pixel_x == xl || pixel_x == xu) && (pixel_y <= yu && pixel_y >= yl)) + // || (pixel_y == yl || pixel_y == yu) && (pixel_x <= xu && pixel_x >= xl) + // { + // offset.0 = offset.0.saturating_add(0xFF); + // offset.1 = offset.1.saturating_add(0xFF); + // offset.2 = offset.2.saturating_add(0xFF); + // } + + // // grid + // if pixel_x % 30 == 0 || pixel_y % 30 == 0 { + // offset.0 = offset.0.saturating_add(0x10); + // offset.1 = offset.1.saturating_add(0x10); + // offset.2 = offset.2.saturating_add(0x10); + // } + + // // brush/selection + // if input.last_mouse_pos_on_board.is_some_and(|p| { + // ((pixel_x - p.0).pow(2) + (pixel_y - p.1).pow(2)) < (config.brush_radius as i32).pow(2) + // }) { + // offset.0 = offset.0.saturating_add(0x82); + // offset.1 = offset.1.saturating_add(0xA1); + // offset.2 = offset.2.saturating_add(0xAD); + // } + + return offset; + } +} 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); + }; + } +} diff --git a/src/sim/cell_sim/world.rs b/src/sim/cell_sim/world.rs new file mode 100644 index 0000000..924639a --- /dev/null +++ b/src/sim/cell_sim/world.rs @@ -0,0 +1,69 @@ +use fxhash::FxHashMap; + +use crate::{ + config::CHUNK_SIZE, + sim::{cell::cell::Cell, cell_sim::chunk::Chunk}, +}; + +pub struct World { + pub chunks: Vec<Chunk>, + // TODO FxFxHashMap? + pub chunk_position_to_chunk_idx: FxHashMap<(i32, i32), usize>, +} + +impl World { + #[inline] + pub fn split_game_position(x: i32, y: i32) -> ((i32, i32), (u8, u8)) { + ( + ( + // TODO is this cast expensive? + x.div_euclid(CHUNK_SIZE), + y.div_euclid(CHUNK_SIZE), + ), + ( + x.rem_euclid(CHUNK_SIZE) as u8, + y.rem_euclid(CHUNK_SIZE) as u8, + ), + ) + } + + // VERY EXPENSIVE + pub fn get_cell_from_game_position(&self, x: i32, y: i32) -> Option<Cell> { + let ((cx, cy), (dx, dy)) = World::split_game_position(x, y); + self.chunk_position_to_chunk_idx + .get(&(cx, cy)) + .map(|&idx| self.chunks[idx].get_cell_at_local_position(dx, dy)) + } + + // VERY EXPENSIVE + pub fn set_cell_from_game_position(&mut self, x: i32, y: i32, cell: Cell, sleeping: bool) { + let ((cx, cy), (dx, dy)) = World::split_game_position(x, y); + if let Some(&idx) = self.chunk_position_to_chunk_idx.get(&(cx, cy)) { + self.chunks[idx].set_cell_at_local_position(dx, dy, cell); + // this is a temporary hack + self.chunks[idx].sleeping = sleeping; + self.chunks[idx].needs_texture_update = true; + } + } + + pub fn insert(&mut self, x: i32, y: i32, chunk: Chunk) { + self.chunk_position_to_chunk_idx + .insert((x, y), self.chunks.len()); + self.chunks.push(chunk); + } + + pub fn from_default_size() -> Self { + let mut world = World { + chunks: Vec::new(), + chunk_position_to_chunk_idx: FxHashMap::default(), + }; + + for y in -10..1 { + for x in -100..100 { + world.insert(x, y, Chunk::void()); + } + } + + world + } +} |
