use std::{collections::HashMap, fmt::Display, sync::{Arc, Mutex}}; use bevy_ecs::system::Resource; use futures::channel::oneshot::Receiver; use image::EncodableLayout; use resvg::{tiny_skia, usvg}; use wasm_bindgen_futures::spawn_local; #[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_receivers: Arc>>>, textures: Arc>>, } impl Assets { pub fn new() -> Self { Assets { textures: Arc::new(Mutex::new(HashMap::new())), texture_receivers: Arc::new(Mutex::new(HashMap::new())), } } pub fn get(&self, local_path: impl Into) -> Option { let (tx, rx) = futures::channel::oneshot::channel(); let local_path = local_path.into(); let contains_texture = { self.textures.lock().unwrap().contains_key(&local_path) }; let contains_texture_receiver = { self.texture_receivers.lock().unwrap().contains_key(&local_path) }; if !contains_texture && !contains_texture_receiver { let textures = self.textures.clone(); { self.texture_receivers.lock().unwrap().insert(local_path.clone(), rx); } spawn_local(async move { let window = web_sys::window().unwrap(); let resp = match reqwest::get(format!("{}/src/textures/{}", window.location().origin().unwrap(), local_path)).await { Ok(resp) => resp, Err(e) => { if e.is_request() { panic!("Error in request {}", e); } else if e.is_body() { panic!("Error in body {}", e); } else if e.is_status() { panic!("Bad status: {}", e.status().unwrap()); } else if e.is_decode() { panic!("Couldn't decode response's body {}", e); } else if e.is_redirect() { panic!("Response caused redirect loop {}", e); } else if e.is_builder() { panic!("Error in builder {}", e); } panic!(); }, }; let bytes = match resp.bytes().await { Ok(bytes) => bytes, Err(e) => todo!(), }.to_vec(); if local_path.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(&bytes, &opt).expect(&format!("Couldn't parse svg {}", local_path)); let tree_size = tree.size().to_int_size(); let size = usvg::Size::from_wh(100.0, 100.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(), }; { textures.lock().unwrap().insert(local_path, data.clone()); } tx.send(data).unwrap(); } else if local_path.ends_with(".png") { let img = image::load_from_memory(&bytes).unwrap(); let rgba = img.to_rgba8(); let data = ImgData { bytes: rgba.as_bytes().to_vec(), width: rgba.width(), height: rgba.height(), }; { textures.lock().unwrap().insert(local_path, data.clone()); } tx.send(data).unwrap(); } }); None } else if !contains_texture { let mut texture_receivers = self.texture_receivers.lock().unwrap(); let rx = texture_receivers.get_mut(&local_path).unwrap(); if let Ok(Some(texture)) = rx.try_recv() { self.texture_receivers.lock().unwrap().remove(&local_path); self.textures.lock().unwrap().insert(local_path, texture.clone()); return Some(texture); } None } else { self.textures.lock().unwrap().get(&local_path).cloned() } } }