refactor: unify cross-platform asset loading, avoid hard-coding with folder-based asset embedding for desktop

This commit is contained in:
Ryan Walters
2025-09-11 01:11:00 -05:00
parent 43532dac56
commit 00a65954e6
9 changed files with 193 additions and 71 deletions

136
Cargo.lock generated
View File

@@ -228,6 +228,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.19.0" version = "3.19.0"
@@ -268,6 +277,15 @@ dependencies = [
"portable-atomic", "portable-atomic",
] ]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "critical-section" name = "critical-section"
version = "1.2.0" version = "1.2.0"
@@ -289,6 +307,16 @@ version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "deprecate-until" name = "deprecate-until"
version = "0.1.1" version = "0.1.1"
@@ -337,6 +365,16 @@ version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]] [[package]]
name = "disqualified" name = "disqualified"
version = "1.0.0" version = "1.0.0"
@@ -408,6 +446,16 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.3" version = "0.3.3"
@@ -663,7 +711,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]] [[package]]
name = "pacman" name = "pacman"
version = "0.79.2" version = "0.79.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bevy_ecs", "bevy_ecs",
@@ -678,6 +726,7 @@ dependencies = [
"phf", "phf",
"pretty_assertions", "pretty_assertions",
"rand", "rand",
"rust-embed",
"sdl2", "sdl2",
"serde", "serde",
"serde_json", "serde_json",
@@ -907,6 +956,40 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rust-embed"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594"
dependencies = [
"sha2",
"walkdir",
]
[[package]] [[package]]
name = "rustc-hash" name = "rustc-hash"
version = "2.1.1" version = "2.1.1"
@@ -925,6 +1008,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
@@ -994,6 +1086,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"
@@ -1253,6 +1356,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.11" version = "1.0.11"
@@ -1306,6 +1415,22 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.14.2+wasi-0.2.4" version = "0.14.2+wasi-0.2.4"
@@ -1396,6 +1521,15 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.0",
]
[[package]] [[package]]
name = "windows" name = "windows"
version = "0.62.0" version = "0.62.0"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "pacman" name = "pacman"
version = "0.79.2" version = "0.79.3"
authors = ["Xevion"] authors = ["Xevion"]
edition = "2021" edition = "2021"
rust-version = "1.86.0" rust-version = "1.86.0"
@@ -50,6 +50,7 @@ windows-sys = { version = "0.61.0", features = ["Win32_System_Console"] }
# On desktop platforms, build SDL2 with cargo-vcpkg # On desktop platforms, build SDL2 with cargo-vcpkg
sdl2 = { version = "0.38", default-features = false, features = ["image", "ttf", "gfx", "mixer", "unsafe_textures", "static-link", "use-vcpkg"] } sdl2 = { version = "0.38", default-features = false, features = ["image", "ttf", "gfx", "mixer", "unsafe_textures", "static-link", "use-vcpkg"] }
rand = { version = "0.9.2", default-features = false, features = ["thread_rng"] } rand = { version = "0.9.2", default-features = false, features = ["thread_rng"] }
rust-embed = "8.7.2"
spin_sleep = "1.3.3" spin_sleep = "1.3.3"
# Browser-specific dependencies # Browser-specific dependencies

View File

