mod camera; mod config; mod renderer; mod sim; use futures::executor; use fxhash::FxHashMap; use rand::random_range; use std::{collections::VecDeque, sync::Arc, time::Instant}; use winit::{ application::ApplicationHandler, event::{ ElementState, KeyEvent, MouseButton, WindowEvent::{self}, }, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::{KeyCode, PhysicalKey}, window::Window, }; use crate::{ camera::Camera, config::{PHYSICS_DELTA_TIME, PHYSICS_FPS, SIM_FPS, WINDOW_TITLE}, renderer::RendererState, sim::{ cell::{cell::Cell, materials::MaterialId}, cell_sim::{sim::sim_tick, world::World}, rb_sim::RbSimManager, write_rb_entity_to_world, }, }; pub type Error = Box; pub type Result = std::result::Result; struct Config { brush_radius: f32, brush_material: MaterialId, dropper_material: MaterialId, use_threading: bool, } struct Input { last_mouse_pos_on_screen: Option<(f32, f32)>, last_mouse_world_pos: Option<(f32, f32)>, is_lmb_pressed: bool, trigger_test_1: bool, // keybindings is_up_pressed: bool, is_left_pressed: bool, is_down_pressed: bool, is_right_pressed: bool, } struct Diagnostics { frame_times: VecDeque, fps: f32, } struct App { window: Option>, renderer_state: Option, input: Input, camera: Option, // grid world: Option, // physics rb_sim_manager: Option, // 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, last_render: Instant, config: Config, diagnostics: Diagnostics, } impl App { // called as often as possible // delta time is the real seconds elapsed since the last time this was called fn update(&mut self, delta_time: f32) { // apply inputs if let Some(camera) = &mut self.camera { 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); } // --TEST DRAWING-- if self.input.is_lmb_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; // 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 mut cell = Cell::from_material(self.config.brush_material); cell.match_parity(self.sim_seqno); // 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 ) } } } } } } // 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 = 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)); } sim_tick(world, self.sim_seqno, self.config.use_threading); self.sim_seqno += 1; // 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? 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!"); // } 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) { if let Some(physics_manager) = &mut self.rb_sim_manager { physics_manager.rb_tick(physics_delta_time); } } } impl Default for App { fn default() -> Self { Self { window: None, renderer_state: None, input: Input { last_mouse_pos_on_screen: None, last_mouse_world_pos: None, trigger_test_1: false, is_lmb_pressed: false, is_up_pressed: false, is_left_pressed: false, is_down_pressed: false, is_right_pressed: false, }, camera: None, world: Some(World::from_default_size()), rb_sim_manager: Some(RbSimManager::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, last_render: Instant::now(), config: Config { use_threading: true, brush_radius: 10.0, brush_material: MaterialId::Sand, dropper_material: MaterialId::Sand, }, diagnostics: Diagnostics { fps: 0.0, frame_times: VecDeque::new(), }, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = Arc::new( event_loop .create_window(Window::default_attributes().with_title(WINDOW_TITLE)) .unwrap(), ); self.window = Some(window.clone()); self.renderer_state = Some(executor::block_on(RendererState::new(window.clone()))); if let Some(rbsm) = &mut self.rb_sim_manager { rbsm.test(); } let size = window.inner_size(); self.camera = Some(Camera::new((size.width as i32, size.height as i32))); // let surface = SurfaceTexture::new(size.width, size.height, window_ref); // let mut pixels = Pixels::new(PIXEL_BUFFER_WIDTH, PIXEL_BUFFER_HEIGHT, surface).unwrap(); // pixels.set_scaling_mode(ScalingMode::Fill); } fn window_event( &mut self, event_loop: &ActiveEventLoop, _: winit::window::WindowId, event: WindowEvent, ) { if let Some(renderer_state) = &mut self.renderer_state && let Some(window) = &mut self.window { let egui_response = renderer_state.egui_state.on_window_event(window, &event); // if egui consumed the event, it means we shouldn't treat any e.g., mouse clicks if egui_response.consumed { return; } } match event { WindowEvent::KeyboardInput { event: KeyEvent { physical_key: PhysicalKey::Code(code), state, repeat, .. }, .. } => { 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::KeyV => { if let Some(rbsm) = self.rb_sim_manager.as_mut() { let entity_ids: Vec = rbsm.rb_entities.keys().copied().collect(); for entity_id in entity_ids { rbsm.destroy_rb_entity(entity_id); } } } 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 = 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::KeyQ if pressed && !repeat => self.input.trigger_test_1 = true, _ => {} } } WindowEvent::CursorMoved { position, .. } => { self.input.last_mouse_pos_on_screen = Some((position.x as f32, position.y as f32)); self.input.last_mouse_world_pos = self.camera.as_mut().map(|camera| { camera.screen_position_to_world(position.x as f32, position.y as f32) }) } WindowEvent::MouseInput { state, button, .. } => { if button == MouseButton::Left { self.input.is_lmb_pressed = state == ElementState::Pressed } } WindowEvent::Resized(size) => { if let Some(renderer_state) = &mut self.renderer_state && let Some(camera) = &mut self.camera { renderer_state.resize(size.width, size.height); camera.resize((size.width as i32, size.height as i32)); } } WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::RedrawRequested => { #[cfg(feature = "profiler")] puffin::GlobalProfiler::lock().new_frame(); puffin::profile_scope!("redraw_requested"); // compute FPS diagnostics let now = Instant::now(); let secs_since_last_frame = (now - self.last_render).as_secs_f32(); self.last_render = now; let delta_time = secs_since_last_frame / (1.0 / 60.0); self.diagnostics .frame_times .push_back(secs_since_last_frame); if self.diagnostics.frame_times.len() > 30 { self.diagnostics.frame_times.pop_front(); } let average_frame_time = self.diagnostics.frame_times.iter().sum::() / self.diagnostics.frame_times.len() as f32; 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(); self.ignore_pause_next_tick = false; } 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.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); } 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(camera) = &mut self.camera { renderer_state.render( world, rb_sim_manager, camera, &mut self.config, &self.diagnostics, &self.input, ); } } _ => {} } } fn about_to_wait(&mut self, _: &ActiveEventLoop) { if let Some(window) = &self.window { window.request_redraw(); } else { panic!("No window!") } } } #[cfg(feature = "profiler")] fn start_profiler() { let _server = puffin_http::Server::new("127.0.0.1:8585").unwrap(); puffin::set_scopes_on(true); std::mem::forget(_server); // keep serving for the process lifetime std::process::Command::new("puffin_viewer") .args(["--url", "127.0.0.1:8585"]) .spawn() .ok(); // don't die if it isn't installed } fn main() -> Result<()> { #[cfg(feature = "profiler")] start_profiler(); let event_loop = EventLoop::new()?; event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::default(); event_loop.run_app(&mut app)?; Ok(()) }