use std::time::Instant; use fxhash::FxHashMap; use crate::{ Config, InputManager, config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS}, input::Input, sim::{ cell_manager::manager::CellManager, entity::{Entity, EntityDef, EntityId}, particle_manager::ParticleManager, rb_manager::RbManager, sim_manager::utils::{ process_entity_update_result, read_back_entities_from_world, write_entities_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, // entities // TODO entity manager? next_entity_id: u32, pub entities: FxHashMap, } pub struct SimCtx<'a> { pub input_manager: &'a InputManager, pub cell_manager: &'a mut CellManager, pub rb_manager: &'a mut RbManager, pub particle_manager: &'a mut ParticleManager, } impl SimManager { pub fn create_entity(&mut self, def: EntityDef) -> EntityId { let (rb_h, collider_h) = if let Some(rb) = def.rb { let rb_h = self.rb_manager.physics_manager.world.insert_body(rb); if let Some(collider) = def.collider { let collider_h = self .rb_manager .physics_manager .world .insert_collider(collider, Some(rb_h)); (Some(rb_h), Some(collider_h)) } else { (Some(rb_h), None) } } else if let Some(collider) = def.collider { let collider_h = self .rb_manager .physics_manager .world .insert_collider(collider, None); (None, Some(collider_h)) } else { (None, None) }; let id = EntityId(self.next_entity_id); let entity = Entity::new(id, rb_h, collider_h, def.cells, def.behaviour); self.entities.insert(id, entity); self.next_entity_id += 1; id } pub fn destroy_entity(&mut self, id: EntityId) { if let Some(entity) = self.entities.get(&id) { if let Some(rb_h) = entity.data.rb_h { self.rb_manager.physics_manager.world.remove_body(rb_h); } else if let Some(collider_h) = entity.data.collider_h { self.rb_manager .physics_manager .world .remove_collider(collider_h); } self.entities.remove(&id); } } fn cell_update(&mut self, config: &Config, input_manager: &InputManager, delta_time: f32) { // before we tick, write all the entities into the sim world let cells_written_by_entity = write_entities_to_world(self); // tick self.cell_manager.tick(config.use_threading); // update entities let entity_ids: Vec = self.entities.keys().cloned().collect(); for id in entity_ids { let entity = self.entities.get_mut(&id); let mut ctx = SimCtx { input_manager, cell_manager: &mut self.cell_manager, rb_manager: &mut self.rb_manager, particle_manager: &mut self.particle_manager, }; if let Some(entity) = entity && let Some(result) = entity.update(&mut ctx, delta_time) { process_entity_update_result(self, result); } } // after we tick, remove the written entity cells and update the entities read_back_entities_from_world(self, cells_written_by_entity); } fn physics_update(&mut self, input_manager: &InputManager, delta_time: f32) { let entity_ids: Vec = self.entities.keys().cloned().collect(); for id in entity_ids { let entity = self.entities.get_mut(&id); let mut ctx = SimCtx { input_manager, cell_manager: &mut self.cell_manager, rb_manager: &mut self.rb_manager, particle_manager: &mut self.particle_manager, }; if let Some(entity) = entity && let Some(result) = entity.physics_update(&mut ctx, delta_time) { process_entity_update_result(self, result); } } // before we move the rigidbodies, upsert the current terrain state // TODO 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, input_manager: &InputManager, delta_time: f32) { // handle sim controls if input_manager.pressed(Input::Pause) { self.paused = !self.paused; } if input_manager.pressed(Input::Step) { self.ignore_pause_next_tick = true; } if input_manager.pressed(Input::ClearGrid) { self.cell_manager = CellManager::from_default_size(); } if input_manager.pressed(Input::ClearEntities) { let entities: Vec = self.entities.drain().map(|(id, _)| id).collect(); entities.iter().for_each(|&id| self.destroy_entity(id)); } if input_manager.pressed(Input::ClearParticles) { self.particle_manager.particles = Vec::new(); } 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, input_manager, delta_time); } 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, input_manager, delta_time); 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(input_manager, 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(input_manager, 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; } 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(), next_entity_id: 0, entities: FxHashMap::default(), } } }