lyra-engine/lyra-resource/src/resource_manager.rs

540 lines
19 KiB
Rust
Raw Normal View History

use std::{any::Any, collections::HashMap, path::Path, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration};
2023-09-12 18:25:33 +00:00
use async_std::task;
2024-01-16 04:22:21 +00:00
use crossbeam::channel::Receiver;
2024-01-15 23:07:47 +00:00
use notify::{Watcher, RecommendedWatcher};
2024-01-16 04:22:21 +00:00
use notify_debouncer_full::{DebouncedEvent, FileIdMap};
use thiserror::Error;
use uuid::Uuid;
use crate::{gltf::ModelLoader, loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceData, ResourceState};
2023-09-12 18:25:33 +00:00
/// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`]
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>;
2024-01-15 23:07:47 +00:00
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.
2024-01-16 04:22:21 +00:00
fn set_watched(&self, watched: bool);
fn version(&self) -> usize;
fn uuid(&self) -> Uuid;
fn path(&self) -> Option<String>;
fn is_watched(&self) -> bool;
fn is_loaded(&self) -> bool;
fn set_state(&self, new: ResourceState);
2023-09-12 18:25:33 +00:00
}
#[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),
#[error("The resource was not loaded from a path so cannot be reloaded")]
NoReloadPath
}
impl From<LoaderError> for RequestError {
fn from(value: LoaderError) -> Self {
RequestError::Loader(value)
}
}
/// A struct that stores some things used for watching resources.
2024-01-15 23:07:47 +00:00
pub struct ResourceWatcher {
2024-01-16 04:22:21 +00:00
debouncer: Arc<RwLock<notify_debouncer_full::Debouncer<RecommendedWatcher, FileIdMap>>>,
events_recv: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
2024-01-15 23:07:47 +00:00
}
/// The state of the ResourceManager
pub struct ResourceManagerState {
2023-09-12 18:25:33 +00:00
resources: HashMap<String, Arc<dyn ResourceStorage>>,
uuid_resources: HashMap<Uuid, Arc<dyn ResourceStorage>>,
loaders: Vec<Arc<dyn ResourceLoader>>,
2024-01-15 23:07:47 +00:00
watchers: HashMap<String, ResourceWatcher>,
2023-09-12 18:25:33 +00:00
}
/// The ResourceManager
///
/// This exists since we need the manager to be `Send + Sync`.
#[derive(Clone)]
pub struct ResourceManager {
inner: Arc<RwLock<ResourceManagerState>>,
}
2023-10-23 01:49:31 +00:00
impl Default for ResourceManager {
fn default() -> Self {
Self {
inner: Arc::new(RwLock::new(
ResourceManagerState {
resources: HashMap::new(),
uuid_resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
watchers: HashMap::new(),
}
))
}
2023-10-23 01:49:31 +00:00
}
}
2023-09-12 18:25:33 +00:00
impl ResourceManager {
pub fn new() -> Self {
Self::default()
}
/// Retrieves a non-mutable guard of the manager's state.
pub fn state(&self) -> RwLockReadGuard<ResourceManagerState> {
self.inner.read().unwrap()
}
/// Retrieves a mutable guard of the manager's state.
pub fn state_mut(&self) -> RwLockWriteGuard<ResourceManagerState> {
self.inner.write().unwrap()
2023-09-12 18:25:33 +00:00
}
/// Registers a loader to the manager.
pub fn register_loader<L>(&self)
where
L: ResourceLoader + Default + 'static
{
let mut state = self.state_mut();
state.loaders.push(Arc::new(L::default()));
}
/// Request a resource at `path`.
///
/// When a resource for a path is requested for the first time, the resource will be loaded
/// and cached. In the future, the cached version will be returned. There is only ever one copy
/// of the resource's data in memory at a time.
///
/// Loading resources is done asynchronously on a task spawned by `async-std`. You can use the
/// handle to check if the resource is loaded.
pub fn request<T>(&self, path: &str) -> Result<ResHandle<T>, RequestError>
2024-01-15 23:07:47 +00:00
where
T: ResourceData
2024-01-15 23:07:47 +00:00
{
let mut state = self.state_mut();
match state.resources.get(&path.to_string()) {
2023-09-12 18:25:33 +00:00
Some(res) => {
let res = res.clone().as_arc_any();
2024-01-15 23:07:47 +00:00
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
let res = ResHandle::<T>::clone(&res);
2023-09-12 18:25:33 +00:00
Ok(res)
2023-09-12 18:25:33 +00:00
},
None => {
if let Some(loader) = state.loaders.iter()
.find(|l| l.does_support_file(path)) {
2023-09-12 18:25:33 +00:00
// Load the resource and store it
2023-10-23 01:49:31 +00:00
let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self.clone(), path);
let handle = ResHandle::<T>::new_loading(Some(path));
let thand = handle.clone();
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.write();
d.state = ResourceState::Ready(data);
d.condvar.1.notify_all();
}
Err(err) => {
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone());
state.resources.insert(path.to_string(), res.clone());
state.uuid_resources.insert(res.uuid(), res);
Ok(handle)
} else {
Err(RequestError::UnsupportedFileExtension(path.to_string()))
}
2023-09-12 18:25:33 +00:00
}
}
}
/// Request a resource without downcasting to a `ResHandle<T>`.
/// Whenever you're ready to downcast, you can do so like this:
/// ```nobuild
/// 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(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
let mut state = self.state_mut();
match state.resources.get(&path.to_string()) {
Some(res) => {
Ok(res.clone())
},
None => {
if let Some(loader) = state.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.clone(), path);
let handle = loader.create_erased_handle();
//let handle = ResHandle::<T>::new_loading();
let thand = handle.clone();
task::spawn(async move {
match res.await {
Ok(data) => {
thand.set_state(ResourceState::Ready(data));
}
Err(err) => {
thand.set_state(ResourceState::Error(Arc::new(err)));
}
}
});
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone());
state.uuid_resources.insert(res.uuid(), res);
Ok(handle)
} else {
Err(RequestError::UnsupportedFileExtension(path.to_string()))
}
}
}
}
/// Store a resource using its uuid.
///
/// The resource cannot be requested with [`ResourceManager::request`], it can only be
/// retrieved with [`ResourceManager::request_uuid`].
pub fn store_uuid<T: ResourceData>(&self, res: ResHandle<T>) {
let mut state = self.state_mut();
state.resources.insert(res.uuid().to_string(), Arc::new(res));
}
/// Request a resource via its uuid.
///
/// Returns `None` if the resource was not found. The resource must of had been
/// stored with [`ResourceManager::request`] to return `Some`.
pub fn request_uuid<T: ResourceData>(&self, uuid: &Uuid) -> Option<ResHandle<T>> {
let state = self.state();
match state.resources.get(&uuid.to_string())
.or_else(|| state.uuid_resources.get(&uuid))
{
Some(res) => {
let res = res.clone().as_arc_any();
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
Some(ResHandle::<T>::clone(&res))
},
None => None,
}
}
/// 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>(&self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<ResHandle<T>, RequestError>
2024-01-15 23:07:47 +00:00
where
T: ResourceData
2024-01-15 23:07:47 +00:00
{
let mut state = self.state_mut();
if let Some(loader) = state.loaders.iter()
.find(|l| l.does_support_mime(mime_type)) {
let loader = loader.clone();
let res = loader.load_bytes(self.clone(), bytes, offset, length);
let handle = ResHandle::<T>::new_loading(None);
let thand = handle.clone();
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.write();
d.state = ResourceState::Ready(data);
}
Err(err) => {
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone());
state.resources.insert(ident.to_string(), res.clone());
state.uuid_resources.insert(res.uuid(), res);
Ok(handle)
} else {
Err(RequestError::UnsupportedMime(mime_type.to_string()))
}
}
/// Requests bytes from the manager.
pub fn request_loaded_bytes<T>(&self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
2024-01-15 23:07:47 +00:00
where
T: ResourceData
2024-01-15 23:07:47 +00:00
{
let state = self.state();
match state.resources.get(&ident.to_string()) {
Some(res) => {
let res = res.clone().as_arc_any();
2024-01-15 23:07:47 +00:00
let res = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
Ok(res)
},
None => {
Err(RequestError::IdentNotFound(ident.to_string()))
}
}
}
2024-01-15 23:07:47 +00:00
/// Start watching a path for changes. Returns a mspc channel that will send events
pub fn watch(&self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
2024-01-15 23:07:47 +00:00
let (send, recv) = crossbeam::channel::bounded(15);
2024-01-16 04:22:21 +00:00
let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_millis(1000), None, send)?;
2024-01-15 23:07:47 +00:00
let recurse_mode = match recursive {
true => notify::RecursiveMode::Recursive,
false => notify::RecursiveMode::NonRecursive,
};
2024-01-16 04:22:21 +00:00
watcher.watcher().watch(path.as_ref(), recurse_mode)?;
2024-01-15 23:07:47 +00:00
let watcher = Arc::new(RwLock::new(watcher));
let watcher = ResourceWatcher {
2024-01-16 04:22:21 +00:00
debouncer: watcher,
2024-01-15 23:07:47 +00:00
events_recv: recv.clone(),
};
let mut state = self.state_mut();
state.watchers.insert(path.to_string(), watcher);
2024-01-15 23:07:47 +00:00
let res = state.resources.get(&path.to_string())
2024-01-16 04:22:21 +00:00
.expect("The path that was watched has not been loaded as a resource yet");
res.set_watched(true);
2024-01-15 23:07:47 +00:00
Ok(recv)
}
/// Stops watching a path
pub fn stop_watching(&self, path: &str) -> notify::Result<()> {
let state = self.state();
if let Some(watcher) = state.watchers.get(path) {
2024-01-16 04:22:21 +00:00
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 = state.resources.get(&path.to_string()).unwrap();
2024-01-16 04:22:21 +00:00
res.set_watched(false);
2024-01-15 23:07:47 +00:00
}
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`.
2024-01-16 04:22:21 +00:00
pub fn watcher_event_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let state = self.state();
state.watchers.get(&path.to_string())
2024-01-15 23:07:47 +00:00
.map(|w| w.events_recv.clone())
}
/// Trigger a reload of a resource.
///
/// The version of the resource will be incremented by one.
///
/// > Note: Since reloading is done asynchronously, the reloaded data will not be immediately
/// accessible. Until the resource is reloaded, the previous data will stay inside of
/// the handle.
pub fn reload<T>(&self, resource: ResHandle<T>) -> Result<(), RequestError>
2024-01-15 23:07:47 +00:00
where
T: ResourceData
2024-01-15 23:07:47 +00:00
{
let state = self.state();
let path = resource.path()
.ok_or(RequestError::NoReloadPath)?;
if let Some(loader) = state.loaders.iter()
2024-01-15 23:07:47 +00:00
.find(|l| l.does_support_file(&path)) {
let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self.clone(), &path);
/* let res_lock = &resource.data;
2024-01-15 23:07:47 +00:00
let mut res_lock = res_lock.write().unwrap();
res_lock.state = ResourceState::Loading;
drop(res_lock); */
2024-01-16 04:22:21 +00:00
let thand = resource.clone();
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.write();
d.state = ResourceState::Ready(data);
d.version += 1;
}
Err(err) => {
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
2024-01-15 23:07:47 +00:00
}
Ok(())
}
2023-09-21 13:36:44 +00:00
}
#[cfg(test)]
pub(crate) mod tests {
use std::{io, ops::Deref};
use instant::Instant;
2023-09-21 13:36:44 +00:00
use crate::{Image, ResourceData, Texture};
2023-09-21 13:36:44 +00:00
use super::*;
fn get_image(path: &str) -> String {
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
format!("{manifest}/test_files/img/{path}")
}
pub(crate) fn busy_wait_resource<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
let start = Instant::now();
while !handle.is_loaded() {
// loop until the image is loaded
let now = Instant::now();
let diff = now - start;
if diff.as_secs_f32() >= timeout {
panic!("Image never loaded");
}
}
}
pub(crate) fn busy_wait_resource_reload<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
let version = handle.version();
let start = Instant::now();
while !handle.is_loaded() || handle.version() == version {
// loop until the image is loaded
let now = Instant::now();
let diff = now - start;
if diff.as_secs_f32() >= timeout {
panic!("Image never loaded");
}
}
}
2023-09-21 13:36:44 +00:00
#[test]
fn load_image() {
let man = ResourceManager::new();
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
assert!(!res.is_loaded());
res.wait_for_load();
//busy_wait_resource(&res, 10.0);
// shouldn't panic because of the loop
res.data_ref().unwrap();
2023-09-21 13:36:44 +00:00
}
2024-01-15 23:07:47 +00:00
/// Ensures that only one copy of the same data was made
2023-09-21 13:36:44 +00:00
#[test]
fn ensure_single() {
let man = ResourceManager::new();
2023-09-21 13:36:44 +00:00
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(Arc::strong_count(&res.handle.res), 3);
2023-09-21 13:36:44 +00:00
let resagain = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert_eq!(Arc::strong_count(&resagain.handle.res), 4);
2023-09-21 13:36:44 +00:00
}
/// Ensures that an error is returned when a file that doesn't exist is requested
#[test]
fn ensure_none() {
let man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squigglesfff.png")).unwrap();
//let err = res.err().unwrap();
// 1 second should be enough to run into an error
std::thread::sleep(Duration::from_secs(1));
//busy_wait_resource(&res, 10.0);
let state = &res.read().state;
2023-09-21 13:36:44 +00:00
assert!(
match state {
2023-09-21 13:36:44 +00:00
// make sure the error is NotFound
//RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
ResourceState::Error(err) => {
match err.deref() {
LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true,
_ => false,
}
},
2023-09-21 13:36:44 +00:00
_ => false
}
);
}
2024-01-15 23:07:47 +00:00
#[test]
fn reload_image() {
let man = ResourceManager::new();
2024-01-15 23:07:47 +00:00
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
busy_wait_resource(&res, 10.0);
let img = res.data_ref();
2024-01-15 23:07:47 +00:00
img.unwrap();
man.reload(res.clone()).unwrap();
busy_wait_resource_reload(&res, 10.0);
2024-01-15 23:07:47 +00:00
assert_eq!(res.version(), 1);
man.reload(res.clone()).unwrap();
busy_wait_resource_reload(&res, 10.0);
2024-01-15 23:07:47 +00:00
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();
2024-01-15 23:07:47 +00:00
let man = ResourceManager::new();
2024-01-15 23:07:47 +00:00
let res = man.request::<Texture>(&image_path).unwrap();
busy_wait_resource(&res, 10.0);
let img = res.data_ref();
2024-01-15 23:07:47 +00:00
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();
2024-01-16 04:22:21 +00:00
assert!(event.iter().any(|ev| ev.kind.is_remove() || ev.kind.is_modify()));
2024-01-15 23:07:47 +00:00
}
2023-09-12 18:25:33 +00:00
}