diff options
| -rw-r--r-- | src/config.rs | 2 | ||||
| -rw-r--r-- | src/main.rs | 7 | ||||
| -rw-r--r-- | src/renderer/ui.rs | 9 | ||||
| -rw-r--r-- | src/sim/cell/cell.rs | 4 | ||||
| -rw-r--r-- | src/sim/cell/materials/fire.rs | 17 | ||||
| -rw-r--r-- | src/sim/cell/materials/gas.rs | 22 | ||||
| -rw-r--r-- | src/sim/cell/materials/liquid.rs | 25 | ||||
| -rw-r--r-- | src/sim/cell/materials/mod.rs | 4 | ||||
| -rw-r--r-- | src/sim/cell/materials/powder.rs | 8 | ||||
| -rw-r--r-- | src/sim/cell_sim/sim.rs | 48 | ||||
| -rw-r--r-- | src/sim/lib/ray.rs | 10 | ||||
| -rw-r--r-- | src/sim/particle_sim/mod.rs | 35 |
12 files changed, 114 insertions, 77 deletions
diff --git a/src/config.rs b/src/config.rs index 7506c44..25986ae 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,3 +11,5 @@ pub const PHYSICS_FPS: u32 = 60; pub const PHYSICS_DELTA_TIME: f32 = 1.0 / PHYSICS_FPS as f32; pub const PIXELS_TO_METRES: f32 = 20.0; + +pub const SETTLED_THRESOHLD: u8 = 7; diff --git a/src/main.rs b/src/main.rs index e066e56..caedc23 100644 --- a/src/main.rs +++ b/src/main.rs @@ -502,7 +502,6 @@ impl ApplicationHandler for App { self.last_sim_update = now; if self.sim_paused && self.ignore_pause_next_tick { self.sim_update(); - self.ignore_pause_next_tick = false; } else if !self.sim_paused { self.sim_updates_due += secs_since_last_sim_update / expected_secs_since_last_sim_update; @@ -521,7 +520,9 @@ impl ApplicationHandler for App { let expected_secs_since_last_physics_update = 1.0 / PHYSICS_FPS as f32; self.last_physics_update = now; - if !self.sim_paused { + if self.sim_paused && self.ignore_pause_next_tick { + self.physics_update(PHYSICS_DELTA_TIME); + } else if !self.sim_paused { self.physics_updates_due += secs_since_last_physics_update / expected_secs_since_last_physics_update; let mut updates_done = 0; @@ -534,6 +535,8 @@ impl ApplicationHandler for App { self.physics_updates_due = self.physics_updates_due.min(3.0); } + self.ignore_pause_next_tick = false; + if let Some(renderer_state) = &mut self.renderer_state && let Some(world) = &mut self.world && let Some(rb_sim_manager) = &mut self.rb_sim_manager diff --git a/src/renderer/ui.rs b/src/renderer/ui.rs index 27b3be4..c4f7585 100644 --- a/src/renderer/ui.rs +++ b/src/renderer/ui.rs @@ -107,9 +107,12 @@ pub fn draw_egui<'a>( .last_mouse_world_pos .map(|p| ui.label(format!("Mouse (world): x,y=({x}, {y})", x = p.0, y = p.1,))); - input - .last_mouse_chunk_pos - .map(|p| ui.label(format!("Mouse (chunk): x,y=({x}, {y})", x = p.0, y = p.1,))); + if let Some(p) = input.last_mouse_chunk_pos { + ui.label(format!("Mouse (chunk): x,y=({x}, {y})", x = p.0, y = p.1,)); + if let Some(&chunk) = world.chunk_position_to_chunk_idx.get(&p) { + ui.label(format!("Sleeping={}", world.chunks[chunk].sleeping)); + } + } input .last_mouse_local_pos diff --git a/src/sim/cell/cell.rs b/src/sim/cell/cell.rs index edb2402..054ac62 100644 --- a/src/sim/cell/cell.rs +++ b/src/sim/cell/cell.rs @@ -1,4 +1,4 @@ -use crate::sim::cell::materials::MaterialId; +use crate::{config::SETTLED_THRESOHLD, sim::cell::materials::MaterialId}; #[derive(Clone, Copy)] pub struct Cell { @@ -60,7 +60,7 @@ impl Cell { #[inline] pub fn increment_settled(&mut self) { let s = self.settled(); - if s < 7 { + if s < SETTLED_THRESOHLD { self.set_settled(s + 1); } } diff --git a/src/sim/cell/materials/fire.rs b/src/sim/cell/materials/fire.rs index 2a1225e..8667cea 100644 --- a/src/sim/cell/materials/fire.rs +++ b/src/sim/cell/materials/fire.rs @@ -2,7 +2,7 @@ use rand::RngExt; use crate::sim::{ cell::{cell::Cell, materials::MaterialId}, - cell_sim::sim::UpdateCtx, + cell_sim::sim::{PostUpdateAction, UpdateCtx}, }; pub trait FireCellView { @@ -27,21 +27,16 @@ impl FireCellView for Cell { // TODO optimize number of rng calls? // TODO wtf is fire #[inline] -pub fn sim_update(ctx: &mut UpdateCtx) { +pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction { let ticks_lived = ctx.cell.get_ticks_lived(); // 2 seconds if ticks_lived > 240 { // we have a chance to live longer--roughly ?% chance of living an extra second if ctx.rng.random_range(0.0..1.0) > 0.9 { // kill ourselves, with a chance to turn into ash - if ctx.rng.random_range(0.0..1.0) > 0.8 { - // TODO ash material - // ctx.set_cell(0, 0, Cell::from_material(MaterialId::Sand)); - } else { - ctx.do_rewrite = false; - ctx.set_cell(0, 0, Cell::void()); - } - return; + ctx.set_cell(0, 0, Cell::void()); + // the updater should take no action since we already killed ourselves + return PostUpdateAction::None; } } @@ -77,4 +72,6 @@ pub fn sim_update(ctx: &mut UpdateCtx) { } ctx.cell.set_ticks_lived(ticks_lived + 1); + // apply our new lifespan + PostUpdateAction::Apply } diff --git a/src/sim/cell/materials/gas.rs b/src/sim/cell/materials/gas.rs index 2ee7c00..af8f4fd 100644 --- a/src/sim/cell/materials/gas.rs +++ b/src/sim/cell/materials/gas.rs @@ -1,20 +1,24 @@ use rand::RngExt; -use crate::sim::cell_sim::sim::UpdateCtx; +use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx}; #[inline] -pub fn sim_update(ctx: &mut UpdateCtx) { +pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction { // only allow upward movement some of the time to limit movement speed - if ctx.rng.random_range(0.0..1.0) > 0.8 && ctx.candidates_swap(&[(0, -1)]) { - return; + if ctx.rng.random_range(0.0..1.0) > 0.8 + && ctx.swap_or_settle(&[(0, -1)]) == PostUpdateAction::None + { + return PostUpdateAction::None; } // same for each horizontal direction if ctx.rng.random_range(0.0..1.0) > 0.8 - && ctx.candidates_swap(&[(1 - ctx.seqno_parity as i32 * 2, 0)]) + && ctx.swap_or_settle(&[(1 - ctx.seqno_parity as i32 * 2, 0)]) == PostUpdateAction::None { - return; + return PostUpdateAction::None; } - if ctx.rng.random_range(0.0..1.0) > 0.8 - && ctx.candidates_swap(&[(-1 + ctx.seqno_parity as i32 * 2, 0)]) - {} + if ctx.rng.random_range(0.0..1.0) > 0.8 { + return ctx.swap_or_settle(&[(-1 + ctx.seqno_parity as i32 * 2, 0)]); + } + + PostUpdateAction::Apply } diff --git a/src/sim/cell/materials/liquid.rs b/src/sim/cell/materials/liquid.rs index 4b848fb..bff0ac0 100644 --- a/src/sim/cell/materials/liquid.rs +++ b/src/sim/cell/materials/liquid.rs @@ -1,14 +1,15 @@ -use crate::sim::cell_sim::sim::UpdateCtx; +use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx}; #[inline] -pub fn sim_update(ctx: &mut UpdateCtx) { +pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction { // if the water can fall, do so - if ctx.candidates_swap(&[ + if ctx.swap_or_settle(&[ (0, 1), (-1 + 2 * ctx.seqno_parity as i32, 1), (1 - 2 * ctx.seqno_parity as i32, 1), - ]) { - return; + ]) == PostUpdateAction::None + { + return PostUpdateAction::None; } // if the water can't fall, check if we can move left or right @@ -22,18 +23,16 @@ pub fn sim_update(ctx: &mut UpdateCtx) { // we can't move down or to other side, so we're stuck if !can_move_left && !can_move_right { - return; + return PostUpdateAction::Settle; } // if we can't move left, just move right if !can_move_left { - ctx.candidates_swap(&[(1, 0)]); - return; + return ctx.swap_or_settle(&[(1, 0)]); } // and vice versa if !can_move_right { - ctx.candidates_swap(&[(-1, 0)]); - return; + return ctx.swap_or_settle(&[(-1, 0)]); } // find the closest hole within 20 pixels (TODO optimize) @@ -54,12 +53,12 @@ pub fn sim_update(ctx: &mut UpdateCtx) { { // we identified a hole and we know that the space on this side is open // move toward the hole - // new_target.flags = new_target.flags ^ 0b1; - ctx.candidates_swap(&[(side, 0)]); - return; + return ctx.swap_or_settle(&[(side, 0)]); } } + return PostUpdateAction::Settle; + // we didn't find a hole, so just move "randomly" on the same surface // TODO when to settle? // let target_x = if !can_move_left { diff --git a/src/sim/cell/materials/mod.rs b/src/sim/cell/materials/mod.rs index e2b5fc6..ee2b4d0 100644 --- a/src/sim/cell/materials/mod.rs +++ b/src/sim/cell/materials/mod.rs @@ -1,4 +1,4 @@ -use crate::sim::cell_sim::sim::UpdateCtx; +use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx}; pub mod fire; pub mod gas; @@ -31,7 +31,7 @@ pub struct MaterialDef { pub color: (u8, u8, u8, u8), pub density: u8, pub form: MaterialForm, - pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> ()>, + pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> PostUpdateAction>, } static MATERIALS: [MaterialDef; 6] = [ diff --git a/src/sim/cell/materials/powder.rs b/src/sim/cell/materials/powder.rs index d2d1265..e08ea1f 100644 --- a/src/sim/cell/materials/powder.rs +++ b/src/sim/cell/materials/powder.rs @@ -1,10 +1,10 @@ -use crate::sim::cell_sim::sim::UpdateCtx; +use crate::sim::cell_sim::sim::{PostUpdateAction, UpdateCtx}; #[inline] -pub fn sim_update(ctx: &mut UpdateCtx) { - ctx.candidates_swap(&[ +pub fn sim_update(ctx: &mut UpdateCtx) -> PostUpdateAction { + ctx.swap_or_settle(&[ (0, 1), (-1 + 2 * ctx.seqno_parity as i32, 1), (1 - 2 * ctx.seqno_parity as i32, 1), - ]); + ]) } diff --git a/src/sim/cell_sim/sim.rs b/src/sim/cell_sim/sim.rs index 92a263e..fbaf2f0 100644 --- a/src/sim/cell_sim/sim.rs +++ b/src/sim/cell_sim/sim.rs @@ -5,7 +5,7 @@ use rand::{Rng, SeedableRng, rngs::SmallRng}; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; use crate::{ - config::CHUNK_SIZE, + config::{CHUNK_SIZE, SETTLED_THRESOHLD}, sim::{ cell::{cell::Cell, materials::MaterialDef}, cell_sim::{chunk::Chunk, world::World}, @@ -127,7 +127,7 @@ fn adjacent_chunks(x: u8, y: u8) -> Vec<usize> { } } -pub fn set_cell(chunks: &mut [Option<&mut Chunk>; 9], x: i32, y: i32, cell: Cell) { +fn internal_set_cell(chunks: &mut [Option<&mut Chunk>; 9], x: i32, y: i32, cell: Cell) { let cx = x.div_euclid(CHUNK_SIZE); let cy = y.div_euclid(CHUNK_SIZE); let lx = x.rem_euclid(CHUNK_SIZE) as u8; @@ -160,6 +160,18 @@ pub fn set_cell(chunks: &mut [Option<&mut Chunk>; 9], x: i32, y: i32, cell: Cell } } +#[derive(PartialEq, Eq, Clone, Copy)] +// the action that the cell's update fn took +pub enum PostUpdateAction { + // the cell managed its own lifecycle, the updater will take no action + None, + // the updater should rewrite this cell, settling it, and let the chunk sleep if it's fully settled + // this cell's parity should be flipped if it is not yet settled + Settle, + // the cell changed itself, the updater should unconditionally rewrite it and not settle the cell or sleep + Apply, +} + pub struct UpdateCtx<'a, 'b, 'c> { pub chunks: &'a mut [Option<&'b mut Chunk>; 9], pub seqno: u64, @@ -171,7 +183,6 @@ pub struct UpdateCtx<'a, 'b, 'c> { pub material: &'c MaterialDef, pub rng: &'c mut dyn Rng, - pub do_rewrite: bool, } impl UpdateCtx<'_, '_, '_> { @@ -188,10 +199,10 @@ impl UpdateCtx<'_, '_, '_> { debug_assert!(dy > -CHUNK_SIZE + 1 && dy < CHUNK_SIZE - 1); let x = self.x + dx; let y = self.y + dy; - set_cell(self.chunks, x, y, cell); + internal_set_cell(self.chunks, x, y, cell); } - pub fn candidates_swap(&mut self, candidates: &[(i32, i32)]) -> bool { + pub fn swap_or_settle(&mut self, candidates: &[(i32, i32)]) -> PostUpdateAction { for &(dx, dy) in candidates { if let Some(mut candidate_cell) = self.get_cell(dx, dy) && candidate_cell.material.def().density < self.material.density @@ -202,11 +213,10 @@ impl UpdateCtx<'_, '_, '_> { self.set_cell(0, 0, candidate_cell); self.set_cell(dx, dy, *self.cell); - self.do_rewrite = false; - return true; + return PostUpdateAction::None; } } - false + PostUpdateAction::Settle } } @@ -245,16 +255,24 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) { // TODO this is platform-dependent, will break for multiplayer rng: &mut rng, - do_rewrite: true, }; - update(&mut update_ctx); + let action = update(&mut update_ctx); - if update_ctx.do_rewrite { - // the cell didn't move, so it's more settled, and we also need to update its state for parity - cell.match_parity(seqno + 1); - cell.increment_settled(); - set_cell(chunks, x, y, cell); + match action { + PostUpdateAction::None => {} + PostUpdateAction::Settle => { + // the cell didn't move, so it's more settled, and we also need to update its state for parity + if cell.settled() < SETTLED_THRESOHLD { + cell.match_parity(seqno + 1); + cell.increment_settled(); + internal_set_cell(chunks, x, y, cell); + } + } + PostUpdateAction::Apply => { + cell.match_parity(seqno + 1); + internal_set_cell(chunks, x, y, cell); + } } } } diff --git a/src/sim/lib/ray.rs b/src/sim/lib/ray.rs index 52d5ba9..ec90d00 100644 --- a/src/sim/lib/ray.rs +++ b/src/sim/lib/ray.rs @@ -1,11 +1,12 @@ use glam::{IVec2, Vec2}; -// Amanatides and Woo's fast Voxel Traversal +// Amanatide's and Woo's fast Voxel Traversal pub struct AwDda { next: IVec2, step_sign: IVec2, step_delta: Vec2, t_max: Vec2, + done: bool, } impl AwDda { @@ -37,6 +38,7 @@ impl AwDda { step_sign, step_delta, t_max, + done: false, } } } @@ -44,9 +46,13 @@ impl AwDda { impl Iterator for AwDda { type Item = IVec2; fn next(&mut self) -> Option<Self::Item> { - if self.t_max.min_element() >= 1.0 { + if self.done { return None; } + if self.t_max.min_element() >= 1.0 { + self.done = true; + return Some(self.next); + } let cur = self.next; diff --git a/src/sim/particle_sim/mod.rs b/src/sim/particle_sim/mod.rs index 4378d25..84a728a 100644 --- a/src/sim/particle_sim/mod.rs +++ b/src/sim/particle_sim/mod.rs @@ -1,12 +1,7 @@ -use glam::{IVec2, Vec2}; - use crate::{ config::PIXELS_TO_METRES, sim::{ - cell::{ - cell::Cell, - materials::{MaterialForm, MaterialId}, - }, + cell::{cell::Cell, materials::MaterialForm}, cell_sim::world::World, lib::ray::AwDda, particle_sim::particle::Particle, @@ -23,6 +18,7 @@ const PARTICLE_GRAVITY: f32 = 9.81 * PIXELS_TO_METRES; impl ParticleManager { pub fn particle_tick(&mut self, world: &mut World, delta_time: f32) { + puffin::profile_function!(); let mut i = 0; 'outer: while i < self.particles.len() { let p = &mut self.particles[i]; @@ -36,22 +32,30 @@ impl ParticleManager { p.velocity.y += PARTICLE_GRAVITY * delta_time; let dt_velocity = p.velocity * delta_time; - let mut dda = AwDda::new(p.position, p.position + p.velocity * delta_time); + let mut dda = AwDda::new(p.position, p.position + dt_velocity); - if let Some(prev) = dda.next() { + if let Some(mut prev) = dda.next() { if let Some(cell) = world.get_cell_from_game_position(prev.x, prev.y) - && [MaterialForm::Solid, MaterialForm::Powder] - .contains(&cell.material.def().form) + && [ + MaterialForm::Solid, + MaterialForm::Powder, + MaterialForm::Liquid, + ] + .contains(&cell.material.def().form) { // the particle is already inside a collider, we should just kill it self.particles.swap_remove(i); continue 'outer; } - for cell in dda.map(|p| world.get_cell_from_game_position(p.x, p.y)) { - if let Some(cell) = cell - && [MaterialForm::Solid, MaterialForm::Powder] - .contains(&cell.material.def().form) + for next in dda { + if let Some(cell) = world.get_cell_from_game_position(next.x, next.y) + && [ + MaterialForm::Solid, + MaterialForm::Powder, + MaterialForm::Liquid, + ] + .contains(&cell.material.def().form) { // write ourselves to the board world.set_cell_from_game_position( @@ -63,11 +67,12 @@ impl ParticleManager { self.particles.swap_remove(i); continue 'outer; } + prev = next; } } // no collision, just move - p.position = p.position + dt_velocity; + p.position += dt_velocity; i += 1; } } |
