summaryrefslogtreecommitdiff
path: root/src/sim/sim_manager
diff options
context:
space:
mode:
Diffstat (limited to 'src/sim/sim_manager')
-rw-r--r--src/sim/sim_manager/mod.rs152
-rw-r--r--src/sim/sim_manager/utils.rs62
2 files changed, 214 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;
+ }
+}
diff --git a/src/sim/sim_manager/utils.rs b/src/sim/sim_manager/utils.rs
new file mode 100644
index 0000000..b636186
--- /dev/null
+++ b/src/sim/sim_manager/utils.rs
@@ -0,0 +1,62 @@
+use crate::sim::{cell::materials::MaterialId, sim_manager::SimManager};
+
+pub fn write_rb_entity_to_world(
+ sim: &mut SimManager,
+ rb_entity_id: u32,
+ // make sure these cells will be simulated
+ seqno: u64,
+ // (entity_x, entity_y, cell_x, cell_y)
+) -> Vec<(u8, u8, i32, i32)> {
+ let mut cells_written: Vec<(u8, u8, i32, i32)> = Vec::new();
+ if let Some(rb_entity) = sim.rb_manager.rb_entities.get(&rb_entity_id)
+ && let Some((rb_x, rb_y, cos, sin)) = sim.rb_manager.get_rb_entity_transform(rb_entity_id)
+ {
+ let (half_size_x, half_size_y) =
+ (rb_entity.width as f32 / 2.0, rb_entity.height as f32 / 2.0);
+ // half-extent of the rotated grid's axis-aligned bounding box, plus a cell of margin
+ let (radius_x, radius_y) = (
+ half_size_x * (cos.abs() + sin.abs()) + 1.0,
+ half_size_y * (cos.abs() + sin.abs()) + 1.0,
+ );
+
+ let world_xl = (rb_x - radius_x).floor() as i32;
+ let world_xu = (rb_x + radius_x).ceil() as i32;
+ let world_yl = (rb_y - radius_y).floor() as i32;
+ let world_yu = (rb_y + radius_y).ceil() as i32;
+
+ for world_x in world_xl..=world_xu {
+ for world_y in world_yl..=world_yu {
+ if let Some(cur_world_cell) = sim
+ .cell_manager
+ .get_cell_from_game_position(world_x, world_y)
+ && cur_world_cell.material == MaterialId::Void
+ {
+ // same as shader
+ let d = (world_x as f32 + 0.5 - rb_x, world_y as f32 + 0.5 - rb_y);
+ let q = (d.0.floor() + 0.5, d.1.floor() + 0.5);
+ let (lx, ly) = (
+ (q.0 * cos + q.1 * sin + half_size_x).floor() as i32,
+ (-q.0 * sin + q.1 * cos + half_size_y).floor() as i32,
+ );
+
+ if lx < 0 || ly < 0 || lx >= rb_entity.width || ly >= rb_entity.height {
+ continue;
+ }
+
+ let mut cell = rb_entity.get_cell_at_local_position(lx as u8, ly as u8);
+ if cell.material == MaterialId::Void {
+ continue;
+ }
+
+ cell.match_parity(seqno);
+
+ // TODO: OPTIMIZE!!
+ sim.cell_manager
+ .set_cell_from_game_position(world_x, world_y, cell, false);
+ cells_written.push((lx as u8, ly as u8, world_x, world_y));
+ }
+ }
+ }
+ }
+ cells_written
+}