From 2f1ff85d8fe1697954d6ac940cc025e569d761e5 Mon Sep 17 00:00:00 2001 From: Xevion Date: Thu, 14 Aug 2025 10:36:39 -0500 Subject: [PATCH] refactor: handle pausing within game, reduce input system allocations --- src/app.rs | 48 ++++++++++++++++++---------------------------- src/game/events.rs | 4 ++-- src/game/mod.rs | 21 ++++++++++++++++---- src/game/state.rs | 3 +++ src/input/mod.rs | 13 ++++++------- 5 files changed, 47 insertions(+), 42 deletions(-) diff --git a/src/app.rs b/src/app.rs index cf20442..0f03791 100644 --- a/src/app.rs +++ b/src/app.rs @@ -6,7 +6,7 @@ use sdl2::render::{Canvas, ScaleMode, Texture, TextureCreator}; use sdl2::ttf::Sdl2TtfContext; use sdl2::video::{Window, WindowContext}; use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem}; -use tracing::{debug, error, info, warn}; +use tracing::{error, info, warn}; use crate::error::{GameError, GameResult}; @@ -20,11 +20,11 @@ pub struct App { game: Game, input_system: InputSystem, canvas: Canvas, - event_pump: &'static mut EventPump, backbuffer: Texture<'static>, - paused: bool, - focused: bool, + event_pump: &'static mut EventPump, + last_tick: Instant, + focused: bool, cursor_pos: Vec2, } @@ -87,7 +87,6 @@ impl App { canvas, event_pump, backbuffer, - paused: false, focused: true, last_tick: Instant::now(), cursor_pos: Vec2::ZERO, @@ -105,17 +104,10 @@ impl App { self.focused = true; } WindowEvent::FocusLost => { - debug!("Window focus lost"); self.focused = false; } _ => {} }, - // It doesn't really make sense to have this available in the browser - #[cfg(not(target_os = "emscripten"))] - Event::Quit { .. } => { - info!("Exit requested. Exiting..."); - return false; - } Event::MouseMotion { x, y, .. } => { // Convert window coordinates to logical coordinates self.cursor_pos = Vec2::new(x as f32, y as f32); @@ -123,17 +115,12 @@ impl App { _ => {} } - let commands = self.input_system.handle_event(&event); - for command in commands { + if let Some(command) = self.input_system.handle_event(&event) { match command { GameCommand::Exit => { info!("Exit requested. Exiting..."); return false; } - GameCommand::TogglePause => { - self.paused = !self.paused; - info!("{}", if self.paused { "Paused" } else { "Unpaused" }); - } _ => self.game.post_event(command.into()), } } @@ -142,17 +129,20 @@ impl App { let dt = self.last_tick.elapsed().as_secs_f32(); self.last_tick = Instant::now(); - if !self.paused { - self.game.tick(dt); - if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) { - error!("Failed to draw game: {}", e); - } - if let Err(e) = self - .game - .present_backbuffer(&mut self.canvas, &self.backbuffer, self.cursor_pos) - { - error!("Failed to present backbuffer: {}", e); - } + let exit = self.game.tick(dt); + + if exit { + return false; + } + + if let Err(e) = self.game.draw(&mut self.canvas, &mut self.backbuffer) { + error!("Failed to draw game: {}", e); + } + if let Err(e) = self + .game + .present_backbuffer(&mut self.canvas, &self.backbuffer, self.cursor_pos) + { + error!("Failed to present backbuffer: {}", e); } if start.elapsed() < LOOP_TIME { diff --git a/src/game/events.rs b/src/game/events.rs index 157e188..b0e5351 100644 --- a/src/game/events.rs +++ b/src/game/events.rs @@ -2,11 +2,11 @@ use crate::input::commands::GameCommand; #[derive(Debug, Clone, Copy)] pub enum GameEvent { - InputCommand(GameCommand), + Command(GameCommand), } impl From for GameEvent { fn from(command: GameCommand) -> Self { - GameEvent::InputCommand(command) + GameEvent::Command(command) } } diff --git a/src/game/mod.rs b/src/game/mod.rs index 435f1f8..d53467d 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -60,16 +60,17 @@ impl Game { tracing::error!("Failed to reset game state: {}", e); } } - GameCommand::Exit | GameCommand::TogglePause => { - // These are handled in app.rs + GameCommand::TogglePause => { + self.state.paused = !self.state.paused; } + GameCommand::Exit => {} } } fn process_events(&mut self) { while let Some(event) = self.state.event_queue.pop_front() { match event { - GameEvent::InputCommand(command) => self.handle_command(command), + GameEvent::Command(command) => self.handle_command(command), } } } @@ -114,8 +115,18 @@ impl Game { Ok(()) } - pub fn tick(&mut self, dt: f32) { + /// Ticks the game state. + /// + /// Returns true if the game should exit. + pub fn tick(&mut self, dt: f32) -> bool { + // Process any events that have been posted (such as unpausing) self.process_events(); + + // If the game is paused, we don't need to do anything beyond returning + if self.state.paused { + return false; + } + self.state.pacman.tick(dt, &self.state.map.graph); // Update all ghosts @@ -128,6 +139,8 @@ impl Game { // Check for collisions self.check_collisions(); + + false } /// Toggles the debug mode on and off. diff --git a/src/game/state.rs b/src/game/state.rs index 85947c4..9c9da59 100644 --- a/src/game/state.rs +++ b/src/game/state.rs @@ -35,6 +35,8 @@ include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); /// we can cleanly separate it from the game's logic, making it easier to manage /// and reason about. pub struct GameState { + pub paused: bool, + pub score: u32, pub map: Map, pub pacman: Pacman, @@ -128,6 +130,7 @@ impl GameState { .collect(); Ok(Self { + paused: false, map, atlas, pacman, diff --git a/src/input/mod.rs b/src/input/mod.rs index 3723a16..5924876 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -36,13 +36,12 @@ impl InputSystem { Self { key_bindings } } - pub fn handle_event(&self, event: &Event) -> Vec { - let mut commands = Vec::new(); - if let Event::KeyDown { keycode: Some(key), .. } = event { - if let Some(command) = self.key_bindings.get(key) { - commands.push(*command); - } + /// Handles an event and returns a command if one is bound to the event. + pub fn handle_event(&self, event: &Event) -> Option { + match event { + Event::Quit { .. } => Some(GameCommand::Exit), + Event::KeyDown { keycode: Some(key), .. } => self.key_bindings.get(key).copied(), + _ => None, } - commands } }