resource: implement waiting for resource dependencies to be loaded

This commit is contained in:
SeanOMik 2024-03-10 00:11:15 -05:00
parent 4a285e5866
commit aa3a4a17d7
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
15 changed files with 683 additions and 116 deletions

2
Cargo.lock generated
View File

@ -1849,10 +1849,12 @@ dependencies = [
"instant", "instant",
"lyra-ecs", "lyra-ecs",
"lyra-math", "lyra-math",
"lyra-reflect",
"mime", "mime",
"notify", "notify",
"notify-debouncer-full", "notify-debouncer-full",
"percent-encoding", "percent-encoding",
"rand 0.8.5",
"thiserror", "thiserror",
"tracing", "tracing",
"uuid", "uuid",

View File

@ -110,6 +110,7 @@ async fn main() {
let sponza_model = resman.request::<Gltf>("assets/sponza/Sponza.gltf").unwrap(); let sponza_model = resman.request::<Gltf>("assets/sponza/Sponza.gltf").unwrap();
drop(resman); drop(resman);
sponza_model.wait_recurse_dependencies_load();
let sponza_scene = &sponza_model.data_ref() let sponza_scene = &sponza_model.data_ref()
.unwrap().scenes[0]; .unwrap().scenes[0];

View File

@ -441,9 +441,9 @@ fn gen_enum_variant_type(enum_id: &proc_macro2::Ident, data: &DataEnum) -> proc_
let vty = VariantType::from(var); let vty = VariantType::from(var);
match vty { match vty {
VariantType::Struct => quote! { #variant_ident => EnumType::Struct }, VariantType::Struct => quote! { #variant_ident => lyra_engine::reflect::EnumType::Struct },
VariantType::Tuple => quote! { #variant_ident => EnumType::Tuple }, VariantType::Tuple => quote! { #variant_ident => lyra_engine::reflect::EnumType::Tuple },
VariantType::Unit => quote! { #variant_ident => EnumType::Unit }, VariantType::Unit => quote! { #variant_ident => lyra_engine::reflect::EnumType::Unit },
} }
}); });

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] } lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect" }
lyra-math = { path = "../lyra-math" } lyra-math = { path = "../lyra-math" }
anyhow = "1.0.75" anyhow = "1.0.75"
base64 = "0.21.4" base64 = "0.21.4"
@ -26,3 +27,6 @@ tracing = "0.1.37"
uuid = { version = "1.4.1", features = ["v4"] } uuid = { version = "1.4.1", features = ["v4"] }
instant = "0.1" instant = "0.1"
async-std = "1.12.0" async-std = "1.12.0"
[dev-dependencies]
rand = "0.8.5"

View File

@ -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<LoaderError>,
},
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)
}
}

View File

