From aa3a4a17d712497fc81547288cb4cd9daa3684a0 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 10 Mar 2024 00:11:15 -0500 Subject: [PATCH] resource: implement waiting for resource dependencies to be loaded --- Cargo.lock | 2 + examples/testbed/src/main.rs | 1 + .../lyra-reflect-derive/src/enum_derive.rs | 6 +- lyra-resource/Cargo.toml | 4 + lyra-resource/src/dep_state.rs | 109 +++++ lyra-resource/src/gltf/material.rs | 33 +- lyra-resource/src/gltf/mesh.rs | 27 +- lyra-resource/src/gltf/mod.rs | 31 ++ lyra-resource/src/gltf/scene.rs | 40 ++ lyra-resource/src/lib.rs | 7 + lyra-resource/src/loader/image.rs | 6 +- lyra-resource/src/resource.rs | 433 ++++++++++++++---- lyra-resource/src/resource_manager.rs | 49 +- lyra-resource/src/texture.rs | 45 +- lyra-resource/src/world_ext.rs | 6 +- 15 files changed, 683 insertions(+), 116 deletions(-) create mode 100644 lyra-resource/src/dep_state.rs diff --git a/Cargo.lock b/Cargo.lock index 61318a4..90b0d29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1849,10 +1849,12 @@ dependencies = [ "instant", "lyra-ecs", "lyra-math", + "lyra-reflect", "mime", "notify", "notify-debouncer-full", "percent-encoding", + "rand 0.8.5", "thiserror", "tracing", "uuid", diff --git a/examples/testbed/src/main.rs b/examples/testbed/src/main.rs index abe3d54..dba017e 100644 --- a/examples/testbed/src/main.rs +++ b/examples/testbed/src/main.rs @@ -110,6 +110,7 @@ async fn main() { let sponza_model = resman.request::("assets/sponza/Sponza.gltf").unwrap(); drop(resman); + sponza_model.wait_recurse_dependencies_load(); let sponza_scene = &sponza_model.data_ref() .unwrap().scenes[0]; diff --git a/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs b/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs index a80740d..6e41c4c 100644 --- a/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs +++ b/lyra-reflect/lyra-reflect-derive/src/enum_derive.rs @@ -441,9 +441,9 @@ fn gen_enum_variant_type(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_ let vty = VariantType::from(var); match vty { - VariantType::Struct => quote! { #variant_ident => EnumType::Struct }, - VariantType::Tuple => quote! { #variant_ident => EnumType::Tuple }, - VariantType::Unit => quote! { #variant_ident => EnumType::Unit }, + VariantType::Struct => quote! { #variant_ident => lyra_engine::reflect::EnumType::Struct }, + VariantType::Tuple => quote! { #variant_ident => lyra_engine::reflect::EnumType::Tuple }, + VariantType::Unit => quote! { #variant_ident => lyra_engine::reflect::EnumType::Unit }, } }); diff --git a/lyra-resource/Cargo.toml b/lyra-resource/Cargo.toml index 64083b0..77833fc 100644 --- a/lyra-resource/Cargo.toml +++ b/lyra-resource/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } +lyra-reflect = { path = "../lyra-reflect" } lyra-math = { path = "../lyra-math" } anyhow = "1.0.75" base64 = "0.21.4" @@ -26,3 +27,6 @@ tracing = "0.1.37" uuid = { version = "1.4.1", features = ["v4"] } instant = "0.1" async-std = "1.12.0" + +[dev-dependencies] +rand = "0.8.5" \ No newline at end of file diff --git a/lyra-resource/src/dep_state.rs b/lyra-resource/src/dep_state.rs new file mode 100644 index 0000000..60ebe13 --- /dev/null +++ b/lyra-resource/src/dep_state.rs @@ -0,0 +1,109 @@ +use std::sync::Arc; + +use crate::{loader::LoaderError, ResourceState, UntypedResHandle}; + +#[derive(Clone)] +pub enum DependencyState { + Loading, + Error { + /// The resource that had the error. + handle: UntypedResHandle, + /// The error that the resource ran into when loading. + error: Arc, + }, + Ready, +} + +impl DependencyState { + /// Creates a DependencyState from a resource by retrieving its state. Does not include + /// the states of the dependencies. + pub fn shallow_from_res(handle: &UntypedResHandle) -> DependencyState { + let res = handle.read(); + match &res.state { + ResourceState::Loading => DependencyState::Loading, + ResourceState::Error(er) => DependencyState::Error { + handle: handle.clone(), + error: er.clone(), + }, + ResourceState::Ready(_) => DependencyState::Ready, + } + } + + /// Retrieve the state of the handle and its dependencies, does not recursively retrieve. + pub fn from_res(handle: &UntypedResHandle) -> DependencyState { + let res = handle.read(); + match &res.state { + ResourceState::Loading => DependencyState::Loading, + ResourceState::Error(er) => DependencyState::Error { + handle: handle.clone(), + error: er.clone(), + }, + ResourceState::Ready(res) => { + let mut lowest_state = DependencyState::Ready; + + for dep in res.dependencies() { + let state = DependencyState::shallow_from_res(&dep); + + // try to find the "lowest" dependency. Meaning the state of a dependency + // that would stop the parent from being ready. + if state.is_loading() { + lowest_state = state; + break; + } else if state.is_error() { + lowest_state = state; + break; + } + + // anything else would be loaded, so no need to update `lowest_state` + } + + lowest_state + }, + } + } + + /// Retrieve the state of the handle and its dependencies, does not recursively retrieve. + pub fn from_res_recurse(handle: &UntypedResHandle) -> DependencyState { + let res = handle.read(); + match &res.state { + ResourceState::Loading => DependencyState::Loading, + ResourceState::Error(er) => DependencyState::Error { + handle: handle.clone(), + error: er.clone(), + }, + ResourceState::Ready(res) => { + let mut lowest_state = DependencyState::Ready; + + for dep in res.dependencies() { + let state = DependencyState::from_res_recurse(&dep); + + // try to find the "lowest" dependency. Meaning the state of a dependency + // that would stop the parent from being ready. + if state.is_loading() { + lowest_state = state; + break; + } else if state.is_error() { + lowest_state = state; + break; + } + + // anything else would be loaded, so no need to update `lowest_state` + } + + lowest_state + }, + } + } + + pub fn is_ready(&self) -> bool { + matches!(self, DependencyState::Ready) + } + + pub fn is_error(&self) -> bool { + matches!(self, DependencyState::Error { handle: _, error: _ }) + } + + pub fn is_loading(&self) -> bool { + matches!(self, DependencyState::Loading) + } +} \ No newline at end of file diff --git a/lyra-resource/src/gltf/material.rs b/lyra-resource/src/gltf/material.rs index a497cee..96d69a5 100644 --- a/lyra-resource/src/gltf/material.rs +++ b/lyra-resource/src/gltf/material.rs @@ -1,12 +1,14 @@ use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; use gltf::texture::{MagFilter, MinFilter}; +use lyra_reflect::Reflect; +use crate::{lyra_engine, optionally_add_to_dep, ResourceData}; use crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode}; use super::loader::GltfLoadContext; /// PBR metallic roughness -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Reflect)] pub struct PbrRoughness { /// The rgba base color of the PBR material pub base_color: [f32; 4], @@ -54,7 +56,7 @@ impl From> for PbrGlossiness { /// The alpha rendering mode of a material. /// This is essentially a re-export of gltf::material::AlphaMode -#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)] +#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Reflect)] pub enum AlphaMode { /// The alpha value is ignored and the rendered output is fully opaque. #[default] @@ -181,6 +183,33 @@ pub struct Material { pub specular: Option, } +impl ResourceData for Material { + + + fn dependencies(&self) -> Vec { + let mut deps = vec![]; + + optionally_add_to_dep(&mut deps, &self.metallic_roughness_texture); + optionally_add_to_dep(&mut deps, &self.base_color_texture); + optionally_add_to_dep(&mut deps, &self.metallic_roughness_texture); + + if let Some(spec) = &self.specular { + optionally_add_to_dep(&mut deps, &spec.texture); + optionally_add_to_dep(&mut deps, &spec.color_texture); + } + + deps + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + #[allow(dead_code)] impl Material { /// Get a uri's identifier diff --git a/lyra-resource/src/gltf/mesh.rs b/lyra-resource/src/gltf/mesh.rs index 49c559f..f4552f7 100644 --- a/lyra-resource/src/gltf/mesh.rs +++ b/lyra-resource/src/gltf/mesh.rs @@ -1,11 +1,15 @@ use std::collections::HashMap; -use crate::{lyra_engine, ResHandle}; +use lyra_reflect::Reflect; +use crate::lyra_engine; + +use crate::ResHandle; +use crate::ResourceData; use super::Material; #[repr(C)] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Reflect)] pub enum MeshIndices { //U8(Vec), U16(Vec), @@ -68,7 +72,7 @@ impl VertexAttributeData { } } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash, Reflect)] pub enum MeshVertexAttribute { Position, Normals, @@ -90,6 +94,23 @@ pub struct Mesh { pub material: Option>, } +impl ResourceData for Mesh { + fn dependencies(&self) -> Vec { + match &self.material { + Some(m) => vec![m.untyped_clone()], + None => vec![] + } + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + impl Mesh { pub fn add_attribute(&mut self, attribute: MeshVertexAttribute, data: VertexAttributeData) { self.attributes.insert(attribute, data); diff --git a/lyra-resource/src/gltf/mod.rs b/lyra-resource/src/gltf/mod.rs index 862acdf..5eef200 100644 --- a/lyra-resource/src/gltf/mod.rs +++ b/lyra-resource/src/gltf/mod.rs @@ -3,6 +3,7 @@ pub use loader::*; pub mod material; use lyra_math::Transform; +use crate::ResourceData; pub use material::*; pub mod mesh; @@ -21,6 +22,36 @@ pub struct Gltf { pub meshes: Vec>, } +impl ResourceData for Gltf { + fn dependencies(&self) -> Vec { + let mut deps = vec![]; + + for scene in self.scenes.iter() { + deps.push(scene.untyped_clone()) + } + + for mat in self.materials.iter() { + deps.push(mat.untyped_clone()) + } + + for mesh in self.meshes.iter() { + deps.push(mesh.untyped_clone()) + } + + deps + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + +} + impl Gltf { /// Collects all Gltf meshes and gets their world Transform. pub fn collect_world_meshes(&self) -> Vec<(ResHandle, Transform)> { diff --git a/lyra-resource/src/gltf/scene.rs b/lyra-resource/src/gltf/scene.rs index 39f1d12..28ee149 100644 --- a/lyra-resource/src/gltf/scene.rs +++ b/lyra-resource/src/gltf/scene.rs @@ -1,4 +1,5 @@ use lyra_math::Transform; +use crate::{optionally_add_to_dep, ResourceData, UntypedResHandle}; use super::Mesh; use crate::ResHandle; @@ -12,12 +13,51 @@ pub struct GltfNode { pub children: Vec, } +impl ResourceData for GltfNode { + fn dependencies(&self) -> Vec { + let mut deps: Vec = self.children.iter() + .flat_map(|c| c.mesh.as_ref().map(|h| h.untyped_clone())) + .collect(); + + optionally_add_to_dep(&mut deps, &self.mesh); + + deps + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + /// A Scene in a Gltf file #[derive(Clone)] pub struct GltfScene { pub nodes: Vec, } +impl ResourceData for GltfScene { + fn dependencies(&self) -> Vec { + let deps: Vec = self.nodes.iter() + .map(|n| n.dependencies()) + .flatten() + .collect(); + + deps + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } +} + impl GltfScene { fn collect_node(&self, parent_node: &GltfNode, node: &GltfNode) -> Vec<(ResHandle, Transform)> { let mut v = vec![]; diff --git a/lyra-resource/src/lib.rs b/lyra-resource/src/lib.rs index 0f907a5..0d6cbcd 100644 --- a/lyra-resource/src/lib.rs +++ b/lyra-resource/src/lib.rs @@ -7,6 +7,9 @@ pub use resource::*; mod texture; pub use texture::*; +mod dep_state; +pub use dep_state::*; + pub mod loader; pub mod gltf; @@ -24,4 +27,8 @@ pub(crate) mod lyra_engine { pub(crate) mod ecs { pub use lyra_ecs::*; } + + pub(crate) mod reflect { + pub use lyra_reflect::*; + } } diff --git a/lyra-resource/src/loader/image.rs b/lyra-resource/src/loader/image.rs index 9ebb7c3..5bb2a54 100644 --- a/lyra-resource/src/loader/image.rs +++ b/lyra-resource/src/loader/image.rs @@ -2,7 +2,7 @@ use std::{ffi::OsStr, path::Path, sync::Arc}; use async_std::io::ReadExt; use image::ImageError; -use tracing::trace; +use tracing::debug; use crate::{Image, ResHandle, ResourceData, ResourceManager}; @@ -87,14 +87,14 @@ impl ResourceLoader for ImageLoader { fn load_bytes(&self, _resource_manager: ResourceManager, bytes: Vec, offset: usize, length: usize) -> PinedBoxLoaderFuture { Box::pin(async move { - trace!("Loading {} bytes as an image", length); - let image = image::load_from_memory(&bytes[offset..(length-offset)]) .map_err(|e| match e { ImageError::IoError(e) => LoaderError::IoError(e), _ => LoaderError::DecodingError(e.into()), })?; let image = Image::from(image); + debug!("Finished loading image ({} bytes)", length); + Ok(Box::new(image) as Box) }) } diff --git a/lyra-resource/src/resource.rs b/lyra-resource/src/resource.rs index 926daeb..7604f38 100644 --- a/lyra-resource/src/resource.rs +++ b/lyra-resource/src/resource.rs @@ -1,40 +1,74 @@ -use std::{any::{Any, TypeId}, marker::PhantomData, sync::{Arc, RwLock}}; +use std::{any::{Any, TypeId}, marker::PhantomData, ops::{Deref, DerefMut}, sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}}; use lyra_ecs::Component; -use crate::{loader::LoaderError, lyra_engine}; +use crate::{loader::LoaderError, lyra_engine, DependencyState}; use uuid::Uuid; use crate::ResourceStorage; +pub fn optionally_add_to_dep(deps: &mut Vec, handle: &Option>) { + if let Some(h) = handle { + deps.push(h.untyped_clone()); + } +} + /// A trait that that each resource type should implement. pub trait ResourceData: Send + Sync + Any + 'static { fn as_any(&self) -> &dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any; - fn type_id(&self) -> TypeId; -} -impl ResourceData for T { - fn as_any(&self) -> &dyn Any { - self - } + /// Collect the dependencies of the Resource. + fn dependencies(&self) -> Vec; - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } + /// Recursively collect the dependencies of the Resource. + /// + /// If a dependency has a child dependency, it will not show up in this list until its + /// parent is loaded. + fn recur_dependencies(&self) -> Vec { + let deps = self.dependencies(); + let mut all_deps = deps.clone(); - fn type_id(&self) -> TypeId { - TypeId::of::() + for dep in deps.into_iter() { + let dep = dep.read(); + match &dep.state { + ResourceState::Ready(data) => { + let mut deps_dep = data.dependencies(); + all_deps.append(&mut deps_dep); + }, + _ => {} + } + } + + all_deps } } +//impl ResourceData for T { } + pub enum ResourceState { Loading, Error(Arc), Ready(Box), } +impl ResourceState { + /// Returns a boolean indicating if the state of still loading + pub fn is_loading(&self) -> bool { + matches!(self, ResourceState::Loading) + } + + pub fn is_error(&self) -> bool { + matches!(self, ResourceState::Error(_)) + } + + pub fn is_ready(&self) -> bool { + matches!(self, ResourceState::Ready(_)) + } +} + pub struct ResourceDataRef<'a, T> { - guard: std::sync::RwLockReadGuard<'a, Resource>, + guard: std::sync::RwLockReadGuard<'a, UntypedResource>, + _marker: PhantomData, } impl<'a, T: 'static> std::ops::Deref for ResourceDataRef<'a, T> { @@ -52,13 +86,125 @@ impl<'a, T: 'static> std::ops::Deref for ResourceDataRef<'a, T> { } } -pub struct Resource { +pub struct UntypedResource { pub(crate) version: usize, pub(crate) state: ResourceState, uuid: Uuid, path: Option, pub(crate) is_watched: bool, - _marker: PhantomData, + /// can be used to wait for the resource to load. + pub(crate) condvar: Arc<(Mutex, Condvar)>, +} + +#[derive(Clone)] +pub struct UntypedResHandle{ + pub(crate) res: Arc>, + #[allow(dead_code)] + tyid: TypeId, +} + +impl UntypedResHandle { + pub fn new(res: UntypedResource, tyid: TypeId) -> Self { + Self { + res: Arc::new(RwLock::new(res)), + tyid + } + } + + pub fn read(&self) -> RwLockReadGuard { + self.res.read().unwrap() + } + + pub fn write(&self) -> RwLockWriteGuard { + self.res.write().unwrap() + } + + /// Returns a boolean indicating if this resource's path is being watched. + pub fn is_watched(&self) -> bool { + let d = self.read(); + d.is_watched + } + + /// Returns a boolean indicating if this resource is loaded + pub fn is_loaded(&self) -> bool { + let d = self.read(); + matches!(d.state, ResourceState::Ready(_)) + } + + /// Returns the uuid of the resource. + pub fn uuid(&self) -> Uuid { + let d = self.read(); + d.uuid + } + + pub fn path(&self) -> Option { + let d = self.read(); + d.path.clone() + } + + /// Retrieves the current version of the resource. This gets incremented when the resource + /// is reloaded. + pub fn version(&self) -> usize { + let d = self.read(); + d.version + } + + /// Wait for the resource to be loaded, not including its dependencies + /// (see[`UntypedResHandle::wait_recurse_dependencies_load`]). + /// + /// This blocks the thread without consuming CPU time; its backed by a + /// [`Condvar`](std::sync::Condvar). + pub fn wait_for_load(&self) { + let d = self.read(); + + if matches!(d.state, ResourceState::Ready(_)) { + return; + } + + let cv = d.condvar.clone(); + drop(d); + + let l = cv.0.lock().unwrap(); + let _unused = cv.1.wait(l).unwrap(); + } + + /// Wait for the entire resource, including its dependencies to be loaded. + /// + /// This blocks the thread without consuming CPU time; its backed by a + /// [`Condvar`](std::sync::Condvar). + pub fn wait_recurse_dependencies_load(&self) { + self.wait_for_load(); + + let res = self.read(); + match &res.state { + ResourceState::Ready(data) => { + // `recur_dependencies` wont return resources that are not loaded in yet + // if we did not check if the resource was finished loading, we could miss + // waiting for some resources and finish early. + while self.recurse_dependency_state().is_loading() { + for dep in data.recur_dependencies() { + dep.wait_for_load(); + } + } + }, + _ => unreachable!() // the self.wait_for_load ensures that the state is ready + } + } + + /// Recursively get the state of the dependencies. + /// + /// This doesn't return any resource data, it can be used to check if the resource and its + /// dependencies are loaded. + pub fn recurse_dependency_state(&self) -> DependencyState { + DependencyState::from_res_recurse(self) + } + + pub fn as_typed(&self) -> ResHandle { + ResHandle { + handle: self.clone(), + _marker: PhantomData::, + } + } } /// A handle to a resource. @@ -69,89 +215,69 @@ pub struct Resource { /// and has a write lock on the data. This means that most of the time, it is not blocking. #[derive(Component)] pub struct ResHandle { - pub(crate) data: Arc>>, + pub(crate) handle: UntypedResHandle, + _marker: PhantomData, } impl Clone for ResHandle { fn clone(&self) -> Self { - Self { data: self.data.clone() } + Self { + handle: self.handle.clone(), + _marker: PhantomData:: + } + } +} + +impl Deref for ResHandle { + type Target = UntypedResHandle; + + fn deref(&self) -> &Self::Target { + &self.handle + } +} + +impl DerefMut for ResHandle { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.handle } } impl ResHandle { pub fn new_loading(path: Option<&str>) -> Self { - let res_version = Resource { + let res_version = UntypedResource { version: 0, path: path.map(str::to_string), state: ResourceState::Loading, uuid: Uuid::new_v4(), is_watched: false, - _marker: PhantomData:: + condvar: Arc::new((Mutex::new(false), Condvar::new())), }; Self { - data: Arc::new(RwLock::new(res_version)), + handle: UntypedResHandle::new(res_version, TypeId::of::()), + _marker: PhantomData::, } } /// Create the resource with data, its assumed the state is `Ready` pub fn new_ready(path: Option<&str>, data: T) -> Self { - let res_version = Resource { - version: 0, - path: path.map(str::to_string), - state: ResourceState::Ready(Box::new(data)), - uuid: Uuid::new_v4(), - is_watched: false, - _marker: PhantomData:: - }; - - Self { - data: Arc::new(RwLock::new(res_version)), - } + let han = Self::new_loading(path); + han.set_state(ResourceState::Ready(Box::new(data))); + han } - /// Returns a boolean indicating if this resource's path is being watched. - pub fn is_watched(&self) -> bool { - let d = self.data.read().expect("Resource mutex was poisoned!"); - d.is_watched - } - - /// Returns a boolean indicating if this resource is loaded - pub fn is_loaded(&self) -> bool { - let d = self.data.read().expect("Resource mutex was poisoned!"); - matches!(d.state, ResourceState::Ready(_)) - } - - /// Returns the current state of the resource. - /* pub fn state(&self) -> &ResourceState { - let d = self.data.read().expect("Resource mutex was poisoned!"); - &d.state - } */ - - /// Returns the uuid of the resource. - pub fn uuid(&self) -> Uuid { - let d = self.data.read().expect("Resource mutex was poisoned!"); - d.uuid - } - - pub fn path(&self) -> Option { - let d = self.data.read().expect("Resource mutex was poisoned!"); - d.path.clone() - } - - /// Retrieves the current version of the resource. This gets incremented when the resource - /// is reloaded. - pub fn version(&self) -> usize { - let d = self.data.read().expect("Resource mutex was poisoned!"); - d.version + /// Retrieve an untyped clone of the handle + pub fn untyped_clone(&self) -> UntypedResHandle { + self.handle.clone() } /// Get a reference to the data in the resource pub fn data_ref<'a>(&'a self) -> Option> { if self.is_loaded() { - let d = self.data.read().expect("Resource mutex was poisoned!"); + let d = self.handle.read(); Some(ResourceDataRef { - guard: d + guard: d, + _marker: PhantomData:: }) } else { None @@ -177,34 +303,185 @@ impl ResourceStorage for ResHandle { } fn set_watched(&self, watched: bool) { - let mut w = self.data.write().unwrap(); - w.is_watched = watched; + let mut d = self.handle.write(); + d.is_watched = watched; } fn version(&self) -> usize { - self.version() + self.handle.version() } fn uuid(&self) -> Uuid { - self.uuid() + self.handle.uuid() } fn path(&self) -> Option { - self.path() + self.handle.path() } fn is_watched(&self) -> bool { - self.is_watched() + self.handle.is_watched() } fn is_loaded(&self) -> bool { - self.is_loaded() + self.handle.is_loaded() } fn set_state(&self, new: ResourceState) { - let mut d = self.data.write().expect("Resource mutex was poisoned!"); + let mut d = self.handle.write(); d.state = new; } +} - +#[cfg(test)] +mod tests { + use std::{path::PathBuf, str::FromStr, sync::Arc}; + + use async_std::task; + use instant::Duration; + use rand::Rng; + + use crate::{loader::ResourceLoader, ResHandle, ResourceData, ResourceManager}; + + #[allow(dead_code)] + struct SimpleDepend { + file_name: String, + ext: String, + } + + impl ResourceData for SimpleDepend { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn dependencies(&self) -> Vec { + vec![] + } + } + + #[derive(Default)] + struct SlowSimpleDependLoader; + + impl ResourceLoader for SlowSimpleDependLoader { + fn extensions(&self) -> &[&str] { + &["txt", "buf"] + } + + fn mime_types(&self) -> &[&str] { + &[] + } + + fn load(&self, _: crate::ResourceManager, path: &str) -> crate::loader::PinedBoxLoaderFuture { + let path = path.to_string(); + Box::pin(async move { + let path = PathBuf::from_str(&path).unwrap(); + + let file_name = path.file_name() + .and_then(|os| os.to_str()) + .unwrap(); + let path_ext = path.extension() + .and_then(|os| os.to_str()) + .unwrap(); + + let res = rand::thread_rng().gen_range(500..1000); + + task::sleep(Duration::from_millis(res)).await; + + let simple = SimpleDepend { + file_name: file_name.to_string(), + ext: path_ext.to_string(), + }; + + Ok(Box::new(simple) as Box) + }) + } + + fn load_bytes(&self, _: crate::ResourceManager, _: Vec, _: usize, _: usize) -> crate::loader::PinedBoxLoaderFuture { + unreachable!() + } + + fn create_erased_handle(&self) -> std::sync::Arc { + Arc::from(ResHandle::::new_loading(None)) + } + } + + + struct SimpleResource { + depend_a: ResHandle, + } + + impl ResourceData for SimpleResource { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + fn dependencies(&self) -> Vec { + vec![self.depend_a.untyped_clone()] + } + } + + #[derive(Default)] + struct SlowSimpleResourceLoader; + + impl ResourceLoader for SlowSimpleResourceLoader { + fn extensions(&self) -> &[&str] { + &["res", "large"] + } + + fn mime_types(&self) -> &[&str] { + &[] + } + + fn load(&self, res_man: crate::ResourceManager, _: &str) -> crate::loader::PinedBoxLoaderFuture { + Box::pin(async move { + let res = rand::thread_rng().gen_range(500..1000); + + task::sleep(Duration::from_millis(res)).await; + + // load dummy dependency that will take a bit + let depend_path = "depend.txt"; + let depend_han = res_man.request::(depend_path).unwrap(); + + let simple = SimpleResource { + depend_a: depend_han, + }; + + Ok(Box::new(simple) as Box) + }) + } + + fn load_bytes(&self, _: crate::ResourceManager, _: Vec, _: usize, _: usize) -> crate::loader::PinedBoxLoaderFuture { + unreachable!() + } + + fn create_erased_handle(&self) -> std::sync::Arc { + Arc::from(ResHandle::::new_loading(None)) + } + } + + #[test] + fn recursive() { + let man = ResourceManager::new(); + man.register_loader::(); + man.register_loader::(); + + let res = man.request::("massive_asset.res").unwrap(); + + let state = res.recurse_dependency_state(); + assert!(state.is_loading()); + + // this will take a bit + res.wait_recurse_dependencies_load(); + + let state = res.recurse_dependency_state(); + assert!(!state.is_loading()); + } } \ No newline at end of file diff --git a/lyra-resource/src/resource_manager.rs b/lyra-resource/src/resource_manager.rs index f184148..1781434 100644 --- a/lyra-resource/src/resource_manager.rs +++ b/lyra-resource/src/resource_manager.rs @@ -7,7 +7,7 @@ use notify_debouncer_full::{DebouncedEvent, FileIdMap}; use thiserror::Error; use uuid::Uuid; -use crate::{gltf::ModelLoader, loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState}; +use crate::{gltf::ModelLoader, loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceData, ResourceState}; /// A trait for type erased storage of a resource. /// Implemented for [`ResHandle`] @@ -61,6 +61,7 @@ pub struct ResourceWatcher { /// The state of the ResourceManager pub struct ResourceManagerState { resources: HashMap>, + uuid_resources: HashMap>, loaders: Vec>, watchers: HashMap, } @@ -79,6 +80,7 @@ impl Default for ResourceManager { inner: Arc::new(RwLock::new( ResourceManagerState { resources: HashMap::new(), + uuid_resources: HashMap::new(), loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], watchers: HashMap::new(), } @@ -121,7 +123,7 @@ impl ResourceManager { /// handle to check if the resource is loaded. pub fn request(&self, path: &str) -> Result, RequestError> where - T: Send + Sync + Any + 'static + T: ResourceData { let mut state = self.state_mut(); match state.resources.get(&path.to_string()) { @@ -146,18 +148,20 @@ impl ResourceManager { task::spawn(async move { match res.await { Ok(data) => { - let mut d = thand.data.write().unwrap(); + let mut d = thand.write(); d.state = ResourceState::Ready(data); + d.condvar.1.notify_all(); } Err(err) => { - let mut d = thand.data.write().unwrap(); + let mut d = thand.write(); d.state = ResourceState::Error(Arc::new(err)); } } }); let res: Arc = Arc::from(handle.clone()); - state.resources.insert(path.to_string(), res); + state.resources.insert(path.to_string(), res.clone()); + state.uuid_resources.insert(res.uuid(), res); Ok(handle) } else { @@ -174,13 +178,13 @@ impl ResourceManager { /// let res: Arc> = res.downcast::>().expect("Failure to downcast resource"); /// ``` pub fn request_raw(&self, path: &str) -> Result, RequestError> { - let inner = self.inner.write().unwrap(); - match inner.resources.get(&path.to_string()) { + let mut state = self.state_mut(); + match state.resources.get(&path.to_string()) { Some(res) => { Ok(res.clone()) }, None => { - if let Some(loader) = inner.loaders.iter() + if let Some(loader) = state.loaders.iter() .find(|l| l.does_support_file(path)) { // Load the resource and store it @@ -202,6 +206,9 @@ impl ResourceManager { } }); + let res: Arc = Arc::from(handle.clone()); + state.uuid_resources.insert(res.uuid(), res); + Ok(handle) } else { Err(RequestError::UnsupportedFileExtension(path.to_string())) @@ -225,7 +232,9 @@ impl ResourceManager { /// stored with [`ResourceManager::request`] to return `Some`. pub fn request_uuid(&self, uuid: &Uuid) -> Option> { let state = self.state(); - match state.resources.get(&uuid.to_string()) { + 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> = res.downcast::>().expect("Failure to downcast resource"); @@ -246,7 +255,7 @@ impl ResourceManager { /// Returns: The `Arc` to the now stored resource pub fn load_bytes(&self, ident: &str, mime_type: &str, bytes: Vec, offset: usize, length: usize) -> Result, RequestError> where - T: Send + Sync + Any + 'static + T: ResourceData { let mut state = self.state_mut(); if let Some(loader) = state.loaders.iter() @@ -259,18 +268,19 @@ impl ResourceManager { task::spawn(async move { match res.await { Ok(data) => { - let mut d = thand.data.write().unwrap(); + let mut d = thand.write(); d.state = ResourceState::Ready(data); } Err(err) => { - let mut d = thand.data.write().unwrap(); + let mut d = thand.write(); d.state = ResourceState::Error(Arc::new(err)); } } }); let res: Arc = Arc::from(handle.clone()); - state.resources.insert(ident.to_string(), res); + state.resources.insert(ident.to_string(), res.clone()); + state.uuid_resources.insert(res.uuid(), res); Ok(handle) } else { @@ -376,12 +386,12 @@ impl ResourceManager { task::spawn(async move { match res.await { Ok(data) => { - let mut d = thand.data.write().unwrap(); + let mut d = thand.write(); d.state = ResourceState::Ready(data); d.version += 1; } Err(err) => { - let mut d = thand.data.write().unwrap(); + let mut d = thand.write(); d.state = ResourceState::Error(Arc::new(err)); } } @@ -444,7 +454,8 @@ pub(crate) mod tests { let res = man.request::(&get_image("squiggles.png")).unwrap(); assert!(!res.is_loaded()); - busy_wait_resource(&res, 10.0); + res.wait_for_load(); + //busy_wait_resource(&res, 10.0); // shouldn't panic because of the loop res.data_ref().unwrap(); @@ -455,10 +466,10 @@ pub(crate) mod tests { fn ensure_single() { let man = ResourceManager::new(); let res = man.request::(&get_image("squiggles.png")).unwrap(); - assert_eq!(Arc::strong_count(&res.data), 3); + assert_eq!(Arc::strong_count(&res.handle.res), 3); let resagain = man.request::(&get_image("squiggles.png")).unwrap(); - assert_eq!(Arc::strong_count(&resagain.data), 4); + assert_eq!(Arc::strong_count(&resagain.handle.res), 4); } /// Ensures that an error is returned when a file that doesn't exist is requested @@ -471,7 +482,7 @@ pub(crate) mod tests { // 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.data.read().unwrap().state; + let state = &res.read().state; assert!( match state { diff --git a/lyra-resource/src/texture.rs b/lyra-resource/src/texture.rs index 179b190..6663c56 100644 --- a/lyra-resource/src/texture.rs +++ b/lyra-resource/src/texture.rs @@ -2,20 +2,23 @@ use std::ops::{Deref, DerefMut}; //pub use gltf::texture::{MagFilter, MinFilter, WrappingMode}; use image::DynamicImage; +use lyra_reflect::Reflect; +use crate::lyra_engine; use crate::ResHandle; +use crate::ResourceData; /// The filter mode of the sampler. /// /// This is used for minification, magnification, and mipmap filters -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Reflect)] pub enum FilterMode { Nearest, Linear, } /// The wrapping mode of the Texture coordinates -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Reflect)] pub enum WrappingMode { ClampToEdge, MirroredRepeat, @@ -23,7 +26,7 @@ pub enum WrappingMode { } /// The descriptor of the sampler for a Texture. -#[derive(Clone)] +#[derive(Clone, Reflect)] pub struct TextureSampler { pub mag_filter: Option, pub min_filter: Option, @@ -33,8 +36,24 @@ pub struct TextureSampler { pub wrap_w: WrappingMode, } -#[derive(Clone)] -pub struct Image(DynamicImage); +#[derive(Clone, Reflect)] +pub struct Image(#[reflect(skip)] DynamicImage); + +impl ResourceData for Image { + fn dependencies(&self) -> Vec { + vec![] + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + +} impl Deref for Image { type Target = DynamicImage; @@ -62,6 +81,22 @@ pub struct Texture { pub sampler: Option, } +impl ResourceData for Texture { + fn dependencies(&self) -> Vec { + vec![self.image.untyped_clone()] + } + + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + self + } + + +} + impl Texture { /// Create a texture from an image. pub fn from_image(image: ResHandle) -> Self { diff --git a/lyra-resource/src/world_ext.rs b/lyra-resource/src/world_ext.rs index 39856ca..c885ca4 100644 --- a/lyra-resource/src/world_ext.rs +++ b/lyra-resource/src/world_ext.rs @@ -4,7 +4,7 @@ use crossbeam::channel::Receiver; use lyra_ecs::World; use notify_debouncer_full::DebouncedEvent; -use crate::{loader::ResourceLoader, RequestError, ResHandle, ResourceManager}; +use crate::{loader::ResourceLoader, RequestError, ResHandle, ResourceData, ResourceManager}; pub trait WorldAssetExt { /// Register a resource loader with the resource manager. @@ -15,7 +15,7 @@ pub trait WorldAssetExt { /// Request a resource from the resource manager. fn request_res(&mut self, path: &str) -> Result, RequestError> where - T: Send + Sync + Any + 'static; + T: ResourceData; /// Start watching a resource for changes. Returns a crossbeam channel that can be used to listen for events. fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result, Vec>>>; @@ -44,7 +44,7 @@ impl WorldAssetExt for World { fn request_res(&mut self, path: &str) -> Result, RequestError> where - T: Send + Sync + Any + 'static + T: ResourceData { let man = self.get_resource_or_default::(); man.request(path)