use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, OnceLock};
use image::DynamicImage;
pub struct AssetManager {
asset_root: PathBuf,
image_cache: HashMap<String, Arc<DynamicImage>>,
}
impl AssetManager {
pub fn new(asset_root: PathBuf) -> Self {
Self {
asset_root,
image_cache: HashMap::new(),
}
}
pub fn load_image(&mut self, relative_path: &str) -> AssetResult<Arc<DynamicImage>> {
if relative_path.is_empty() {
return Err(AssetError::InvalidFormat("Empty path".to_string()));
}
if relative_path == "placeholder" {
log::debug!("Using in-memory placeholder image");
return Ok(Self::placeholder_image());
}
if let Some(image) = self.image_cache.get(relative_path) {
return Ok(Arc::clone(image));
}
let full_path = self.asset_root.join(relative_path);
if !full_path.exists() {
log::warn!("이미지 파일 없음: {}, placeholder 사용", relative_path);
return Ok(Self::placeholder_image());
}
let image = image::open(&full_path)
.map_err(|e| AssetError::ImageLoadFailed {
path: full_path.clone(),
reason: e.to_string(),
})?;
let image_arc = Arc::new(image);
self.image_cache.insert(
relative_path.to_string(),
Arc::clone(&image_arc)
);
log::info!("✅ 이미지 로드: {}", relative_path);
Ok(image_arc)
}
fn placeholder_image() -> Arc<DynamicImage> {
static PLACEHOLDER: OnceLock<Arc<DynamicImage>> = OnceLock::new();
PLACEHOLDER.get_or_init(|| {
let img = DynamicImage::ImageRgba8(
image::RgbaImage::from_pixel(1, 1, image::Rgba([255, 255, 255, 255]))
);
Arc::new(img)
}).clone()
}
pub fn asset_root(&self) -> &PathBuf {
&self.asset_root
}
pub fn clear_cache(&mut self) {
self.image_cache.clear();
log::info!("AssetManager 캐시 초기화");
}
}
#[derive(Debug, thiserror::Error)]
pub enum AssetError {
#[error("Asset not found: {0}")]
NotFound(PathBuf),
#[error("Failed to load image: {path} - {reason}")]
ImageLoadFailed {
path: PathBuf,
reason: String,
},
#[error("Failed to create GPU texture: {0}")]
TextureCreationFailed(String),
#[error("Invalid image format: {0}")]
InvalidFormat(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
pub type AssetResult<T> = Result<T, AssetError>;