2023-09-12 18:25:33 +00:00
|
|
|
use std::{sync::Arc, collections::{HashMap, hash_map::DefaultHasher}, hash::{Hash, Hasher}, any::Any};
|
|
|
|
|
2023-09-12 23:07:03 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
2023-10-18 02:04:25 +00:00
|
|
|
use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}};
|
2023-09-12 18:25:33 +00:00
|
|
|
|
|
|
|
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<Self>) -> Arc<dyn Any + Send + Sync>;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Implements this trait for anything that fits the type bounds
|
|
|
|
impl<T: Send + Sync + 'static> 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<Self>) -> Arc<dyn Any + Send + Sync> {
|
|
|
|
self.clone()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 23:07:03 +00:00
|
|
|
#[derive(Error, Debug)]
|
|
|
|
pub enum RequestError {
|
|
|
|
#[error("{0}")]
|
|
|
|
Loader(LoaderError),
|
|
|
|
#[error("The file extension is unsupported: '{0}'")]
|
|
|
|
UnsupportedFileExtension(String),
|
2023-10-18 02:04:25 +00:00
|
|
|
#[error("The mimetype is unsupported: '{0}'")]
|
|
|
|
UnsupportedMime(String),
|
|
|
|
#[error("The identifier is not found: '{0}'")]
|
|
|
|
IdentNotFound(String),
|
2023-09-12 23:07:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<LoaderError> for RequestError {
|
|
|
|
fn from(value: LoaderError) -> Self {
|
|
|
|
RequestError::Loader(value)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-18 02:04:25 +00:00
|
|
|
/// A struct that stores all Manager data. This is requried for sending
|
|
|
|
//struct ManagerStorage
|
|
|
|
|
2023-09-12 18:25:33 +00:00
|
|
|
pub struct ResourceManager {
|
|
|
|
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
2023-10-18 02:04:25 +00:00
|
|
|
loaders: Vec<Arc<dyn ResourceLoader>>,
|
2023-09-12 18:25:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ResourceManager {
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self {
|
|
|
|
resources: HashMap::new(),
|
2023-10-18 02:04:25 +00:00
|
|
|
loaders: vec![ Arc::new(ImageLoader::default()), Arc::new(ModelLoader::default()) ],
|
2023-09-12 18:25:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-12 23:07:03 +00:00
|
|
|
pub fn request<T: Send + Sync + Any + 'static>(&mut self, path: &str) -> Result<Arc<Resource<T>>, RequestError> {
|
2023-09-12 18:25:33 +00:00
|
|
|
match self.resources.get(&path.to_string()) {
|
|
|
|
Some(res) => {
|
|
|
|
let res = res.clone().as_arc_any();
|
2023-09-12 23:07:03 +00:00
|
|
|
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource");
|
2023-09-12 18:25:33 +00:00
|
|
|
|
2023-09-12 23:07:03 +00:00
|
|
|
Ok(res)
|
2023-09-12 18:25:33 +00:00
|
|
|
},
|
|
|
|
None => {
|
2023-09-12 23:07:03 +00:00
|
|
|
if let Some(loader) = self.loaders.iter()
|
|
|
|
.find(|l| l.does_support_file(path)) {
|
2023-09-12 18:25:33 +00:00
|
|
|
|
2023-09-12 23:07:03 +00:00
|
|
|
// Load the resource and store it
|
2023-10-18 02:04:25 +00:00
|
|
|
let loader = Arc::clone(&loader); // stop borrowing from self
|
|
|
|
let res = loader.load(self, path)?;
|
2023-09-12 23:07:03 +00:00
|
|
|
self.resources.insert(path.to_string(), res.clone());
|
2023-09-12 18:25:33 +00:00
|
|
|
|
2023-10-18 02:04:25 +00:00
|
|
|
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
2023-09-12 23:07:03 +00:00
|
|
|
let res = res.as_arc_any();
|
2023-09-26 21:14:38 +00:00
|
|
|
let res = res.downcast::<Resource<T>>()
|
|
|
|
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
2023-09-12 18:25:33 +00:00
|
|
|
|
2023-09-12 23:07:03 +00:00
|
|
|
Ok(res)
|
|
|
|
} else {
|
|
|
|
Err(RequestError::UnsupportedFileExtension(path.to_string()))
|
|
|
|
}
|
2023-09-12 18:25:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-10-18 02:04:25 +00:00
|
|
|
|
|
|
|
/// 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<T: Send + Sync + Any + 'static>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<Resource<T>>, 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<dyn ResourceStorage> to Arc<Resource<T>
|
|
|
|
let res = res.as_arc_any();
|
|
|
|
let res = res.downcast::<Resource<T>>()
|
|
|
|
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
} else {
|
|
|
|
Err(RequestError::UnsupportedMime(mime_type.to_string()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Requests bytes from the manager.
|
|
|
|
pub fn request_loaded_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str) -> Result<Arc<Resource<T>>, RequestError> {
|
|
|
|
match self.resources.get(&ident.to_string()) {
|
|
|
|
Some(res) => {
|
|
|
|
let res = res.clone().as_arc_any();
|
|
|
|
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource");
|
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
},
|
|
|
|
None => {
|
|
|
|
Err(RequestError::IdentNotFound(ident.to_string()))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-09-21 13:36:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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::<Texture>(&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::<Texture>(&get_image("squiggles.png")).unwrap();
|
|
|
|
assert_eq!(Arc::strong_count(&res), 2);
|
|
|
|
|
|
|
|
let resagain = man.request::<Texture>(&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::<Texture>(&get_image("squigglesfff.png"));
|
|
|
|
let err = res.err().unwrap();
|
|
|
|
|
|
|
|
assert!(
|
|
|
|
match err {
|
|
|
|
// make sure the error is NotFound
|
2023-09-21 18:22:46 +00:00
|
|
|
RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
|
2023-09-21 13:36:44 +00:00
|
|
|
_ => false
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
2023-09-12 18:25:33 +00:00
|
|
|
}
|