diff options
Diffstat (limited to 'src/sim')
| -rw-r--r-- | src/sim/board.rs | 72 | ||||
| -rw-r--r-- | src/sim/cell.rs | 19 | ||||
| -rw-r--r-- | src/sim/chunk.rs | 25 | ||||
| -rw-r--r-- | src/sim/materials/mod.rs | 10 | ||||
| -rw-r--r-- | src/sim/materials/sand.rs | 6 | ||||
| -rw-r--r-- | src/sim/materials/water.rs | 46 | ||||
| -rw-r--r-- | src/sim/mod.rs | 4 | ||||
| -rw-r--r-- | src/sim/overlay.rs | 67 | ||||
| -rw-r--r-- | src/sim/sim.rs | 208 | ||||
| -rw-r--r-- | src/sim/world.rs | 66 |
10 files changed, 341 insertions, 182 deletions
diff --git a/src/sim/board.rs b/src/sim/board.rs deleted file mode 100644 index 7af19be..0000000 --- a/src/sim/board.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::{ - config::{PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH}, - sim::materials::MaterialId, -}; - -#[derive(Clone, Copy)] -pub struct Cell { - pub material: MaterialId, - pub flags: u8, -} - -impl Cell { - pub fn void() -> Cell { - Cell { - material: MaterialId::Void, - flags: 0, - } - } - pub fn from_material(material: MaterialId) -> Cell { - Cell { material, flags: 0 } - } -} - -pub struct Board { - pub size_x: u32, - pub size_y: u32, - pub cells: Vec<Cell>, -} - -impl Board { - pub fn index_to_position(&self, idx: usize) -> (i32, i32) { - let y = idx / self.size_x as usize; - let x = idx % self.size_x as usize; - let board_x = x as i32 - self.size_x as i32 / 2; - let board_y = y as i32 - self.size_x as i32 / 2; - return (board_x, board_y); - } - pub fn set_cell_at_position(&mut self, x: i32, y: i32, c: Cell) { - // TODO: option? - let idx = self.position_to_index(x, y).unwrap(); - self.cells[idx] = c; - } - pub fn cell_at_position(&self, x: i32, y: i32) -> Option<Cell> { - Some(self.cells[self.position_to_index(x, y)?]) - } - pub fn position_to_index(&self, x: i32, y: i32) -> Option<usize> { - let board_x = x + self.size_x as i32 / 2; - let board_y = y + self.size_y as i32 / 2; - - let on_board = board_x >= 0 - && board_x < self.size_x as i32 - && board_y >= 0 - && board_y < self.size_y as i32; - - if !on_board { - return None; - } - - return Some((board_y * self.size_x as i32 + board_x) as usize); - } - pub fn empty() -> Board { - let size_x = PIXEL_BUFFER_WIDTH * 2; - let size_y = PIXEL_BUFFER_HEIGHT * 2; - let cells: Vec<Cell> = vec![Cell::void(); (size_x * size_y) as usize]; - - Board { - size_x, - size_y, - cells, - } - } -} diff --git a/src/sim/cell.rs b/src/sim/cell.rs new file mode 100644 index 0000000..2e26ddc --- /dev/null +++ b/src/sim/cell.rs @@ -0,0 +1,19 @@ +use crate::sim::materials::MaterialId; + +#[derive(Clone, Copy)] +pub struct Cell { + pub material: MaterialId, + pub flags: u8, +} + +impl Cell { + pub fn void() -> Cell { + Cell { + material: MaterialId::Void, + flags: 0, + } + } + pub fn from_material(material: MaterialId) -> Cell { + Cell { material, flags: 0 } + } +} diff --git a/src/sim/chunk.rs b/src/sim/chunk.rs new file mode 100644 index 0000000..154cf82 --- /dev/null +++ b/src/sim/chunk.rs @@ -0,0 +1,25 @@ +use crate::{ + config::{CELLS_IN_CHUNK, CHUNK_SIZE}, + sim::cell::Cell, +}; + +pub struct Chunk { + pub cells: Box<[Cell; CELLS_IN_CHUNK as usize]>, +} + +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]), + } + } +} diff --git a/src/sim/materials/mod.rs b/src/sim/materials/mod.rs index 410236d..86ae7d5 100644 --- a/src/sim/materials/mod.rs +++ b/src/sim/materials/mod.rs @@ -14,7 +14,7 @@ pub enum MaterialId { pub struct MaterialDef { pub name: &'static str, - pub color: [u8; 4], + pub color: (u8, u8, u8, u8), pub density: u8, pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> ()>, } @@ -22,25 +22,25 @@ pub struct MaterialDef { static MATERIALS: [MaterialDef; 4] = [ MaterialDef { name: "Void", - color: [0x00, 0x00, 0x00, 0xFF], + color: (0x00, 0x00, 0x00, 0xFF), density: 0, sim_update: None, }, MaterialDef { name: "Sand", - color: [0xDE, 0xCB, 0x85, 0xFF], + color: (0xDE, 0xCB, 0x85, 0xFF), density: 50, sim_update: Some(sand::sim_update), }, MaterialDef { name: "Wood", - color: [0x85, 0x56, 0x1D, 0xFF], + color: (0x85, 0x56, 0x1D, 0xFF), density: 50, sim_update: None, }, MaterialDef { name: "Water", - color: [0x38, 0xA9, 0xFF, 0xFF], + color: (0x38, 0xA9, 0xFF, 0xFF), density: 40, sim_update: Some(water::sim_update), }, diff --git a/src/sim/materials/sand.rs b/src/sim/materials/sand.rs index 6a780f3..ac8889f 100644 --- a/src/sim/materials/sand.rs +++ b/src/sim/materials/sand.rs @@ -3,8 +3,8 @@ 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), + (0, 1), + (-1 + 2 * ctx.seqno_parity as i32, 1), + (1 - 2 * ctx.seqno_parity as i32, 1), ]); } diff --git a/src/sim/materials/water.rs b/src/sim/materials/water.rs index d875aee..65782dd 100644 --- a/src/sim/materials/water.rs +++ b/src/sim/materials/water.rs @@ -4,21 +4,21 @@ use crate::sim::sim::UpdateCtx; 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), + (0, 1), + (-1 + 2 * ctx.seqno_parity as i32, 1), + (1 - 2 * ctx.seqno_parity as i32, 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); + 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 { @@ -40,39 +40,29 @@ pub fn sim_update(ctx: &mut UpdateCtx) { } let offset = side * (1 + i / 2); - let hole_target = ctx - .board - .cell_at_position(ctx.self_x + offset, ctx.self_y + 1); + let hole_target = ctx.get_cell(offset, 1); if let Some(target) = hole_target - && target.material.def().density < ctx.self_cell.material.def().density + && 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 - 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); + 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, target_x) = if !can_move_left { - (right_target, 1) + let target_x = if !can_move_left { + 1 } else if !can_move_right { - (left_target, -1) + -1 } else if ctx.seqno_parity % 2 == 1 { - (right_target, 1) + 1 } else { - (left_target, -1) + -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); + ctx.candidates_swap(&[(target_x, 0)]); } diff --git a/src/sim/mod.rs b/src/sim/mod.rs index 2b29628..7553f89 100644 --- a/src/sim/mod.rs +++ b/src/sim/mod.rs @@ -1,4 +1,6 @@ -pub mod board; +pub mod cell; +pub mod chunk; pub mod materials; pub mod overlay; pub mod sim; +pub mod world; diff --git a/src/sim/overlay.rs b/src/sim/overlay.rs index e0560b5..c9fdf17 100644 --- a/src/sim/overlay.rs +++ b/src/sim/overlay.rs @@ -1,48 +1,49 @@ -use crate::{Config, Input, sim::board::Board}; +use crate::{Config, Input, sim::world::World}; pub fn create_compute_combined_overlay_offset( - board: &Board, + world: &World, config: &Config, input: &Input, ) -> impl Fn(i32, i32) -> (u8, u8, u8, u8) { + puffin::profile_function!(); // TODO fix this move? - move |x: i32, y: i32| { + 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.size_x / 2 + 1) as i32); - // right - let xu = (board.size_x / 2 + 1) as i32; - // bottom - let yl = -((board.size_y / 2 + 1) as i32); - // top - let yu = (board.size_y / 2 + 1) as i32; + // // 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 ((x == xl || x == xu) && (y <= yu && y >= yl)) - || (y == yl || y == yu) && (x <= xu && x >= xl) - { - offset.0 = offset.0.saturating_add(0xFF); - offset.1 = offset.1.saturating_add(0xFF); - offset.2 = offset.2.saturating_add(0xFF); - } + // 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 x % 30 == 0 || y % 30 == 0 { - offset.0 = offset.0.saturating_add(0x10); - offset.1 = offset.1.saturating_add(0x10); - offset.2 = offset.2.saturating_add(0x10); - } + // // 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| { - ((x - p.0).pow(2) + (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); - } + // // 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/sim.rs b/src/sim/sim.rs index 350b823..5cd065e 100644 --- a/src/sim/sim.rs +++ b/src/sim/sim.rs @@ -1,26 +1,100 @@ -use crate::{Board, sim::board::Cell}; +use std::marker::PhantomData; -pub struct UpdateCtx<'a> { - pub self_x: i32, - pub self_y: i32, - pub self_cell: Cell, - pub delta_time: f32, +use crate::{ + config::CHUNK_SIZE, + sim::{cell::Cell, chunk::Chunk, materials::MaterialDef, 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) } + } +} + +pub struct UpdateCtx<'a, 'b, 'c> { + pub chunks: &'a mut [Option<&'b mut Chunk>; 9], + pub seqno: u64, pub seqno_parity: u8, - pub board: &'a mut Board, + + pub x: i32, + pub y: i32, + pub cell: &'c mut Cell, + pub material: &'c MaterialDef, +} + +fn get_cell(chunks: &[Option<&mut Chunk>; 9], x: i32, y: i32) -> Option<Cell> { + let dcx = x.div_euclid(CHUNK_SIZE as i32); + let dcy = y.div_euclid(CHUNK_SIZE as i32); + if dcx != 0 || dcy != 0 { + // in a different chunk + let nc_x = x.rem_euclid(CHUNK_SIZE as i32) as u8; + let nc_y = y.rem_euclid(CHUNK_SIZE as i32) as u8; + + return if let Some(chunk) = &chunks[(dcx + 1 + (dcy + 1) * 3) as usize] { + Some(chunk.get_cell_at_local_position(nc_x, nc_y)) + } else { + None + }; + } else { + if let Some(target) = &chunks[4] { + Some(target.get_cell_at_local_position(x as u8, y as u8)) + } else { + None + } + } +} + +pub fn set_cell(chunks: &mut [Option<&mut Chunk>; 9], x: i32, y: i32, cell: Cell) { + let dcx = x.div_euclid(CHUNK_SIZE as i32); + let dcy = y.div_euclid(CHUNK_SIZE as i32); + if dcx != 0 || dcy != 0 { + // in a different chunk + let nc_x = x.rem_euclid(CHUNK_SIZE as i32) as u8; + let nc_y = y.rem_euclid(CHUNK_SIZE as i32) 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); + } + } else { + if let Some(target) = &mut chunks[4] { + target.set_cell_at_local_position(x as u8, y as u8, cell); + } + } } -impl UpdateCtx<'_> { +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) + } + + fn set_cell(&mut self, dx: i32, dy: i32, cell: Cell) { + let x = self.x + dx; + let y = self.y + dy; + set_cell(self.chunks, x, y, cell); + } + 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); + 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; } } @@ -28,36 +102,90 @@ impl UpdateCtx<'_> { } } -// TODO: chunks -pub fn sim_tick(board: &mut Board, seqno: u64, delta_time: f32) { +pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) { + puffin::profile_function!(); // scan bottom to top to enable contiguous falling let seqno_parity = (seqno as u8) & 0b1; - let bx = (board.size_x / 2) as i32; - let by = (board.size_y / 2) as i32; - for y in (-by..by + 1).rev() { - // invert scan order on every other frame - for col in -bx..bx + 1 { - let x = if seqno_parity == 0 { col } else { -col }; + if chunks[4].is_some() { + for y in (0..CHUNK_SIZE as i32).rev() { + for i in 0..CHUNK_SIZE as i32 { + let x = if seqno_parity == 0 { + i + } else { + (CHUNK_SIZE as i32) - i - 1 + }; - let cell = board.cell_at_position(x, y); - if let Some(mut cur) = cell - && cur.flags & 0b1 == seqno_parity - { - // flip the parity bit - cur.flags = cur.flags ^ 0b1; + let mut cell = get_cell(chunks, x, y).unwrap(); + if cell.flags & 0b1 == seqno_parity { + // flip the parity bit + // TODO if the cell doesn't move this doesn't stay + cell.flags = cell.flags ^ 0b1; + let material = cell.material.def(); - if let Some(update) = cur.material.def().sim_update { - update(&mut UpdateCtx { - self_x: x, - self_y: y, - self_cell: cur, - board, - delta_time, + let mut update_ctx = UpdateCtx { + chunks, + seqno, seqno_parity, - }); + + x, + y, + cell: &mut cell, + material, + }; + + if let Some(update) = material.sim_update { + 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) { + puffin::profile_function!(); + let mut update_groups: [Vec<(i32, i32)>; 9] = Default::default(); + + // assign a color to each chunk s.t. every chunk is surrounded by <= 8 chunks of different colors + // -------------------- + // | 0, 1, 2, 0, 1, 2 | + // | 3, 4, 5, 3, 4, 5 | + // | 6, 7, 8, 6, 7, 8 | + // | 0, 1, 2, 0, 1, 2 | + // | 3, 4, 5, 3, 4, 5 | + // | 6, 7, 8, 6, 7, 8 | + // -------------------- + + for (&(cx, cy), _) in &world.chunk_position_to_chunk_idx { + let color = (cx.rem_euclid(3) * 3 + cy.rem_euclid(3)) as usize; + update_groups[color].push((cx, cy)); + } + + for group in &update_groups { + let access = ChunkAccess::new(&mut world.chunks); + // TODO this can be parallelized since they will never share neighbours + for &(cx, cy) in group { + 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) }) + }); + + sim_tick_chunk(&mut chunks, seqno); + } + } +} diff --git a/src/sim/world.rs b/src/sim/world.rs new file mode 100644 index 0000000..3c7b05b --- /dev/null +++ b/src/sim/world.rs @@ -0,0 +1,66 @@ +use std::collections::HashMap; + +use crate::{ + config::CHUNK_SIZE, + sim::{cell::Cell, chunk::Chunk}, +}; + +pub struct World { + pub chunks: Vec<Chunk>, + // TODO FxHashMap? + pub chunk_position_to_chunk_idx: HashMap<(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 as i32), + y.div_euclid(CHUNK_SIZE as i32), + ), + ( + x.rem_euclid(CHUNK_SIZE as i32) as u8, + y.rem_euclid(CHUNK_SIZE as i32) 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) -> () { + 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); + } + } + + 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: HashMap::new(), + }; + + for y in -10..10 { + for x in -10..10 { + world.insert(x, y, Chunk::void()); + } + } + + world + } +} |
