summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/camera.rs104
-rw-r--r--src/config.rs5
-rw-r--r--src/main.rs85
-rw-r--r--src/sim/board.rs72
-rw-r--r--src/sim/cell.rs19
-rw-r--r--src/sim/chunk.rs25
-rw-r--r--src/sim/materials/mod.rs10
-rw-r--r--src/sim/materials/sand.rs6
-rw-r--r--src/sim/materials/water.rs46
-rw-r--r--src/sim/mod.rs4
-rw-r--r--src/sim/overlay.rs67
-rw-r--r--src/sim/sim.rs208
-rw-r--r--src/sim/world.rs66
-rw-r--r--src/ui.rs7
14 files changed, 466 insertions, 258 deletions
diff --git a/src/camera.rs b/src/camera.rs
index 604cb0d..9310cac 100644
--- a/src/camera.rs
+++ b/src/camera.rs
@@ -1,7 +1,7 @@
use crate::{
Input,
- config::{CAMERA_MOVEMENT_SPEED, PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH},
- sim::board::Board,
+ config::{CAMERA_MOVEMENT_SPEED, CHUNK_SIZE, PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH},
+ sim::{chunk::Chunk, world::World},
};
pub struct Camera {
@@ -42,14 +42,9 @@ impl Camera {
self.y += adjusted_y as f64;
}
- pub fn screen_position_to_board(
- &self,
- board: &Board,
- screen_x: f64,
- screen_y: f64,
- ) -> (f64, f64) {
- let camera_width = self.zoom * f64::from(board.size_x);
- let camera_height = self.zoom * f64::from(board.size_y);
+ pub fn screen_position_to_world(&self, screen_x: f64, screen_y: f64) -> (f64, f64) {
+ let camera_width = self.zoom * f64::from(PIXEL_BUFFER_WIDTH);
+ let camera_height = self.zoom * f64::from(PIXEL_BUFFER_HEIGHT);
let camera_start_x = self.x - camera_width / 2.0;
let camera_start_y = self.y - camera_height / 2.0;
(
@@ -58,32 +53,81 @@ impl Camera {
)
}
- pub fn write_frame_view(
+ pub fn write_frame_view(&self, frame: &mut [u8], world: &World) {
+ puffin::profile_function!();
+
+ const BG: [u8; 4] = [0x00, 0x00, 0x00, 0xFF];
+ for px in frame.chunks_exact_mut(4) {
+ px.copy_from_slice(&BG);
+ }
+
+ // world-space rect covered by the screen
+ let (xl, yl) = self.screen_position_to_world(0.0, 0.0);
+ let (xu, yu) =
+ self.screen_position_to_world(PIXEL_BUFFER_WIDTH as f64, PIXEL_BUFFER_HEIGHT as f64);
+
+ let c = CHUNK_SIZE as i32;
+ let cx0 = (xl.floor() as i32).div_euclid(c);
+ let cx1 = (xu.ceil() as i32).div_euclid(c);
+ let cy0 = (yl.floor() as i32).div_euclid(c);
+ let cy1 = (yu.ceil() as i32).div_euclid(c);
+
+ let scale = 1.0 / self.zoom; // pixels per cell
+
+ for cy in cy0..=cy1 {
+ for cx in cx0..=cx1 {
+ let Some(&idx) = world.chunk_position_to_chunk_idx.get(&(cx, cy)) else {
+ continue;
+ };
+ self.write_chunk(frame, &world.chunks[idx], cx, cy, xl, yl, scale);
+ }
+ }
+ }
+
+ fn write_chunk(
&self,
frame: &mut [u8],
- board: &Board,
- get_overlay: impl Fn(i32, i32) -> (u8, u8, u8, u8),
+ chunk: &Chunk,
+ cx: i32,
+ cy: i32,
+ xl: f64,
+ yl: f64,
+ scale: f64,
) {
- for frame_y in 0..PIXEL_BUFFER_HEIGHT {
- for frame_x in 0..PIXEL_BUFFER_WIDTH {
- let (x_coord, y_coord) =
- self.screen_position_to_board(board, frame_x as f64, frame_y as f64);
+ let c = CHUNK_SIZE as i32;
+ let w = PIXEL_BUFFER_WIDTH as i32;
+ let h = PIXEL_BUFFER_HEIGHT as i32;
+
+ for ly in 0..c {
+ let wy = (cy * c + ly) as f64;
+ let sy0 = (((wy - yl) * scale).floor() as i32).max(0);
+ let sy1 = (((wy + 1.0 - yl) * scale).floor() as i32).min(h);
+ if sy0 >= sy1 {
+ continue;
+ }
- let cell = board.cell_at_position(x_coord as i32, y_coord as i32);
- let cell_color: Option<(u8, u8, u8, u8)> = cell.map(|c| {
- let m = c.material.def();
- (m.color[0], m.color[1], m.color[2], 0xFF)
- });
- let off_grid_color: (u8, u8, u8, u8) = (0x00, 0x00, 0x00, 0xFF);
+ for lx in 0..c {
+ let wx = (cx * c + lx) as f64;
+ let sx0 = (((wx - xl) * scale).floor() as i32).max(0);
+ let sx1 = (((wx + 1.0 - xl) * scale).floor() as i32).min(w);
+ if sx0 >= sx1 {
+ continue;
+ }
- let target_color: (u8, u8, u8, u8) = cell_color.unwrap_or(off_grid_color);
- let overlay = get_overlay(x_coord as i32, y_coord as i32);
+ let color = chunk
+ .get_cell_at_local_position(lx as u8, ly as u8)
+ .material
+ .def()
+ .color;
+ let rgba = [color.0, color.1, color.2, color.3];
- let frame_idx = ((frame_y * PIXEL_BUFFER_WIDTH + frame_x) * 4) as usize;
- frame[frame_idx] = target_color.0.saturating_add(overlay.0);
- frame[frame_idx + 1] = target_color.1.saturating_add(overlay.1);
- frame[frame_idx + 2] = target_color.2.saturating_add(overlay.2);
- frame[frame_idx + 3] = target_color.3.saturating_add(overlay.3);
+ for sy in sy0..sy1 {
+ let start = (sy * w + sx0) as usize * 4;
+ let end = (sy * w + sx1) as usize * 4;
+ for px in frame[start..end].chunks_exact_mut(4) {
+ px.copy_from_slice(&rgba);
+ }
+ }
}
}
}
diff --git a/src/config.rs b/src/config.rs
index 74a98e4..6e367eb 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -1,4 +1,9 @@
pub const WINDOW_TITLE: &str = "pxs";
+
pub const PIXEL_BUFFER_WIDTH: u32 = 320;
pub const PIXEL_BUFFER_HEIGHT: u32 = 240;
+
+pub const CHUNK_SIZE: u32 = 32;
+pub const CELLS_IN_CHUNK: usize = (CHUNK_SIZE * CHUNK_SIZE) as usize;
+
pub const CAMERA_MOVEMENT_SPEED: f32 = 10.0;
diff --git a/src/main.rs b/src/main.rs
index 02920d6..b048122 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -7,10 +7,7 @@ use egui::Id;
use egui_wgpu::{RendererOptions, ScreenDescriptor};
use egui_winit::egui::{self, Context};
use pixels::{Pixels, ScalingMode, SurfaceTexture};
-use std::{
- cmp::{max, min},
- time::{Duration, Instant},
-};
+use std::time::{Duration, Instant};
use winit::{
application::ApplicationHandler,
event::{
@@ -25,10 +22,7 @@ use winit::{
use crate::{
camera::Camera,
config::{PIXEL_BUFFER_HEIGHT, PIXEL_BUFFER_WIDTH, WINDOW_TITLE},
- sim::{
- board::Board, materials::MaterialId, overlay::create_compute_combined_overlay_offset,
- sim::sim_tick,
- },
+ sim::{cell::Cell, materials::MaterialId, sim::sim_tick, world::World},
ui::draw_egui,
};
@@ -70,7 +64,7 @@ struct App {
camera: Option<Camera>,
- board: Option<Board>,
+ world: Option<World>,
// sim state
// the last/current (not yet completed) seqno
@@ -110,10 +104,10 @@ impl Default for App {
camera: Some(Camera {
x: 0.0,
y: 0.0,
- zoom: 0.5,
+ zoom: 1.0,
}),
- board: Some(Board::empty()),
+ world: Some(World::from_default_size()),
sim_seqno: 0,
sim_paused: false,
@@ -182,7 +176,7 @@ impl ApplicationHandler for App {
&& let Some(egui_context) = &self.egui_context
&& let Some(egui_state) = &mut self.egui_state
&& let Some(egui_renderer) = &mut self.egui_renderer
- && let Some(board) = &mut self.board
+ && let Some(world) = &mut self.world
&& let Some(camera) = &mut self.camera
{
let egui_response = egui_state.on_window_event(window, &event);
@@ -207,7 +201,7 @@ impl ApplicationHandler for App {
KeyCode::KeyA => self.input.is_left_pressed = pressed,
KeyCode::KeyS => self.input.is_down_pressed = pressed,
KeyCode::KeyD => self.input.is_right_pressed = pressed,
- KeyCode::KeyC => self.board = Some(Board::empty()),
+ KeyCode::KeyC => self.world = Some(World::from_default_size()),
KeyCode::Space => {
if pressed {
self.sim_paused = !self.sim_paused
@@ -225,9 +219,9 @@ impl ApplicationHandler for App {
self.input.last_mouse_pos_on_screen = Some((position.x, position.y));
self.input.last_mouse_pos_on_board = pixels
.window_pos_to_pixel((position.x as f32, position.y as f32))
- .map(|v| camera.screen_position_to_board(board, v.0 as f64, v.1 as f64))
+ .map(|v| camera.screen_position_to_world(v.0 as f64, v.1 as f64))
.map(|v| (v.0 as i32, v.1 as i32))
- .ok()
+ .ok();
}
WindowEvent::MouseInput { state, button, .. } => {
if button == MouseButton::Left {
@@ -242,6 +236,9 @@ impl ApplicationHandler for App {
event_loop.exit();
}
WindowEvent::RedrawRequested => {
+ #[cfg(feature = "profiler")]
+ puffin::GlobalProfiler::lock().new_frame();
+ puffin::profile_scope!("redraw_requested");
// compute frame delta
let now = Instant::now();
let secs_since_last_frame = (now - self.last_frame_real).as_secs_f32();
@@ -259,43 +256,28 @@ impl ApplicationHandler for App {
frame.fill(0);
// --TEST DRAWING--
- if self.input.is_lmb_pressed && self.input.last_mouse_pos_on_board.is_some() {
+ if self.input.is_lmb_pressed
+ && let Some(lm) = self.input.last_mouse_pos_on_board
+ {
// start with the bounding box of the drawing brush circle + some margin
// clamp the bounding box to the board sie
// TODO better way to do this without unwrap?
- let bb_xl = max(
- self.input.last_mouse_pos_on_board.unwrap().0
- - self.config.brush_radius as i32,
- -((board.size_x / 2) as i32),
- );
- let bb_xu = min(
- self.input.last_mouse_pos_on_board.unwrap().0
- + self.config.brush_radius as i32,
- (board.size_x / 2) as i32,
- );
- let bb_yl = max(
- self.input.last_mouse_pos_on_board.unwrap().1
- - self.config.brush_radius as i32,
- -((board.size_y / 2) as i32),
- );
- let bb_yu = min(
- self.input.last_mouse_pos_on_board.unwrap().1
- + self.config.brush_radius as i32,
- (board.size_y / 2) as i32,
- );
+ let bb_xl = lm.0 - self.config.brush_radius as i32;
+ let bb_xu = lm.0 + self.config.brush_radius as i32;
+ let bb_yl = lm.1 - self.config.brush_radius as i32;
+ let bb_yu = lm.1 + self.config.brush_radius as i32;
// for each point, check if the distance is less than the brush size and write the pixel
for x in bb_xl..bb_xu {
for y in bb_yl..bb_yu {
// brush/selection
- if ((x - self.input.last_mouse_pos_on_board.unwrap().0).pow(2)
- + (y - self.input.last_mouse_pos_on_board.unwrap().1).pow(2))
+ if ((x - lm.0).pow(2) + (y - lm.1).pow(2))
< (self.config.brush_radius as i32).pow(2)
{
- board.set_cell_at_position(
+ world.set_cell_from_game_position(
x,
y,
- sim::board::Cell::from_material(self.config.brush_material),
+ Cell::from_material(self.config.brush_material),
);
}
}
@@ -305,14 +287,14 @@ impl ApplicationHandler for App {
// TODO check if we need to run another sim tick given the sim speed
// SIM logic
if !self.sim_paused || self.ignore_pause_next_tick {
- sim_tick(board, self.sim_seqno, delta_time);
+ sim_tick(world, self.sim_seqno);
self.sim_seqno += 1;
self.ignore_pause_next_tick = false;
}
- let get_overlay =
- create_compute_combined_overlay_offset(board, &self.config, &self.input);
- camera.write_frame_view(frame, board, get_overlay);
+ // let get_overlay =
+ // create_compute_combined_overlay_offset(&world, &self.config, &self.input);
+ camera.write_frame_view(frame, &world);
// egui logic
let raw_input = egui_state.take_egui_input(window);
@@ -410,8 +392,21 @@ impl ApplicationHandler for App {
}
}
+#[cfg(feature = "profiler")]
+fn start_profiler() {
+ let _server = puffin_http::Server::new("127.0.0.1:8585").unwrap();
+ puffin::set_scopes_on(true);
+ std::mem::forget(_server); // keep serving for the process lifetime
+
+ std::process::Command::new("puffin_viewer")
+ .args(["--url", "127.0.0.1:8585"])
+ .spawn()
+ .ok(); // don't die if it isn't installed
+}
+
fn main() -> Result<()> {
- env_logger::init();
+ #[cfg(feature = "profiler")]
+ start_profiler();
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
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
+ }
+}
diff --git a/src/ui.rs b/src/ui.rs
index e55aff1..9c8f6c3 100644
--- a/src/ui.rs
+++ b/src/ui.rs
@@ -9,6 +9,7 @@ pub fn draw_egui<'a>(
diagnostics: &Diagnostics,
input: &Input,
) {
+ puffin::profile_function!();
ui.heading("Config");
ui.add(egui::Slider::new(&mut config.brush_radius, 1..=100).text("Brush radius"));
@@ -25,7 +26,7 @@ pub fn draw_egui<'a>(
center: rect.center(),
radius: rect.width() / 2.5,
stroke: Stroke::NONE,
- fill: Color32::from_rgb(material.color[0], material.color[1], material.color[2]),
+ fill: Color32::from_rgb(material.color.0, material.color.1, material.color.2),
}));
})
.show_ui(ui, |ui| {
@@ -59,4 +60,8 @@ pub fn draw_egui<'a>(
input
.last_mouse_pos_on_board
.map(|p| ui.label(format!("Mouse (board): x,y=({x}, {y})", x = p.0, y = p.1,)));
+
+ input
+ .last_mouse_pos_on_board
+ .map(|p| ui.label(format!("Mouse (chunk): x,y=({x}, {y})", x = p.0, y = p.1,)));
}