From c489f32908fbfc62fb1c48ab3de3cdb776caf188 Mon Sep 17 00:00:00 2001 From: Xevion Date: Tue, 12 Aug 2025 13:07:17 -0500 Subject: [PATCH] fix: audio and other subsystems being dropped in App::new(), use Box::leak to ensure static ownership --- src/app.rs | 36 ++++++++++++++++++++---------------- src/game.rs | 24 +++++++++++------------- src/texture/sprite.rs | 17 ----------------- tests/item.rs | 2 +- 4 files changed, 32 insertions(+), 47 deletions(-) diff --git a/src/app.rs b/src/app.rs index 7e2dbc6..a63c859 100644 --- a/src/app.rs +++ b/src/app.rs @@ -4,8 +4,9 @@ use glam::Vec2; use sdl2::event::{Event, WindowEvent}; use sdl2::keyboard::Keycode; use sdl2::render::{Canvas, ScaleMode, Texture, TextureCreator}; +use sdl2::ttf::Sdl2TtfContext; use sdl2::video::{Window, WindowContext}; -use sdl2::EventPump; +use sdl2::{AudioSubsystem, EventPump, Sdl, VideoSubsystem}; use tracing::{error, event}; use crate::error::{GameError, GameResult}; @@ -14,26 +15,31 @@ use crate::constants::{CANVAS_SIZE, LOOP_TIME, SCALE}; use crate::game::Game; use crate::platform::get_platform; -pub struct App<'a> { +pub struct App { game: Game, canvas: Canvas, - event_pump: EventPump, - backbuffer: Texture<'a>, + event_pump: &'static mut EventPump, + backbuffer: Texture<'static>, paused: bool, last_tick: Instant, cursor_pos: Vec2, } -impl App<'_> { +impl App { pub fn new() -> GameResult { + let sdl_context: &'static Sdl = Box::leak(Box::new(sdl2::init().map_err(|e| GameError::Sdl(e.to_string()))?)); + let video_subsystem: &'static VideoSubsystem = + Box::leak(Box::new(sdl_context.video().map_err(|e| GameError::Sdl(e.to_string()))?)); + let audio_subsystem: &'static AudioSubsystem = + Box::leak(Box::new(sdl_context.audio().map_err(|e| GameError::Sdl(e.to_string()))?)); + let ttf_context: &'static Sdl2TtfContext = + Box::leak(Box::new(sdl2::ttf::init().map_err(|e| GameError::Sdl(e.to_string()))?)); + let event_pump: &'static mut EventPump = + Box::leak(Box::new(sdl_context.event_pump().map_err(|e| GameError::Sdl(e.to_string()))?)); + // Initialize platform-specific console get_platform().init_console()?; - let sdl_context = sdl2::init().map_err(|e| GameError::Sdl(e.to_string()))?; - let video_subsystem = sdl_context.video().map_err(|e| GameError::Sdl(e.to_string()))?; - let audio_subsystem = sdl_context.audio().map_err(|e| GameError::Sdl(e.to_string()))?; - let ttf_context = sdl2::ttf::init().map_err(|e| GameError::Sdl(e.to_string()))?; - let window = video_subsystem .window( "Pac-Man", @@ -50,18 +56,16 @@ impl App<'_> { .set_logical_size(CANVAS_SIZE.x, CANVAS_SIZE.y) .map_err(|e| GameError::Sdl(e.to_string()))?; - let texture_creator_static: &'static TextureCreator = Box::leak(Box::new(canvas.texture_creator())); + let texture_creator: &'static TextureCreator = Box::leak(Box::new(canvas.texture_creator())); - let mut game = Game::new(texture_creator_static, &ttf_context, &audio_subsystem)?; - game.audio.set_mute(cfg!(debug_assertions)); + let mut game = Game::new(texture_creator, ttf_context, audio_subsystem)?; + // game.audio.set_mute(cfg!(debug_assertions)); - let mut backbuffer = texture_creator_static + let mut backbuffer = texture_creator .create_texture_target(None, CANVAS_SIZE.x, CANVAS_SIZE.y) .map_err(|e| GameError::Sdl(e.to_string()))?; backbuffer.set_scale_mode(ScaleMode::Nearest); - let event_pump = sdl_context.event_pump().map_err(|e| GameError::Sdl(e.to_string()))?; - // Initial draw game.draw(&mut canvas, &mut backbuffer) .map_err(|e| GameError::Sdl(e.to_string()))?; diff --git a/src/game.rs b/src/game.rs index 43643a1..140ba3d 100644 --- a/src/game.rs +++ b/src/game.rs @@ -25,7 +25,7 @@ use crate::{ }, map::Map, texture::{ - sprite::{self, AtlasMapper, AtlasTile, SpriteAtlas}, + sprite::{AtlasMapper, AtlasTile, SpriteAtlas}, text::TextTexture, }, }; @@ -59,7 +59,7 @@ pub struct Game { impl Game { pub fn new( - texture_creator: &TextureCreator, + texture_creator: &'static TextureCreator, _ttf_context: &sdl2::ttf::Sdl2TtfContext, _audio_subsystem: &sdl2::AudioSubsystem, ) -> GameResult { @@ -74,19 +74,16 @@ impl Game { .ok_or_else(|| GameError::NotFound("Pac-Man starting position not found in graph".to_string()))?; let atlas_bytes = get_asset_bytes(Asset::Atlas)?; - let atlas_texture = unsafe { - let texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { - if e.to_string().contains("format") || e.to_string().contains("unsupported") { - GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {e}"))) - } else { - GameError::Texture(TextureError::LoadFailed(e.to_string())) - } - })?; - sprite::texture_to_static(texture) - }; + let atlas_texture = Box::leak(Box::new(texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { + if e.to_string().contains("format") || e.to_string().contains("unsupported") { + GameError::Texture(TextureError::InvalidFormat(format!("Unsupported texture format: {e}"))) + } else { + GameError::Texture(TextureError::LoadFailed(e.to_string())) + } + })?)); let atlas_json = get_asset_bytes(Asset::AtlasJson)?; let atlas_mapper: AtlasMapper = serde_json::from_slice(&atlas_json)?; - let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper); + let atlas = SpriteAtlas::new(unsafe { std::mem::transmute_copy(atlas_texture) }, atlas_mapper); let mut map_texture = SpriteAtlas::get_tile(&atlas, "maze/full.png") .ok_or_else(|| GameError::Texture(TextureError::AtlasTileNotFound("maze/full.png".to_string())))?; @@ -255,6 +252,7 @@ impl Game { if !item.is_collected() { item.collect(); self.score += item.get_score(); + self.audio.eat(); // Handle energizer effects if matches!(item.item_type, crate::entity::item::ItemType::Energizer) { diff --git a/src/texture/sprite.rs b/src/texture/sprite.rs index 6e8af3c..0a61741 100644 --- a/src/texture/sprite.rs +++ b/src/texture/sprite.rs @@ -138,20 +138,3 @@ impl SpriteAtlas { self.default_color } } - -/// Converts a `Texture` to a `Texture<'static>` using transmute. -/// -/// # Safety -/// -/// This function is unsafe because it uses `std::mem::transmute` to change the lifetime -/// of the texture from the original lifetime to `'static`. The caller must ensure that: -/// -/// - The original `Texture` will live for the entire duration of the program -/// - No references to the original texture exist that could become invalid -/// - The texture is not dropped while still being used as a `'static` reference -/// -/// This is typically used when you have a texture that you know will live for the entire -/// program duration and need to store it in a structure that requires a `'static` lifetime. -pub unsafe fn texture_to_static(texture: Texture) -> Texture<'static> { - std::mem::transmute(texture) -} diff --git a/tests/item.rs b/tests/item.rs index 01598fe..7a881f9 100644 --- a/tests/item.rs +++ b/tests/item.rs @@ -25,7 +25,7 @@ fn test_fruit_kind_increasing_score() { .collect::>(); kinds.sort_unstable_by_key(|(index, _)| *index); - assert_eq!(kinds.len(), FruitKind::COUNT as usize); + assert_eq!(kinds.len(), FruitKind::COUNT); // Check that the score increases as expected for window in kinds.windows(2) {