Return Result for asset handle wait_for_load, create shader asset loader that uses the preprocessor

This commit is contained in:
SeanOMik 2024-11-14 21:44:19 -05:00
parent 8d54c42d74
commit 95b01613fe
Signed by: SeanOMik
GPG Key ID: FEC9E2FC15235964
24 changed files with 282 additions and 169 deletions

View File

@ -4,6 +4,7 @@ use lyra_gltf::GltfLoader;
use lyra_resource::ResourceManager; use lyra_resource::ResourceManager;
use crate::game::App; use crate::game::App;
use crate::render::PreprocessShaderLoader;
use crate::winit::{WinitPlugin, WindowPlugin}; use crate::winit::{WinitPlugin, WindowPlugin};
use crate::DeltaTimePlugin; use crate::DeltaTimePlugin;
use crate::input::InputPlugin; use crate::input::InputPlugin;
@ -101,7 +102,10 @@ pub struct ResourceManagerPlugin;
impl Plugin for ResourceManagerPlugin { impl Plugin for ResourceManagerPlugin {
fn setup(&mut self, app: &mut App) { fn setup(&mut self, app: &mut App) {
app.world.add_resource(ResourceManager::new()); let rm = ResourceManager::new();
rm.register_loader::<PreprocessShaderLoader>();
app.world.add_resource(rm);
} }
} }
@ -111,12 +115,12 @@ pub struct DefaultPlugins;
impl Plugin for DefaultPlugins { impl Plugin for DefaultPlugins {
fn setup(&mut self, app: &mut App) { fn setup(&mut self, app: &mut App) {
WinitPlugin::default().setup(app);
CommandQueuePlugin.setup(app);
InputPlugin.setup(app);
ResourceManagerPlugin.setup(app); ResourceManagerPlugin.setup(app);
GltfPlugin.setup(app); GltfPlugin.setup(app);
WinitPlugin::default().setup(app);
WindowPlugin::default().setup(app); WindowPlugin::default().setup(app);
CommandQueuePlugin.setup(app);
InputPlugin.setup(app);
DeltaTimePlugin.setup(app); DeltaTimePlugin.setup(app);
} }
} }

View File

@ -4,6 +4,7 @@ use std::{
}; };
use lyra_ecs::World; use lyra_ecs::World;
use lyra_resource::{RequestError, ResHandle, ResourceManager};
pub use node::*; pub use node::*;
mod passes; mod passes;
@ -22,7 +23,7 @@ use rustc_hash::FxHashMap;
use tracing::{debug_span, instrument, trace, warn}; use tracing::{debug_span, instrument, trace, warn};
use wgpu::CommandEncoder; use wgpu::CommandEncoder;
use super::resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}; use super::{resource::{ComputePipeline, Pass, Pipeline, RenderPipeline}, Shader};
/// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`]. /// A trait that represents the label of a resource, slot, or node in the [`RenderGraph`].
pub trait RenderGraphLabel: Debug + 'static { pub trait RenderGraphLabel: Debug + 'static {
@ -118,11 +119,15 @@ pub struct RenderGraph {
/// A directed graph used to determine dependencies of nodes. /// A directed graph used to determine dependencies of nodes.
node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>, node_graph: petgraph::matrix_graph::DiMatrix<RenderGraphLabelValue, (), Option<()>, usize>,
view_target: Rc<RefCell<ViewTarget>>, view_target: Rc<RefCell<ViewTarget>>,
shader_prepoc: wgsl_preprocessor::Processor, /// Type erased data of ResourceManager ecs resource
resource_manager: lyra_ecs::ResourceData,
} }
impl RenderGraph { impl RenderGraph {
pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>) -> Self { pub fn new(device: Arc<wgpu::Device>, queue: Arc<wgpu::Queue>, view_target: Rc<RefCell<ViewTarget>>, world: &World) -> Self {
let rm = world.get_resource_data::<ResourceManager>()
.expect("RenderGraph requires ResourceManager ECS resource");
Self { Self {
device, device,
queue, queue,
@ -132,7 +137,7 @@ impl RenderGraph {
bind_groups: Default::default(), bind_groups: Default::default(),
node_graph: Default::default(), node_graph: Default::default(),
view_target, view_target,
shader_prepoc: wgsl_preprocessor::Processor::new(), resource_manager: rm,
} }
} }
@ -518,7 +523,7 @@ impl RenderGraph {
/// This step also parses the shader and will return errors if it failed to parse. /// This step also parses the shader and will return errors if it failed to parse.
/// ///
/// Returns: The shader module import path if the module specified one. /// Returns: The shader module import path if the module specified one.
#[inline(always)] /* #[inline(always)]
pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> { pub fn register_shader(&mut self, shader_src: &str) -> Result<Option<String>, wgsl_preprocessor::Error> {
self.shader_prepoc.parse_module(shader_src) self.shader_prepoc.parse_module(shader_src)
} }
@ -527,6 +532,16 @@ impl RenderGraph {
#[inline(always)] #[inline(always)]
pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> { pub fn preprocess_shader(&mut self, shader_path: &str) -> Result<String, wgsl_preprocessor::Error> {
self.shader_prepoc.preprocess_module(shader_path) self.shader_prepoc.preprocess_module(shader_path)
} */
/// Load a shader from a `str`
///
/// This will also wait until the shader is loaded before returning.
pub fn load_shader_str(&self, shader_name: &str, shader_src: &str) -> Result<ResHandle<Shader>, RequestError> {
let rm = self.resource_manager.get::<ResourceManager>();
let shader = rm.load_str(shader_name, "text/wgsl", shader_src)?;
shader.wait_for_load()?;
Ok(shader)
} }
} }

View File

@ -1,10 +1,10 @@
use std::{collections::HashMap, rc::Rc, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeType}, graph::{Node, NodeDesc, NodeType},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState},
}; };
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
@ -64,10 +64,8 @@ impl Node for FxaaPass {
..Default::default() ..Default::default()
})); }));
let shader = Rc::new(Shader { let shader = graph.load_shader_str("fxaa_shader", include_str!("../../shaders/fxaa.wgsl"))
label: Some("fxaa_shader".into()), .expect("failed to load wgsl shader from manager");
source: include_str!("../../shaders/fxaa.wgsl").to_string(),
});
let vt = graph.view_target(); let vt = graph.view_target();