@@ -5,6 +5,7 @@ use std::borrow::Cow;
use std::iter; use std::iter;
use crate::audio::Sound; use crate::audio::Sound;
use crate::error::AssetError;
/// Enumeration of all game assets with cross-platform loading support. /// Enumeration of all game assets with cross-platform loading support.
/// ///
@@ -66,7 +67,6 @@ impl Asset {
/// Paths are consistent across platforms and used by the Emscripten backend /// Paths are consistent across platforms and used by the Emscripten backend
/// for filesystem loading. Desktop builds embed assets directly and don't /// for filesystem loading. Desktop builds embed assets directly and don't
/// use these paths at runtime. /// use these paths at runtime.
#[allow(dead_code)]
pub fn path(&self) -> &str { pub fn path(&self) -> &str {
use Asset::*; use Asset::*;
match self { match self {
@@ -84,34 +84,54 @@ impl Asset {
Font => "TerminalVector.ttf", Font => "TerminalVector.ttf",
} }
} }
}
mod imp {
use super::*;
use crate::error::AssetError;
use crate::platform;
use tracing::trace;
/// Loads asset bytes using the appropriate platform-specific method. /// Loads asset bytes using the appropriate platform-specific method.
/// ///
/// On desktop platforms, returns embedded compile-time data via `include_bytes!`. /// On desktop platforms, returns embedded compile-time data via `rust-embed`.
/// On Emscripten, loads from the filesystem using the asset's path. The returned /// On Emscripten, loads from the filesystem using the asset's path. The returned
/// `Cow` allows zero-copy access to embedded data while supporting owned data /// `Cow` allows zero-copy access to embedded data while supporting owned data
/// when loaded from disk. /// when loaded from disk.
/// ///
/// # Errors /// # Errors
/// ///
/// Returns `AssetError::NotFound` if the asset file cannot be located (Emscripten only), /// Returns `AssetError::NotFound` if the asset file cannot be located,
/// or `AssetError::Io` for filesystem I/O failures. /// or `AssetError::Io` for filesystem I/O failures.
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> { pub fn get_bytes(&self) -> Result<Cow<'static, [u8]>, AssetError> {
trace!(asset = ?asset, "Loading game asset"); use tracing::trace;
let result = platform::get_asset_bytes(asset); trace!(asset = ?self, "Loading game asset");
let result = self.get_bytes_platform();
match &result { match &result {
Ok(bytes) => trace!(asset = ?asset, size_bytes = bytes.len(), "Asset loaded successfully"), Ok(bytes) => trace!(asset = ?self, size_bytes = bytes.len(), "Asset loaded successfully"),
Err(e) => trace!(asset = ?asset, error = ?e, "Asset loading failed"), Err(e) => trace!(asset = ?self, error = ?e, "Asset loading failed"),
} }
result result
} }
}
pub use imp::get_asset_bytes; #[cfg(not(target_os = "emscripten"))]
fn get_bytes_platform(&self) -> Result<Cow<'static, [u8]>, AssetError> {
#[derive(rust_embed::Embed)]
#[folder = "assets/game/"]
struct EmbeddedAssets;
let path = self.path();
EmbeddedAssets::get(path)
.map(|file| file.data)
.ok_or_else(|| AssetError::NotFound(path.to_string()))
}
#[cfg(target_os = "emscripten")]
fn get_bytes_platform(&self) -> Result<Cow<'static, [u8]>, AssetError> {
use sdl2::rwops::RWops;
use std::io::{self, Read};
let path = format!("assets/game/{}", self.path());
let mut rwops = RWops::from_file(&path, "rb").map_err(|_| AssetError::NotFound(self.path().to_string()))?;
let len = rwops.len().ok_or_else(|| AssetError::NotFound(self.path().to_string()))?;
let mut buf = vec![0u8; len];
rwops.read_exact(&mut buf).map_err(|e| AssetError::Io(io::Error::other(e)))?;
Ok(Cow::Owned(buf))
}
}

View File