@ -1,12 +1,14 @@
use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}}; use std::{collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use gltf::texture::{MagFilter, MinFilter}; 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 crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode};
use super::loader::GltfLoadContext; use super::loader::GltfLoadContext;
/// PBR metallic roughness /// PBR metallic roughness
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default, Reflect)]
pub struct PbrRoughness { pub struct PbrRoughness {
/// The rgba base color of the PBR material /// The rgba base color of the PBR material
pub base_color: [f32; 4], pub base_color: [f32; 4],
@ -54,7 +56,7 @@ impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
/// The alpha rendering mode of a material. /// The alpha rendering mode of a material.
/// This is essentially a re-export of gltf::material::AlphaMode /// 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 { pub enum AlphaMode {
/// The alpha value is ignored and the rendered output is fully opaque. /// The alpha value is ignored and the rendered output is fully opaque.
#[default] #[default]
@ -181,6 +183,33 @@ pub struct Material {
pub specular: Option<Specular>, pub specular: Option<Specular>,
} }
impl ResourceData for Material {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
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)] #[allow(dead_code)]
impl Material { impl Material {
/// Get a uri's identifier /// Get a uri's identifier

View File

@ -1,11 +1,15 @@
use std::collections::HashMap; 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; use super::Material;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, Reflect)]
pub enum MeshIndices { pub enum MeshIndices {
//U8(Vec<u8>), //U8(Vec<u8>),
U16(Vec<u16>), U16(Vec<u16>),
@ -68,7 +72,7 @@ impl VertexAttributeData {
} }
} }
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Reflect)]
pub enum MeshVertexAttribute { pub enum MeshVertexAttribute {
Position, Position,
Normals, Normals,
@ -90,6 +94,23 @@ pub struct Mesh {
pub material: Option<ResHandle<Material>>, pub material: Option<ResHandle<Material>>,
} }
impl ResourceData for Mesh {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
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 { impl Mesh {
pub fn add_attribute(&mut self, attribute: MeshVertexAttribute, data: VertexAttributeData) { pub fn add_attribute(&mut self, attribute: MeshVertexAttribute, data: VertexAttributeData) {
self.attributes.insert(attribute, data); self.attributes.insert(attribute, data);

View File

@ -3,6 +3,7 @@ pub use loader::*;
pub mod material; pub mod material;
use lyra_math::Transform; use lyra_math::Transform;
use crate::ResourceData;
pub use material::*; pub use material::*;
pub mod mesh; pub mod mesh;
@ -21,6 +22,36 @@ pub struct Gltf {
pub meshes: Vec<ResHandle<Mesh>>, pub meshes: Vec<ResHandle<Mesh>>,
} }
impl ResourceData for Gltf {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
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 { impl Gltf {
/// Collects all Gltf meshes and gets their world Transform. /// Collects all Gltf meshes and gets their world Transform.
pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> { pub fn collect_world_meshes(&self) -> Vec<(ResHandle<Mesh>, Transform)> {

View File

@ -1,4 +1,5 @@
use lyra_math::Transform; use lyra_math::Transform;
use crate::{optionally_add_to_dep, ResourceData, UntypedResHandle};
use super::Mesh; use super::Mesh;
use crate::ResHandle; use crate::ResHandle;
@ -12,12 +13,51 @@ pub struct GltfNode {
pub children: Vec<GltfNode>, pub children: Vec<GltfNode>,
} }
impl ResourceData for GltfNode {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let mut deps: Vec<UntypedResHandle> = 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 /// A Scene in a Gltf file
#[derive(Clone)] #[derive(Clone)]
pub struct GltfScene { pub struct GltfScene {
pub nodes: Vec<GltfNode>, pub nodes: Vec<GltfNode>,
} }
impl ResourceData for GltfScene {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
let deps: Vec<UntypedResHandle> = 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 { impl GltfScene {
fn collect_node(&self, parent_node: &GltfNode, node: &GltfNode) -> Vec<(ResHandle<Mesh>, Transform)> { fn collect_node(&self, parent_node: &GltfNode, node: &GltfNode) -> Vec<(ResHandle<Mesh>, Transform)> {
let mut v = vec![]; let mut v = vec![];

View File

@ -7,6 +7,9 @@ pub use resource::*;
mod texture; mod texture;
pub use texture::*; pub use texture::*;
mod dep_state;
pub use dep_state::*;
pub mod loader; pub mod loader;
pub mod gltf; pub mod gltf;
@ -24,4 +27,8 @@ pub(crate) mod lyra_engine {
pub(crate) mod ecs { pub(crate) mod ecs {
pub use lyra_ecs::*; pub use lyra_ecs::*;
} }
pub(crate) mod reflect {
pub use lyra_reflect::*;
}
} }

View File

@ -2,7 +2,7 @@ use std::{ffi::OsStr, path::Path, sync::Arc};
use async_std::io::ReadExt; use async_std::io::ReadExt;
use image::ImageError; use image::ImageError;
use tracing::trace; use tracing::debug;
use crate::{Image, ResHandle, ResourceData, ResourceManager}; use crate::{Image, ResHandle, ResourceData, ResourceManager};
@ -87,14 +87,14 @@ impl ResourceLoader for ImageLoader {
fn load_bytes(&self, _resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture { fn load_bytes(&self, _resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture {
Box::pin(async move { Box::pin(async move {
trace!("Loading {} bytes as an image", length);
let image = image::load_from_memory(&bytes[offset..(length-offset)]) let image = image::load_from_memory(&bytes[offset..(length-offset)])
.map_err(|e| match e { .map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e), ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()), _ => LoaderError::DecodingError(e.into()),
})?; })?;
let image = Image::from(image); let image = Image::from(image);
debug!("Finished loading image ({} bytes)", length);
Ok(Box::new(image) as Box<dyn ResourceData>) Ok(Box::new(image) as Box<dyn ResourceData>)
}) })
} }

View File

@ -1,31 +1,49 @@
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 lyra_ecs::Component;
use crate::{loader::LoaderError, lyra_engine}; use crate::{loader::LoaderError, lyra_engine, DependencyState};
use uuid::Uuid; use uuid::Uuid;
use crate::ResourceStorage; use crate::ResourceStorage;
pub fn optionally_add_to_dep<R: ResourceData>(deps: &mut Vec<UntypedResHandle>, handle: &Option<ResHandle<R>>) {
if let Some(h) = handle {
deps.push(h.untyped_clone());
}
}
/// A trait that that each resource type should implement. /// A trait that that each resource type should implement.
pub trait ResourceData: Send + Sync + Any + 'static { pub trait ResourceData: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any; fn as_any_mut(&mut self) -> &mut dyn Any;
fn type_id(&self) -> TypeId;
/// Collect the dependencies of the Resource.
fn dependencies(&self) -> Vec<UntypedResHandle>;
/// 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<UntypedResHandle> {
let deps = self.dependencies();
let mut all_deps = deps.clone();
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);
},
_ => {}
}
} }
impl<T: Send + Sync + Any + 'static> ResourceData for T { all_deps
fn as_any(&self) -> &dyn Any { }
self
} }
fn as_any_mut(&mut self) -> &mut dyn Any { //impl<T: Send + Sync + Reflect> ResourceData for T { }
self
}
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
pub enum ResourceState { pub enum ResourceState {
Loading, Loading,
@ -33,8 +51,24 @@ pub enum ResourceState {
Ready(Box<dyn ResourceData>), Ready(Box<dyn ResourceData>),
} }
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> { pub struct ResourceDataRef<'a, T> {
guard: std::sync::RwLockReadGuard<'a, Resource<T>>, guard: std::sync::RwLockReadGuard<'a, UntypedResource>,
_marker: PhantomData<T>,
} }
impl<'a, T: 'static> std::ops::Deref for ResourceDataRef<'a, T> { 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<T> { pub struct UntypedResource {
pub(crate) version: usize, pub(crate) version: usize,
pub(crate) state: ResourceState, pub(crate) state: ResourceState,
uuid: Uuid, uuid: Uuid,
path: Option<String>, path: Option<String>,
pub(crate) is_watched: bool, pub(crate) is_watched: bool,
_marker: PhantomData<T>, /// can be used to wait for the resource to load.
pub(crate) condvar: Arc<(Mutex<bool>, Condvar)>,
}
#[derive(Clone)]
pub struct UntypedResHandle{
pub(crate) res: Arc<RwLock<UntypedResource>>,
#[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<UntypedResource> {
self.res.read().unwrap()
}
pub fn write(&self) -> RwLockWriteGuard<UntypedResource> {
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<String> {
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<T: ResourceData>(&self) -> ResHandle<T> {
ResHandle {
handle: self.clone(),
_marker: PhantomData::<T>,
}
}
} }
/// A handle to a resource. /// A handle to a resource.
@ -69,89 +215,69 @@ pub struct Resource<T> {
/// and has a write lock on the data. This means that most of the time, it is not blocking. /// and has a write lock on the data. This means that most of the time, it is not blocking.
#[derive(Component)] #[derive(Component)]
pub struct ResHandle<T: 'static> { pub struct ResHandle<T: 'static> {
pub(crate) data: Arc<RwLock<Resource<T>>>, pub(crate) handle: UntypedResHandle,
_marker: PhantomData<T>,
} }
impl<T> Clone for ResHandle<T> { impl<T> Clone for ResHandle<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { data: self.data.clone() } Self {
handle: self.handle.clone(),
_marker: PhantomData::<T>
}
}
}
impl<T> Deref for ResHandle<T> {
type Target = UntypedResHandle;
fn deref(&self) -> &Self::Target {
&self.handle
}
}
impl<T> DerefMut for ResHandle<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.handle
} }
} }
impl<T: ResourceData> ResHandle<T> { impl<T: ResourceData> ResHandle<T> {
pub fn new_loading(path: Option<&str>) -> Self { pub fn new_loading(path: Option<&str>) -> Self {
let res_version = Resource { let res_version = UntypedResource {
version: 0, version: 0,
path: path.map(str::to_string), path: path.map(str::to_string),
state: ResourceState::Loading, state: ResourceState::Loading,
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
is_watched: false, is_watched: false,
_marker: PhantomData::<T> condvar: Arc::new((Mutex::new(false), Condvar::new())),
}; };
Self { Self {
data: Arc::new(RwLock::new(res_version)), handle: UntypedResHandle::new(res_version, TypeId::of::<T>()),
_marker: PhantomData::<T>,
} }
} }
/// Create the resource with data, its assumed the state is `Ready` /// Create the resource with data, its assumed the state is `Ready`
pub fn new_ready(path: Option<&str>, data: T) -> Self { pub fn new_ready(path: Option<&str>, data: T) -> Self {
let res_version = Resource { let han = Self::new_loading(path);
version: 0, han.set_state(ResourceState::Ready(Box::new(data)));
path: path.map(str::to_string), han
state: ResourceState::Ready(Box::new(data)),
uuid: Uuid::new_v4(),
is_watched: false,
_marker: PhantomData::<T>
};
Self {
data: Arc::new(RwLock::new(res_version)),
}
} }
/// Returns a boolean indicating if this resource's path is being watched. /// Retrieve an untyped clone of the handle
pub fn is_watched(&self) -> bool { pub fn untyped_clone(&self) -> UntypedResHandle {
let d = self.data.read().expect("Resource mutex was poisoned!"); self.handle.clone()
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<String> {
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
} }
/// Get a reference to the data in the resource /// Get a reference to the data in the resource
pub fn data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> { pub fn data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
if self.is_loaded() { if self.is_loaded() {
let d = self.data.read().expect("Resource mutex was poisoned!"); let d = self.handle.read();
Some(ResourceDataRef { Some(ResourceDataRef {
guard: d guard: d,
_marker: PhantomData::<T>
}) })
} else { } else {
None None
@ -177,34 +303,185 @@ impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> {
} }
fn set_watched(&self, watched: bool) { fn set_watched(&self, watched: bool) {
let mut w = self.data.write().unwrap(); let mut d = self.handle.write();
w.is_watched = watched; d.is_watched = watched;
} }
fn version(&self) -> usize { fn version(&self) -> usize {
self.version() self.handle.version()
} }
fn uuid(&self) -> Uuid { fn uuid(&self) -> Uuid {
self.uuid() self.handle.uuid()
} }
fn path(&self) -> Option<String> { fn path(&self) -> Option<String> {
self.path() self.handle.path()
} }
fn is_watched(&self) -> bool { fn is_watched(&self) -> bool {
self.is_watched() self.handle.is_watched()
} }
fn is_loaded(&self) -> bool { fn is_loaded(&self) -> bool {
self.is_loaded() self.handle.is_loaded()
} }
fn set_state(&self, new: ResourceState) { 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; 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<crate::UntypedResHandle> {
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<dyn ResourceData>)
})
}
fn load_bytes(&self, _: crate::ResourceManager, _: Vec<u8>, _: usize, _: usize) -> crate::loader::PinedBoxLoaderFuture {
unreachable!()
}
fn create_erased_handle(&self) -> std::sync::Arc<dyn crate::ResourceStorage> {
Arc::from(ResHandle::<SimpleDepend>::new_loading(None))
}
}
struct SimpleResource {
depend_a: ResHandle<SimpleDepend>,
}
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<crate::UntypedResHandle> {
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::<SimpleDepend>(depend_path).unwrap();
let simple = SimpleResource {
depend_a: depend_han,
};
Ok(Box::new(simple) as Box<dyn ResourceData>)
})
}
fn load_bytes(&self, _: crate::ResourceManager, _: Vec<u8>, _: usize, _: usize) -> crate::loader::PinedBoxLoaderFuture {
unreachable!()
}
fn create_erased_handle(&self) -> std::sync::Arc<dyn crate::ResourceStorage> {
Arc::from(ResHandle::<SimpleResource>::new_loading(None))
}
}
#[test]
fn recursive() {
let man = ResourceManager::new();
man.register_loader::<SlowSimpleDependLoader>();
man.register_loader::<SlowSimpleResourceLoader>();
let res = man.request::<SimpleResource>("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());
}
} }

