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 = 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; } }