View File

@ -1,4 +1,4 @@
use std::{mem, rc::Rc, sync::Arc}; use std::{mem, sync::Arc};
use glam::Vec2Swizzles; use glam::Vec2Swizzles;
use lyra_ecs::World; use lyra_ecs::World;
@ -8,7 +8,7 @@ use wgpu::util::DeviceExt;
use crate::render::{ use crate::render::{
graph::{ graph::{
Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext, SlotAttribute, SlotValue
}, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor, Shader} }, renderer::ScreenSize, resource::{ComputePipeline, ComputePipelineDescriptor}
}; };
use super::{BasePassSlots, LightBasePassSlots}; use super::{BasePassSlots, LightBasePassSlots};
@ -219,10 +219,8 @@ impl Node for LightCullComputePass {
let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize); let screen_size_bgl = graph.bind_group_layout(BasePassSlots::ScreenSize);
let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); let light_indices_bg_layout = graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
let shader = Rc::new(Shader { let shader = graph.load_shader_str("light_cull_comp_shader", include_str!("../../shaders/light_cull.comp.wgsl"))
label: Some("light_cull_comp_shader".into()), .expect("failed to load light cull compute shader");
source: include_str!("../../shaders/light_cull.comp.wgsl").to_string(),
});
let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor { let pipeline = ComputePipeline::create(device, &ComputePipelineDescriptor {
label: Some("light_cull_pipeline".into()), label: Some("light_cull_pipeline".into()),

View File

@ -1,4 +1,4 @@
use std::{rc::Rc, sync::Arc}; use std::sync::Arc;
use lyra_ecs::{AtomicRef, ResourceData}; use lyra_ecs::{AtomicRef, ResourceData};
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
@ -7,7 +7,7 @@ use tracing::{instrument, warn};
use crate::render::{ use crate::render::{
desc_buf_lay::DescVertexBufferLayout, desc_buf_lay::DescVertexBufferLayout,
graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext}, graph::{Node, NodeDesc, NodeType, RenderGraph, RenderGraphContext},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState},
texture::RenderTexture, texture::RenderTexture,
transform_buffer_storage::TransformBuffers, transform_buffer_storage::TransformBuffers,
vertex::Vertex, vertex::Vertex,
@ -102,10 +102,8 @@ impl Node for MeshPass {
_: &mut RenderGraphContext, _: &mut RenderGraphContext,
) { ) {
if self.pipeline.is_none() { if self.pipeline.is_none() {
let shader_mod = graph.register_shader(include_str!("../../shaders/base.wgsl")) let shader = graph.load_shader_str("mesh_base", include_str!("../../shaders/base.wgsl"))
.expect("failed to register shader").expect("base shader missing module"); .expect("failed to load base mesh shader");
let shader_src = graph.preprocess_shader(&shader_mod)
.expect("failed to preprocess shader");
let device = graph.device(); let device = graph.device();
let surface_config_format = graph.view_target().format(); let surface_config_format = graph.view_target().format();
@ -299,11 +297,6 @@ impl Node for MeshPass {
graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup); graph.bind_group_layout(LightCullComputePassSlots::LightIndicesGridGroup);
let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone(); let atlas_bgl = self.shadows_atlas.as_ref().unwrap().layout.clone();
let shader = Rc::new(Shader {
label: Some(shader_mod.into()),
source: shader_src,
});
let transforms = world let transforms = world
.get_resource_data::<TransformBuffers>() .get_resource_data::<TransformBuffers>()
.expect("Missing transform buffers"); .expect("Missing transform buffers");

View File

@ -14,17 +14,13 @@ use lyra_ecs::{
}; };
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use lyra_math::{Angle, Transform}; use lyra_math::{Angle, Transform};
use lyra_resource::{RequestError, ResHandle};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use tracing::{debug, warn}; use tracing::{debug, warn};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, graph::{Node, NodeDesc, NodeType, RenderGraph, SlotAttribute, SlotValue}, light::{directional::DirectionalLight, LightType, PointLight, SpotLight}, resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, VertexState}, transform_buffer_storage::TransformBuffers, vertex::Vertex, AtlasFrame, GpuSlotBuffer, Shader, TextureAtlas
light::{directional::DirectionalLight, LightType, PointLight, SpotLight},
resource::{FragmentState, RenderPipeline, RenderPipelineDescriptor, Shader, VertexState},
transform_buffer_storage::TransformBuffers,
vertex::Vertex,
AtlasFrame, GpuSlotBuffer, TextureAtlas,
}; };
use super::{MeshBufferStorage, RenderAssets, RenderMeshes}; use super::{MeshBufferStorage, RenderAssets, RenderMeshes};
@ -74,7 +70,7 @@ pub struct ShadowMapsPass {
transform_buffers: Option<ResourceData>, transform_buffers: Option<ResourceData>,
render_meshes: Option<ResourceData>, render_meshes: Option<ResourceData>,
mesh_buffers: Option<ResourceData>, mesh_buffers: Option<ResourceData>,
shader: Option<String>, shader: ResHandle<Shader>,
pipeline: Option<RenderPipeline>, pipeline: Option<RenderPipeline>,
point_light_pipeline: Option<RenderPipeline>, point_light_pipeline: Option<RenderPipeline>,
@ -171,7 +167,7 @@ impl ShadowMapsPass {
transform_buffers: None, transform_buffers: None,
render_meshes: None, render_meshes: None,
mesh_buffers: None, mesh_buffers: None,
shader: None, shader: Default::default(),
pipeline: None, pipeline: None,
point_light_pipeline: None, point_light_pipeline: None,
@ -503,21 +499,14 @@ impl ShadowMapsPass {
queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice())); queue.write_buffer(buffer, 0, bytemuck::cast_slice(points.as_slice()));
} }
/// Register all the shaders, returning the module of the /// Load all shadow related registers and return the shader for the shadow depth pass.
fn register_shaders(&self, graph: &mut RenderGraph) -> Result<(), wgsl_preprocessor::Error> { fn loader_shaders(&self, graph: &mut RenderGraph) -> Result<ResHandle<Shader>, RequestError> {
let src = include_str!("../../shaders/shadows/shadows_structs.wgsl"); graph.load_shader_str("shadows_structs", include_str!("../../shaders/shadows/shadows_structs.wgsl"))?;
graph.register_shader(src)?; graph.load_shader_str("shadows_bindings", include_str!("../../shaders/shadows/shadows_bindings.wgsl"))?;
graph.load_shader_str("shadows_calc", include_str!("../../shaders/shadows/shadows_calc.wgsl"))?;
let depth = graph.load_shader_str("shadows_depth", include_str!("../../shaders/shadows/shadows_depth.wgsl"))?;
let src = include_str!("../../shaders/shadows/shadows_bindings.wgsl"); Ok(depth)
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_calc.wgsl");
graph.register_shader(src)?;
let src = include_str!("../../shaders/shadows/shadows_depth.wgsl");
graph.register_shader(src)?;
Ok(())
} }
} }
@ -526,11 +515,8 @@ impl Node for ShadowMapsPass {
&mut self, &mut self,
graph: &mut crate::render::graph::RenderGraph, graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::NodeDesc { ) -> crate::render::graph::NodeDesc {
self.register_shaders(graph) self.shader = self.loader_shaders(graph)
.expect("failed to register shaders"); .expect("failed to load depth shadow shader");
self.shader = Some(graph.preprocess_shader("lyra::shadows::depth_pass")
.expect("failed to preprocess depth shadow shaders"));
println!("{}", self.shader.as_ref().unwrap());
let mut node = NodeDesc::new(NodeType::Render, None, vec![]); let mut node = NodeDesc::new(NodeType::Render, None, vec![]);
@ -773,11 +759,6 @@ impl Node for ShadowMapsPass {
} }
if self.pipeline.is_none() { if self.pipeline.is_none() {
let shader = Rc::new(Shader {
label: Some("lyra::shadows::depth_pass".into()),
source: self.shader.clone().unwrap(),
});
let bgl = self.bgl.clone(); let bgl = self.bgl.clone();
let transforms = self.transform_buffers().bindgroup_layout.clone(); let transforms = self.transform_buffers().bindgroup_layout.clone();
@ -788,7 +769,7 @@ impl Node for ShadowMapsPass {
layouts: vec![bgl.clone(), transforms.clone()], layouts: vec![bgl.clone(), transforms.clone()],
push_constant_ranges: vec![], push_constant_ranges: vec![],
vertex: VertexState { vertex: VertexState {
module: shader.clone(), module: self.shader.clone(),
entry_point: "vs_main".into(), entry_point: "vs_main".into(),
buffers: vec![Vertex::position_desc().into()], buffers: vec![Vertex::position_desc().into()],
}, },
@ -817,12 +798,12 @@ impl Node for ShadowMapsPass {
layouts: vec![bgl, transforms], layouts: vec![bgl, transforms],
push_constant_ranges: vec![], push_constant_ranges: vec![],
vertex: VertexState { vertex: VertexState {
module: shader.clone(), module: self.shader.clone(),
entry_point: "vs_main".into(), entry_point: "vs_main".into(),
buffers: vec![Vertex::position_desc().into()], buffers: vec![Vertex::position_desc().into()],
}, },
fragment: Some(FragmentState { fragment: Some(FragmentState {
module: shader, module: self.shader.clone(),
entry_point: "fs_point_light_main".into(), entry_point: "fs_point_light_main".into(),
targets: vec![], targets: vec![],
}), }),

View File

@ -1,17 +1,15 @@
use std::{ use std::{
cell::RefMut, collections::VecDeque,
collections::{HashMap, VecDeque},
rc::Rc,
sync::Arc, sync::Arc,
}; };
use glam::{Vec2, Vec3}; use glam::{Vec2, Vec3};
use image::GenericImageView; use image::GenericImageView;
use lyra_ecs::{ use lyra_ecs::{
query::{Entities, ResMut}, AtomicRef, Entity, ResourceData query::{Entities, ResMut}, AtomicRef, ResourceData
}; };
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use lyra_resource::{Image, Texture}; use lyra_resource::Image;
use tracing::{info, instrument, warn}; use tracing::{info, instrument, warn};
use uuid::Uuid; use uuid::Uuid;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
@ -21,10 +19,9 @@ use crate::{
graph::{Node, NodeDesc, NodeType, SlotAttribute}, graph::{Node, NodeDesc, NodeType, SlotAttribute},
render_job::RenderJob, render_job::RenderJob,
resource::{ resource::{
FragmentState, PipelineDescriptor, RenderPipeline, RenderPipelineDescriptor, Shader, FragmentState, RenderPipeline, RenderPipelineDescriptor,
VertexState, VertexState,
}, },
texture::{res_filter_to_wgpu, res_wrap_to_wgpu},
transform_buffer_storage::{TransformBuffers, TransformIndex}, transform_buffer_storage::{TransformBuffers, TransformIndex},
vertex::Vertex2D, vertex::Vertex2D,
}, },
@ -44,8 +41,12 @@ pub enum SpritePassSlots {
} }
struct SpriteTexture { struct SpriteTexture {
// this field is actually read, its given to the bind group
#[allow(dead_code)]
texture: wgpu::Texture, texture: wgpu::Texture,
texture_sampler: wgpu::Sampler, // this field is actually read, its given to the bind group
#[allow(dead_code)]
sampler: wgpu::Sampler,
texture_bg: Arc<wgpu::BindGroup>, texture_bg: Arc<wgpu::BindGroup>,
vertex_buffers: wgpu::Buffer, vertex_buffers: wgpu::Buffer,
@ -217,7 +218,7 @@ impl Node for SpritePass {
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 1, binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT, visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::NonFiltering), ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None, count: None,
}, },
], ],
@ -241,10 +242,8 @@ impl Node for SpritePass {
let vt = graph.view_target(); let vt = graph.view_target();
if self.pipeline.is_none() { if self.pipeline.is_none() {
let shader = Rc::new(Shader { let shader = graph.load_shader_str("sprite_shader", include_str!("../../shaders/2d/sprite_main.wgsl"))
label: Some("sprite_shader".into()), .expect("failed to load wgsl shader from manager");
source: include_str!("../../shaders/2d/sprite_main.wgsl").to_string(),
});
let diffuse_bgl = self.texture_bgl.clone().unwrap(); let diffuse_bgl = self.texture_bgl.clone().unwrap();
let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera).clone(); let camera_bgl = graph.bind_group_layout(BasePassSlots::Camera).clone();
@ -310,7 +309,7 @@ impl Node for SpritePass {
let texture_uuid = sprite.texture.uuid(); let texture_uuid = sprite.texture.uuid();
if !sprite_store.contains_key(&texture_uuid) { if !sprite_store.contains_key(&texture_uuid) {
// returns `None` if the Texture image is not loaded. // returns `None` if the Texture image is not loaded.
if let Some((tex, samp, tex_bg)) = if let Some((texture, sampler, tex_bg)) =
self.load_sprite_texture(device, queue, &texture_uuid, &image) self.load_sprite_texture(device, queue, &texture_uuid, &image)
{ {
let (vertex, index) = self.create_vertex_index_buffers(device, &sprite); let (vertex, index) = self.create_vertex_index_buffers(device, &sprite);
@ -318,8 +317,8 @@ impl Node for SpritePass {
sprite_store.insert( sprite_store.insert(
texture_uuid, texture_uuid,
SpriteTexture { SpriteTexture {
texture: tex, texture,
texture_sampler: samp, sampler,
texture_bg: Arc::new(tex_bg), texture_bg: Arc::new(tex_bg),
vertex_buffers: vertex, vertex_buffers: vertex,
index_buffers: index, index_buffers: index,

View File

@ -1,10 +1,10 @@
use std::{collections::HashMap, rc::Rc, sync::Arc}; use std::{collections::HashMap, sync::Arc};
use lyra_game_derive::RenderGraphLabel; use lyra_game_derive::RenderGraphLabel;
use crate::render::{ use crate::render::{
graph::{Node, NodeDesc, NodeType}, graph::{Node, NodeDesc, NodeType},
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState}, resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, VertexState},
}; };
#[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)] #[derive(Default, Debug, Clone, Copy, Hash, RenderGraphLabel)]
@ -58,13 +58,9 @@ impl Node for TintPass {
self.bgl = Some(bgl.clone()); self.bgl = Some(bgl.clone());
self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default())); self.target_sampler = Some(device.create_sampler(&wgpu::SamplerDescriptor::default()));
let shader = Rc::new(Shader {
label: Some("tint_shader".into()),
source: include_str!("../../shaders/tint.wgsl").to_string(),
});
let vt = graph.view_target(); let vt = graph.view_target();
let shader = graph.load_shader_str("tint_shader", include_str!("../../shaders/tint.wgsl"))
.expect("failed to load tint shader");
NodeDesc::new( NodeDesc::new(
NodeType::Render, NodeType::Render,

View File

@ -6,7 +6,6 @@ pub mod render_buffer;
pub mod render_job; pub mod render_job;
pub mod mesh; pub mod mesh;
pub mod texture; pub mod texture;
pub mod shader_loader;
pub mod material; pub mod material;
pub mod camera; pub mod camera;
pub mod transform_buffer_storage; pub mod transform_buffer_storage;
@ -19,4 +18,7 @@ mod texture_atlas;
pub use texture_atlas::*; pub use texture_atlas::*;
mod slot_buffer; mod slot_buffer;
pub use slot_buffer::*; pub use slot_buffer::*;
mod shader_loader;
pub use shader_loader::*;

View File

@ -135,13 +135,13 @@ impl BasicRenderer {
let surface_target = RenderTarget::from_surface(surface, config); let surface_target = RenderTarget::from_surface(surface, config);
let view_target = Rc::new(RefCell::new(ViewTarget::new(device.clone(), surface_target))); let view_target = Rc::new(RefCell::new(ViewTarget::new(device.clone(), surface_target)));
let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone()); let mut main_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone(), &world);
debug!("Adding base pass"); debug!("Adding base pass");
main_graph.add_node(BasePassLabel, BasePass::new()); main_graph.add_node(BasePassLabel, BasePass::new());
{ {
let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone()); let mut forward_plus_graph = RenderGraph::new(device.clone(), queue.clone(), view_target.clone(), &world);
debug!("Adding light base pass"); debug!("Adding light base pass");
forward_plus_graph.add_node(LightBasePassLabel, LightBasePass::new()); forward_plus_graph.add_node(LightBasePassLabel, LightBasePass::new());

View File

@ -1,16 +1,18 @@
use std::{ops::Deref, rc::Rc, sync::Arc}; use std::{ops::Deref, rc::Rc, sync::Arc};
use lyra_resource::ResHandle;
use wgpu::PipelineLayout; use wgpu::PipelineLayout;
use super::{PipelineCompilationOptions, Shader}; use crate::render::Shader;
use super::PipelineCompilationOptions;
//#[derive(Debug, Clone)] //#[derive(Debug, Clone)]
pub struct ComputePipelineDescriptor { pub struct ComputePipelineDescriptor {
pub label: Option<String>, pub label: Option<String>,
pub layouts: Vec<Arc<wgpu::BindGroupLayout>>, pub layouts: Vec<Arc<wgpu::BindGroupLayout>>,
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage. /// The compiled shader module for the stage.
pub shader: Rc<Shader>, pub shader: ResHandle<Shader>,
/// The entry point in the compiled shader. /// The entry point in the compiled shader.
/// There must be a function in the shader with the same name. /// There must be a function in the shader with the same name.
pub shader_entry_point: String, pub shader_entry_point: String,
@ -76,13 +78,16 @@ impl ComputePipeline {
None None
}; };
let shader = desc.shader.data_ref()
.expect("shader is not loaded, ensure its loaded before creating a pipeline!");
// an Rc was used here so that this shader could be reused by the fragment stage if // an Rc was used here so that this shader could be reused by the fragment stage if
// they share the same shader. I tried to do it without an Rc but couldn't get past // they share the same shader. I tried to do it without an Rc but couldn't get past
// the borrow checker // the borrow checker
let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { let compiled_shader = Rc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.shader.label.as_deref(), label: shader.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
&desc.shader.source, &shader.source,
)), )),
})); }));

View File

@ -84,13 +84,16 @@ impl RenderPipeline {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let vertex_shader = desc.vertex.module.data_ref()
.expect("vertex shader is not loaded, ensure its loaded before creating a pipeline!");
// an Rc was used here so that this shader could be reused by the fragment stage if // an Rc was used here so that this shader could be reused by the fragment stage if
// they share the same shader. I tried to do it without an Rc but couldn't get past // they share the same shader. I tried to do it without an Rc but couldn't get past
// the borrow checker // the borrow checker
let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { let vrtx_shad = Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: desc.vertex.module.label.as_deref(), label: vertex_shader.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed( source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(
&desc.vertex.module.source, &vertex_shader.source,
)), )),
})); }));
let vrtx_state = wgpu::VertexState { let vrtx_state = wgpu::VertexState {
@ -101,12 +104,14 @@ impl RenderPipeline {
}; };
let frag_module = desc.fragment.as_ref().map(|f| { let frag_module = desc.fragment.as_ref().map(|f| {
if f.module == desc.vertex.module { if f.module.uuid() == desc.vertex.module.uuid() {
vrtx_shad.clone() vrtx_shad.clone()
} else { } else {
let frag_shader = f.module.data_ref()
.expect("vertex shader is not loaded, ensure its loaded before creating a pipeline!");
Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor { Arc::new(device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: f.module.label.as_deref(), label: frag_shader.label.as_deref(),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&f.module.source)), source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&frag_shader.source)),
})) }))
} }
}); });

