moltrun 1.7.2

High-performance game engine library with AI capabilities, built on wgpu for modern 3D graphics and physics simulation
Documentation
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()));
        }
        
        // "placeholder" 특수 처리 (메모리에서 생성)
        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)
    }
    
    /// 1x1 흰색 placeholder 이미지 (메모리 생성, 전역 캐시)
    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>;