use std::{sync::Arc, collections::HashMap, any::Any}; use thiserror::Error; use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}}; pub trait ResourceStorage: Send + Sync + Any + 'static { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; fn as_arc_any(self: Arc) -> Arc; } /// Implements this trait for anything that fits the type bounds impl ResourceStorage for T { fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } fn as_arc_any(self: Arc) -> Arc { self.clone() } } #[derive(Error, Debug)] pub enum RequestError { #[error("{0}")] Loader(LoaderError), #[error("The file extension is unsupported: '{0}'")] UnsupportedFileExtension(String), #[error("The mimetype is unsupported: '{0}'")] UnsupportedMime(String), #[error("The identifier is not found: '{0}'")] IdentNotFound(String), } impl From for RequestError { fn from(value: LoaderError) -> Self { RequestError::Loader(value) } } /// A struct that stores all Manager data. This is requried for sending //struct ManagerStorage pub struct ResourceManager { resources: HashMap>, loaders: Vec>, } impl Default for ResourceManager { fn default() -> Self { Self::new() } } impl ResourceManager { pub fn new() -> Self { Self { resources: HashMap::new(), loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], } } pub fn request(&mut self, path: &str) -> Result>, RequestError> { match self.resources.get(&path.to_string()) { Some(res) => { let res = res.clone().as_arc_any(); let res = res.downcast::>().expect("Failure to downcast resource"); Ok(res) }, None => { if let Some(loader) = self.loaders.iter() .find(|l| l.does_support_file(path)) { // Load the resource and store it let loader = Arc::clone(loader); // stop borrowing from self let res = loader.load(self, path)?; self.resources.insert(path.to_string(), res.clone()); // cast Arc to Arc let res = res.as_arc_any(); let res = res.downcast::>() .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); Ok(res) } else { Err(RequestError::UnsupportedFileExtension(path.to_string())) } } } } /// Store bytes in the manager. If there is already an entry with the same identifier it will be updated. /// /// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic. /// /// Parameters: /// * `ident` - The identifier to store along with these bytes. Make sure its unique to avoid overriding something. /// * `bytes` - The bytes to store. /// /// Returns: The `Arc` to the now stored resource pub fn load_bytes(&mut self, ident: &str, mime_type: &str, bytes: Vec, offset: usize, length: usize) -> Result>, RequestError> { if let Some(loader) = self.loaders.iter() .find(|l| l.does_support_mime(mime_type)) { let loader = loader.clone(); let res = loader.load_bytes(self, bytes, offset, length)?; self.resources.insert(ident.to_string(), res.clone()); // code here... // cast Arc to Arc let res = res.as_arc_any(); let res = res.downcast::>() .expect("Failure to downcast resource! Does the loader return an `Arc>`?"); Ok(res) } else { Err(RequestError::UnsupportedMime(mime_type.to_string())) } } /// Requests bytes from the manager. pub fn request_loaded_bytes(&mut self, ident: &str) -> Result>, RequestError> { match self.resources.get(&ident.to_string()) { Some(res) => { let res = res.clone().as_arc_any(); let res = res.downcast::>().expect("Failure to downcast resource"); Ok(res) }, None => { Err(RequestError::IdentNotFound(ident.to_string())) } } } } #[cfg(test)] mod tests { use std::io; use crate::{Texture, ResourceState}; use super::*; fn get_image(path: &str) -> String { let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap(); format!("{manifest}/test_files/img/{path}") } #[test] fn load_image() { let mut man = ResourceManager::new(); let res = man.request::(&get_image("squiggles.png")).unwrap(); assert_eq!(res.state, ResourceState::Ready); let img = res.data.as_ref(); img.unwrap(); } /// Ensures that only one copy of the same thing made #[test] fn ensure_single() { let mut man = ResourceManager::new(); let res = man.request::(&get_image("squiggles.png")).unwrap(); assert_eq!(Arc::strong_count(&res), 2); let resagain = man.request::(&get_image("squiggles.png")).unwrap(); assert_eq!(Arc::strong_count(&resagain), 3); } /// Ensures that an error is returned when a file that doesn't exist is requested #[test] fn ensure_none() { let mut man = ResourceManager::new(); let res = man.request::(&get_image("squigglesfff.png")); let err = res.err().unwrap(); assert!( match err { // make sure the error is NotFound RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true, _ => false } ); } }