@@ -1,7 +1,7 @@
//! This module handles the audio playback for the game. //! This module handles the audio playback for the game.
use std::collections::HashMap; use std::collections::HashMap;
use crate::asset::{get_asset_bytes, Asset}; use crate::asset::Asset;
use sdl2::{ use sdl2::{
mixer::{self, Chunk, InitFlag, LoaderRWops, AUDIO_S16LSB, DEFAULT_CHANNELS}, mixer::{self, Chunk, InitFlag, LoaderRWops, AUDIO_S16LSB, DEFAULT_CHANNELS},
rwops::RWops, rwops::RWops,
@@ -117,12 +117,13 @@ impl Audio {
// Try to load sounds, but don't panic if any fail // Try to load sounds, but don't panic if any fail
let mut sounds = HashMap::new(); let mut sounds = HashMap::new();
for (i, asset) in Sound::iter().enumerate() { for (i, sound_type) in Sound::iter().enumerate() {
match get_asset_bytes(Asset::SoundFile(asset)) { let asset = Asset::SoundFile(sound_type);
match asset.get_bytes() {
Ok(data) => match RWops::from_bytes(&data) { Ok(data) => match RWops::from_bytes(&data) {
Ok(rwops) => match rwops.load_wav() { Ok(rwops) => match rwops.load_wav() {
Ok(chunk) => { Ok(chunk) => {
sounds.insert(asset, chunk); sounds.insert(sound_type, chunk);
} }
Err(e) => { Err(e) => {
tracing::warn!("Failed to load sound {} from asset API: {}", i + 1, e); tracing::warn!("Failed to load sound {} from asset API: {}", i + 1, e);
@@ -138,7 +139,7 @@ impl Audio {
} }
} }
let death_sound = match get_asset_bytes(Asset::SoundFile(Sound::PacmanDeath)) { let death_sound = match Asset::SoundFile(Sound::PacmanDeath).get_bytes() {
Ok(data) => match RWops::from_bytes(&data) { Ok(data) => match RWops::from_bytes(&data) {
Ok(rwops) => match rwops.load_wav() { Ok(rwops) => match rwops.load_wav() {
Ok(chunk) => Some(chunk), Ok(chunk) => Some(chunk),

View File

@@ -46,8 +46,6 @@ pub enum AssetError {
#[error("IO error: {0}")] #[error("IO error: {0}")]
Io(#[from] io::Error), Io(#[from] io::Error),
// This error is only possible on Emscripten, as the assets are loaded from a 'filesystem' of sorts (while on Desktop, they are included in the binary at compile time)
#[allow(dead_code)]
#[error("Asset not found: {0}")] #[error("Asset not found: {0}")]
NotFound(String), NotFound(String),
} }

View File

@@ -40,7 +40,7 @@ use sdl2::video::{Window, WindowContext};
use sdl2::EventPump; use sdl2::EventPump;
use crate::{ use crate::{
asset::{get_asset_bytes, Asset}, asset::Asset,
events::GameCommand, events::GameCommand,
map::render::MapRenderer, map::render::MapRenderer,
systems::{BatchedLinesResource, Bindings, CursorPosition, TtfAtlasResource}, systems::{BatchedLinesResource, Bindings, CursorPosition, TtfAtlasResource},
@@ -247,7 +247,7 @@ impl Game {
debug_texture.set_blend_mode(BlendMode::Blend); debug_texture.set_blend_mode(BlendMode::Blend);
debug_texture.set_scale_mode(ScaleMode::Nearest); debug_texture.set_scale_mode(ScaleMode::Nearest);
let font_data: &'static [u8] = get_asset_bytes(Asset::Font)?.to_vec().leak(); let font_data: &'static [u8] = Asset::Font.get_bytes()?.to_vec().leak();
let font_asset = RWops::from_bytes(font_data).map_err(|_| GameError::Sdl("Failed to load font".to_string()))?; let font_asset = RWops::from_bytes(font_data).map_err(|_| GameError::Sdl("Failed to load font".to_string()))?;
let debug_font = ttf_context let debug_font = ttf_context
.load_font_from_rwops(font_asset, constants::ui::DEBUG_FONT_SIZE) .load_font_from_rwops(font_asset, constants::ui::DEBUG_FONT_SIZE)
@@ -261,7 +261,7 @@ impl Game {
fn load_atlas_and_map_tiles(texture_creator: &TextureCreator<WindowContext>) -> GameResult<(SpriteAtlas, Vec<AtlasTile>)> { fn load_atlas_and_map_tiles(texture_creator: &TextureCreator<WindowContext>) -> GameResult<(SpriteAtlas, Vec<AtlasTile>)> {
trace!("Loading atlas image from embedded assets"); trace!("Loading atlas image from embedded assets");
let atlas_bytes = get_asset_bytes(Asset::AtlasImage)?; let atlas_bytes = Asset::AtlasImage.get_bytes()?;
let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| { let atlas_texture = texture_creator.load_texture_bytes(&atlas_bytes).map_err(|e| {
if e.to_string().contains("format") || e.to_string().contains("unsupported") { if e.to_string().contains("format") || e.to_string().contains("unsupported") {
GameError::Texture(crate::error::TextureError::InvalidFormat(format!( GameError::Texture(crate::error::TextureError::InvalidFormat(format!(

View File

@@ -1,13 +1,15 @@
//! Desktop platform implementation. //! Desktop platform implementation.
use std::borrow::Cow;
use std::time::Duration; use std::time::Duration;
use rand::rngs::ThreadRng; use rand::rngs::ThreadRng;
use rust_embed::Embed;
use crate::asset::Asset; use crate::error::PlatformError;
use crate::audio::Sound;
use crate::error::{AssetError, PlatformError}; #[derive(Embed)]
#[folder = "assets/game/"]
struct EmbeddedAssets;
/// Desktop platform implementation. /// Desktop platform implementation.
pub fn sleep(duration: Duration, focused: bool) { pub fn sleep(duration: Duration, focused: bool) {
@@ -58,25 +60,6 @@ pub fn init_console() -> Result<(), PlatformError> {
Ok(()) Ok(())
} }
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
match asset {
Asset::SoundFile(sound) => match sound {
Sound::Waka(0) => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/waka/1.ogg"))),
Sound::Waka(1) => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/waka/2.ogg"))),
Sound::Waka(2) => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/waka/3.ogg"))),
Sound::Waka(3..=u8::MAX) => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/waka/4.ogg"))),
Sound::PacmanDeath => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/death.ogg"))),
Sound::ExtraLife => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/extra_life.ogg"))),
Sound::Fruit => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/fruit.ogg"))),
Sound::Ghost => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/pacman/ghost.ogg"))),
Sound::Beginning => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/begin.ogg"))),
Sound::Intermission => Ok(Cow::Borrowed(include_bytes!("../../assets/game/sound/intermission.ogg"))),
},
Asset::AtlasImage => Ok(Cow::Borrowed(include_bytes!("../../assets/game/atlas.png"))),
Asset::Font => Ok(Cow::Borrowed(include_bytes!("../../assets/game/TerminalVector.ttf"))),
}
}
pub fn rng() -> ThreadRng { pub fn rng() -> ThreadRng {
rand::rng() rand::rng()
} }

View File

@@ -1,13 +1,10 @@
//! Emscripten platform implementation. //! Emscripten platform implementation.
use crate::asset::Asset; use crate::error::PlatformError;
use crate::error::{AssetError, PlatformError};
use crate::formatter::CustomFormatter; use crate::formatter::CustomFormatter;
use rand::{rngs::SmallRng, SeedableRng}; use rand::{rngs::SmallRng, SeedableRng};
use sdl2::rwops::RWops;
use std::borrow::Cow;
use std::ffi::CString; use std::ffi::CString;
use std::io::{self, Read, Write}; use std::io::{self, Write};
use std::time::Duration; use std::time::Duration;
// Emscripten FFI functions // Emscripten FFI functions
@@ -62,18 +59,6 @@ impl Write for EmscriptenConsoleWriter {
} }
} }
pub fn get_asset_bytes(asset: Asset) -> Result<Cow<'static, [u8]>, AssetError> {
let path = format!("assets/game/{}", asset.path());
let mut rwops = RWops::from_file(&path, "rb").map_err(|_| AssetError::NotFound(asset.path().to_string()))?;
let len = rwops.len().ok_or_else(|| AssetError::NotFound(asset.path().to_string()))?;
let mut buf = vec![0u8; len];
rwops.read_exact(&mut buf).map_err(|e| AssetError::Io(io::Error::other(e)))?;
Ok(Cow::Owned(buf))
}
pub fn rng() -> SmallRng { pub fn rng() -> SmallRng {
SmallRng::from_os_rng() SmallRng::from_os_rng()
} }

View File

@@ -3,7 +3,7 @@
use bevy_ecs::{entity::Entity, event::Events, schedule::Schedule, world::World}; use bevy_ecs::{entity::Entity, event::Events, schedule::Schedule, world::World};
use glam::{U16Vec2, Vec2}; use glam::{U16Vec2, Vec2};
use pacman::{ use pacman::{
asset::{get_asset_bytes, Asset}, asset::Asset,
constants::RAW_BOARD, constants::RAW_BOARD,
events::{CollisionTrigger, GameEvent}, events::{CollisionTrigger, GameEvent},
game::ATLAS_FRAMES, game::ATLAS_FRAMES,
@@ -43,7 +43,7 @@ pub fn setup_sdl() -> Result<(Canvas<Window>, TextureCreator<WindowContext>, Sdl
pub fn create_atlas(canvas: &mut sdl2::render::Canvas<sdl2::video::Window>) -> SpriteAtlas { pub fn create_atlas(canvas: &mut sdl2::render::Canvas<sdl2::video::Window>) -> SpriteAtlas {
let texture_creator = canvas.texture_creator(); let texture_creator = canvas.texture_creator();
let atlas_bytes = get_asset_bytes(Asset::AtlasImage).unwrap(); let atlas_bytes = Asset::AtlasImage.get_bytes().unwrap();
let texture = texture_creator.load_texture_bytes(&atlas_bytes).unwrap(); let texture = texture_creator.load_texture_bytes(&atlas_bytes).unwrap();