summaryrefslogtreecommitdiff
path: root/src/sim
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-15 22:46:53 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-15 22:46:53 -0700
commitd7eccba2055cd7784a0db6d9ee8692a4f3510931 (patch)
tree37f4c19781c788fa2af1ee4eeb7195f49a6d2d7a /src/sim
parent05135beb87bbf0edace544b80ba40e327d9a89ac (diff)
fire, optimize viewport rendering
Diffstat (limited to 'src/sim')
-rw-r--r--src/sim/cell.rs21
-rw-r--r--src/sim/materials/fire.rs53
-rw-r--r--src/sim/materials/mod.rs14
-rw-r--r--src/sim/sim.rs65
-rw-r--r--src/sim/world.rs5
5 files changed, 123 insertions, 35 deletions
diff --git a/src/sim/cell.rs b/src/sim/cell.rs
index 2e26ddc..d3ca302 100644
--- a/src/sim/cell.rs
+++ b/src/sim/cell.rs
@@ -4,6 +4,20 @@ use crate::sim::materials::MaterialId;
pub struct Cell {
pub material: MaterialId,
pub flags: u8,
+ pub data: u16,
+}
+
+impl Cell {
+ const FLAG_PARITY: u8 = 0b0000_0001;
+
+ #[inline]
+ pub fn parity(self) -> u8 {
+ self.flags & Self::FLAG_PARITY
+ }
+ #[inline]
+ pub fn flip_parity(&mut self) {
+ self.flags ^= Self::FLAG_PARITY;
+ }
}
impl Cell {
@@ -11,9 +25,14 @@ impl Cell {
Cell {
material: MaterialId::Void,
flags: 0,
+ data: 0,
}
}
pub fn from_material(material: MaterialId) -> Cell {
- Cell { material, flags: 0 }
+ Cell {
+ material,
+ flags: 0,
+ data: 0,
+ }
}
}
diff --git a/src/sim/materials/fire.rs b/src/sim/materials/fire.rs
new file mode 100644
index 0000000..b155986
--- /dev/null
+++ b/src/sim/materials/fire.rs
@@ -0,0 +1,53 @@
+use rand::RngExt;
+
+use crate::sim::{cell::Cell, materials::MaterialId, sim::UpdateCtx};
+
+trait FireCellView {
+ fn get_ticks_lived(self) -> u16;
+ fn set_ticks_lived(&mut self, ticks: u16) -> ();
+ fn is_flammable(self) -> bool;
+}
+
+impl FireCellView for Cell {
+ fn get_ticks_lived(self) -> u16 {
+ self.data
+ }
+ fn set_ticks_lived(&mut self, ticks: u16) -> () {
+ self.data = ticks;
+ }
+ // this could be a property of the material def, I think it's better here for now
+ fn is_flammable(self) -> bool {
+ [MaterialId::Wood].contains(&self.material)
+ }
+}
+
+#[inline]
+pub fn sim_update(ctx: &mut UpdateCtx) {
+ let ticks_lived = ctx.cell.get_ticks_lived();
+ // 2 seconds
+ if ticks_lived > 240 {
+ // 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.set_cell(0, 0, Cell::void());
+ }
+ return;
+ }
+
+ // 4 times in our lifespan, try to spread in a random direction
+ // since it's random, fire may die out if there's nothing in that direction
+ // a 1-thick line of flammable cells has a 31% chance to die each iteration
+ if ticks_lived % 60 == 0 {
+ let (dx, dy) = (ctx.rng.random_range(-1..=1), ctx.rng.random_range(-1..=1));
+ if let Some(target) = ctx.get_cell(dx, dy)
+ && target.is_flammable()
+ {
+ ctx.set_cell(dx, dy, Cell::from_material(MaterialId::Fire));
+ }
+ }
+
+ ctx.cell.set_ticks_lived(ticks_lived + 1);
+ ctx.set_cell(0, 0, *ctx.cell);
+}
diff --git a/src/sim/materials/mod.rs b/src/sim/materials/mod.rs
index d2d0e0e..764318a 100644
--- a/src/sim/materials/mod.rs
+++ b/src/sim/materials/mod.rs
@@ -1,5 +1,6 @@
use crate::sim::sim::UpdateCtx;
+mod fire;
mod gas;
mod sand;
mod water;
@@ -12,6 +13,7 @@ pub enum MaterialId {
Wood,
Water,
Gas,
+ Fire,
}
pub struct MaterialDef {
@@ -21,7 +23,7 @@ pub struct MaterialDef {
pub sim_update: Option<fn(ctx: &mut UpdateCtx) -> ()>,
}
-static MATERIALS: [MaterialDef; 5] = [
+static MATERIALS: [MaterialDef; 6] = [
MaterialDef {
name: "Void",
color: (0x00, 0x00, 0x00, 0x00),
@@ -52,15 +54,23 @@ static MATERIALS: [MaterialDef; 5] = [
density: 10,
sim_update: Some(gas::sim_update),
},
+ MaterialDef {
+ name: "Fire",
+ color: (0xFC, 0x66, 0x00, 0xAA),
+ // for now this matches wood
+ density: 50,
+ sim_update: Some(fire::sim_update),
+ },
];
impl MaterialId {
- pub const ALL: [MaterialId; 5] = [
+ pub const ALL: [MaterialId; 6] = [
MaterialId::Void,
MaterialId::Sand,
MaterialId::Wood,
MaterialId::Water,
MaterialId::Gas,
+ MaterialId::Fire,
];
#[inline]
pub fn def(self) -> &'static MaterialDef {
diff --git a/src/sim/sim.rs b/src/sim/sim.rs
index 4c6d908..84c3126 100644
--- a/src/sim/sim.rs
+++ b/src/sim/sim.rs
@@ -1,6 +1,7 @@
use std::marker::PhantomData;
use fxhash::FxHashMap;
+use rand::{Rng, SeedableRng, rngs::SmallRng};
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
use crate::{
@@ -30,17 +31,6 @@ impl<'a> ChunkAccess<'a> {
unsafe impl Sync for ChunkAccess<'_> {}
-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,
-}
-
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);
@@ -74,17 +64,28 @@ pub fn set_cell(chunks: &mut [Option<&mut Chunk>; 9], x: i32, y: i32, cell: Cell
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;
- chunk.sleeping = false;
}
} 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;
- target.sleeping = false;
}
}
}
+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;
@@ -92,7 +93,7 @@ impl UpdateCtx<'_, '_, '_> {
get_cell(self.chunks, x, y)
}
- fn set_cell(&mut self, dx: i32, dy: i32, cell: Cell) {
+ 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);
@@ -122,8 +123,8 @@ impl UpdateCtx<'_, '_, '_> {
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 mut rng = SmallRng::seed_from_u64(seqno);
if chunks[4].is_some() {
for y in (0..CHUNK_SIZE).rev() {
@@ -135,24 +136,28 @@ pub fn sim_tick_chunk(chunks: &mut [Option<&mut Chunk>; 9], seqno: u64) {
};
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();
+ let material = cell.material.def();
+
+ if let Some(update) = material.sim_update {
+ if 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,
- let mut update_ctx = UpdateCtx {
- chunks,
- seqno,
- seqno_parity,
+ x,
+ y,
+ cell: &mut cell,
+ material,
- x,
- y,
- cell: &mut cell,
- material,
- };
+ // TODO this is platform-dependent, will break for multiplayer
+ rng: &mut rng,
+ };
- if let Some(update) = material.sim_update {
update(&mut update_ctx);
}
}
diff --git a/src/sim/world.rs b/src/sim/world.rs
index 6645d4e..5993b10 100644
--- a/src/sim/world.rs
+++ b/src/sim/world.rs
@@ -48,6 +48,7 @@ impl World {
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;
}
}
@@ -63,8 +64,8 @@ impl World {
chunk_position_to_chunk_idx: FxHashMap::default(),
};
- for y in -10..10 {
- for x in -10..10 {
+ for y in -10..1 {
+ for x in -100..100 {
world.insert(x, y, Chunk::void());
}
}