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",
"lyra-ecs",
"lyra-math",
"lyra-reflect",
"mime",
"notify",
"notify-debouncer-full",
"percent-encoding",
"rand 0.8.5",
"thiserror",
"tracing",
"uuid",

View File

@ -110,6 +110,7 @@ async fn main() {
let sponza_model = resman.request::<Gltf>("assets/sponza/Sponza.gltf").unwrap();
drop(resman);
sponza_model.wait_recurse_dependencies_load();
let sponza_scene = &sponza_model.data_ref()
.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);
match vty {
VariantType::Struct => quote! { #variant_ident => EnumType::Struct },
VariantType::Tuple => quote! { #variant_ident => EnumType::Tuple },
VariantType::Unit => quote! { #variant_ident => EnumType::Unit },
VariantType::Struct => quote! { #variant_ident => lyra_engine::reflect::EnumType::Struct },
VariantType::Tuple => quote! { #variant_ident => lyra_engine::reflect::EnumType::Tuple },
VariantType::Unit => quote! { #variant_ident => lyra_engine::reflect::EnumType::Unit },
}
});

View File

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

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 gltf::texture::{MagFilter, MinFilter};
use lyra_reflect::Reflect;
use crate::{lyra_engine, optionally_add_to_dep, ResourceData};
use crate::{util, FilterMode, Image, ResHandle, Texture, TextureSampler, WrappingMode};
use super::loader::GltfLoadContext;
/// PBR metallic roughness
#[derive(Clone, Debug, Default)]
#[derive(Clone, Debug, Default, Reflect)]
pub struct PbrRoughness {
/// The rgba base color of the PBR material
pub base_color: [f32; 4],
@ -54,7 +56,7 @@ impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
/// The alpha rendering mode of a material.
/// This is essentially a re-export of gltf::material::AlphaMode
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default, Reflect)]
pub enum AlphaMode {
/// The alpha value is ignored and the rendered output is fully opaque.
#[default]
@ -181,6 +183,33 @@ pub struct Material {
pub specular: Option<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)]
impl Material {
/// Get a uri's identifier

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,40 +1,74 @@
use std::{any::{Any, TypeId}, marker::PhantomData, sync::{Arc, RwLock}};
use std::{any::{Any, TypeId}, marker::PhantomData, ops::{Deref, DerefMut}, sync::{Arc, Condvar, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}};
use lyra_ecs::Component;
use crate::{loader::LoaderError, lyra_engine};
use crate::{loader::LoaderError, lyra_engine, DependencyState};
use uuid::Uuid;
use crate::ResourceStorage;
pub fn optionally_add_to_dep<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.
pub trait ResourceData: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any;
fn as_any_mut(&mut self) -> &mut dyn Any;
fn type_id(&self) -> TypeId;
}
impl<T: Send + Sync + Any + 'static> ResourceData for T {
fn as_any(&self) -> &dyn Any {
self
}
/// Collect the dependencies of the Resource.
fn dependencies(&self) -> Vec<UntypedResHandle>;
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
/// Recursively collect the dependencies of the Resource.
///
/// If a dependency has a child dependency, it will not show up in this list until its
/// parent is loaded.
fn recur_dependencies(&self) -> Vec<UntypedResHandle> {
let deps = self.dependencies();
let mut all_deps = deps.clone();
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
for dep in deps.into_iter() {
let dep = dep.read();
match &dep.state {
ResourceState::Ready(data) => {
let mut deps_dep = data.dependencies();
all_deps.append(&mut deps_dep);
},
_ => {}
}
}
all_deps
}
}
//impl<T: Send + Sync + Reflect> ResourceData for T { }
pub enum ResourceState {
Loading,
Error(Arc<LoaderError>),
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> {
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> {
@ -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) state: ResourceState,
uuid: Uuid,
path: Option<String>,
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.
@ -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.
#[derive(Component)]
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> {
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> {
pub fn new_loading(path: Option<&str>) -> Self {
let res_version = Resource {
let res_version = UntypedResource {
version: 0,
path: path.map(str::to_string),
state: ResourceState::Loading,
uuid: Uuid::new_v4(),
is_watched: false,
_marker: PhantomData::<T>
condvar: Arc::new((Mutex::new(false), Condvar::new())),
};
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`
pub fn new_ready(path: Option<&str>, data: T) -> Self {
let res_version = Resource {
version: 0,
path: path.map(str::to_string),
state: ResourceState::Ready(Box::new(data)),
uuid: Uuid::new_v4(),
is_watched: false,
_marker: PhantomData::<T>
};
Self {
data: Arc::new(RwLock::new(res_version)),
}
let han = Self::new_loading(path);
han.set_state(ResourceState::Ready(Box::new(data)));
han
}
/// Returns a boolean indicating if this resource's path is being watched.
pub fn is_watched(&self) -> bool {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.is_watched
}
/// Returns a boolean indicating if this resource is loaded
pub fn is_loaded(&self) -> bool {
let d = self.data.read().expect("Resource mutex was poisoned!");
matches!(d.state, ResourceState::Ready(_))
}
/// Returns the current state of the resource.
/* pub fn state(&self) -> &ResourceState {
let d = self.data.read().expect("Resource mutex was poisoned!");
&d.state
} */
/// Returns the uuid of the resource.
pub fn uuid(&self) -> Uuid {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.uuid
}
pub fn path(&self) -> Option<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
/// Retrieve an untyped clone of the handle
pub fn untyped_clone(&self) -> UntypedResHandle {
self.handle.clone()
}
/// Get a reference to the data in the resource
pub fn data_ref<'a>(&'a self) -> Option<ResourceDataRef<'a, T>> {
if self.is_loaded() {
let d = self.data.read().expect("Resource mutex was poisoned!");
let d = self.handle.read();
Some(ResourceDataRef {
guard: d
guard: d,
_marker: PhantomData::<T>
})
} else {
None
@ -177,34 +303,185 @@ impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> {
}
fn set_watched(&self, watched: bool) {
let mut w = self.data.write().unwrap();
w.is_watched = watched;
let mut d = self.handle.write();
d.is_watched = watched;
}
fn version(&self) -> usize {
self.version()
self.handle.version()
}
fn uuid(&self) -> Uuid {
self.uuid()
self.handle.uuid()
}
fn path(&self) -> Option<String> {
self.path()
self.handle.path()
}
fn is_watched(&self) -> bool {
self.is_watched()
self.handle.is_watched()
}
fn is_loaded(&self) -> bool {
self.is_loaded()
self.handle.is_loaded()
}
fn set_state(&self, new: ResourceState) {
let mut d = self.data.write().expect("Resource mutex was poisoned!");
let mut d = self.handle.write();
d.state = new;
}
}
#[cfg(test)]
mod tests {
use std::{path::PathBuf, str::FromStr, sync::Arc};
use async_std::task;
use instant::Duration;
use rand::Rng;
use crate::{loader::ResourceLoader, ResHandle, ResourceData, ResourceManager};
#[allow(dead_code)]
struct SimpleDepend {
file_name: String,
ext: String,
}
impl ResourceData for SimpleDepend {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn dependencies(&self) -> Vec<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 uuid::Uuid;
use crate::{gltf::ModelLoader, loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
use crate::{gltf::ModelLoader, loader::{ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceData, ResourceState};
/// A trait for type erased storage of a resource.
/// Implemented for [`ResHandle<T>`]
@ -61,6 +61,7 @@ pub struct ResourceWatcher {
/// The state of the ResourceManager
pub struct ResourceManagerState {
resources: HashMap<String, Arc<dyn ResourceStorage>>,
uuid_resources: HashMap<Uuid, Arc<dyn ResourceStorage>>,
loaders: Vec<Arc<dyn ResourceLoader>>,
watchers: HashMap<String, ResourceWatcher>,
}
@ -79,6 +80,7 @@ impl Default for ResourceManager {
inner: Arc::new(RwLock::new(
ResourceManagerState {
resources: HashMap::new(),
uuid_resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
watchers: HashMap::new(),
}
@ -121,7 +123,7 @@ impl ResourceManager {
/// handle to check if the resource is loaded.
pub fn request<T>(&self, path: &str) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static
T: ResourceData
{
let mut state = self.state_mut();
match state.resources.get(&path.to_string()) {
@ -146,18 +148,20 @@ impl ResourceManager {
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.data.write().unwrap();
let mut d = thand.write();
d.state = ResourceState::Ready(data);
d.condvar.1.notify_all();
}
Err(err) => {
let mut d = thand.data.write().unwrap();
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
let res: Arc<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)
} else {
@ -174,13 +178,13 @@ impl ResourceManager {
/// let res: Arc<ResHandle<T>> = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
/// ```
pub fn request_raw(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
let inner = self.inner.write().unwrap();
match inner.resources.get(&path.to_string()) {
let mut state = self.state_mut();
match state.resources.get(&path.to_string()) {
Some(res) => {
Ok(res.clone())
},
None => {
if let Some(loader) = inner.loaders.iter()
if let Some(loader) = state.loaders.iter()
.find(|l| l.does_support_file(path)) {
// Load the resource and store it
@ -202,6 +206,9 @@ impl ResourceManager {
}
});
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone());
state.uuid_resources.insert(res.uuid(), res);
Ok(handle)
} else {
Err(RequestError::UnsupportedFileExtension(path.to_string()))
@ -225,7 +232,9 @@ impl ResourceManager {
/// stored with [`ResourceManager::request`] to return `Some`.
pub fn request_uuid<T: Send + Sync + 'static>(&self, uuid: &Uuid) -> Option<ResHandle<T>> {
let state = self.state();
match state.resources.get(&uuid.to_string()) {
match state.resources.get(&uuid.to_string())
.or_else(|| state.uuid_resources.get(&uuid))
{
Some(res) => {
let res = res.clone().as_arc_any();
let res: Arc<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
pub fn load_bytes<T>(&self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<ResHandle<T>, RequestError>
where
T: Send + Sync + Any + 'static
T: ResourceData
{
let mut state = self.state_mut();
if let Some(loader) = state.loaders.iter()
@ -259,18 +268,19 @@ impl ResourceManager {
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.data.write().unwrap();
let mut d = thand.write();
d.state = ResourceState::Ready(data);
}
Err(err) => {
let mut d = thand.data.write().unwrap();
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
let res: Arc<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)
} else {
@ -376,12 +386,12 @@ impl ResourceManager {
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.data.write().unwrap();
let mut d = thand.write();
d.state = ResourceState::Ready(data);
d.version += 1;
}
Err(err) => {
let mut d = thand.data.write().unwrap();
let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err));
}
}
@ -444,7 +454,8 @@ pub(crate) mod tests {
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
assert!(!res.is_loaded());
busy_wait_resource(&res, 10.0);
res.wait_for_load();
//busy_wait_resource(&res, 10.0);
// shouldn't panic because of the loop
res.data_ref().unwrap();
@ -455,10 +466,10 @@ pub(crate) mod tests {
fn ensure_single() {
let man = ResourceManager::new();
let res = man.request::<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();
assert_eq!(Arc::strong_count(&resagain.data), 4);
assert_eq!(Arc::strong_count(&resagain.handle.res), 4);
}
/// Ensures that an error is returned when a file that doesn't exist is requested
@ -471,7 +482,7 @@ pub(crate) mod tests {
// 1 second should be enough to run into an error
std::thread::sleep(Duration::from_secs(1));
//busy_wait_resource(&res, 10.0);
let state = &res.data.read().unwrap().state;
let state = &res.read().state;
assert!(
match state {

View File

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

View File

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