use hashbrown::HashMap;
use image::DynamicImage;
use std::{
mem,
path::{Path, PathBuf},
sync::mpsc::{Receiver, Sender, channel},
};
use anyhow::{Result, anyhow};
use io::{ErasedAssetReader, get_default_reader};
use crate::{
font::Font,
handle::{DropEvent, Handle, HandleId},
};
mod io;
#[derive(Debug)]
pub enum AssetType {
Raw,
Texture,
Font,
}
#[derive(Debug)]
pub(crate) struct PendingTask {
pub handle: Handle,
pub asset_type: AssetType,
pub path: PathBuf,
}
pub(crate) enum FetchedTask {
CreateTexture { handle: Handle, data: DynamicImage },
RemoveTexture { handle: u64 },
CreateFont { handle: Handle, font: Font },
RemoveFont { handle: u64 },
}
pub struct Asset {
pub asset_type: AssetType,
pub bytes: Option<Vec<u8>>,
}
pub struct AssetManager {
reader: Box<dyn ErasedAssetReader>,
pending: Vec<PendingTask>,
pending_fetched: Vec<FetchedTask>,
asset_id: u64,
receiver: Receiver<DropEvent>,
sender: Sender<DropEvent>,
assets: HashMap<HandleId, Asset>,
}
impl AssetManager {
pub fn new<P: AsRef<Path>>(path: P) -> Self {
let (sender, receiver) = channel();
Self {
reader: get_default_reader(path),
pending: Vec::default(),
pending_fetched: Vec::default(),
asset_id: 0,
sender,
receiver,
assets: Default::default(),
}
}
fn alloc_handle(&mut self) -> Handle {
let id = self.asset_id;
self.asset_id += 1;
let drop_sender = self.sender.clone();
Handle::new(id, drop_sender)
}
pub fn load<P: AsRef<Path>>(&mut self, asset_type: AssetType, path: P) -> Handle {
let handle = self.alloc_handle();
let task = PendingTask {
handle: handle.clone(),
path: path.as_ref().to_owned(),
asset_type,
};
self.pending.push(task);
if self.pending.len() > 4096 {
log::warn!("Too many pending tasks");
}
handle
}
pub fn insert(&mut self, asset: Asset) -> Handle {
let handle = self.alloc_handle();
self.assets.insert(handle.id(), asset);
if self.assets.len() > 4096 {
log::warn!("Too many assets");
}
handle
}
pub fn load_texture<P: AsRef<Path>>(&mut self, path: P) -> Handle {
self.load(AssetType::Texture, path)
}
pub fn load_font<P: AsRef<Path>>(&mut self, path: P) -> Handle {
self.load(AssetType::Font, path)
}
pub fn load_bytes<P: AsRef<Path>>(&mut self, path: P) -> Handle {
self.load(AssetType::Raw, path)
}
pub fn add_asset(&mut self, asset_type: AssetType, data: Vec<u8>) -> Result<Handle> {
let handle = self.alloc_handle();
self.register_asset(handle.clone(), asset_type, data)?;
Ok(handle)
}
pub fn get_asset(&self, handle: &Handle) -> Option<&Asset> {
self.assets.get(&handle.id())
}
pub fn remove_asset<P: AsRef<Path>>(&mut self, handle: &Handle) -> Option<Asset> {
self.assets.remove(&handle.id())
}
pub(crate) async fn fetch(&mut self) -> Result<Vec<FetchedTask>> {
let pending = mem::take(&mut self.pending);
for task in pending {
let bytes = self.reader.read(task.path.to_str().unwrap()).await?;
self.register_asset(task.handle, task.asset_type, bytes)?;
}
let mut tasks = mem::take(&mut self.pending_fetched);
while let Ok(event) = self.receiver.try_recv() {
let Some(asset) = self.assets.remove(&event.0) else {
continue;
};
match asset.asset_type {
AssetType::Raw => {
}
AssetType::Texture => {
let fetched_task = FetchedTask::RemoveTexture { handle: event.0 };
tasks.push(fetched_task);
}
AssetType::Font => {
let fetched_task = FetchedTask::RemoveFont { handle: event.0 };
tasks.push(fetched_task);
}
}
}
Ok(tasks)
}
pub(crate) fn register_asset(
&mut self,
handle: Handle,
asset_type: AssetType,
data: Vec<u8>,
) -> Result<()> {
match asset_type {
AssetType::Raw => {
self.assets.insert(
handle.id(),
Asset {
bytes: Some(data),
asset_type: AssetType::Raw,
},
);
}
AssetType::Texture => {
let im = image::load_from_memory(&data)?;
let fetched_task = FetchedTask::CreateTexture {
handle: handle.clone(),
data: im,
};
self.pending_fetched.push(fetched_task);
self.assets.insert(
handle.id(),
Asset {
bytes: None,
asset_type: AssetType::Texture,
},
);
}
AssetType::Font => {
let font = Font::from_bytes(data).ok_or(anyhow!("Failed to load font"))?;
let fetched_task = FetchedTask::CreateFont {
handle: handle.clone(),
font,
};
self.pending_fetched.push(fetched_task);
self.assets.insert(
handle.id(),
Asset {
bytes: None,
asset_type: AssetType::Font,
},
);
}
}
Ok(())
}
}