use std::{collections::HashMap, fmt::Display, sync::{Arc, Mutex}}; use bevy_ecs::system::Resource; use image::EncodableLayout; use poll_promise::Promise; use resvg::{tiny_skia, usvg}; #[derive(Debug, Clone)] pub enum AssetError { AssetNotFound, ResponseNotOk(u16), } impl Display for AssetError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { AssetError::AssetNotFound => write!(f, "Asset not found"), AssetError::ResponseNotOk(code) => write!(f, "Server response was not ok {}", code), } } } impl std::error::Error for AssetError {} #[derive(Debug, Clone)] pub struct ImgData { pub bytes: Vec, pub width: u32, pub height: u32, } #[derive(Resource)] pub struct Assets { texture_promises: Arc>>>, textures: Arc>>, } impl Assets { pub fn new() -> Self { Assets { textures: Arc::new(Mutex::new(HashMap::new())), texture_promises: Arc::new(Mutex::new(HashMap::new())), } } pub fn get(&self, local_path: impl Into) -> Option { let local_path = local_path.into(); let contains_texture = { self.textures.lock().unwrap().contains_key(&local_path) }; let contains_texture_promise = { self.texture_promises.lock().unwrap().contains_key(&local_path) }; if !contains_texture && !contains_texture_promise { let local_path_clone = local_path.clone(); let request_promise = poll_promise::Promise::spawn_local(async move { let window = web_sys::window().unwrap(); let request = ehttp::Request::get(format!("{}/src/assets/{}", window.location().origin().unwrap(), local_path_clone)); let response = match ehttp::fetch_async(request).await { Ok(resp) => resp, Err(e) => { panic!("{}", e); }, }; if local_path_clone.ends_with(".svg") { let opt = usvg::Options { default_size: usvg::Size::from_wh(20.0, 20.0).unwrap(), ..Default::default() }; let tree = usvg::Tree::from_data(&response.bytes, &opt).expect(&format!("Couldn't parse svg {}", local_path_clone)); let tree_size = tree.size().to_int_size(); let size = usvg::Size::from_wh(500.0, 500.0).unwrap().to_int_size(); assert!(size.width() > 0 && size.height() > 0); let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).expect("Failed to construct pixmap"); resvg::render(&tree, tiny_skia::Transform::from_scale((size.width() as f32)/(tree_size.height() as f32), (size.height() as f32)/(tree_size.height() as f32)), &mut pixmap.as_mut()); let data = ImgData { bytes: pixmap.data().to_vec(), width: size.width(), height: size.height(), }; data } else if local_path_clone.ends_with(".png") { let img = image::load_from_memory(&response.bytes).unwrap(); let rgba = img.to_rgba8(); let data = ImgData { bytes: rgba.as_bytes().to_vec(), width: rgba.width(), height: rgba.height(), }; data } else { panic!("Unsupported sprite type"); } }); { self.texture_promises.lock().unwrap().insert(local_path.clone(), request_promise); } None } else if !contains_texture { let mut texture_promises = self.texture_promises.lock().unwrap(); let promise = texture_promises.get_mut(&local_path).unwrap(); let mut returned_value = None; if let Some(texture) = promise.ready() { self.textures.lock().unwrap().insert(local_path.clone(), texture.clone()); returned_value = Some(texture.clone()); texture_promises.remove(&local_path); } return returned_value } else { self.textures.lock().unwrap().get(&local_path).cloned() } } }