resonance 0.1.0

A modular game engine. Heavy work in progress.
Documentation
use crate::assets::cache::AssetCache;
use crate::assets::handle::{AssetHandle, AssetId};
use anyhow::Result;
use std::path::Path;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum LoadError {
    #[error("Asset not found: {0}")]
    NotFound(String),
    #[error("Failed to load asset: {0}")]
    LoadFailed(String),
    #[error("Unsupported asset type: {0}")]
    UnsupportedType(String),
}

pub trait AssetLoader: Send + Sync {
    type Asset: Send + Sync + 'static;
    fn load(&self, path: &Path) -> Result<Self::Asset, LoadError>;
    fn extensions(&self) -> &[&str];
}

pub struct ImageLoader;

impl AssetLoader for ImageLoader {
    type Asset = image::DynamicImage;

    fn load(&self, path: &Path) -> Result<Self::Asset, LoadError> {
        image::open(path).map_err(|e| LoadError::LoadFailed(e.to_string()))
    }

    fn extensions(&self) -> &[&str] {
        &["png", "jpg", "jpeg", "bmp", "gif"]
    }
}

pub fn load_asset<L: AssetLoader>(
    loader: &L,
    path: impl AsRef<Path>,
    cache: &AssetCache,
) -> Result<AssetHandle<L::Asset>, LoadError> {
    let path = path.as_ref();
    let path_str = path.to_string_lossy().to_string();
    let id = AssetId::from_path(&path_str);

    if cache.contains::<L::Asset>(id) {
        log::debug!("Asset already cached: {}", path_str);
        return Ok(AssetHandle::new(id, path_str));
    }

    let asset = loader.load(path)?;

    cache.insert(id, asset);

    Ok(AssetHandle::new(id, path_str))
}