Compare commits

..

No commits in common. "4a285e586643b20deba465cd928ef4dabde0e972" and "1d7d13eb7b6510726beb47b691720c126630b8ac" have entirely different histories.

19 changed files with 320 additions and 523 deletions

1
Cargo.lock generated
View File

@ -1839,7 +1839,6 @@ name = "lyra-resource"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std",
"base64 0.21.5", "base64 0.21.5",
"crossbeam", "crossbeam",
"glam", "glam",

View File

@ -1,6 +1,6 @@
use std::ptr::NonNull; use std::ptr::NonNull;
use lyra_engine::{assets::gltf::Gltf, ecs::{system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Transform, Vec3}, render::light::{directional::DirectionalLight, SpotLight}, scene::CameraComponent, DeltaTime}; use lyra_engine::{assets::gltf::Gltf, ecs::{system::{BatchedSystem, Criteria, CriteriaSchedule, IntoSystem}, Component, World}, game::Game, input::{Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource, InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput}, math::{self, Transform, Vec3}, render::light::{directional::DirectionalLight, PointLight, SpotLight}, scene::{CameraComponent, MeshComponent}, DeltaTime};
use lyra_engine::assets::ResourceManager; use lyra_engine::assets::ResourceManager;
mod free_fly_camera; mod free_fly_camera;
@ -89,7 +89,7 @@ async fn main() {
window_options.cursor_visible = false; */ window_options.cursor_visible = false; */
} }
let resman = world.get_resource_mut::<ResourceManager>(); let mut resman = world.get_resource_mut::<ResourceManager>();
//let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap(); //let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
//let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap(); //let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
//let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap(); //let cube_model = resman.request::<Model>("assets/cube-texture-bin.glb").unwrap();

View File

@ -1,4 +1,4 @@
use std::{any::TypeId, cell::Ref, marker::PhantomData}; use std::{any::{Any, TypeId}, cell::Ref, marker::PhantomData};
use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World}; use crate::{query::{AsQuery, Fetch, Query}, Archetype, ComponentColumn, Entity, World};

View File

