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<u8>,
pub width: u32,
pub height: u32,
}
#[derive(Resource)]
pub struct Assets {
texture_receivers: Arc<Mutex<HashMap<String, Receiver<ImgData>>>>,
textures: Arc<Mutex<HashMap<String, ImgData>>>,
}
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<String>) -> Option<ImgData> {
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()
}
}
}