use std::time::Instant; use fxhash::FxHashMap; use glam::Vec2; use rand::random_range; use crate::{ Config, InputManager, config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS}, input::Input, sim::{ cell::Cell, cell_manager::manager::CellManager, entity::{Entity, EntityDef, EntityId}, particle_manager::{ParticleManager, particle::Particle}, rb_manager::RbManager, sim_manager::utils::{ process_entity_update_result, read_back_entities_from_world, write_entities_to_world, }, }, vfx::VfxWriter, }; 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 vfx_writer: &'a mut dyn VfxWriter, 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); } } pub fn atomize_entity(&mut self, id: EntityId) { if let Some(entity) = self.entities.get(&id) && let Some(cells) = &entity.data.cells { // TODO these unwraps exist because we don't have separate entity position from rb let rb = self .rb_manager .physics_manager .world .bodies .get(entity.data.rb_h.unwrap()) .unwrap(); for x in 0..cells.size.x { for y in 0..cells.size.y { let r = rb.position().transform_vector( Vec2::new(x as f32, y as f32) - (cells.size / 2).as_vec2(), ); self.particle_manager.particles.push(Particle::new( rb.translation() + r, rb.linvel(), cells.cells[(x + y * cells.size.x) as usize].material, 3.0, 0.1, )) } } self.destroy_entity(id); } } fn cell_update( &mut self, vfx_writer: &mut dyn VfxWriter, config: &Config, input_manager: &InputManager, delta_time: f32, ) { // before we tick, write all the entities into the sim world let written_entities_scope = write_entities_to_world(self); // --TEST DRAWING-- if input_manager.lmb_held || input_manager.rmb_held { // start with the bounding box of the drawing brush circle + some margin // clamp the bounding box to the board sie let bb_xl = (input_manager.world_mouse_pos.x - config.brush_radius).round() as i32; let bb_xu = (input_manager.world_mouse_pos.x + config.brush_radius).round() as i32; let bb_yl = (input_manager.world_mouse_pos.y - config.brush_radius).round() as i32; let bb_yu = (input_manager.world_mouse_pos.y + config.brush_radius).round() as i32; // for each point, check if the distance is less than the brush size and write the pixel for x in bb_xl..bb_xu { for y in bb_yl..bb_yu { let r = random_range(0.0..1.0); if ((x - input_manager.world_mouse_pos.x.round() as i32).pow(2) + (y - input_manager.world_mouse_pos.y.round() as i32).pow(2)) < (config.brush_radius as i32).pow(2) && r > 0.9 { let cell = if input_manager.lmb_held { let mut cell = Cell::from_material(config.brush_material); // ensure we simulate on the first tick cell.match_parity(self.cell_manager.seqno); cell } else { Cell::void() }; self.cell_manager.set_cell_from_game_position( x, y, cell, false, // wake the chunk ) } } } } if input_manager.pressed(Input::Action2) { if let Some(entity_id) = written_entities_scope .get_entity_id_at_position(input_manager.world_mouse_pos.round().as_ivec2()) { self.atomize_entity(entity_id); } } // TEST ATOMIZATION // 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 { vfx_writer, 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, written_entities_scope); } fn physics_update( &mut self, vfx_writer: &mut dyn VfxWriter, 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 { vfx_writer, 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)) .and_then(|&idx| self.cell_manager.chunks.get_mut(idx)) && let Some(ds) = chunk.collider_dirty_seqno // debounce && self.cell_manager.seqno - ds > 5 { self.rb_manager.upsert_chunk_collider(cx, cy, chunk); chunk.collider_dirty_seqno = None; } } } // 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, vfx_writer: &mut dyn VfxWriter, 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) { // TODO drain doesn't work here? let entities: Vec = self.entities.keys().cloned().collect(); entities.iter().for_each(|&id| self.destroy_entity(id)); self.entities.clear(); } 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(vfx_writer, 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(vfx_writer, 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(vfx_writer, 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(vfx_writer, 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(), } } }