View File

@ -7,7 +7,7 @@ use notify_debouncer_full::{DebouncedEvent, FileIdMap};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; 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. /// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`] /// Implemented for [`ResHandle<T>`]
@ -61,6 +61,7 @@ pub struct ResourceWatcher {
/// The state of the ResourceManager /// The state of the ResourceManager
pub struct ResourceManagerState { pub struct ResourceManagerState {
resources: HashMap<String, Arc<dyn ResourceStorage>>, resources: HashMap<String, Arc<dyn ResourceStorage>>,
uuid_resources: HashMap<Uuid, Arc<dyn ResourceStorage>>,
loaders: Vec<Arc<dyn ResourceLoader>>, loaders: Vec<Arc<dyn ResourceLoader>>,
watchers: HashMap<String, ResourceWatcher>, watchers: HashMap<String, ResourceWatcher>,
} }
@ -79,6 +80,7 @@ impl Default for ResourceManager {
inner: Arc::new(RwLock::new( inner: Arc::new(RwLock::new(
ResourceManagerState { ResourceManagerState {
resources: HashMap::new(), resources: HashMap::new(),
uuid_resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ], loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
watchers: HashMap::new(), watchers: HashMap::new(),
} }
@ -121,7 +123,7 @@ impl ResourceManager {
/// handle to check if the resource is loaded. /// handle to check if the resource is loaded.
pub fn request<T>(&self, path: &str) -> Result<ResHandle<T>, RequestError> pub fn request<T>(&self, path: &str) -> Result<ResHandle<T>, RequestError>
where where
T: Send + Sync + Any + 'static T: ResourceData
{ {
let mut state = self.state_mut(); let mut state = self.state_mut();
match state.resources.get(&path.to_string()) { match state.resources.get(&path.to_string()) {
@ -146,18 +148,20 @@ impl ResourceManager {
task::spawn(async move { task::spawn(async move {
match res.await { match res.await {
Ok(data) => { Ok(data) => {
let mut d = thand.data.write().unwrap(); let mut d = thand.write();
d.state = ResourceState::Ready(data); d.state = ResourceState::Ready(data);
d.condvar.1.notify_all();
} }
Err(err) => { Err(err) => {
let mut d = thand.data.write().unwrap(); let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err)); d.state = ResourceState::Error(Arc::new(err));
} }
} }
}); });
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone()); let res: Arc<dyn ResourceStorage> = 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) Ok(handle)
} else { } else {
@ -174,13 +178,13 @@ impl ResourceManager {
/// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource"); /// 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> { pub fn request_raw(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
let inner = self.inner.write().unwrap(); let mut state = self.state_mut();
match inner.resources.get(&path.to_string()) { match state.resources.get(&path.to_string()) {
Some(res) => { Some(res) => {
Ok(res.clone()) Ok(res.clone())
}, },
None => { None => {
if let Some(loader) = inner.loaders.iter() if let Some(loader) = state.loaders.iter()
.find(|l| l.does_support_file(path)) { .find(|l| l.does_support_file(path)) {
// Load the resource and store it // Load the resource and store it
@ -202,6 +206,9 @@ impl ResourceManager {
} }
}); });
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone());
state.uuid_resources.insert(res.uuid(), res);
Ok(handle) Ok(handle)
} else { } else {
Err(RequestError::UnsupportedFileExtension(path.to_string())) Err(RequestError::UnsupportedFileExtension(path.to_string()))
@ -225,7 +232,9 @@ impl ResourceManager {
/// stored with [`ResourceManager::request`] to return `Some`. /// stored with [`ResourceManager::request`] to return `Some`.
pub fn request_uuid<T: Send + Sync + 'static>(&self, uuid: &Uuid) -> Option<ResHandle<T>> { pub fn request_uuid<T: Send + Sync + 'static>(&self, uuid: &Uuid) -> Option<ResHandle<T>> {
let state = self.state(); 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) => { Some(res) => {
let res = res.clone().as_arc_any(); let res = res.clone().as_arc_any();
let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource"); let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
@ -246,7 +255,7 @@ impl ResourceManager {
/// Returns: The `Arc` to the now stored resource /// 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> pub fn load_bytes<T>(&self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<ResHandle<T>, RequestError>
where where
T: Send + Sync + Any + 'static T: ResourceData
{ {
let mut state = self.state_mut(); let mut state = self.state_mut();
if let Some(loader) = state.loaders.iter() if let Some(loader) = state.loaders.iter()
@ -259,18 +268,19 @@ impl ResourceManager {
task::spawn(async move { task::spawn(async move {
match res.await { match res.await {
Ok(data) => { Ok(data) => {
let mut d = thand.data.write().unwrap(); let mut d = thand.write();
d.state = ResourceState::Ready(data); d.state = ResourceState::Ready(data);
} }
Err(err) => { Err(err) => {
let mut d = thand.data.write().unwrap(); let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err)); d.state = ResourceState::Error(Arc::new(err));
} }
} }
}); });
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone()); let res: Arc<dyn ResourceStorage> = 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) Ok(handle)
} else { } else {
@ -376,12 +386,12 @@ impl ResourceManager {
task::spawn(async move { task::spawn(async move {
match res.await { match res.await {
Ok(data) => { Ok(data) => {
let mut d = thand.data.write().unwrap(); let mut d = thand.write();
d.state = ResourceState::Ready(data); d.state = ResourceState::Ready(data);
d.version += 1; d.version += 1;
} }
Err(err) => { Err(err) => {
let mut d = thand.data.write().unwrap(); let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err)); d.state = ResourceState::Error(Arc::new(err));
} }
} }
@ -444,7 +454,8 @@ pub(crate) mod tests {
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
assert!(!res.is_loaded()); 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 // shouldn't panic because of the loop
res.data_ref().unwrap(); res.data_ref().unwrap();
@ -455,10 +466,10 @@ pub(crate) mod tests {
fn ensure_single() { fn ensure_single() {
let man = ResourceManager::new(); let man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&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::<Texture>(&get_image("squiggles.png")).unwrap(); let resagain = man.request::<Texture>(&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 /// 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 // 1 second should be enough to run into an error
std::thread::sleep(Duration::from_secs(1)); std::thread::sleep(Duration::from_secs(1));
//busy_wait_resource(&res, 10.0); //busy_wait_resource(&res, 10.0);
let state = &res.data.read().unwrap().state; let state = &res.read().state;
assert!( assert!(
match state { match state {

View File

@ -2,20 +2,23 @@ use std::ops::{Deref, DerefMut};
//pub use gltf::texture::{MagFilter, MinFilter, WrappingMode}; //pub use gltf::texture::{MagFilter, MinFilter, WrappingMode};
use image::DynamicImage; use image::DynamicImage;
use lyra_reflect::Reflect;
use crate::lyra_engine;
use crate::ResHandle; use crate::ResHandle;
use crate::ResourceData;
/// The filter mode of the sampler. /// The filter mode of the sampler.
/// ///
/// This is used for minification, magnification, and mipmap filters /// This is used for minification, magnification, and mipmap filters
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq, Reflect)]
pub enum FilterMode { pub enum FilterMode {
Nearest, Nearest,
Linear, Linear,
} }
/// The wrapping mode of the Texture coordinates /// The wrapping mode of the Texture coordinates
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq, Reflect)]
pub enum WrappingMode { pub enum WrappingMode {
ClampToEdge, ClampToEdge,
MirroredRepeat, MirroredRepeat,
@ -23,7 +26,7 @@ pub enum WrappingMode {
} }
/// The descriptor of the sampler for a Texture. /// The descriptor of the sampler for a Texture.
#[derive(Clone)] #[derive(Clone, Reflect)]
pub struct TextureSampler { pub struct TextureSampler {
pub mag_filter: Option<FilterMode>, pub mag_filter: Option<FilterMode>,
pub min_filter: Option<FilterMode>, pub min_filter: Option<FilterMode>,
@ -33,8 +36,24 @@ pub struct TextureSampler {
pub wrap_w: WrappingMode, pub wrap_w: WrappingMode,
} }
#[derive(Clone)] #[derive(Clone, Reflect)]
pub struct Image(DynamicImage); pub struct Image(#[reflect(skip)] DynamicImage);
impl ResourceData for Image {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
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 { impl Deref for Image {
type Target = DynamicImage; type Target = DynamicImage;
@ -62,6 +81,22 @@ pub struct Texture {
pub sampler: Option<TextureSampler>, pub sampler: Option<TextureSampler>,
} }
impl ResourceData for Texture {
fn dependencies(&self) -> Vec<crate::UntypedResHandle> {
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 { impl Texture {
/// Create a texture from an image. /// Create a texture from an image.
pub fn from_image(image: ResHandle<Image>) -> Self { pub fn from_image(image: ResHandle<Image>) -> Self {

View File

@ -4,7 +4,7 @@ use crossbeam::channel::Receiver;
use lyra_ecs::World; use lyra_ecs::World;
use notify_debouncer_full::DebouncedEvent; use notify_debouncer_full::DebouncedEvent;
use crate::{loader::ResourceLoader, RequestError, ResHandle, ResourceManager}; use crate::{loader::ResourceLoader, RequestError, ResHandle, ResourceData, ResourceManager};
pub trait WorldAssetExt { pub trait WorldAssetExt {
/// Register a resource loader with the resource manager. /// Register a resource loader with the resource manager.
@ -15,7 +15,7 @@ pub trait WorldAssetExt {
/// Request a resource from the resource manager. /// Request a resource from the resource manager.
fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError> fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where 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. /// 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<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>>; fn watch_res(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>>;
@ -44,7 +44,7 @@ impl WorldAssetExt for World {
fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError> fn request_res<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
where where
T: Send + Sync + Any + 'static T: ResourceData
{ {
let man = self.get_resource_or_default::<ResourceManager>(); let man = self.get_resource_or_default::<ResourceManager>();
man.request(path) man.request(path)