Merge branch 'feature/async-resource-loading' into main
This commit is contained in:
commit
4a285e5866
|
@ -1839,6 +1839,7 @@ 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",
|
||||||
|
|
|
@ -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, PointLight, SpotLight}, scene::{CameraComponent, MeshComponent}, 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, SpotLight}, scene::CameraComponent, 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 mut resman = world.get_resource_mut::<ResourceManager>();
|
let 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();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{any::{Any, TypeId}, cell::Ref, marker::PhantomData};
|
use std::{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};
|
||||||
|
|
||||||
|
|
|
@ -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, KeyCode, InputButtons, MouseMotion};
|
use super::{Button, InputButtons, KeyCode, MouseButton, MouseMotion};
|
||||||
|
|
||||||
pub trait ActionLabel: Debug {
|
pub trait ActionLabel: Debug {
|
||||||
/// Returns a unique hash of the label.
|
/// Returns a unique hash of the label.
|
||||||
|
@ -105,15 +105,6 @@ 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 {
|
||||||
|
|
|
@ -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(Clone, Copy, Debug, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum MouseButton {
|
pub enum MouseButton {
|
||||||
Left,
|
Left,
|
||||||
Right,
|
Right,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use proc_macro2::Ident;
|
use proc_macro2::Ident;
|
||||||
use quote::{quote, ToTokens};
|
use quote::quote;
|
||||||
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;
|
||||||
|
|
|
@ -33,9 +33,6 @@ 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::*;
|
||||||
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -25,3 +25,4 @@ 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"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use std::{sync::Arc, path::PathBuf};
|
use std::{ffi::OsStr, path::{Path, PathBuf}, sync::Arc};
|
||||||
|
|
||||||
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, util, LoaderError, ResHandle, ResourceLoader, ResourceManager};
|
use crate::{gltf::GltfScene, loader::{LoaderError, PinedBoxLoaderFuture, ResourceLoader}, util, ResHandle, ResourceData, ResourceManager, ResourceStorage};
|
||||||
use super::{GltfNode, Material, Mesh, MeshIndices, MeshVertexAttribute, VertexAttributeData};
|
use super::{Gltf, 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: &'a mut ResourceManager,
|
pub resource_manager: 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::with_data("", new_mesh);
|
let handle = ResHandle::new_ready(None, 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,6 +137,21 @@ 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 {
|
||||||
|
@ -150,82 +165,92 @@ impl ResourceLoader for ModelLoader {
|
||||||
&[]
|
&[]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
|
fn load(&self, resource_manager: ResourceManager, path: &str) -> PinedBoxLoaderFuture {
|
||||||
// check if the file is supported by this loader
|
// cant use &str across async
|
||||||
if !self.does_support_file(path) {
|
let path = path.to_string();
|
||||||
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut parent_path = PathBuf::from(path);
|
Box::pin(async move {
|
||||||
parent_path.pop();
|
// check if the file is supported by this loader
|
||||||
let parent_path = parent_path.display().to_string();
|
if !Self::does_support_file(&path) {
|
||||||
|
return Err(LoaderError::UnsupportedExtension(path.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 gltf_out = super::Gltf::default();
|
|
||||||
|
|
||||||
let mut context = GltfLoadContext {
|
|
||||||
resource_manager,
|
|
||||||
gltf: &gltf,
|
|
||||||
gltf_path: path,
|
|
||||||
gltf_parent_path: &parent_path,
|
|
||||||
buffers: &buffers,
|
|
||||||
};
|
|
||||||
|
|
||||||
let start_inst = Instant::now();
|
|
||||||
let materials: Vec<ResHandle<Material>> = gltf.materials()
|
|
||||||
.map(|mat| ResHandle::with_data("", Material::from_gltf(&mut context, mat)))
|
|
||||||
.collect();
|
|
||||||
let mat_time = Instant::now() - start_inst;
|
|
||||||
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 nodes: Vec<GltfNode> = scene.nodes()
|
|
||||||
.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 {
|
let mut parent_path = PathBuf::from(&path);
|
||||||
nodes,
|
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 gltf_out = super::Gltf::default();
|
||||||
|
|
||||||
|
let mut context = GltfLoadContext {
|
||||||
|
resource_manager: resource_manager.clone(),
|
||||||
|
gltf: &gltf,
|
||||||
|
gltf_path: &path,
|
||||||
|
gltf_parent_path: &parent_path,
|
||||||
|
buffers: &buffers,
|
||||||
};
|
};
|
||||||
let scene = ResHandle::with_data(&format!("{}:Scene{}", path, idx), scene);
|
|
||||||
gltf_out.scenes.push(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
gltf_out.materials = materials;
|
let start_inst = Instant::now();
|
||||||
|
let materials: Vec<ResHandle<Material>> = gltf.materials()
|
||||||
|
.map(|mat| ResHandle::new_ready(None, Material::from_gltf(&mut context, mat)))
|
||||||
|
.collect();
|
||||||
|
let mat_time = Instant::now() - start_inst;
|
||||||
|
debug!("Loaded {} materials in {}s", materials.len(), mat_time.as_secs_f32());
|
||||||
|
|
||||||
Ok(Arc::new(ResHandle::with_data(path, gltf_out)))
|
for (_idx, scene) in gltf.scenes().enumerate() {
|
||||||
|
let start_inst = Instant::now();
|
||||||
|
let nodes: Vec<GltfNode> = scene.nodes()
|
||||||
|
.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;
|
||||||
|
|
||||||
|
Ok(Box::new(gltf_out) as Box<dyn ResourceData>)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
|
fn load_bytes(&self, resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture {
|
||||||
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::{gltf::Gltf, ResourceLoader};
|
use crate::tests::busy_wait_resource;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn test_file_path(path: &str) -> String {
|
fn test_file_path(path: &str) -> String {
|
||||||
|
@ -238,10 +263,9 @@ 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 mut manager = ResourceManager::new();
|
let manager = ResourceManager::new();
|
||||||
let loader = ModelLoader::default();
|
let gltf = manager.request::<Gltf>(&path).unwrap();
|
||||||
let gltf = loader.load(&mut manager, &path).unwrap();
|
busy_wait_resource(&gltf, 15.0);
|
||||||
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);
|
||||||
|
|
|
@ -271,7 +271,7 @@ impl Material {
|
||||||
wrap_w: WrappingMode::ClampToEdge,
|
wrap_w: WrappingMode::ClampToEdge,
|
||||||
};
|
};
|
||||||
|
|
||||||
let handler = ResHandle::with_data("", Texture {
|
let handler = ResHandle::new_ready(None, Texture {
|
||||||
image: tex_img,
|
image: tex_img,
|
||||||
sampler: Some(samp),
|
sampler: Some(samp),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
pub mod resource_manager;
|
mod resource_manager;
|
||||||
pub use resource_manager::*;
|
pub use resource_manager::*;
|
||||||
|
|
||||||
pub mod resource;
|
mod resource;
|
||||||
pub use resource::*;
|
pub use resource::*;
|
||||||
|
|
||||||
pub mod texture;
|
mod texture;
|
||||||
pub use texture::*;
|
pub use texture::*;
|
||||||
|
|
||||||
pub mod loader;
|
pub mod loader;
|
||||||
pub use loader::*;
|
|
||||||
|
|
||||||
pub mod gltf;
|
pub mod gltf;
|
||||||
|
|
||||||
pub mod world_ext;
|
mod world_ext;
|
||||||
pub use world_ext::*;
|
pub use world_ext::*;
|
||||||
|
|
||||||
pub(crate) mod util;
|
pub(crate) mod util;
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use std::{fs::File, sync::Arc, io::Read};
|
use std::{ffi::OsStr, path::Path, sync::Arc};
|
||||||
|
|
||||||
|
use async_std::io::ReadExt;
|
||||||
use image::ImageError;
|
use image::ImageError;
|
||||||
use tracing::{debug, trace};
|
use tracing::trace;
|
||||||
|
|
||||||
use crate::{resource::ResHandle, resource_manager::ResourceStorage, Image, ResourceManager};
|
use crate::{Image, ResHandle, ResourceData, ResourceManager};
|
||||||
|
|
||||||
use super::{LoaderError, ResourceLoader};
|
use super::{LoaderError, PinedBoxLoaderFuture, ResourceLoader};
|
||||||
|
|
||||||
impl From<ImageError> for LoaderError {
|
impl From<ImageError> for LoaderError {
|
||||||
fn from(value: ImageError) -> Self {
|
fn from(value: ImageError) -> Self {
|
||||||
|
@ -17,8 +18,8 @@ impl From<ImageError> for LoaderError {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct ImageLoader;
|
pub struct ImageLoader;
|
||||||
|
|
||||||
impl ResourceLoader for ImageLoader {
|
impl ImageLoader {
|
||||||
fn extensions(&self) -> &[&str] {
|
fn extensions() -> &'static [&'static 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",
|
||||||
|
@ -31,6 +32,21 @@ impl ResourceLoader for 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",
|
||||||
|
@ -43,48 +59,55 @@ impl ResourceLoader for ImageLoader {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, _resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
fn load(&self, _resource_manager: ResourceManager, path: &str) -> PinedBoxLoaderFuture {
|
||||||
// check if the file is supported by this loader
|
let path = path.to_string();
|
||||||
if !self.does_support_file(path) {
|
Box::pin(async move {
|
||||||
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
// check if the file is supported by this loader
|
||||||
}
|
if !Self::does_support_file(&path) {
|
||||||
|
return Err(LoaderError::UnsupportedExtension(path.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
// read file bytes
|
// read file bytes
|
||||||
let mut file = File::open(path)?;
|
let mut file = async_std::fs::File::open(path).await?;
|
||||||
let mut buf = vec![];
|
let mut buf = vec![];
|
||||||
file.read_to_end(&mut buf)?;
|
file.read_to_end(&mut buf).await?;
|
||||||
|
|
||||||
// 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 res = ResHandle::with_data(path, image);
|
let image = Box::new(image) as Box<dyn ResourceData>;
|
||||||
|
|
||||||
Ok(Arc::new(res))
|
Ok(image)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
|
fn load_bytes(&self, _resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture {
|
||||||
trace!("Loading {} bytes as an image", length);
|
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);
|
||||||
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> {
|
||||||
|
Arc::from(ResHandle::<Image>::new_loading(None))
|
||||||
Ok(Arc::new(res))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
||||||
|
@ -102,15 +125,24 @@ mod tests {
|
||||||
/// Tests loading an image
|
/// Tests loading an image
|
||||||
#[test]
|
#[test]
|
||||||
fn image_load() {
|
fn image_load() {
|
||||||
let mut manager = ResourceManager::new();
|
let 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 mut manager = ResourceManager::new();
|
let 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())
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
pub mod image;
|
mod image;
|
||||||
|
pub use image::*;
|
||||||
|
|
||||||
use std::{io, sync::Arc, path::Path, ffi::OsStr};
|
use std::{ffi::OsStr, future::Future, io, path::Path, pin::Pin, sync::Arc};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{resource_manager::ResourceStorage, ResourceManager};
|
use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum LoaderError {
|
pub enum LoaderError {
|
||||||
|
@ -28,7 +29,9 @@ impl From<io::Error> for LoaderError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ResourceLoader {
|
pub type PinedBoxLoaderFuture = Pin<Box<dyn Future<Output = Result<Box<dyn ResourceData>, LoaderError>> + Send>>;
|
||||||
|
|
||||||
|
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.
|
||||||
|
@ -50,9 +53,21 @@ pub trait ResourceLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load a resource from a path.
|
/// Load a resource from a path.
|
||||||
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
|
fn load(&self, resource_manager: ResourceManager, path: &str) -> PinedBoxLoaderFuture;
|
||||||
/// Load a resource from bytes.
|
/// Load a resource from bytes.
|
||||||
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
|
fn load_bytes(&self, resource_manager: ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> PinedBoxLoaderFuture;
|
||||||
|
|
||||||
|
/// 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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,64 @@
|
||||||
use std::{any::Any, sync::{Arc, RwLock}};
|
use std::{any::{Any, TypeId}, marker::PhantomData, sync::{Arc, RwLock}};
|
||||||
|
|
||||||
use lyra_ecs::Component;
|
use lyra_ecs::Component;
|
||||||
use crate::lyra_engine;
|
use crate::{loader::LoaderError, lyra_engine};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::ResourceStorage;
|
use crate::ResourceStorage;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
/// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
Ready,
|
Error(Arc<LoaderError>),
|
||||||
|
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> std::ops::Deref for ResourceDataRef<'a, T> {
|
impl<'a, T: 'static> std::ops::Deref for ResourceDataRef<'a, T> {
|
||||||
type Target = T;
|
type Target = T;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
// safety: this struct must only be created if the resource is loaded
|
match &self.guard.state {
|
||||||
self.guard.data.as_ref().unwrap()
|
ResourceState::Ready(d) => {
|
||||||
|
// 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.
|
||||||
|
@ -51,16 +78,31 @@ impl<T> Clone for ResHandle<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> ResHandle<T> {
|
impl<T: ResourceData> ResHandle<T> {
|
||||||
/// Create the resource with data, its assumed the state is `Ready`
|
pub fn new_loading(path: Option<&str>) -> 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,
|
||||||
state: ResourceState::Ready,
|
path: path.map(str::to_string),
|
||||||
|
state: ResourceState::Loading,
|
||||||
uuid: Uuid::new_v4(),
|
uuid: Uuid::new_v4(),
|
||||||
is_watched: false,
|
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`
|
||||||
|
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 {
|
Self {
|
||||||
|
@ -77,20 +119,14 @@ impl<T> 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!");
|
||||||
d.state == ResourceState::Ready
|
matches!(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 {
|
||||||
|
@ -98,6 +134,11 @@ impl<T> 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 {
|
||||||
|
@ -140,22 +181,18 @@ 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 state(&self) -> ResourceState {
|
|
||||||
self.state()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn uuid(&self) -> Uuid {
|
fn uuid(&self) -> Uuid {
|
||||||
self.uuid()
|
self.uuid()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> Option<String> {
|
||||||
|
self.path()
|
||||||
|
}
|
||||||
|
|
||||||
fn is_watched(&self) -> bool {
|
fn is_watched(&self) -> bool {
|
||||||
self.is_watched()
|
self.is_watched()
|
||||||
}
|
}
|
||||||
|
@ -163,4 +200,11 @@ 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
use std::{sync::{Arc, RwLock}, collections::HashMap, any::Any, path::Path, time::Duration};
|
use std::{any::Any, collections::HashMap, path::Path, sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard}, 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::{image::ImageLoader, LoaderError, ResourceLoader}, resource::ResHandle, ResourceState};
|
use crate::{gltf::ModelLoader, loader::{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>`]
|
||||||
|
@ -19,24 +20,30 @@ 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 {
|
||||||
|
@ -45,51 +52,79 @@ impl From<LoaderError> for RequestError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct that stores all Manager data. This is requried for sending
|
/// A struct that stores some things used for watching resources.
|
||||||
//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>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ResourceManager {
|
/// The state of the 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::new()
|
Self {
|
||||||
|
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 {
|
Self::default()
|
||||||
resources: HashMap::new(),
|
}
|
||||||
uuid_resources: HashMap::new(),
|
|
||||||
loaders: vec![ Arc::new(ImageLoader), Arc::new(ModelLoader) ],
|
/// Retrieves a non-mutable guard of the manager's state.
|
||||||
watchers: HashMap::new(),
|
pub fn state(&self) -> RwLockReadGuard<ResourceManagerState> {
|
||||||
}
|
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>(&mut self)
|
pub fn register_loader<L>(&self)
|
||||||
where
|
where
|
||||||
L: ResourceLoader + Default + 'static
|
L: ResourceLoader + Default + 'static
|
||||||
{
|
{
|
||||||
self.loaders.push(Arc::new(L::default()));
|
let mut state = self.state_mut();
|
||||||
|
state.loaders.push(Arc::new(L::default()));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request<T>(&mut self, path: &str) -> Result<ResHandle<T>, RequestError>
|
/// Request a resource at `path`.
|
||||||
|
///
|
||||||
|
/// 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
|
||||||
{
|
{
|
||||||
match self.resources.get(&path.to_string()) {
|
let mut state = self.state_mut();
|
||||||
|
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");
|
||||||
|
@ -98,22 +133,33 @@ impl ResourceManager {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if let Some(loader) = self.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
|
||||||
let loader = Arc::clone(loader); // stop borrowing from self
|
let loader = Arc::clone(loader); // stop borrowing from self
|
||||||
let res = loader.load(self, path)?;
|
let res = loader.load(self.clone(), path);
|
||||||
let res: Arc<dyn ResourceStorage> = Arc::from(res);
|
|
||||||
self.resources.insert(path.to_string(), res.clone());
|
|
||||||
|
|
||||||
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
let handle = ResHandle::<T>::new_loading(Some(path));
|
||||||
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(res)
|
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());
|
||||||
|
state.resources.insert(path.to_string(), res);
|
||||||
|
|
||||||
|
Ok(handle)
|
||||||
} else {
|
} else {
|
||||||
Err(RequestError::UnsupportedFileExtension(path.to_string()))
|
Err(RequestError::UnsupportedFileExtension(path.to_string()))
|
||||||
}
|
}
|
||||||
|
@ -121,28 +167,42 @@ 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(&mut self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
|
pub fn request_raw(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, RequestError> {
|
||||||
match self.resources.get(&path.to_string()) {
|
let inner = self.inner.write().unwrap();
|
||||||
|
match inner.resources.get(&path.to_string()) {
|
||||||
Some(res) => {
|
Some(res) => {
|
||||||
Ok(res.clone())
|
Ok(res.clone())
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
if let Some(loader) = self.loaders.iter()
|
if let Some(loader) = inner.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, path)?;
|
let res = loader.load(self.clone(), path);
|
||||||
let res: Arc<dyn ResourceStorage> = Arc::from(res);
|
|
||||||
self.resources.insert(path.to_string(), res.clone());
|
|
||||||
|
|
||||||
Ok(res)
|
let handle = loader.create_erased_handle();
|
||||||
|
//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()))
|
||||||
}
|
}
|
||||||
|
@ -154,16 +214,18 @@ 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>(&mut self, res: ResHandle<T>) {
|
pub fn store_uuid<T: Send + Sync + 'static>(&self, res: ResHandle<T>) {
|
||||||
self.uuid_resources.insert(res.uuid(), Arc::new(res));
|
let mut state = self.state_mut();
|
||||||
|
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>(&mut self, uuid: &Uuid) -> Option<ResHandle<T>> {
|
pub fn request_uuid<T: Send + Sync + 'static>(&self, uuid: &Uuid) -> Option<ResHandle<T>> {
|
||||||
match self.uuid_resources.get(uuid) {
|
let state = self.state();
|
||||||
|
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");
|
||||||
|
@ -182,36 +244,47 @@ 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>(&mut 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: Send + Sync + Any + 'static
|
||||||
{
|
{
|
||||||
if let Some(loader) = self.loaders.iter()
|
let mut state = self.state_mut();
|
||||||
|
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, bytes, offset, length)?;
|
let res = loader.load_bytes(self.clone(), bytes, offset, length);
|
||||||
let res: Arc<dyn ResourceStorage> = Arc::from(res);
|
|
||||||
self.resources.insert(ident.to_string(), res.clone());
|
|
||||||
// code here...
|
|
||||||
|
|
||||||
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
|
let handle = ResHandle::<T>::new_loading(None);
|
||||||
let res = res.as_arc_any();
|
let thand = handle.clone();
|
||||||
let res = res.downcast::<ResHandle<T>>()
|
task::spawn(async move {
|
||||||
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
|
match res.await {
|
||||||
let res = ResHandle::<T>::clone(&res);
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Ok(res)
|
let res: Arc<dyn ResourceStorage> = Arc::from(handle.clone());
|
||||||
|
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>(&mut self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
|
pub fn request_loaded_bytes<T>(&self, ident: &str) -> Result<Arc<ResHandle<T>>, RequestError>
|
||||||
where
|
where
|
||||||
T: Send + Sync + Any + 'static
|
T: Send + Sync + Any + 'static
|
||||||
{
|
{
|
||||||
match self.resources.get(&ident.to_string()) {
|
let state = self.state();
|
||||||
|
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");
|
||||||
|
@ -225,7 +298,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(&mut self, path: &str, recursive: bool) -> notify::Result<Receiver<Result<Vec<DebouncedEvent>, Vec<notify::Error>>>> {
|
pub fn watch(&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)?;
|
||||||
|
|
||||||
|
@ -241,9 +314,10 @@ impl ResourceManager {
|
||||||
events_recv: recv.clone(),
|
events_recv: recv.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.watchers.insert(path.to_string(), watcher);
|
let mut state = self.state_mut();
|
||||||
|
state.watchers.insert(path.to_string(), watcher);
|
||||||
|
|
||||||
let res = self.resources.get(&path.to_string())
|
let res = state.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);
|
||||||
|
|
||||||
|
@ -251,13 +325,14 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stops watching a path
|
/// Stops watching a path
|
||||||
pub fn stop_watching(&mut self, path: &str) -> notify::Result<()> {
|
pub fn stop_watching(&self, path: &str) -> notify::Result<()> {
|
||||||
if let Some(watcher) = self.watchers.get(path) {
|
let state = self.state();
|
||||||
|
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 = self.resources.get(&path.to_string()).unwrap();
|
let res = state.resources.get(&path.to_string()).unwrap();
|
||||||
res.set_watched(false);
|
res.set_watched(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,43 +342,50 @@ 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>>>> {
|
||||||
self.watchers.get(&path.to_string())
|
let state = self.state();
|
||||||
|
state.watchers.get(&path.to_string())
|
||||||
.map(|w| w.events_recv.clone())
|
.map(|w| w.events_recv.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reloads a resource. The data inside the resource will be updated, the state may
|
/// Trigger a reload of a resource.
|
||||||
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 path = resource.path();
|
let state = self.state();
|
||||||
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 loaded = loader.load(self, &path)?;
|
let res = loader.load(self.clone(), &path);
|
||||||
let loaded = loaded.as_arc_any();
|
|
||||||
|
|
||||||
let loaded = loaded.downcast::<ResHandle<T>>()
|
/* let res_lock = &resource.data;
|
||||||
.unwrap();
|
|
||||||
let loaded = match Arc::try_unwrap(loaded) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
|
|
||||||
};
|
|
||||||
let loaded = loaded.data;
|
|
||||||
let loaded = match Arc::try_unwrap(loaded) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => panic!("Seems impossible that this would happen, the resource was just loaded!"),
|
|
||||||
};
|
|
||||||
let loaded = loaded.into_inner().unwrap();
|
|
||||||
|
|
||||||
let res_lock = &resource.data;
|
|
||||||
let mut res_lock = res_lock.write().unwrap();
|
let mut res_lock = res_lock.write().unwrap();
|
||||||
let version = res_lock.version;
|
res_lock.state = ResourceState::Loading;
|
||||||
|
drop(res_lock); */
|
||||||
|
|
||||||
res_lock.data = loaded.data;
|
let thand = resource.clone();
|
||||||
res_lock.state = loaded.state;
|
task::spawn(async move {
|
||||||
res_lock.version = version + 1;
|
match res.await {
|
||||||
|
Ok(data) => {
|
||||||
|
let mut d = thand.data.write().unwrap();
|
||||||
|
d.state = ResourceState::Ready(data);
|
||||||
|
d.version += 1;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let mut d = thand.data.write().unwrap();
|
||||||
|
d.state = ResourceState::Error(Arc::new(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -311,10 +393,12 @@ impl ResourceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
pub(crate) mod tests {
|
||||||
use std::io;
|
use std::{io, ops::Deref};
|
||||||
|
|
||||||
use crate::{Texture, ResourceState};
|
use instant::Instant;
|
||||||
|
|
||||||
|
use crate::{Image, ResourceData, Texture};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -324,37 +408,81 @@ 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 mut man = ResourceManager::new();
|
let man = ResourceManager::new();
|
||||||
let res = man.request::<Texture>(&get_image("squiggles.png")).unwrap();
|
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
|
||||||
assert_eq!(res.state(), ResourceState::Ready);
|
assert!(!res.is_loaded());
|
||||||
let img = res.data_ref();
|
|
||||||
img.unwrap();
|
busy_wait_resource(&res, 10.0);
|
||||||
|
|
||||||
|
// 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 mut 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), 2);
|
assert_eq!(Arc::strong_count(&res.data), 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), 3);
|
assert_eq!(Arc::strong_count(&resagain.data), 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
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_none() {
|
fn ensure_none() {
|
||||||
let mut man = ResourceManager::new();
|
let man = ResourceManager::new();
|
||||||
let res = man.request::<Texture>(&get_image("squigglesfff.png"));
|
let res = man.request::<Texture>(&get_image("squigglesfff.png")).unwrap();
|
||||||
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 err {
|
match state {
|
||||||
// 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
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -362,17 +490,18 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn reload_image() {
|
fn reload_image() {
|
||||||
let mut 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!(res.state(), ResourceState::Ready);
|
busy_wait_resource(&res, 10.0);
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,9 +511,9 @@ 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 mut man = ResourceManager::new();
|
let man = ResourceManager::new();
|
||||||
let res = man.request::<Texture>(&image_path).unwrap();
|
let res = man.request::<Texture>(&image_path).unwrap();
|
||||||
assert_eq!(res.state(), ResourceState::Ready);
|
busy_wait_resource(&res, 10.0);
|
||||||
let img = res.data_ref();
|
let img = res.data_ref();
|
||||||
img.unwrap();
|
img.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -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::{RequestError, ResHandle, ResourceLoader, ResourceManager};
|
use crate::{loader::ResourceLoader, RequestError, ResHandle, 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 mut man = self.get_resource_or_default::<ResourceManager>();
|
let 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 mut man = self.get_resource_or_default::<ResourceManager>();
|
let 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 mut man = self.get_resource_or_default::<ResourceManager>();
|
let 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 mut man = self.get_resource_or_default::<ResourceManager>();
|
let 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 mut man = self.get_resource_or_default::<ResourceManager>();
|
let man = self.get_resource_or_default::<ResourceManager>();
|
||||||
man.reload(resource)
|
man.reload(resource)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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::{query::Entities, relation::ChildOf, Component, World};
|
use lyra_ecs::Component;
|
||||||
use lyra_math::{Transform, Vec3};
|
use lyra_math::{Transform, Vec3};
|
||||||
|
|
||||||
use crate::{lyra_engine, SceneGraph, SceneNodeRoot};
|
use crate::{lyra_engine, SceneGraph};
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct FakeMesh;
|
pub struct FakeMesh;
|
||||||
|
|
|
@ -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::with_data(path, LuaScript {
|
let s = ResHandle::new_ready(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::with_data("from bytes", LuaScript {
|
let s = ResHandle::new_ready("from bytes", LuaScript {
|
||||||
bytes
|
bytes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue