375 lines
13 KiB
Rust
375 lines
13 KiB
Rust
use std::{sync::{Arc, RwLock}, collections::HashMap, any::Any, path::Path, time::Duration};
|
|
|
|
use crossbeam::channel::Receiver;
|
|
use notify::{Watcher, RecommendedWatcher};
|
|
use notify_debouncer_full::{DebouncedEvent, FileIdMap};
|
|
use thiserror::Error;
|
|
use uuid::Uuid;
|
|
|
|
use crate::{loader::{image::ImageLoader, model::ModelLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
|
|
|
|
/// A trait for type erased storage of a resource.
|
|
/// Implemented for [`ResHandle<T>`]
|
|
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>;
|
|
fn as_box_any(self: Box<Self>) -> Box<dyn Any + Send + Sync>;
|
|
/// Do not set a resource to watched if it is not actually watched.
|
|
/// This is used internally.
|
|
fn set_watched(&self, watched: bool);
|
|
|
|
fn path(&self) -> String;
|
|
fn version(&self) -> usize;
|
|
fn state(&self) -> ResourceState;
|
|
fn uuid(&self) -> Uuid;
|
|
fn is_watched(&self) -> bool;
|
|
fn is_loaded(&self) -> bool;
|
|
}
|
|
|
|
#[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<LoaderError> for RequestError {
|
|
fn from(value: LoaderError) -> Self {
|
|
RequestError::Loader(value)
|
|
}
|
|
}
|
|
|
|
/// A struct that stores all Manager data. This is requried for sending
|
|
//struct ManagerStorage
|
|
|
|
/// A struct that
|
|
pub struct ResourceWatcher {
|
|
debouncer: Arc<RwLock<notify_debouncer_full::Debouncer<RecommendedWatcher, FileIdMap>>>,
|
|
events_recv: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
|
|
}
|
|
|
|
pub struct ResourceManager {
|
|
resources: HashMap<String, Arc<dyn ResourceStorage>>,
|
|
loaders: Vec<Arc<dyn ResourceLoader>>,
|
|
watchers: HashMap<String, ResourceWatcher>,
|
|
}
|
|
|
|
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) ],
|
|
watchers: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Registers a loader to the manager.
|
|
pub fn register_loader<L>(&mut self)
|
|
where
|
|
L: ResourceLoader + Default + 'static
|
|
{
|
|
self.loaders.push(Arc::new(L::default()));
|
|
}
|
|
|
|
pub fn request<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
|
|
where
|
|
T: Send + Sync + Any + 'static
|
|
{
|
|
match self.resources.get(&path.to_string()) {
|
|
Some(res) => {
|
|
let res = res.clone().as_arc_any();
|
|
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
|
|
let res = ResHandle::<T>::clone(&res);
|
|
|
|
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)?;
|
|
let res: Arc<dyn ResourceStorage> = Arc::from(res);
|
|
self.resources.insert(path.to_string(), res.clone());
|
|
|
|
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
|
let res = res.as_arc_any();
|
|
let res = res.downcast::<ResHandle<T>>()
|
|
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
|
let res = ResHandle::<T>::clone(&res);
|
|
|
|
Ok(res)
|
|
} else {
|
|
Err(RequestError::UnsupportedFileExtension(path.to_string()))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Request a resource without downcasting to a ResHandle<T>.
|
|
/// Whenever you're ready to downcast, you can do so like this:
|
|
/// ```compile_fail
|
|
/// let arc_any = res_arc.as_arc_any();
|
|
/// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
|
|
/// ```
|
|
pub fn request_raw(&mut self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
|
|
match self.resources.get(&path.to_string()) {
|
|
Some(res) => {
|
|
Ok(res.clone())
|
|
},
|
|
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)?;
|
|
let res: Arc<dyn ResourceStorage> = Arc::from(res);
|
|
self.resources.insert(path.to_string(), res.clone());
|
|
|
|
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<T>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<ResHandle<T>, RequestError>
|
|
where
|
|
T: Send + Sync + Any + 'static
|
|
{
|
|
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)?;
|
|
let res: Arc<dyn ResourceStorage> = Arc::from(res);
|
|
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::<ResHandle<T>>()
|
|
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
|
let res = ResHandle::<T>::clone(&res);
|
|
|
|
Ok(res)
|
|
} else {
|
|
Err(RequestError::UnsupportedMime(mime_type.to_string()))
|
|
}
|
|
}
|
|
|
|
/// Requests bytes from the manager.
|
|
pub fn request_loaded_bytes<T>(&mut self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
|
|
where
|
|
T: Send + Sync + Any + 'static
|
|
{
|
|
match self.resources.get(&ident.to_string()) {
|
|
Some(res) => {
|
|
let res = res.clone().as_arc_any();
|
|
let res = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
|
|
|
|
Ok(res)
|
|
},
|
|
None => {
|
|
Err(RequestError::IdentNotFound(ident.to_string()))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Start watching a path for changes. Returns a mspc channel that will send events
|
|
pub fn watch(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
|
|
let (send, recv) = crossbeam::channel::bounded(15);
|
|
let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_millis(1000), None, send)?;
|
|
|
|
let recurse_mode = match recursive {
|
|
true => notify::RecursiveMode::Recursive,
|
|
false => notify::RecursiveMode::NonRecursive,
|
|
};
|
|
watcher.watcher().watch(path.as_ref(), recurse_mode)?;
|
|
|
|
let watcher = Arc::new(RwLock::new(watcher));
|
|
let watcher = ResourceWatcher {
|
|
debouncer: watcher,
|
|
events_recv: recv.clone(),
|
|
};
|
|
|
|
self.watchers.insert(path.to_string(), watcher);
|
|
|
|
let res = self.resources.get(&path.to_string())
|
|
.expect("The path that was watched has not been loaded as a resource yet");
|
|
res.set_watched(true);
|
|
|
|
Ok(recv)
|
|
}
|
|
|
|
/// Stops watching a path
|
|
pub fn stop_watching(&mut self, path: &str) -> notify::Result<()> {
|
|
if let Some(watcher) = self.watchers.get(path) {
|
|
let mut watcher = watcher.debouncer.write().unwrap();
|
|
watcher.watcher().unwatch(Path::new(path))?;
|
|
|
|
// unwrap is safe since only loaded resources can be watched
|
|
let res = self.resources.get(&path.to_string()).unwrap();
|
|
res.set_watched(false);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Returns a mspc receiver for watcher events of a specific path. The path must already
|
|
/// be watched with [`ResourceManager::watch`] for this to return `Some`.
|
|
pub fn watcher_event_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
|
|
self.watchers.get(&path.to_string())
|
|
.map(|w| w.events_recv.clone())
|
|
}
|
|
|
|
/// Reloads a resource. The data inside the resource will be updated, the state may
|
|
pub fn reload<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
|
|
where
|
|
T: Send + Sync + Any + 'static
|
|
{
|
|
let path = resource.path();
|
|
if let Some(loader) = self.loaders.iter()
|
|
.find(|l| l.does_support_file(&path)) {
|
|
println!("got loader");
|
|
let loader = Arc::clone(loader); // stop borrowing from self
|
|
let loaded = loader.load(self, &path)?;
|
|
let loaded = loaded.as_arc_any();
|
|
|
|
let loaded = loaded.downcast::<ResHandle<T>>()
|
|
.unwrap();
|
|
let loaded = match Arc::try_unwrap(loaded) {
|
|
Ok(v) => v,
|
|
Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
|
|
};
|
|
let loaded = loaded.data;
|
|
let loaded = match Arc::try_unwrap(loaded) {
|
|
Ok(v) => v,
|
|
Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
|
|
};
|
|
let loaded = loaded.into_inner().unwrap();
|
|
|
|
let res_lock = &resource.data;
|
|
let mut res_lock = res_lock.write().unwrap();
|
|
let version = res_lock.version;
|
|
|
|
res_lock.data = loaded.data;
|
|
res_lock.state = loaded.state;
|
|
res_lock.version = version + 1;
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[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.try_data_ref();
|
|
img.unwrap();
|
|
}
|
|
|
|
/// Ensures that only one copy of the same data was 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.data), 2);
|
|
|
|
let resagain = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
|
assert_eq!(Arc::strong_count(&resagain.data), 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
|
|
RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
|
|
_ => false
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reload_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.try_data_ref();
|
|
img.unwrap();
|
|
|
|
println!("Path = {}", res.path());
|
|
man.reload(res.clone()).unwrap();
|
|
assert_eq!(res.version(), 1);
|
|
|
|
man.reload(res.clone()).unwrap();
|
|
assert_eq!(res.version(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn watch_image() {
|
|
let orig_path = get_image("squiggles.png");
|
|
let image_path = get_image("squiggles_test.png");
|
|
std::fs::copy(orig_path, &image_path).unwrap();
|
|
|
|
let mut man = ResourceManager::new();
|
|
let res = man.request::<Texture>(&image_path).unwrap();
|
|
assert_eq!(res.state(), ResourceState::Ready);
|
|
let img = res.try_data_ref();
|
|
img.unwrap();
|
|
|
|
let recv = man.watch(&image_path, false).unwrap();
|
|
|
|
std::fs::remove_file(&image_path).unwrap();
|
|
|
|
let event = recv.recv().unwrap();
|
|
let event = event.unwrap();
|
|
|
|
assert!(event.iter().any(|ev| ev.kind.is_remove() || ev.kind.is_modify()));
|
|
}
|
|
} |