resource: implement waiting for resource dependencies to be loaded
This commit is contained in:
parent
4a285e5866
commit
aa3a4a17d7
|
@ -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",
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
|
@ -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 },
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)> {
|
||||||
|
|
|
@ -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![];
|
||||||
|
|
|
@ -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::*;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue