summaryrefslogtreecommitdiff
path: root/src/sim/sim_manager/mod.rs
diff options
context:
space:
mode:
authorKai Stevenson <kai@kaistevenson.com>2026-08-22 15:00:35 -0700
committerKai Stevenson <kai@kaistevenson.com>2026-08-22 15:00:35 -0700
commit1a515237afb7ad09353a65f5fbc6e98a7c29ce8e (patch)
tree48cd4b4bcf8f8e357811f905f22607838d7c1f96 /src/sim/sim_manager/mod.rs
parent6350e4ffca8ce1e46465284ef3d7559e0f40229b (diff)
sim manager refactor
Diffstat (limited to 'src/sim/sim_manager/mod.rs')
-rw-r--r--src/sim/sim_manager/mod.rs152
1 files changed, 152 insertions, 0 deletions
diff --git a/src/sim/sim_manager/mod.rs b/src/sim/sim_manager/mod.rs
new file mode 100644
index 0000000..a98f3b2
--- /dev/null
+++ b/src/sim/sim_manager/mod.rs
@@ -0,0 +1,152 @@
+use std::time::Instant;
+
+use crate::{
+ Config,
+ config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS},
+ sim::{
+ cell::{cell::Cell, materials::MaterialId},
+ cell_manager::manager::CellManager,
+ particle_manager::ParticleManager,
+ rb_manager::RbManager,
+ sim_manager::utils::write_rb_entity_to_world,
+ },
+};
+
+mod utils;
+
+pub struct SimManager {
+ // timing
+ pub paused: bool,
+ pub ignore_pause_next_tick: bool,
+ pub last_cell_update: Instant,
+ pub cell_updates_due: f32,
+ pub last_physics_update: Instant,
+ pub physics_updates_due: f32,
+
+ // systems
+ pub cell_manager: CellManager,
+ pub rb_manager: RbManager,
+ pub particle_manager: ParticleManager,
+}
+
+impl SimManager {
+ pub fn new() -> Self {
+ SimManager {
+ paused: false,
+ ignore_pause_next_tick: false,
+ last_cell_update: Instant::now(),
+ cell_updates_due: 0.0,
+ last_physics_update: Instant::now(),
+ physics_updates_due: 0.0,
+ cell_manager: CellManager::from_default_size(),
+ rb_manager: RbManager::new(),
+ particle_manager: ParticleManager::new(),
+ }
+ }
+
+ fn cell_update(&mut self, config: &Config) {
+ // before we tick, write all the rb entities into the sim world
+ // TODO optimize
+ let entity_ids: Vec<u32> = self.rb_manager.rb_entities.keys().copied().collect();
+ let mut cells_written_by_entity: Vec<(u32, Vec<(u8, u8, i32, i32)>)> = Vec::new();
+
+ for entity_id in entity_ids {
+ let cells_written = write_rb_entity_to_world(self, entity_id, self.cell_manager.seqno);
+ cells_written_by_entity.push((entity_id, cells_written));
+ }
+
+ self.cell_manager.tick(config.use_threading);
+
+ // after we tick, remove the written rb cells and update the entities
+ // TODO optimize
+ for (entity_id, cells_written) in cells_written_by_entity {
+ let rb_entity = self.rb_manager.rb_entities.get_mut(&entity_id).unwrap();
+ for (lx, ly, x, y) in cells_written {
+ // update the entity
+ // TODO we should skip cells that weren't changed?
+ // TODO optimize
+ let new_local_cell = self.cell_manager.get_cell_from_game_position(x, y).unwrap();
+ if new_local_cell.material != MaterialId::Void && !new_local_cell.rb() {
+ panic!(
+ "Someone swapped into this rb's cell! ({x}, {y}, {m:#?}, {f})",
+ m = new_local_cell.material,
+ f = new_local_cell.flags
+ );
+ }
+ rb_entity.set_cell_at_local_position(lx, ly, new_local_cell);
+ // update the world
+ self.cell_manager
+ .set_cell_from_game_position(x, y, Cell::void(), false);
+ }
+ }
+ }
+
+ fn physics_update(&mut self, delta_time: f32) {
+ // before we move the rigidbodies, upsert the current terrain state
+ // TODO use the chunk sleeping, and make this range dynamic
+ for cx in -5..5 {
+ for cy in -5..5 {
+ if let Some(chunk) = self
+ .cell_manager
+ .chunk_position_to_chunk_idx
+ .get(&(cx, cy))
+ .map(|&idx| &self.cell_manager.chunks[idx])
+ && !chunk.sleeping
+ {
+ self.rb_manager.upsert_chunk_collider(cx, cy, chunk);
+ }
+ }
+ }
+
+ // move the rbs
+ self.rb_manager.tick(delta_time);
+
+ // move the particles
+ self.particle_manager
+ .tick(&mut self.cell_manager, delta_time);
+ }
+
+ pub fn update(&mut self, config: &Config, _delta_time: f32) -> () {
+ let now = Instant::now();
+ let secs_since_last_cell_update = (now - self.last_cell_update).as_secs_f32();
+ let expected_secs_since_last_cell_update = 1.0 / SIM_FPS as f32;
+
+ self.last_cell_update = now;
+ if self.paused && self.ignore_pause_next_tick {
+ self.cell_update(config);
+ } else if !self.paused {
+ self.cell_updates_due +=
+ secs_since_last_cell_update / expected_secs_since_last_cell_update;
+ let mut cell_updates_done = 0;
+ // don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
+ while self.cell_updates_due >= 1.0 && cell_updates_done < 3 {
+ self.cell_update(config);
+ self.cell_updates_due -= 1.0;
+ cell_updates_done += 1;
+ }
+ self.cell_updates_due = self.cell_updates_due.min(3.0);
+ }
+
+ // PHYSICS UPDATE
+ let secs_since_last_physics_update = (now - self.last_physics_update).as_secs_f32();
+ let expected_secs_since_last_physics_update = 1.0 / PHYSICS_FPS as f32;
+
+ self.last_physics_update = now;
+ if self.paused && self.ignore_pause_next_tick {
+ self.physics_update(PHYSICS_DELTA_TIME);
+ } else if !self.paused {
+ self.physics_updates_due +=
+ secs_since_last_physics_update / expected_secs_since_last_physics_update;
+ let mut updates_done = 0;
+ // don't ever update more than 3 times per frame, or else we can get a pseudo deadlock
+ while self.physics_updates_due >= 1.0 && updates_done < 3 {
+ self.physics_update(PHYSICS_DELTA_TIME);
+ self.physics_updates_due -= 1.0;
+ updates_done += 1;
+ }
+ self.physics_updates_due = self.physics_updates_due.min(3.0);
+ }
+
+ self.ignore_pause_next_tick = false;
+ }
+}