@ -6,7 +6,7 @@ use lyra_reflect::Reflect;
use crate::{plugin::Plugin, game::GameStages, EventQueue}; use crate::{plugin::Plugin, game::GameStages, EventQueue};
use super::{Button, InputButtons, KeyCode, MouseButton, MouseMotion}; use super::{Button, KeyCode, InputButtons, MouseMotion};
pub trait ActionLabel: Debug { pub trait ActionLabel: Debug {
/// Returns a unique hash of the label. /// Returns a unique hash of the label.
@ -105,6 +105,15 @@ pub enum GamepadInput {
Axis(GamepadAxis), Axis(GamepadAxis),
} }
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MouseButton {
Left,
Right,
Middle,
Other(u16),
}
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum MouseAxis { pub enum MouseAxis {

View File

@ -83,7 +83,7 @@ impl Touches {
/// A mouse button event /// A mouse button event
/// ///
/// Translated `WindowEvent::MouseButton` from `winit` crate /// Translated `WindowEvent::MouseButton` from `winit` crate
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum MouseButton { pub enum MouseButton {
Left, Left,
Right, Right,

View File

@ -1,6 +1,6 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::Ident; use proc_macro2::Ident;
use quote::quote; use quote::{quote, ToTokens};
use syn::{Generics, Path, Attribute, GenericParam, parse_macro_input, DeriveInput, TypeParamBound}; use syn::{Generics, Path, Attribute, GenericParam, parse_macro_input, DeriveInput, TypeParamBound};
mod enum_derive; mod enum_derive;

View File

@ -33,6 +33,9 @@ pub use reflected_list::*;
pub mod dynamic_tuple; pub mod dynamic_tuple;
pub use dynamic_tuple::*; pub use dynamic_tuple::*;
pub mod reflected_field;
pub use reflected_field::*;
pub mod component; pub mod component;
pub use component::*; pub use component::*;

View File

@ -0,0 +1,30 @@
use super::Reflect;
/// A struct that can set fields on different types of reflected values.
pub struct ReflectField<'a> {
val: &'a dyn Reflect,
getter: fn() -> &'a dyn Reflect,
setter: fn(new_val: &'a dyn Reflect),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FieldType {
Struct,
Enum,
Tuple,
}
enum FieldIdent {
Index(usize),
Named(String)
}
impl<'a> ReflectField<'a> {
pub fn new(reflect: &'a dyn Reflect, getter: fn() -> &'a dyn Reflect, setter: fn(new_val: &'a dyn Reflect)) -> Self {
Self {
val: reflect,
getter,
setter
}
}
}

View File

@ -24,5 +24,4 @@ percent-encoding = "2.3.0"
thiserror = "1.0.48" thiserror = "1.0.48"
tracing = "0.1.37" 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"

View File

@ -1,12 +1,12 @@
use std::{ffi::OsStr, path::{Path, PathBuf}, sync::Arc}; use std::{sync::Arc, path::PathBuf};
use glam::{Quat, Vec3}; use glam::{Quat, Vec3};
use instant::Instant; use instant::Instant;
use lyra_math::Transform; use lyra_math::Transform;
use thiserror::Error; use thiserror::Error;
use crate::{gltf::GltfScene, loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, util, ResHandle, ResourceData, ResourceManager, ResourceStorage}; use crate::{gltf::GltfScene, util, LoaderError, ResHandle, ResourceLoader, ResourceManager};
use super::{Gltf, GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData}; use super::{GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData};
use tracing::debug; use tracing::debug;
@ -32,7 +32,7 @@ impl From<ModelLoaderError> for LoaderError {
#[allow(dead_code)] #[allow(dead_code)]
pub(crate) struct GltfLoadContext<'a> { pub(crate) struct GltfLoadContext<'a> {
pub resource_manager: ResourceManager, pub resource_manager: &'a mut ResourceManager,
pub gltf: &'a gltf::Gltf, pub gltf: &'a gltf::Gltf,
/// Path to the gltf /// Path to the gltf
pub gltf_path: &'a str, pub gltf_path: &'a str,
@ -125,7 +125,7 @@ impl ModelLoader {
new_mesh.material = Some(mat.clone()); new_mesh.material = Some(mat.clone());
} }
let handle = ResHandle::new_ready(None, new_mesh); let handle = ResHandle::with_data("", new_mesh);
ctx.resource_manager.store_uuid(handle.clone()); ctx.resource_manager.store_uuid(handle.clone());
node.mesh = Some(handle); node.mesh = Some(handle);
} }
@ -137,21 +137,6 @@ impl ModelLoader {
node node
} }
fn extensions() -> &'static [&'static str] {
&[
"gltf", "glb"
]
}
fn does_support_file(path: &str) -> bool {
match Path::new(path).extension().and_then(OsStr::to_str) {
Some(ext) => {
Self::extensions().contains(&ext)
},
_ => false,
}
}
} }
impl ResourceLoader for ModelLoader { impl ResourceLoader for ModelLoader {
@ -165,92 +150,82 @@ impl ResourceLoader for ModelLoader {
&[] &[]
} }
fn load(&self, resource_manager: ResourceManager, path: &str) -> PinedBoxLoaderFuture { fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
// cant use &str across async // check if the file is supported by this loader
let path = path.to_string(); if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
Box::pin(async move { let mut parent_path = PathBuf::from(path);
// check if the file is supported by this loader parent_path.pop();
if !Self::does_support_file(&path) { let parent_path = parent_path.display().to_string();
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
let mut parent_path = PathBuf::from(&path); let gltf = gltf::Gltf::open(path)?;
parent_path.pop();
let parent_path = parent_path.display().to_string();
let gltf = gltf::Gltf::open(&path)?; let mut use_bin = false;
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
gltf::buffer::Source::Bin => {
use_bin = true;
gltf.blob.as_deref().map(|v| v.to_vec())
.ok_or(ModelLoaderError::MissingBin(path.to_string()))
},
gltf::buffer::Source::Uri(uri) => util::gltf_read_buffer_uri(&parent_path, uri)
.map_err(ModelLoaderError::UriDecodingError),
}).collect();
let mut use_bin = false; let mut gltf_out = super::Gltf::default();
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
gltf::buffer::Source::Bin => {
use_bin = true;
gltf.blob.as_deref().map(|v| v.to_vec())
.ok_or(ModelLoaderError::MissingBin(path.to_string()))
},
gltf::buffer::Source::Uri(uri) => util::gltf_read_buffer_uri(&parent_path, uri)
.map_err(ModelLoaderError::UriDecodingError),
}).collect();
let mut gltf_out = super::Gltf::default(); let mut context = GltfLoadContext {
resource_manager,
gltf: &gltf,
gltf_path: path,
gltf_parent_path: &parent_path,
buffers: &buffers,
};
let mut context = GltfLoadContext { let start_inst = Instant::now();
resource_manager: resource_manager.clone(), let materials: Vec<ResHandle<Material>> = gltf.materials()
gltf: &gltf, .map(|mat| ResHandle::with_data("", Material::from_gltf(&mut context, mat)))
gltf_path: &path, .collect();
gltf_parent_path: &parent_path, let mat_time = Instant::now() - start_inst;
buffers: &buffers, debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
};
for (idx, scene) in gltf.scenes().enumerate() {
let start_inst = Instant::now(); let start_inst = Instant::now();
let materials: Vec<ResHandle<Material>> = gltf.materials() let nodes: Vec<GltfNode> = scene.nodes()
.map(|mat| ResHandle::new_ready(None, Material::from_gltf(&mut context, mat))) .map(|node| ModelLoader::process_node(&mut context, &materials, node))
.collect(); .collect();
let mat_time = Instant::now() - start_inst; let node_time = Instant::now() - start_inst;
debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
debug!("Loaded {} nodes in the scene in {}s", nodes.len(), node_time.as_secs_f32());
for (_idx, scene) in gltf.scenes().enumerate() { for mesh in nodes.iter().map(|n| &n.mesh) {
let start_inst = Instant::now(); if let Some(mesh) = mesh {
let nodes: Vec<GltfNode> = scene.nodes() gltf_out.meshes.push(mesh.clone());
.map(|node| ModelLoader::process_node(&mut context, &materials, node))
.collect();
let node_time = Instant::now() - start_inst;
debug!("Loaded {} nodes in the scene in {}s", nodes.len(), node_time.as_secs_f32());
for mesh in nodes.iter().map(|n| &n.mesh) {
if let Some(mesh) = mesh {
gltf_out.meshes.push(mesh.clone());
}
} }
let scene = GltfScene {
nodes,
};
let scene = ResHandle::new_ready(Some(path.as_str()), scene);
gltf_out.scenes.push(scene);
} }
gltf_out.materials = materials; let scene = GltfScene {
nodes,
Ok(Box::new(gltf_out) as Box<dyn ResourceData>) };
}) let scene = ResHandle::with_data(&format!("{}:Scene{}", path, idx), scene);
gltf_out.scenes.push(scene);
}
gltf_out.materials = materials;
Ok(Arc::new(ResHandle::with_data(path, gltf_out)))
} }
#[allow(unused_variables)] #[allow(unused_variables)]
fn load_bytes(&self, resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture { fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
todo!() todo!()
} }
fn create_erased_handle(&self) -> Arc<dyn ResourceStorage> {
Arc::from(ResHandle::<Gltf>::new_loading(None))
}
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::busy_wait_resource; use crate::{gltf::Gltf, ResourceLoader};
use super::*; use super::*;
fn test_file_path(path: &str) -> String { fn test_file_path(path: &str) -> String {
@ -263,9 +238,10 @@ mod tests {
fn test_loading() { fn test_loading() {
let path = test_file_path("texture-embedded.gltf"); let path = test_file_path("texture-embedded.gltf");
let manager = ResourceManager::new(); let mut manager = ResourceManager::new();
let gltf = manager.request::<Gltf>(&path).unwrap(); let loader = ModelLoader::default();
busy_wait_resource(&gltf, 15.0); let gltf = loader.load(&mut manager, &path).unwrap();
let gltf = Arc::downcast::<ResHandle<Gltf>>(gltf.as_arc_any()).unwrap();
let gltf = gltf.data_ref().unwrap(); let gltf = gltf.data_ref().unwrap();
assert_eq!(gltf.scenes.len(), 1); assert_eq!(gltf.scenes.len(), 1);

View File

@ -271,7 +271,7 @@ impl Material {
wrap_w: WrappingMode::ClampToEdge, wrap_w: WrappingMode::ClampToEdge,
}; };
let handler = ResHandle::new_ready(None, Texture { let handler = ResHandle::with_data("", Texture {
image: tex_img, image: tex_img,
sampler: Some(samp), sampler: Some(samp),
}); });

View File

@ -1,17 +1,18 @@
mod resource_manager; pub mod resource_manager;
pub use resource_manager::*; pub use resource_manager::*;
mod resource; pub mod resource;
pub use resource::*; pub use resource::*;
mod texture; pub mod texture;
pub use texture::*; pub use texture::*;
pub mod loader; pub mod loader;
pub use loader::*;
pub mod gltf; pub mod gltf;
mod world_ext; pub mod world_ext;
pub use world_ext::*; pub use world_ext::*;
pub(crate) mod util; pub(crate) mod util;

View File

@ -1,12 +1,11 @@
use std::{ffi::OsStr, path::Path, sync::Arc}; use std::{fs::File, sync::Arc, io::Read};
use async_std::io::ReadExt;
use image::ImageError; use image::ImageError;
use tracing::trace; use tracing::{debug, trace};
use crate::{Image, ResHandle, ResourceData, ResourceManager}; use crate::{resource::ResHandle, resource_manager::ResourceStorage, Image, ResourceManager};
use super::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}; use super::{LoaderError, ResourceLoader};
impl From<ImageError> for LoaderError { impl From<ImageError> for LoaderError {
fn from(value: ImageError) -> Self { fn from(value: ImageError) -> Self {
@ -18,8 +17,8 @@ impl From<ImageError> for LoaderError {
#[derive(Default)] #[derive(Default)]
pub struct ImageLoader; pub struct ImageLoader;
impl ImageLoader { impl ResourceLoader for ImageLoader {
fn extensions() -> &'static [&'static str] { fn extensions(&self) -> &[&str] {
&[ &[
// the extensions of these are the names of the formats // the extensions of these are the names of the formats
"bmp", "dds", "gif", "ico", "jpeg", "jpg", "png", "qoi", "tga", "tiff", "webp", "bmp", "dds", "gif", "ico", "jpeg", "jpg", "png", "qoi", "tga", "tiff", "webp",
@ -32,21 +31,6 @@ impl ImageLoader {
] ]
} }
fn does_support_file(path: &str) -> bool {
match Path::new(path).extension().and_then(OsStr::to_str) {
Some(ext) => {
Self::extensions().contains(&ext)
},
_ => false,
}
}
}
impl ResourceLoader for ImageLoader {
fn extensions(&self) -> &[&str] {
Self::extensions()
}
fn mime_types(&self) -> &[&str] { fn mime_types(&self) -> &[&str] {
&[ &[
"image/bmp", "image/vnd.ms-dds", "image/gif", "image/x-icon", "image/jpeg", "image/bmp", "image/vnd.ms-dds", "image/gif", "image/x-icon", "image/jpeg",
@ -59,55 +43,48 @@ impl ResourceLoader for ImageLoader {
] ]
} }
fn load(&self, _resource_manager: ResourceManager, path: &str) -> PinedBoxLoaderFuture { fn load(&self, _resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
let path = path.to_string(); // check if the file is supported by this loader
Box::pin(async move { if !self.does_support_file(path) {
// check if the file is supported by this loader return Err(LoaderError::UnsupportedExtension(path.to_string()));
if !Self::does_support_file(&path) { }
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
// read file bytes // read file bytes
let mut file = async_std::fs::File::open(path).await?; let mut file = File::open(path)?;
let mut buf = vec![]; let mut buf = vec![];
file.read_to_end(&mut buf).await?; file.read_to_end(&mut buf)?;
// load the image and construct Resource<Texture> // load the image and construct Resource<Texture>
let image = image::load_from_memory(&buf) let image = image::load_from_memory(&buf)
.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);
let image = Box::new(image) as Box<dyn ResourceData>; let res = ResHandle::with_data(path, image);
Ok(image) Ok(Arc::new(res))
})
} }
fn load_bytes(&self, _resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture { fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
Box::pin(async move { trace!("Loading {} bytes as an image", length);
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); let res = ResHandle::with_data(&uuid::Uuid::new_v4().to_string(), image);
Ok(Box::new(image) as Box<dyn ResourceData>)
}) debug!("Finished loading image of {} bytes", length);
}
fn create_erased_handle(&self) -> Arc<dyn crate::ResourceStorage> { Ok(Arc::new(res))
Arc::from(ResHandle::<Image>::new_loading(None))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use async_std::task;
use super::*; use super::*;
fn get_image(path: &str) -> String { fn get_image(path: &str) -> String {
@ -125,24 +102,15 @@ mod tests {
/// Tests loading an image /// Tests loading an image
#[test] #[test]
fn image_load() { fn image_load() {
let manager = ResourceManager::new(); let mut manager = ResourceManager::new();
let loader = ImageLoader::default(); let loader = ImageLoader::default();
loader.load(&mut manager, &get_image("squiggles.png")).unwrap();
task::block_on(async move {
let r = loader.load(manager, &get_image("squiggles.png")).await.unwrap();
let a = r.as_ref();
a.as_any().downcast_ref::<Image>().unwrap();
});
} }
#[test] #[test]
fn image_load_unsupported() { fn image_load_unsupported() {
let manager = ResourceManager::new(); let mut manager = ResourceManager::new();
let loader = ImageLoader::default(); let loader = ImageLoader::default();
assert!(loader.load(&mut manager, &get_image("squiggles.gltf")).is_err());
task::block_on(async move {
// this file doesn't exist and is also not supported
assert!(loader.load(manager, &get_image("squiggles.jfeh")).await.is_err())
});
} }
} }

View File

@ -1,11 +1,10 @@
mod image; pub mod image;
pub use image::*;
use std::{ffi::OsStr, future::Future, io, path::Path, pin::Pin, sync::Arc}; use std::{io, sync::Arc, path::Path, ffi::OsStr};
use thiserror::Error; use thiserror::Error;
use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager}; use crate::{resource_manager::ResourceStorage, ResourceManager};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LoaderError { pub enum LoaderError {
@ -29,9 +28,7 @@ impl From<io::Error> for LoaderError {
} }
} }
pub type PinedBoxLoaderFuture = Pin<Box<dyn Future<Output = Result<Box<dyn ResourceData>, LoaderError>> + Send>>; pub trait ResourceLoader {
pub trait ResourceLoader: Send + Sync {
/// Returns the extensions that this loader supports. Does not include the `.` /// Returns the extensions that this loader supports. Does not include the `.`
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
/// Returns the mime types that this loader supports. /// Returns the mime types that this loader supports.
@ -53,21 +50,9 @@ pub trait ResourceLoader: Send + Sync {
} }
/// Load a resource from a path. /// Load a resource from a path.
fn load(&self, resource_manager: ResourceManager, path: &str) -> PinedBoxLoaderFuture; fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
/// Load a resource from bytes. /// Load a resource from bytes.
fn load_bytes(&self, resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture; fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
/// Creates and returns a `ResHandle` in a loading state that stores the type that this
/// loader returns.
///
/// This is very simple to implement, you can use this as a template:
/// ```nobuild
/// fn create_erased_handle(&self) -> Arc<dyn crate::ResourceStorage> {
/// // Change the type of the reshandle to match the loader.
/// Arc::from(ResHandle::<Image>::new_loading(None))
/// }
/// ```
fn create_erased_handle(&self) -> Arc<dyn ResourceStorage>;
} }

View File

@ -1,64 +1,37 @@
use std::{any::{Any, TypeId}, marker::PhantomData, sync::{Arc, RwLock}}; use std::{any::Any, sync::{Arc, RwLock}};
use lyra_ecs::Component; use lyra_ecs::Component;
use crate::{loader::LoaderError, lyra_engine}; use crate::lyra_engine;
use uuid::Uuid; use uuid::Uuid;
use crate::ResourceStorage; use crate::ResourceStorage;
/// A trait that that each resource type should implement. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
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
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn type_id(&self) -> TypeId {
TypeId::of::<T>()
}
}
pub enum ResourceState { pub enum ResourceState {
Loading, Loading,
Error(Arc<LoaderError>), Ready,
Ready(Box<dyn ResourceData>),
} }
pub struct ResourceDataRef<'a, T> { pub struct ResourceDataRef<'a, T> {
guard: std::sync::RwLockReadGuard<'a, Resource<T>>, guard: std::sync::RwLockReadGuard<'a, Resource<T>>,
} }
impl<'a, T: 'static> std::ops::Deref for ResourceDataRef<'a, T> { impl<'a, T> std::ops::Deref for ResourceDataRef<'a, T> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
match &self.guard.state { // safety: this struct must only be created if the resource is loaded
ResourceState::Ready(d) => { self.guard.data.as_ref().unwrap()
// for some reason, if I didn't use `.as_ref`, the downcast would fail.
let d = d.as_ref().as_any();
d.downcast_ref::<T>().unwrap()
},
_ => unreachable!() // ResHandler::data_ref shouldn't allow this to run
}
} }
} }
pub struct Resource<T> { pub struct Resource<T> {
path: String,
pub(crate) data: Option<T>,
pub(crate) version: usize, pub(crate) version: usize,
pub(crate) state: ResourceState, pub(crate) state: ResourceState,
uuid: Uuid, uuid: Uuid,
path: Option<String>,
pub(crate) is_watched: bool, pub(crate) is_watched: bool,
_marker: PhantomData<T>,
} }
/// A handle to a resource. /// A handle to a resource.
@ -78,31 +51,16 @@ impl<T> Clone for ResHandle<T> {
} }
} }
impl<T: ResourceData> ResHandle<T> { impl<T> ResHandle<T> {
pub fn new_loading(path: Option<&str>) -> Self {
let res_version = Resource {
version: 0,
path: path.map(str::to_string),
state: ResourceState::Loading,
uuid: Uuid::new_v4(),
is_watched: false,
_marker: PhantomData::<T>
};
Self {
data: Arc::new(RwLock::new(res_version)),
}
}
/// 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 with_data(path: &str, data: T) -> Self {
let res_version = Resource { let res_version = Resource {
path: path.to_string(),
data: Some(data),
version: 0, version: 0,
path: path.map(str::to_string), state: ResourceState::Ready,
state: ResourceState::Ready(Box::new(data)),
uuid: Uuid::new_v4(), uuid: Uuid::new_v4(),
is_watched: false, is_watched: false,
_marker: PhantomData::<T>
}; };
Self { Self {
@ -119,14 +77,20 @@ impl<T: ResourceData> ResHandle<T> {
/// Returns a boolean indicating if this resource is loaded /// Returns a boolean indicating if this resource is loaded
pub fn is_loaded(&self) -> bool { pub fn is_loaded(&self) -> bool {
let d = self.data.read().expect("Resource mutex was poisoned!"); let d = self.data.read().expect("Resource mutex was poisoned!");
matches!(d.state, ResourceState::Ready(_)) d.state == ResourceState::Ready
} }
/// Returns the current state of the resource. /// Returns the current state of the resource.
/* pub fn state(&self) -> &ResourceState { pub fn state(&self) -> ResourceState {
let d = self.data.read().expect("Resource mutex was poisoned!"); let d = self.data.read().expect("Resource mutex was poisoned!");
&d.state d.state
} */ }
/// Returns the path that the resource was loaded from.
pub fn path(&self) -> String {
let d = self.data.read().expect("Resource mutex was poisoned!");
d.path.to_string()
}
/// Returns the uuid of the resource. /// Returns the uuid of the resource.
pub fn uuid(&self) -> Uuid { pub fn uuid(&self) -> Uuid {
@ -134,11 +98,6 @@ impl<T: ResourceData> ResHandle<T> {
d.uuid 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 /// Retrieves the current version of the resource. This gets incremented when the resource
/// is reloaded. /// is reloaded.
pub fn version(&self) -> usize { pub fn version(&self) -> usize {
@ -181,16 +140,20 @@ impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> {
w.is_watched = watched; w.is_watched = watched;
} }
fn path(&self) -> String {
self.path()
}
fn version(&self) -> usize { fn version(&self) -> usize {
self.version() self.version()
} }
fn uuid(&self) -> Uuid { fn state(&self) -> ResourceState {
self.uuid() self.state()
} }
fn path(&self) -> Option<String> { fn uuid(&self) -> Uuid {
self.path() self.uuid()
} }
fn is_watched(&self) -> bool { fn is_watched(&self) -> bool {
@ -200,11 +163,4 @@ impl<T: Send + Sync + 'static> ResourceStorage for ResHandle<T> {
fn is_loaded(&self) -> bool { fn is_loaded(&self) -> bool {
self.is_loaded() self.is_loaded()
} }
fn set_state(&self, new: ResourceState) {
let mut d = self.data.write().expect("Resource mutex was poisoned!");
d.state = new;
}
} }

View File

@ -1,13 +1,12 @@
use std::{any::Any, collections::HashMap, path::Path, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration}; use std::{sync::{Arc, RwLock}, collections::HashMap, any::Any, path::Path, time::Duration};
use async_std::task;
use crossbeam::channel::Receiver; use crossbeam::channel::Receiver;
use notify::{Watcher, RecommendedWatcher}; use notify::{Watcher, RecommendedWatcher};
use notify_debouncer_full::{DebouncedEvent, FileIdMap}; 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::{image::ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, 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>`]
@ -20,30 +19,24 @@ pub trait ResourceStorage: Send + Sync + Any + 'static {
/// This is used internally. /// This is used internally.
fn set_watched(&self, watched: bool); fn set_watched(&self, watched: bool);
fn path(&self) -> String;
fn version(&self) -> usize; fn version(&self) -> usize;
fn state(&self) -> ResourceState;
fn uuid(&self) -> Uuid; fn uuid(&self) -> Uuid;
fn path(&self) -> Option<String>;
fn is_watched(&self) -> bool; fn is_watched(&self) -> bool;
fn is_loaded(&self) -> bool; fn is_loaded(&self) -> bool;
fn set_state(&self, new: ResourceState);
} }
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum RequestError { pub enum RequestError {
#[error("{0}")] #[error("{0}")]
Loader(LoaderError), Loader(LoaderError),
#[error("The file extension is unsupported: '{0}'")] #[error("The file extension is unsupported: '{0}'")]
UnsupportedFileExtension(String), UnsupportedFileExtension(String),
#[error("The mimetype is unsupported: '{0}'")] #[error("The mimetype is unsupported: '{0}'")]
UnsupportedMime(String), UnsupportedMime(String),
#[error("The identifier is not found: '{0}'")] #[error("The identifier is not found: '{0}'")]
IdentNotFound(String), IdentNotFound(String),
#[error("The resource was not loaded from a path so cannot be reloaded")]
NoReloadPath
} }
impl From<LoaderError> for RequestError { impl From<LoaderError> for RequestError {
@ -52,79 +45,51 @@ impl From<LoaderError> for RequestError {
} }
} }
/// A struct that stores some things used for watching resources. /// A struct that stores all Manager data. This is requried for sending
//struct ManagerStorage
/// A struct that
pub struct ResourceWatcher { pub struct ResourceWatcher {
debouncer: Arc<RwLock<notify_debouncer_full::Debouncer<RecommendedWatcher, FileIdMap>>>, debouncer: Arc<RwLock<notify_debouncer_full::Debouncer<RecommendedWatcher, FileIdMap>>>,
events_recv: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>, events_recv: Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>,
} }
/// The state of the ResourceManager pub struct ResourceManager {
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>,
} }
/// The ResourceManager
///
/// This exists since we need the manager to be `Send + Sync`.
#[derive(Clone)]
pub struct ResourceManager {
inner: Arc<RwLock<ResourceManagerState>>,
}
impl Default for ResourceManager { impl Default for ResourceManager {
fn default() -> Self { fn default() -> Self {
Self { Self::new()
inner: Arc::new(RwLock::new(
ResourceManagerState {
resources: HashMap::new(),
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
watchers: HashMap::new(),
}
))
}
} }
} }
impl ResourceManager { impl ResourceManager {
pub fn new() -> Self { pub fn new() -> Self {
Self::default() Self {
} resources: HashMap::new(),
uuid_resources: HashMap::new(),
/// Retrieves a non-mutable guard of the manager's state. loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
pub fn state(&self) -> RwLockReadGuard<ResourceManagerState> { watchers: HashMap::new(),
self.inner.read().unwrap() }
}
/// Retrieves a mutable guard of the manager's state.
pub fn state_mut(&self) -> RwLockWriteGuard<ResourceManagerState> {
self.inner.write().unwrap()
} }
/// Registers a loader to the manager. /// Registers a loader to the manager.
pub fn register_loader<L>(&self) pub fn register_loader<L>(&mut self)
where where
L: ResourceLoader + Default + 'static L: ResourceLoader + Default + 'static
{ {
let mut state = self.state_mut(); self.loaders.push(Arc::new(L::default()));
state.loaders.push(Arc::new(L::default()));
} }
/// Request a resource at `path`. pub fn request<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
///
/// When a resource for a path is requested for the first time, the resource will be loaded
/// and cached. In the future, the cached version will be returned. There is only ever one copy
/// of the resource's data in memory at a time.
///
/// Loading resources is done asynchronously on a task spawned by `async-std`. You can use the
/// handle to check if the resource is loaded.
pub fn request<T>(&self, path: &str) -> Result<ResHandle<T>, RequestError>
where where
T: Send + Sync + Any + 'static T: Send + Sync + Any + 'static
{ {
let mut state = self.state_mut(); match self.resources.get(&path.to_string()) {
match state.resources.get(&path.to_string()) {
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");
@ -133,33 +98,22 @@ impl ResourceManager {
Ok(res) Ok(res)
}, },
None => { None => {
if let Some(loader) = state.loaders.iter() if let Some(loader) = self.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
let loader = Arc::clone(loader); // stop borrowing from self let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self.clone(), path); let res = loader.load(self, path)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
let handle = ResHandle::<T>::new_loading(Some(path)); self.resources.insert(path.to_string(), res.clone());
let thand = handle.clone();
task::spawn(async move {
match res.await {
Ok(data) => {
let mut d = thand.data.write().unwrap();
d.state = ResourceState::Ready(data);
}
Err(err) => {
let mut d = thand.data.write().unwrap();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone()); // cast Arc<dyn ResourceStorage> to Arc<Resource<T>
state.resources.insert(path.to_string(), res); let res = res.as_arc_any();
let res = res.downcast::<ResHandle<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
let res = ResHandle::<T>::clone(&res);
Ok(handle) Ok(res)
} else { } else {
Err(RequestError::UnsupportedFileExtension(path.to_string())) Err(RequestError::UnsupportedFileExtension(path.to_string()))
} }
@ -167,42 +121,28 @@ impl ResourceManager {
} }
} }
/// Request a resource without downcasting to a `ResHandle<T>`. /// Request a resource without downcasting to a ResHandle<T>.
/// Whenever you're ready to downcast, you can do so like this: /// Whenever you're ready to downcast, you can do so like this:
/// ```compile_fail /// ```compile_fail
/// let arc_any = res_arc.as_arc_any(); /// let arc_any = res_arc.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");
/// ``` /// ```
pub fn request_raw(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> { pub fn request_raw(&mut self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
let inner = self.inner.write().unwrap(); match self.resources.get(&path.to_string()) {
match inner.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) = self.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
let loader = Arc::clone(loader); // stop borrowing from self let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self.clone(), path); let res = loader.load(self, path)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
self.resources.insert(path.to_string(), res.clone());
let handle = loader.create_erased_handle(); Ok(res)
//let handle = ResHandle::<T>::new_loading();
let thand = handle.clone();
task::spawn(async move {
match res.await {
Ok(data) => {
thand.set_state(ResourceState::Ready(data));
}
Err(err) => {
thand.set_state(ResourceState::Error(Arc::new(err)));
}
}
});
Ok(handle)
} else { } else {
Err(RequestError::UnsupportedFileExtension(path.to_string())) Err(RequestError::UnsupportedFileExtension(path.to_string()))
} }
@ -214,18 +154,16 @@ impl ResourceManager {
/// ///
/// The resource cannot be requested with [`ResourceManager::request`], it can only be /// The resource cannot be requested with [`ResourceManager::request`], it can only be
/// retrieved with [`ResourceManager::request_uuid`]. /// retrieved with [`ResourceManager::request_uuid`].
pub fn store_uuid<T: Send + Sync + 'static>(&self, res: ResHandle<T>) { pub fn store_uuid<T: Send + Sync + 'static>(&mut self, res: ResHandle<T>) {
let mut state = self.state_mut(); self.uuid_resources.insert(res.uuid(), Arc::new(res));
state.resources.insert(res.uuid().to_string(), Arc::new(res));
} }
/// Request a resource via its uuid. /// Request a resource via its uuid.
/// ///
/// Returns `None` if the resource was not found. The resource must of had been /// Returns `None` if the resource was not found. The resource must of had been
/// 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>(&mut self, uuid: &Uuid) -> Option<ResHandle<T>> {
let state = self.state(); match self.uuid_resources.get(uuid) {
match state.resources.get(&uuid.to_string()) {
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");
@ -244,47 +182,36 @@ impl ResourceManager {
/// * `bytes` - The bytes to store. /// * `bytes` - The bytes to store.
/// ///
/// 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>(&mut 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: Send + Sync + Any + 'static
{ {
let mut state = self.state_mut(); if let Some(loader) = self.loaders.iter()
if let Some(loader) = state.loaders.iter()
.find(|l| l.does_support_mime(mime_type)) { .find(|l| l.does_support_mime(mime_type)) {
let loader = loader.clone(); let loader = loader.clone();
let res = loader.load_bytes(self.clone(), bytes, offset, length); let res = loader.load_bytes(self, bytes, offset, length)?;
let res: Arc<dyn ResourceStorage> = Arc::from(res);
self.resources.insert(ident.to_string(), res.clone());
// code here...
let handle = ResHandle::<T>::new_loading(None); // cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let thand = handle.clone(); let res = res.as_arc_any();
task::spawn(async move { let res = res.downcast::<ResHandle<T>>()
match res.await { .expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
Ok(data) => { let res = ResHandle::<T>::clone(&res);
let mut d = thand.data.write().unwrap();
d.state = ResourceState::Ready(data);
}
Err(err) => {
let mut d = thand.data.write().unwrap();
d.state = ResourceState::Error(Arc::new(err));
}
}
});
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone()); Ok(res)
state.resources.insert(ident.to_string(), res);
Ok(handle)
} else { } else {
Err(RequestError::UnsupportedMime(mime_type.to_string())) Err(RequestError::UnsupportedMime(mime_type.to_string()))
} }
} }
/// Requests bytes from the manager. /// Requests bytes from the manager.
pub fn request_loaded_bytes<T>(&self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError> pub fn request_loaded_bytes<T>(&mut self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
where where
T: Send + Sync + Any + 'static T: Send + Sync + Any + 'static
{ {
let state = self.state(); match self.resources.get(&ident.to_string()) {
match state.resources.get(&ident.to_string()) {
Some(res) => { Some(res) => {
let res = res.clone().as_arc_any(); let res = res.clone().as_arc_any();
let res = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource"); let res = res.downcast::<ResHandle<T>>().expect("Failure to downcast resource");
@ -298,7 +225,7 @@ impl ResourceManager {
} }
/// Start watching a path for changes. Returns a mspc channel that will send events /// Start watching a path for changes. Returns a mspc channel that will send events
pub fn watch(&self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> { pub fn watch(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let (send, recv) = crossbeam::channel::bounded(15); let (send, recv) = crossbeam::channel::bounded(15);
let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_millis(1000), None, send)?; let mut watcher = notify_debouncer_full::new_debouncer(Duration::from_millis(1000), None, send)?;
@ -314,10 +241,9 @@ impl ResourceManager {
events_recv: recv.clone(), events_recv: recv.clone(),
}; };
let mut state = self.state_mut(); self.watchers.insert(path.to_string(), watcher);
state.watchers.insert(path.to_string(), watcher);
let res = state.resources.get(&path.to_string()) let res = self.resources.get(&path.to_string())
.expect("The path that was watched has not been loaded as a resource yet"); .expect("The path that was watched has not been loaded as a resource yet");
res.set_watched(true); res.set_watched(true);
@ -325,14 +251,13 @@ impl ResourceManager {
} }
/// Stops watching a path /// Stops watching a path
pub fn stop_watching(&self, path: &str) -> notify::Result<()> { pub fn stop_watching(&mut self, path: &str) -> notify::Result<()> {
let state = self.state(); if let Some(watcher) = self.watchers.get(path) {
if let Some(watcher) = state.watchers.get(path) {
let mut watcher = watcher.debouncer.write().unwrap(); let mut watcher = watcher.debouncer.write().unwrap();
watcher.watcher().unwatch(Path::new(path))?; watcher.watcher().unwatch(Path::new(path))?;
// unwrap is safe since only loaded resources can be watched // unwrap is safe since only loaded resources can be watched
let res = state.resources.get(&path.to_string()).unwrap(); let res = self.resources.get(&path.to_string()).unwrap();
res.set_watched(false); res.set_watched(false);
} }
@ -342,50 +267,43 @@ impl ResourceManager {
/// Returns a mspc receiver for watcher events of a specific path. The path must already /// Returns a mspc receiver for watcher events of a specific path. The path must already
/// be watched with [`ResourceManager::watch`] for this to return `Some`. /// be watched with [`ResourceManager::watch`] for this to return `Some`.
pub fn watcher_event_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> { pub fn watcher_event_recv(&self, path: &str) -> Option<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
let state = self.state(); self.watchers.get(&path.to_string())
state.watchers.get(&path.to_string())
.map(|w| w.events_recv.clone()) .map(|w| w.events_recv.clone())
} }
/// Trigger a reload of a resource. /// Reloads a resource. The data inside the resource will be updated, the state may
/// pub fn reload<T>(&mut self, resource: ResHandle<T>) -> Result<(), RequestError>
/// The version of the resource will be incremented by one.
///
/// > Note: Since reloading is done asynchronously, the reloaded data will not be immediately
/// accessible. Until the resource is reloaded, the previous data will stay inside of
/// the handle.
pub fn reload<T>(&self, resource: ResHandle<T>) -> Result<(), RequestError>
where where
T: Send + Sync + Any + 'static T: Send + Sync + Any + 'static
{ {
let state = self.state(); let path = resource.path();
if let Some(loader) = self.loaders.iter()
let path = resource.path()
.ok_or(RequestError::NoReloadPath)?;
if let Some(loader) = state.loaders.iter()
.find(|l| l.does_support_file(&path)) { .find(|l| l.does_support_file(&path)) {
println!("got loader");
let loader = Arc::clone(loader); // stop borrowing from self let loader = Arc::clone(loader); // stop borrowing from self
let res = loader.load(self.clone(), &path); let loaded = loader.load(self, &path)?;
let loaded = loaded.as_arc_any();
/* let res_lock = &resource.data;
let mut res_lock = res_lock.write().unwrap();
res_lock.state = ResourceState::Loading;
drop(res_lock); */
let thand = resource.clone(); let loaded = loaded.downcast::<ResHandle<T>>()
task::spawn(async move { .unwrap();
match res.await { let loaded = match Arc::try_unwrap(loaded) {
Ok(data) => { Ok(v) => v,
let mut d = thand.data.write().unwrap(); Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
d.state = ResourceState::Ready(data); };
d.version += 1; let loaded = loaded.data;
} let loaded = match Arc::try_unwrap(loaded) {
Err(err) => { Ok(v) => v,
let mut d = thand.data.write().unwrap(); Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
d.state = ResourceState::Error(Arc::new(err)); };
} let loaded = loaded.into_inner().unwrap();
}
}); let res_lock = &resource.data;
let mut res_lock = res_lock.write().unwrap();
let version = res_lock.version;
res_lock.data = loaded.data;
res_lock.state = loaded.state;
res_lock.version = version + 1;
} }
Ok(()) Ok(())
@ -393,12 +311,10 @@ impl ResourceManager {
} }
#[cfg(test)] #[cfg(test)]
pub(crate) mod tests { mod tests {
use std::{io, ops::Deref}; use std::io;
use instant::Instant; use crate::{Texture, ResourceState};
use crate::{Image, ResourceData, Texture};
use super::*; use super::*;
@ -408,81 +324,37 @@ pub(crate) mod tests {
format!("{manifest}/test_files/img/{path}") format!("{manifest}/test_files/img/{path}")
} }
pub(crate) fn busy_wait_resource<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
let start = Instant::now();
while !handle.is_loaded() {
// loop until the image is loaded
let now = Instant::now();
let diff = now - start;
if diff.as_secs_f32() >= timeout {
panic!("Image never loaded");
}
}
}
pub(crate) fn busy_wait_resource_reload<R: ResourceData>(handle: &ResHandle<R>, timeout: f32) {
let version = handle.version();
let start = Instant::now();
while !handle.is_loaded() || handle.version() == version {
// loop until the image is loaded
let now = Instant::now();
let diff = now - start;
if diff.as_secs_f32() >= timeout {
panic!("Image never loaded");
}
}
}
#[test] #[test]
fn load_image() { fn load_image() {
let man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
assert!(!res.is_loaded()); assert_eq!(res.state(), ResourceState::Ready);
let img = res.data_ref();
busy_wait_resource(&res, 10.0); img.unwrap();
// shouldn't panic because of the loop
res.data_ref().unwrap();
} }
/// Ensures that only one copy of the same data was made /// Ensures that only one copy of the same data was made
#[test] #[test]
fn ensure_single() { fn ensure_single() {
let man = ResourceManager::new(); let mut 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.data), 2);
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.data), 3);
} }
/// 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
#[test] #[test]
fn ensure_none() { fn ensure_none() {
let man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squigglesfff.png")).unwrap(); let res = man.request::<Texture>(&get_image("squigglesfff.png"));
//let err = res.err().unwrap(); let err = res.err().unwrap();
// 1 second should be enough to run into an error
std::thread::sleep(Duration::from_secs(1));
//busy_wait_resource(&res, 10.0);
let state = &res.data.read().unwrap().state;
assert!( assert!(
match state { match err {
// make sure the error is NotFound // make sure the error is NotFound
//RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true, RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
ResourceState::Error(err) => {
match err.deref() {
LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true,
_ => false,
}
},
_ => false _ => false
} }
); );
@ -490,18 +362,17 @@ pub(crate) mod tests {
#[test] #[test]
fn reload_image() { fn reload_image() {
let man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
busy_wait_resource(&res, 10.0); assert_eq!(res.state(), ResourceState::Ready);
let img = res.data_ref(); let img = res.data_ref();
img.unwrap(); img.unwrap();
println!("Path = {}", res.path());
man.reload(res.clone()).unwrap(); man.reload(res.clone()).unwrap();
busy_wait_resource_reload(&res, 10.0);
assert_eq!(res.version(), 1); assert_eq!(res.version(), 1);
man.reload(res.clone()).unwrap(); man.reload(res.clone()).unwrap();
busy_wait_resource_reload(&res, 10.0);
assert_eq!(res.version(), 2); assert_eq!(res.version(), 2);
} }
@ -511,9 +382,9 @@ pub(crate) mod tests {
let image_path = get_image("squiggles_test.png"); let image_path = get_image("squiggles_test.png");
std::fs::copy(orig_path, &image_path).unwrap(); std::fs::copy(orig_path, &image_path).unwrap();
let man = ResourceManager::new(); let mut man = ResourceManager::new();
let res = man.request::<Texture>(&image_path).unwrap(); let res = man.request::<Texture>(&image_path).unwrap();
busy_wait_resource(&res, 10.0); assert_eq!(res.state(), ResourceState::Ready);
let img = res.data_ref(); let img = res.data_ref();
img.unwrap(); img.unwrap();

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::{RequestError, ResHandle, ResourceLoader, ResourceManager};
pub trait WorldAssetExt { pub trait WorldAssetExt {
/// Register a resource loader with the resource manager. /// Register a resource loader with the resource manager.
@ -38,7 +38,7 @@ impl WorldAssetExt for World {
where where
L: ResourceLoader + Default + 'static L: ResourceLoader + Default + 'static
{ {
let man = self.get_resource_or_default::<ResourceManager>(); let mut man = self.get_resource_or_default::<ResourceManager>();
man.register_loader::<L>(); man.register_loader::<L>();
} }
@ -46,17 +46,17 @@ impl WorldAssetExt for World {
where where
T: Send + Sync + Any + 'static T: Send + Sync + Any + 'static
{ {
let man = self.get_resource_or_default::<ResourceManager>(); let mut man = self.get_resource_or_default::<ResourceManager>();
man.request(path) man.request(path)
} }
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>>>> {
let man = self.get_resource_or_default::<ResourceManager>(); let mut man = self.get_resource_or_default::<ResourceManager>();
man.watch(path, recursive) man.watch(path, recursive)
} }
fn stop_watching_res(&mut self, path: &str) -> notify::Result<()> { fn stop_watching_res(&mut self, path: &str) -> notify::Result<()> {
let man = self.get_resource_or_default::<ResourceManager>(); let mut man = self.get_resource_or_default::<ResourceManager>();
man.stop_watching(path) man.stop_watching(path)
} }
@ -69,7 +69,7 @@ impl WorldAssetExt for World {
where where
T: Send + Sync + Any + 'static T: Send + Sync + Any + 'static
{ {
let man = self.get_resource_or_default::<ResourceManager>(); let mut man = self.get_resource_or_default::<ResourceManager>();
man.reload(resource) man.reload(resource)
} }
} }

View File

@ -216,10 +216,10 @@ pub(crate) fn world_add_child_node<B: Bundle>(world: &mut World, parent: &SceneN
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use lyra_ecs::Component; use lyra_ecs::{query::Entities, relation::ChildOf, Component, World};
use lyra_math::{Transform, Vec3}; use lyra_math::{Transform, Vec3};
use crate::{lyra_engine, SceneGraph}; use crate::{lyra_engine, SceneGraph, SceneNodeRoot};
#[derive(Component)] #[derive(Component)]
pub struct FakeMesh; pub struct FakeMesh;

View File

@ -23,7 +23,7 @@ impl ResourceLoader for LuaLoader {
fn load(&self, _resource_manager: &mut lyra_resource::ResourceManager, path: &str) -> Result<std::sync::Arc<dyn lyra_resource::ResourceStorage>, lyra_resource::LoaderError> { fn load(&self, _resource_manager: &mut lyra_resource::ResourceManager, path: &str) -> Result<std::sync::Arc<dyn lyra_resource::ResourceStorage>, lyra_resource::LoaderError> {
let bytes = std::fs::read(path)?; let bytes = std::fs::read(path)?;
let s = ResHandle::new_ready(path, LuaScript { let s = ResHandle::with_data(path, LuaScript {
bytes bytes
}); });
@ -34,7 +34,7 @@ impl ResourceLoader for LuaLoader {
let end = offset + length; let end = offset + length;
let bytes = bytes[offset..end].to_vec(); let bytes = bytes[offset..end].to_vec();
let s = ResHandle::new_ready("from bytes", LuaScript { let s = ResHandle::with_data("from bytes", LuaScript {
bytes bytes
}); });