use std::time::Instant; use fxhash::FxHashMap; use glam::IVec2; use crate::{ Config, config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS}, sim::{ cell::{cell::Cell, materials::MaterialId}, cell_manager::manager::CellManager, entity::{Entity, EntityDef}, particle_manager::ParticleManager, rb_manager::RbManager, sim_manager::utils::write_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, // entities // TODO entity manager? next_entity_id: u32, pub entities: FxHashMap, } pub struct SimCtx<'a> { 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) -> u32 { let (rb_h, collider_h) = if let Some(rb) = def.rb { let rb_h = self.rb_manager.physics_manager.rigid_body_set.insert(rb); if let Some(collider) = def.collider { let collider_h = self .rb_manager .physics_manager .collider_set .insert_with_parent( collider, rb_h, &mut self.rb_manager.physics_manager.rigid_body_set, ); (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 .collider_set .insert(collider); (None, Some(collider_h)) } else { (None, None) }; let entity = Entity::new( self.next_entity_id, rb_h, collider_h, def.cells, def.behaviour, ); let id = self.next_entity_id; self.entities.insert(id, entity); self.next_entity_id += 1; id } pub fn destroy_entity(&mut self, id: u32) { if let Some(entity) = self.entities.get(&id) { if let Some(rb_h) = entity.data.rb_h { self.rb_manager.physics_manager.rigid_body_set.remove( rb_h, &mut self.rb_manager.physics_manager.island_manager, &mut self.rb_manager.physics_manager.collider_set, &mut self.rb_manager.physics_manager.impulse_joint_set, &mut self.rb_manager.physics_manager.multibody_joint_set, true, ); } else if let Some(collider_h) = entity.data.collider_h { self.rb_manager.physics_manager.collider_set.remove( collider_h, &mut self.rb_manager.physics_manager.island_manager, &mut self.rb_manager.physics_manager.rigid_body_set, true, ); } self.entities.remove(&id); } } fn cell_update(&mut self, config: &Config) { // before we tick, write all the entities into the sim world // TODO optimize let entity_ids: Vec = self.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_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 entity cells and update the entities // TODO optimize for (entity_id, cells_written) in cells_written_by_entity { let entity = self.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.entity() { panic!( "Someone swapped into this entity's cell! ({x}, {y}, {m:#?}, {f})", m = new_local_cell.material, f = new_local_cell.flags ); } if let Some(cells) = &mut entity.data.cells { cells.set_cell_at_local_position( IVec2::new(lx as i32, ly as i32), 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) { 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 { cell_manager: &mut self.cell_manager, rb_manager: &mut self.rb_manager, particle_manager: &mut self.particle_manager, }; if let Some(entity) = entity { if let Some(result) = entity.physics_update(&mut ctx, delta_time) { for d in result.deferred_destructions { self.destroy_entity(d); } } } } // 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 entity_ids: Vec = self.entities.keys().cloned().collect(); for id in entity_ids { if let Some(entity) = self.entities.get_mut(&id) { let mut ctx = SimCtx { cell_manager: &mut self.cell_manager, rb_manager: &mut self.rb_manager, particle_manager: &mut self.particle_manager, }; entity.update(&mut ctx, delta_time); if let Some(result) = entity.update(&mut ctx, delta_time) { for d in result.deferred_destructions { self.destroy_entity(d); } } } } 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; } 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(), } } }