From ced4e87d416cfb5eda1031480337a4428c99002c Mon Sep 17 00:00:00 2001 From: Xevion Date: Wed, 13 Aug 2025 00:33:16 -0500 Subject: [PATCH] feat: embed atlas.json via phf instead of runtime parsing --- Cargo.lock | 68 +++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 6 ++++ build.rs | 50 ++++++++++++++++++++++++++++++ src/asset.rs | 4 +-- src/game/state.rs | 8 +++-- src/platform/desktop.rs | 1 - tests/common/mod.rs | 10 +++--- 7 files changed, 135 insertions(+), 12 deletions(-) create mode 100644 build.rs diff --git a/Cargo.lock b/Cargo.lock index f5cdf20..7faa84e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -194,7 +194,8 @@ dependencies = [ "libc", "once_cell", "pathfinding", - "rand", + "phf", + "rand 0.9.2", "sdl2", "serde", "serde_json", @@ -223,6 +224,48 @@ dependencies = [ "thiserror", ] +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.13" @@ -253,15 +296,30 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ - "rand_core", + "rand_core 0.9.3", ] +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + [[package]] name = "rand_core" version = "0.9.3" @@ -399,6 +457,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "smallvec" version = "1.15.1" diff --git a/Cargo.toml b/Cargo.toml index 55a63d8..35d3fed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ serde_json = "1.0.142" smallvec = "1.15.1" strum = "0.27.2" strum_macros = "0.27.2" +phf = { version = "0.11", features = ["macros"] } [profile.release] lto = true @@ -57,3 +58,8 @@ aarch64-apple-darwin = { triplet = "arm64-osx" } [target.'cfg(target_os = "emscripten")'.dependencies] libc = "0.2.175" + +[build-dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +phf = { version = "0.11", features = ["macros"] } diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..08382a0 --- /dev/null +++ b/build.rs @@ -0,0 +1,50 @@ +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{BufWriter, Write}; +use std::path::Path; + +use serde::Deserialize; + +#[derive(Debug, Deserialize)] +struct AtlasMapper { + frames: HashMap, +} + +#[derive(Copy, Clone, Debug, Deserialize)] +struct MapperFrame { + x: u16, + y: u16, + width: u16, + height: u16, +} + +fn main() { + let path = Path::new(&env::var("OUT_DIR").unwrap()).join("atlas_data.rs"); + let mut file = BufWriter::new(File::create(&path).unwrap()); + + let atlas_json = include_str!("./assets/game/atlas.json"); + let atlas_mapper: AtlasMapper = serde_json::from_str(atlas_json).unwrap(); + + writeln!(&mut file, "use phf::phf_map;").unwrap(); + + writeln!(&mut file, "use crate::texture::sprite::MapperFrame;").unwrap(); + + writeln!( + &mut file, + "pub static ATLAS_FRAMES: phf::Map<&'static str, MapperFrame> = phf_map! {{" + ) + .unwrap(); + + for (name, frame) in atlas_mapper.frames { + writeln!( + &mut file, + " \"{}\" => MapperFrame {{ x: {}, y: {}, width: {}, height: {} }},", + name, frame.x, frame.y, frame.width, frame.height + ) + .unwrap(); + } + + writeln!(&mut file, "}};").unwrap(); + println!("cargo:rerun-if-changed=assets/game/atlas.json"); +} diff --git a/src/asset.rs b/src/asset.rs index 5f5379c..27c22de 100644 --- a/src/asset.rs +++ b/src/asset.rs @@ -12,8 +12,6 @@ pub enum Asset { Wav3, Wav4, Atlas, - AtlasJson, - // Add more as needed } impl Asset { @@ -26,7 +24,6 @@ impl Asset { Wav3 => "sound/waka/3.ogg", Wav4 => "sound/waka/4.ogg", Atlas => "atlas.png", - AtlasJson => "atlas.json", } } } @@ -36,6 +33,7 @@ mod imp { use crate::error::AssetError; use crate::platform::get_platform; + /// Returns the raw bytes of the given asset. pub fn get_asset_bytes(asset: Asset) -> Result, AssetError> { get_platform().get_asset_bytes(asset) } diff --git a/src/game/state.rs b/src/game/state.rs index f487380..456260b 100644 --- a/src/game/state.rs +++ b/src/game/state.rs @@ -20,6 +20,8 @@ use crate::{ }, }; +include!(concat!(env!("OUT_DIR"), "/atlas_data.rs")); + /// The `GameState` struct holds all the essential data for the game. /// /// This includes the score, map, entities (Pac-Man, ghosts, items), @@ -68,8 +70,10 @@ impl GameState { 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_mapper = AtlasMapper { + frames: ATLAS_FRAMES.into_iter().map(|(k, v)| (k.to_string(), *v)).collect(), + }; let atlas = SpriteAtlas::new(atlas_texture, atlas_mapper); let mut map_tiles = Vec::with_capacity(35); diff --git a/src/platform/desktop.rs b/src/platform/desktop.rs index 9f42c7e..f3b3a6f 100644 --- a/src/platform/desktop.rs +++ b/src/platform/desktop.rs @@ -72,7 +72,6 @@ impl Platform for DesktopPlatform { Asset::Wav3 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/3.ogg"))), Asset::Wav4 => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/waka/4.ogg"))), Asset::Atlas => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))), - Asset::AtlasJson => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.json"))), } } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs index a440287..2cf25af 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -2,7 +2,8 @@ use pacman::{ asset::{get_asset_bytes, Asset}, - texture::sprite::SpriteAtlas, + game::state::ATLAS_FRAMES, + texture::sprite::{AtlasMapper, SpriteAtlas}, }; use sdl2::{ image::LoadTexture, @@ -28,12 +29,13 @@ pub fn setup_sdl() -> Result<(Canvas, TextureCreator, Sdl pub fn create_atlas(canvas: &mut sdl2::render::Canvas) -> SpriteAtlas { let texture_creator = canvas.texture_creator(); let atlas_bytes = get_asset_bytes(Asset::Atlas).unwrap(); - let atlas_json = get_asset_bytes(Asset::AtlasJson).unwrap(); let texture = texture_creator.load_texture_bytes(&atlas_bytes).unwrap(); let texture: Texture<'static> = unsafe { std::mem::transmute(texture) }; - let mapper: pacman::texture::sprite::AtlasMapper = serde_json::from_slice(&atlas_json).unwrap(); + let atlas_mapper = AtlasMapper { + frames: ATLAS_FRAMES.into_iter().map(|(k, v)| (k.to_string(), *v)).collect(), + }; - SpriteAtlas::new(texture, mapper) + SpriteAtlas::new(texture, atlas_mapper) }