diff options
| author | Kai Stevenson <kai@kaistevenson.com> | 2026-08-22 15:00:35 -0700 |
|---|---|---|
| committer | Kai Stevenson <kai@kaistevenson.com> | 2026-08-22 15:00:35 -0700 |
| commit | 1a515237afb7ad09353a65f5fbc6e98a7c29ce8e (patch) | |
| tree | 48cd4b4bcf8f8e357811f905f22607838d7c1f96 /src/main.rs | |
| parent | 6350e4ffca8ce1e46465284ef3d7559e0f40229b (diff) | |
sim manager refactor
Diffstat (limited to 'src/main.rs')
| -rw-r--r-- | src/main.rs | 389 |
1 files changed, 103 insertions, 286 deletions
diff --git a/src/main.rs b/src/main.rs index caedc23..3976d71 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,8 +4,8 @@ mod renderer; mod sim; use futures::executor; -use glam::{IVec2, Vec2}; -use rand::{random_range, random_ratio}; +use glam::Vec2; +use rand::random_range; use std::{collections::VecDeque, sync::Arc, time::Instant}; use winit::{ application::ApplicationHandler, @@ -20,18 +20,15 @@ use winit::{ use crate::{ camera::Camera, - config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS, WINDOW_TITLE}, + config::WINDOW_TITLE, renderer::RendererState, sim::{ - cell::{ - cell::Cell, - materials::{MaterialId, fire::FireCellView}, - }, - cell_sim::{sim::sim_tick, world::World}, + cell::{cell::Cell, materials::MaterialId}, + cell_manager::manager::CellManager, lib::force::apply_explosion, - particle_sim::{ParticleManager, particle::Particle}, - rb_sim::{DebugRenderMode, RbSimManager, debug_ops::DebugOperator}, - write_rb_entity_to_world, + particle_manager::particle::Particle, + rb_manager::{DebugRenderMode, debug_ops::DebugOperator}, + sim_manager::SimManager, }, }; @@ -74,34 +71,19 @@ struct Diagnostics { } struct App { + // core window: Option<Arc<Window>>, renderer_state: Option<RendererState>, + // game input: Input, - camera: Option<Camera>, + sim_manager: Option<SimManager>, - // grid - world: Option<World>, - - // physics - rb_sim_manager: Option<RbSimManager>, - particle_manager: Option<ParticleManager>, - - // sim state - // the last/current (not yet completed) seqno - sim_seqno: u64, - sim_paused: bool, - ignore_pause_next_tick: bool, - last_sim_update: Instant, - sim_updates_due: f32, - - // physics state - last_physics_update: Instant, - physics_updates_due: f32, - + // renderer last_render: Instant, + // state config: Config, diagnostics: Diagnostics, } @@ -115,171 +97,85 @@ impl App { camera.handle_camera_input(&self.input, delta_time) } - // --TEST SPAWNING-- - if self.input.trigger_test_1 - && let Some(lm) = self.input.last_mouse_world_pos - && let Some(rbsm) = &mut self.rb_sim_manager - { - self.input.trigger_test_1 = false; - rbsm.test_spawn_box(lm.0, lm.1, self.config.dropper_material); - } - - if self.input.trigger_test_2 - && let Some(lm) = self.input.last_mouse_world_pos - && let Some(rbsm) = &mut self.rb_sim_manager - { - self.input.trigger_test_2 = false; - rbsm.test_spawn_ball(lm.0, lm.1, self.config.dropper_material); - } - - if self.input.trigger_test_3 - && let Some(lm) = self.input.last_mouse_world_pos - && let Some(pm) = &mut self.particle_manager - { - self.input.trigger_test_3 = false; - for i in 0..100 { - pm.particles.push(Particle { - position: Vec2::new( - lm.0 + random_range(-25.0..25.0), - lm.1 + random_range(-25.0..25.0), - ), - velocity: Vec2::ZERO, - material: MaterialId::Sand, - life: 2.0, - }) + if let Some(sim) = &mut self.sim_manager { + // --TEST SPAWNING-- + if self.input.trigger_test_1 + && let Some(lm) = self.input.last_mouse_world_pos + { + self.input.trigger_test_1 = false; + sim.rb_manager + .test_spawn_box(lm.0, lm.1, self.config.dropper_material); } - } - if self.input.trigger_test_4 - && let Some(lm) = self.input.last_mouse_world_pos - && let Some(world) = &mut self.world - && let Some(pm) = &mut self.particle_manager - && let Some(rbsm) = &mut self.rb_sim_manager - { - let mouse = Vec2::new(lm.0, lm.1); - self.input.trigger_test_4 = false; - apply_explosion( - mouse, - 30, - Vec2::new(0.0, -0.6), - 300.0, - self.sim_seqno, - pm, - world, - rbsm, - ); - } - - // --TEST DRAWING-- - if (self.input.is_lmb_pressed || self.input.is_rmb_pressed) - && let Some(lm) = self.input.last_mouse_world_pos - { - // start with the bounding box of the drawing brush circle + some margin - // clamp the bounding box to the board sie - let bb_xl = (lm.0 - self.config.brush_radius).round() as i32; - let bb_xu = (lm.0 + self.config.brush_radius).round() as i32; - let bb_yl = (lm.1 - self.config.brush_radius).round() as i32; - let bb_yu = (lm.1 + self.config.brush_radius).round() as i32; + if self.input.trigger_test_2 + && let Some(lm) = self.input.last_mouse_world_pos + { + self.input.trigger_test_2 = false; + sim.rb_manager + .test_spawn_ball(lm.0, lm.1, self.config.dropper_material); + } - // 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 - lm.0.round() as i32).pow(2) + (y - lm.1.round() as i32).pow(2)) - < (self.config.brush_radius as i32).pow(2) - && r > 0.9 - { - let cell = if self.input.is_lmb_pressed { - let mut cell = Cell::from_material(self.config.brush_material); - cell.match_parity(self.sim_seqno); - cell - } else { - Cell::void() - }; - // ensure we simulate on the first tick - if let Some(world) = &mut self.world { - world.set_cell_from_game_position( - x, y, cell, false, // wake the chunk - ) - } - } + if self.input.trigger_test_3 + && let Some(lm) = self.input.last_mouse_world_pos + { + self.input.trigger_test_3 = false; + for _ in 0..100 { + sim.particle_manager.particles.push(Particle { + position: Vec2::new( + lm.0 + random_range(-25.0..25.0), + lm.1 + random_range(-25.0..25.0), + ), + velocity: Vec2::ZERO, + material: MaterialId::Sand, + life: 2.0, + }) } } - } - } - // called SIM_FPS times per second - // will be called before the render and before the physics update(s) - // may be called multiple times if the sim time is behind - fn sim_update(&mut self) { - if let Some(world) = &mut self.world - && let Some(rbsm) = &mut self.rb_sim_manager - { - // before we tick, write all the rb entities into the sim world - // TODO optimize - let entity_ids: Vec<u32> = rbsm.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(world, rbsm, entity_id, self.sim_seqno); - cells_written_by_entity.push((entity_id, cells_written)); + if self.input.trigger_test_4 + && let Some(lm) = self.input.last_mouse_world_pos + { + let mouse = Vec2::new(lm.0, lm.1); + self.input.trigger_test_4 = false; + apply_explosion(sim, mouse, 30, Vec2::new(0.0, -0.6), 300.0); } - sim_tick(world, self.sim_seqno, self.config.use_threading); - self.sim_seqno += 1; + // --TEST DRAWING-- + if (self.input.is_lmb_pressed || self.input.is_rmb_pressed) + && let Some(lm) = self.input.last_mouse_world_pos + { + // start with the bounding box of the drawing brush circle + some margin + // clamp the bounding box to the board sie + let bb_xl = (lm.0 - self.config.brush_radius).round() as i32; + let bb_xu = (lm.0 + self.config.brush_radius).round() as i32; + let bb_yl = (lm.1 - self.config.brush_radius).round() as i32; + let bb_yu = (lm.1 + self.config.brush_radius).round() as i32; - // 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 = rbsm.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 = world.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 - world.set_cell_from_game_position(x, y, Cell::void(), false); - } - } - } - } - // called PHYSICS_FPS times per second - // will be called before the render - // may be called multiple times if the physics time is behind - // physics_delta_time is statically 1/PHYSICS_FPS - fn physics_update(&mut self, physics_delta_time: f32) { - // before we move the rigidbodies, upsert the current terrain state - // TODO use the chunk sleeping, and make this range dynamic - if let Some(physics_manager) = &mut self.rb_sim_manager { - if let Some(world) = &self.world { - for cx in -5..5 { - for cy in -5..5 { - if let Some(chunk) = world - .chunk_position_to_chunk_idx - .get(&(cx, cy)) - .map(|&idx| &world.chunks[idx]) - && !chunk.sleeping + // 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 - lm.0.round() as i32).pow(2) + (y - lm.1.round() as i32).pow(2)) + < (self.config.brush_radius as i32).pow(2) + && r > 0.9 { - physics_manager.upsert_chunk_collider(cx, cy, chunk); + let cell = if self.input.is_lmb_pressed { + let mut cell = Cell::from_material(self.config.brush_material); + // ensure we simulate on the first tick + cell.match_parity(sim.cell_manager.seqno); + cell + } else { + Cell::void() + }; + sim.cell_manager.set_cell_from_game_position( + x, y, cell, false, // wake the chunk + ) } } } } - physics_manager.rb_tick(physics_delta_time); - } - // move the particles - if let Some(particle_manager) = &mut self.particle_manager - && let Some(world) = &mut self.world - { - particle_manager.particle_tick(world, physics_delta_time); + + sim.update(&self.config, delta_time); } } } @@ -311,19 +207,7 @@ impl Default for App { camera: None, - world: Some(World::from_default_size()), - - rb_sim_manager: Some(RbSimManager::new()), - particle_manager: Some(ParticleManager::new()), - - sim_seqno: 0, - sim_paused: false, - ignore_pause_next_tick: false, - last_sim_update: Instant::now(), - sim_updates_due: 0.0, - - last_physics_update: Instant::now(), - physics_updates_due: 0.0, + sim_manager: Some(SimManager::new()), last_render: Instant::now(), @@ -392,51 +276,31 @@ impl ApplicationHandler for App { }, .. } => { - let pressed = state.is_pressed(); - match code { - KeyCode::KeyW => self.input.is_up_pressed = pressed, - KeyCode::KeyA => self.input.is_left_pressed = pressed, - KeyCode::KeyS => self.input.is_down_pressed = pressed, - KeyCode::KeyD => self.input.is_right_pressed = pressed, - KeyCode::KeyC => self.world = Some(World::from_default_size()), - KeyCode::KeyP => { - self.particle_manager - .as_mut() - .map(|pm| pm.particles = Vec::new()); - } - KeyCode::KeyV => { - if let Some(rbsm) = self.rb_sim_manager.as_mut() { - let entity_ids: Vec<u32> = rbsm.rb_entities.keys().copied().collect(); + if let Some(sim) = &mut self.sim_manager { + let pressed = state.is_pressed(); + match code { + KeyCode::KeyW => self.input.is_up_pressed = pressed, + KeyCode::KeyA => self.input.is_left_pressed = pressed, + KeyCode::KeyS => self.input.is_down_pressed = pressed, + KeyCode::KeyD => self.input.is_right_pressed = pressed, + KeyCode::KeyC => sim.cell_manager = CellManager::from_default_size(), + KeyCode::KeyP => sim.particle_manager.particles = Vec::new(), + KeyCode::KeyV => { + let entity_ids: Vec<u32> = + sim.rb_manager.rb_entities.keys().copied().collect(); for entity_id in entity_ids { - rbsm.destroy_rb_entity(entity_id); + sim.rb_manager.destroy_rb_entity(entity_id); } } + KeyCode::Space if pressed => sim.paused = !sim.paused, + KeyCode::KeyX if pressed => sim.ignore_pause_next_tick = true, + KeyCode::Digit1 if pressed && !repeat => self.input.trigger_test_1 = true, + KeyCode::Digit2 if pressed && !repeat => self.input.trigger_test_2 = true, + KeyCode::Digit3 if pressed && !repeat => self.input.trigger_test_3 = true, + KeyCode::Digit4 if pressed && !repeat => self.input.trigger_test_4 = true, + KeyCode::Digit5 if pressed && !repeat => self.input.trigger_test_5 = true, + _ => {} } - KeyCode::KeyP if pressed && !repeat => { - if let Some(rbsm) = self.rb_sim_manager.as_mut() { - for &id in rbsm.rb_entities.keys() { - write_rb_entity_to_world( - self.world.as_mut().unwrap(), - rbsm, - id, - self.sim_seqno, - ); - } - - let entity_ids: Vec<u32> = rbsm.rb_entities.keys().copied().collect(); - for entity_id in entity_ids { - rbsm.destroy_rb_entity(entity_id); - } - } - } - KeyCode::Space if pressed => self.sim_paused = !self.sim_paused, - KeyCode::KeyX if pressed => self.ignore_pause_next_tick = true, - KeyCode::Digit1 if pressed && !repeat => self.input.trigger_test_1 = true, - KeyCode::Digit2 if pressed && !repeat => self.input.trigger_test_2 = true, - KeyCode::Digit3 if pressed && !repeat => self.input.trigger_test_3 = true, - KeyCode::Digit4 if pressed && !repeat => self.input.trigger_test_4 = true, - KeyCode::Digit5 if pressed && !repeat => self.input.trigger_test_5 = true, - _ => {} } } WindowEvent::CursorMoved { position, .. } => { @@ -448,7 +312,7 @@ impl ApplicationHandler for App { self.input.last_mouse_world_pos = Some(world_pos); let ((cx, cy), (lx, ly)) = - World::split_game_position(world_pos.0 as i32, world_pos.1 as i32); + CellManager::split_game_position(world_pos.0 as i32, world_pos.1 as i32); self.input.last_mouse_chunk_pos = Some((cx, cy)); self.input.last_mouse_local_pos = Some((lx, ly)); } @@ -492,61 +356,14 @@ impl ApplicationHandler for App { self.diagnostics.fps = 1.0 / average_frame_time; - // UPDATE self.update(delta_time); - // SIM UPDATE - let secs_since_last_sim_update = (now - self.last_sim_update).as_secs_f32(); - let expected_secs_since_last_sim_update = 1.0 / SIM_FPS as f32; - - self.last_sim_update = now; - if self.sim_paused && self.ignore_pause_next_tick { - self.sim_update(); - } else if !self.sim_paused { - self.sim_updates_due += - secs_since_last_sim_update / expected_secs_since_last_sim_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.sim_updates_due >= 1.0 && updates_done < 3 { - self.sim_update(); - self.sim_updates_due -= 1.0; - updates_done += 1; - } - self.sim_updates_due = self.sim_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.sim_paused && self.ignore_pause_next_tick { - self.physics_update(PHYSICS_DELTA_TIME); - } else if !self.sim_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; - if let Some(renderer_state) = &mut self.renderer_state - && let Some(world) = &mut self.world - && let Some(rb_sim_manager) = &mut self.rb_sim_manager - && let Some(particle_manager) = &mut self.particle_manager + && let Some(sim) = &mut self.sim_manager && let Some(camera) = &mut self.camera { renderer_state.render( - world, - rb_sim_manager, - particle_manager, + sim, camera, &mut self.config, &self.diagnostics, |