View File

@ -1,4 +1,6 @@
use std::rc::Rc; use lyra_resource::ResHandle;
use crate::render::Shader;
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct VertexBufferLayout { pub struct VertexBufferLayout {
@ -18,11 +20,10 @@ impl<'a> From<wgpu::VertexBufferLayout<'a>> for VertexBufferLayout {
} }
/// Describes the vertex stage in a render pipeline. /// Describes the vertex stage in a render pipeline.
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct VertexState { pub struct VertexState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage. /// The compiled shader module for the stage.
pub module: Rc<Shader>, pub module: ResHandle<Shader>,
/// The entry point in the compiled shader. /// The entry point in the compiled shader.
/// There must be a function in the shader with the same name. /// There must be a function in the shader with the same name.
pub entry_point: String, pub entry_point: String,
@ -30,18 +31,11 @@ pub struct VertexState {
pub buffers: Vec<VertexBufferLayout>, pub buffers: Vec<VertexBufferLayout>,
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
}
/// Describes the fragment stage in the render pipeline. /// Describes the fragment stage in the render pipeline.
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct FragmentState { pub struct FragmentState {
// TODO: make this a ResHandle<Shader>
/// The compiled shader module for the stage. /// The compiled shader module for the stage.
pub module: Rc<Shader>, pub module: ResHandle<Shader>,
/// The entry point in the compiled shader. /// The entry point in the compiled shader.
/// There must be a function in the shader with the same name. /// There must be a function in the shader with the same name.
pub entry_point: String, pub entry_point: String,

View File

@ -0,0 +1,97 @@
use std::sync::Arc;
use async_std::sync::RwLock;
use lyra_resource::{loader::{LoaderError, ResourceLoader}, ResHandle, ResourceData};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Shader {
pub label: Option<String>,
pub source: String,
pub dependent_modules: Option<String>,
}
impl ResourceData for Shader {
fn as_any(&self) -> &dyn std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
self
}
fn dependencies(&self) -> Vec<lyra_resource::UntypedResHandle> {
vec![]
}
}
/// A WGSL shader module loader with a preprocessor.
///
/// This will load resources with mimetype of `text/wgsl` and `.wgsl` file extensions.
#[derive(Default)]
pub struct PreprocessShaderLoader {
processor: Arc<RwLock<wgsl_preprocessor::Processor>>,
}
async fn load_shader_str(processor: Arc<RwLock<wgsl_preprocessor::Processor>>, content: &str) -> Result<Shader, LoaderError> {
let mut processor = processor.write().await;
let mod_path = processor.parse_module(content)
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?;
let content = match &mod_path {
Some(path) => processor.preprocess_module(&path)
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?,
None => content.to_owned(),
};
Ok(Shader {
label: mod_path,
source: content,
dependent_modules: None,
})
}
impl ResourceLoader for PreprocessShaderLoader {
fn extensions(&self) -> &[&str] {
&[
"wgsl"
]
}
fn mime_types(&self) -> &[&str] {
&[
"text/wgsl"
]
}
fn load(&self, _: lyra_resource::ResourceManager, path: &str) -> lyra_resource::loader::PinedBoxLoaderFuture {
// cant use &str across async
let path = path.to_string();
let processor = self.processor.clone();
Box::pin(async move {
let module_str = std::fs::read_to_string(&path)
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?;
let shader = load_shader_str(processor, &module_str).await?;
Ok(Box::new(shader) as Box<dyn ResourceData>)
})
}
fn load_bytes(&self, _: lyra_resource::ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> lyra_resource::loader::PinedBoxLoaderFuture {
let processor = self.processor.clone();
Box::pin(async move {
let end = offset + length;
let contents = std::str::from_utf8(&bytes[offset..end])
.map_err(|e| LoaderError::DecodingError(Arc::new(e.into())))?;
let shader = load_shader_str(processor, &contents).await?;
Ok(Box::new(shader) as Box<dyn ResourceData>)
})
}
fn create_erased_handle(&self) -> std::sync::Arc<dyn lyra_resource::ResourceStorage> {
Arc::from(ResHandle::<Shader>::new_loading(None))
}
}

View File

@ -16,7 +16,7 @@ struct CameraUniform {
projection: mat4x4<f32>, projection: mat4x4<f32>,
position: vec3f, position: vec3f,
tile_debug: u32, tile_debug: u32,
}; }
struct Light { struct Light {
position: vec3f, position: vec3f,
@ -32,12 +32,12 @@ struct Light {
spot_cutoff: f32, spot_cutoff: f32,
spot_outer_cutoff: f32, spot_outer_cutoff: f32,
light_shadow_uniform_index: array<i32, 6>, light_shadow_uniform_index: array<i32, 6>,
}; }
struct Lights { struct Lights {
light_count: u32, light_count: u32,
data: array<Light>, data: array<Light>,
}; }
struct Cone { struct Cone {
tip: vec3f, tip: vec3f,

View File

@ -24,7 +24,7 @@ enum ModelLoaderError {
impl From<ModelLoaderError> for LoaderError { impl From<ModelLoaderError> for LoaderError {
fn from(value: ModelLoaderError) -> Self { fn from(value: ModelLoaderError) -> Self {
LoaderError::DecodingError(value.into()) LoaderError::DecodingError(Arc::new(value.into()))
} }
} }
@ -181,7 +181,7 @@ impl ResourceLoader for GltfLoader {
let parent_path = parent_path.display().to_string(); let parent_path = parent_path.display().to_string();
let gltf = gltf::Gltf::open(&path) let gltf = gltf::Gltf::open(&path)
.map_err(|ge| LoaderError::DecodingError(ge.into()))?; .map_err(|ge| LoaderError::DecodingError(Arc::new(ge.into())))?;
let mut use_bin = false; let mut use_bin = false;
let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() { let buffers: Vec<Vec<u8>> = gltf.buffers().flat_map(|b| match b.source() {
@ -290,7 +290,7 @@ mod tests {
let manager = ResourceManager::new(); let manager = ResourceManager::new();
let gltf = manager.request::<Gltf>(&path).unwrap(); let gltf = manager.request::<Gltf>(&path).unwrap();
assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)), "failed to load gltf, hit 10 second timeout"); assert!(gltf.wait_for_load_timeout(Duration::from_secs(10)).is_ok_and(|r| r), "failed to load gltf, hit 10 second timeout");
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

@ -1,5 +1,3 @@
use std::sync::Arc;
use crate::{loader::LoaderError, ResourceState, UntypedResHandle}; use crate::{loader::LoaderError, ResourceState, UntypedResHandle};
#[derive(Clone)] #[derive(Clone)]
@ -9,7 +7,7 @@ pub enum DependencyState {
/// The resource that had the error. /// The resource that had the error.
handle: UntypedResHandle, handle: UntypedResHandle,
/// The error that the resource ran into when loading. /// The error that the resource ran into when loading.
error: Arc<LoaderError>, error: LoaderError,
}, },
Ready, Ready,
} }

View File

@ -15,8 +15,6 @@ pub mod loader;
mod world_ext; mod world_ext;
pub use world_ext::*; pub use world_ext::*;
pub(crate) mod util;
pub use crossbeam::channel as channel; pub use crossbeam::channel as channel;
pub use notify; pub use notify;

View File

@ -10,7 +10,7 @@ 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 {
LoaderError::DecodingError(value.into()) LoaderError::DecodingError(Arc::new(value.into()))
} }
} }
@ -75,8 +75,8 @@ impl ResourceLoader for ImageLoader {
// 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(Arc::new(e)),
_ => LoaderError::DecodingError(e.into()), _ => LoaderError::DecodingError(Arc::new(e.into())),
})?; })?;
let image = Image::from(image); let image = Image::from(image);
let image = Box::new(image) as Box<dyn ResourceData>; let image = Box::new(image) as Box<dyn ResourceData>;
@ -89,8 +89,8 @@ impl ResourceLoader for ImageLoader {
Box::pin(async move { Box::pin(async move {
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(Arc::new(e)),
_ => LoaderError::DecodingError(e.into()), _ => LoaderError::DecodingError(Arc::new(e.into())),
})?; })?;
let image = Image::from(image); let image = Image::from(image);
debug!("Finished loading image ({} bytes)", length); debug!("Finished loading image ({} bytes)", length);

View File

@ -7,7 +7,7 @@ use thiserror::Error;
use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager}; use crate::{resource_manager::ResourceStorage, ResourceData, ResourceManager};
#[derive(Error, Debug)] #[derive(Error, Debug, Clone)]
pub enum LoaderError { pub enum LoaderError {
#[error("A malformed path was given: '{0}'")] #[error("A malformed path was given: '{0}'")]
InvalidPath(String), InvalidPath(String),
@ -16,16 +16,22 @@ pub enum LoaderError {
UnsupportedExtension(String), UnsupportedExtension(String),
#[error("IOError: '{0}'")] #[error("IOError: '{0}'")]
IoError(io::Error), IoError(Arc<io::Error>),
// From is implemented for this field in each loader module // From is implemented for this field in each loader module
#[error("Decoding error: '{0}'")] #[error("Decoding error: '{0}'")]
DecodingError(anyhow::Error), DecodingError(Arc<anyhow::Error>),
} }
impl From<io::Error> for LoaderError { impl From<io::Error> for LoaderError {
fn from(value: io::Error) -> Self { fn from(value: io::Error) -> Self {
LoaderError::IoError(value) LoaderError::IoError(Arc::new(value))
}
}
impl From<anyhow::Error> for LoaderError {
fn from(value: anyhow::Error) -> Self {
LoaderError::DecodingError(Arc::new(value))
} }
} }

View File

@ -48,7 +48,7 @@ pub trait ResourceData: Send + Sync + Any + 'static {
pub enum ResourceState { pub enum ResourceState {
Loading, Loading,
Error(Arc<LoaderError>), Error(LoaderError),
Ready(Box<dyn ResourceData>), Ready(Box<dyn ResourceData>),
} }
@ -165,7 +165,7 @@ impl UntypedResHandle {
matches!(d.state, ResourceState::Ready(_)) matches!(d.state, ResourceState::Ready(_))
} }
pub fn get_error(&self) -> Option<Arc<LoaderError>> { pub fn get_error(&self) -> Option<LoaderError> {
let d = self.read(); let d = self.read();
match &d.state { match &d.state {
@ -197,14 +197,15 @@ impl UntypedResHandle {
/// ///
/// This blocks the thread without consuming CPU time; its backed by a /// This blocks the thread without consuming CPU time; its backed by a
/// [`Condvar`](std::sync::Condvar). /// [`Condvar`](std::sync::Condvar).
pub fn wait_for_load(&self) { pub fn wait_for_load(&self) -> Result<(), LoaderError> {
self.wait_for_load_timeout_option_impl(None); self.wait_for_load_timeout_option_impl(None)?;
Ok(())
} }
/// Does the same as [`UntypedResHandle::wait_for_load`] but has a timeout. /// Does the same as [`UntypedResHandle::wait_for_load`] but has a timeout.
/// ///
/// Returns true if the resource was loaded before hitting the timeout. /// Returns true if the resource was loaded before hitting the timeout.
pub fn wait_for_load_timeout(&self, timeout: Duration) -> bool { pub fn wait_for_load_timeout(&self, timeout: Duration) -> Result<bool, LoaderError> {
self.wait_for_load_timeout_option_impl(Some(timeout)) self.wait_for_load_timeout_option_impl(Some(timeout))
} }
@ -212,41 +213,48 @@ impl UntypedResHandle {
/// ///
/// This blocks the thread without consuming CPU time; its backed by a /// This blocks the thread without consuming CPU time; its backed by a
/// [`Condvar`](std::sync::Condvar). /// [`Condvar`](std::sync::Condvar).
pub fn wait_recurse_dependencies_load(&self) { pub fn wait_recurse_dependencies_load(&self) -> Result<(), LoaderError> {
self.wait_recurse_dependencies_load_timeout_option_impl(None); self.wait_recurse_dependencies_load_timeout_option_impl(None)?;
Ok(())
} }
/// Does the same as [`UntypedResHandle::wait_recurse_dependencies_load`] but has a timeout. /// Does the same as [`UntypedResHandle::wait_recurse_dependencies_load`] but has a timeout.
/// ///
/// Returns true if the resource was loaded before hitting the timeout. /// Returns true if the resource was loaded before hitting the timeout.
pub fn wait_recurse_dependencies_load_timeout(&self, timeout: Duration) -> bool { pub fn wait_recurse_dependencies_load_timeout(&self, timeout: Duration) -> Result<bool, LoaderError> {
self.wait_recurse_dependencies_load_timeout_option_impl(Some(timeout)) self.wait_recurse_dependencies_load_timeout_option_impl(Some(timeout))
} }
fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> bool { fn wait_for_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
let d = self.read(); let d = self.read();
if matches!(d.state, ResourceState::Ready(_)) { if matches!(d.state, ResourceState::Ready(_)) {
return true; return Ok(true);
} }
let cv = d.condvar.clone(); let cv = d.condvar.clone();
// MUST DROP to avoid deadlock
drop(d); drop(d);
let l = cv.0.lock().unwrap(); let l = cv.0.lock().unwrap();
if let Some(timeout) = timeout { if let Some(timeout) = timeout {
let (_unused, timeout) = cv.1.wait_timeout(l, timeout).unwrap(); let (_unused, _) = cv.1.wait_timeout(l, timeout).unwrap();
!timeout.timed_out()
} else { } else {
let _unused = cv.1.wait(l).unwrap(); let _unused = cv.1.wait(l).unwrap();
true }
let d = self.read();
match &d.state {
// it will still be loading if the timeout is exceeded
ResourceState::Loading => Ok(false),
ResourceState::Error(e) => Err(e.clone()),
ResourceState::Ready(_) => Ok(true),
} }
} }
fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option<Duration>) -> bool { fn wait_recurse_dependencies_load_timeout_option_impl(&self, timeout: Option<Duration>) -> Result<bool, LoaderError> {
if !self.wait_for_load_timeout_option_impl(timeout) { if !self.wait_for_load_timeout_option_impl(timeout)? {
return false; return Ok(false);
} }
let res = self.read(); let res = self.read();
@ -257,13 +265,13 @@ impl UntypedResHandle {
// waiting for some resources and finish early. // waiting for some resources and finish early.
while self.recurse_dependency_state().is_loading() { while self.recurse_dependency_state().is_loading() {
for dep in data.recur_dependencies() { for dep in data.recur_dependencies() {
if !dep.wait_for_load_timeout_option_impl(timeout) { if !dep.wait_for_load_timeout_option_impl(timeout)? {
return false; return Ok(false);
} }
} }
} }
true Ok(true)
}, },
// self.wait_for_load at the start ensures that the state is ready // self.wait_for_load at the start ensures that the state is ready
_ => unreachable!() _ => unreachable!()
@ -315,6 +323,12 @@ pub struct ResHandle<T: ResourceData> {
_marker: PhantomData<T>, _marker: PhantomData<T>,
} }
impl<T: ResourceData> Default for ResHandle<T> {
fn default() -> Self {
Self::new_loading(None)
}
}
impl<T: ResourceData> Clone for ResHandle<T> { impl<T: ResourceData> Clone for ResHandle<T> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
@ -591,7 +605,7 @@ mod tests {
assert!(state.is_loading()); assert!(state.is_loading());
// this will take a bit // this will take a bit
res.wait_recurse_dependencies_load(); res.wait_recurse_dependencies_load().unwrap();
let state = res.recurse_dependency_state(); let state = res.recurse_dependency_state();
assert!(!state.is_loading()); assert!(!state.is_loading());

View File

@ -166,7 +166,8 @@ impl ResourceManager {
} }
Err(err) => { Err(err) => {
let mut d = untyped.write(); let mut d = untyped.write();
d.state = ResourceState::Error(Arc::new(err)); d.state = ResourceState::Error(err);
d.condvar.1.notify_all();
} }
} }
}); });
@ -236,10 +237,12 @@ impl ResourceManager {
Ok(data) => { Ok(data) => {
let mut d = thand.write(); let mut d = thand.write();
d.state = ResourceState::Ready(data); d.state = ResourceState::Ready(data);
d.condvar.1.notify_all();
} }
Err(err) => { Err(err) => {
let mut d = thand.write(); let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err)); d.state = ResourceState::Error(err);
d.condvar.1.notify_all();
} }
} }
}); });
@ -273,6 +276,15 @@ impl ResourceManager {
} }
} }
pub fn load_str<T>(&self, ident: &str, mime_type: &str, text: &str) -> Result<ResHandle<T>, RequestError>
where
T: ResourceData
{
let bytes = text.as_bytes().to_vec();
let len = bytes.len();
self.load_bytes(ident, mime_type, bytes, 0, len)
}
/// 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(&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);
@ -358,7 +370,7 @@ impl ResourceManager {
} }
Err(err) => { Err(err) => {
let mut d = thand.write(); let mut d = thand.write();
d.state = ResourceState::Error(Arc::new(err)); d.state = ResourceState::Error(err);
} }
} }
}); });
@ -370,7 +382,7 @@ impl ResourceManager {
#[cfg(test)] #[cfg(test)]
pub mod tests { pub mod tests {
use std::{io, ops::Deref}; use std::io;
use instant::Instant; use instant::Instant;
@ -420,8 +432,7 @@ pub mod tests {
let res = man.request::<Image>(&get_image("squiggles.png")).unwrap(); let res = man.request::<Image>(&get_image("squiggles.png")).unwrap();
assert!(!res.is_loaded()); assert!(!res.is_loaded());
res.wait_for_load(); res.wait_for_load().unwrap();
//busy_wait_resource(&res, 10.0);
// shouldn't panic because of the loop // shouldn't panic because of the loop
res.data_ref().unwrap(); res.data_ref().unwrap();
@ -455,7 +466,7 @@ pub mod tests {
// 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) => { ResourceState::Error(err) => {
match err.deref() { match err {
LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true, LoaderError::IoError(e) if e.kind() == io::ErrorKind::NotFound => true,
_ => false, _ => false,
} }

View File

@ -1 +0,0 @@
use base64::Engine;

View File

@ -121,11 +121,11 @@ fn setup_scene_plugin(app: &mut App) {
.request::<Gltf>("../assets/cube-texture-embedded.gltf") .request::<Gltf>("../assets/cube-texture-embedded.gltf")
.unwrap(); .unwrap();
cube_gltf.wait_recurse_dependencies_load(); cube_gltf.wait_recurse_dependencies_load().unwrap();
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0]; let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
let image = resman.request::<Image>("../assets/Egg_item.png").unwrap(); let image = resman.request::<Image>("../assets/Egg_item.png").unwrap();
image.wait_recurse_dependencies_load(); image.wait_recurse_dependencies_load().unwrap();
drop(resman); drop(resman);
world.spawn(( world.spawn((