diff options
| author | s-ol <s+removethis@s-ol.nu> | 2026-04-22 11:52:07 +0000 |
|---|---|---|
| committer | s-ol <s+removethis@s-ol.nu> | 2026-05-14 15:00:13 +0000 |
| commit | 76e48cc66816a1ec064c12a7181b7751d1c783b2 (patch) | |
| tree | 7368d6ab24518d25eea7e6cf1bf8559519121cb7 /src | |
| parent | rebind outdated textures (diff) | |
| download | wgsl-view-76e48cc66816a1ec064c12a7181b7751d1c783b2.tar.gz wgsl-view-76e48cc66816a1ec064c12a7181b7751d1c783b2.zip | |
switch to WESL shaders
Diffstat (limited to 'src')
| -rw-r--r-- | src/bin/wgsl_render.rs | 115 | ||||
| -rw-r--r-- | src/lib.rs | 1 | ||||
| -rw-r--r-- | src/osc.rs | 30 | ||||
| -rw-r--r-- | src/renderer.rs | 160 | ||||
| -rw-r--r-- | src/uniform.rs | 57 | ||||
| -rw-r--r-- | src/wesl.rs | 86 |
6 files changed, 296 insertions, 153 deletions
diff --git a/src/bin/wgsl_render.rs b/src/bin/wgsl_render.rs index 2288366..bcfb393 100644 --- a/src/bin/wgsl_render.rs +++ b/src/bin/wgsl_render.rs @@ -1,3 +1,4 @@ +use std::error::Error; use std::thread; use std::time::{Duration, Instant}; @@ -6,7 +7,6 @@ use rosc::OscType; use wgsl_view::gpu; use wgsl_view::osc::OscServer; use wgsl_view::renderer::Renderer; -use wgsl_view::uniform::UniformError; fn main() { env_logger::init(); @@ -68,38 +68,44 @@ fn main() { loop { let frame_start = Instant::now(); - let handle_msg = |msg: rosc::OscMessage| -> Result<(), String> { + let handle_msg = |msg: rosc::OscMessage| -> Result<(), Box<dyn Error>> { let path: Vec<_> = msg.addr.strip_prefix("/").unwrap().split('/').collect(); match (&path[..], &msg.args[..]) { - (["shader"], [OscType::String(code)]) => renderer.load_shader(&device, code), - (["uniform", rest @ ..], args) => { - set_uniform(renderer.uniforms(), rest, args).map_err(|e| e.to_string()) + // create shaders + (["module", module_name], [OscType::String(code)]) => { + renderer.load_module(module_name, code)? } + (["entrypoint"], [OscType::String(module_name)]) => { + renderer.compile(&device, module_name)? + } + // bind values + (["uniform", module, item, rest @ ..], args) => { + renderer.set_uniform(module, item, rest, args)? + } + (["binding", module, item], [OscType::String(target)]) => { + renderer.set_binding(&device, module, item, target)? + } + // create/destroy texture resources (["texture", id], [OscType::String(tsv_name)]) => { renderer.uniforms().create_texture(&device, id, tsv_name); - Ok(()) } + (["texture", id, "destroy"], []) => { + renderer.uniforms().destroy_texture(&device, id); + } + // create/destroy sampler resources (["sampler", id], [OscType::String(filter), OscType::String(clamp)]) => { let (filter_mode, address_mode) = parse_sampler_modes(filter, clamp)?; renderer .uniforms() .create_sampler(&device, id, filter_mode, address_mode); - Ok(()) - } - (["texture", id, "destroy"], []) => { - renderer.uniforms().destroy_texture(&device, id); - Ok(()) } (["sampler", id, "destroy"], []) => { renderer.uniforms().destroy_sampler(&device, id); - Ok(()) } - (["binding", name], [OscType::String(target)]) => { - bind_resource(renderer.uniforms(), &device, name, target) - } - _ => Err(format!("unhandled OSC message {} {:?}", msg.addr, msg.args)), + _ => Err(format!("unhandled OSC message {} {:?}", msg.addr, msg.args))?, } + Ok(()) }; let dirty = if continuous { @@ -136,66 +142,6 @@ fn main() { } /// Set a uniform value from OSC args, navigating the path. -fn set_uniform( - cache: &mut wgsl_view::uniform::UniformCache, - path: &[&str], - args: &[OscType], -) -> Result<(), UniformError> { - let mut parts = path.iter(); - let name = parts.next().ok_or(UniformError::NotFound)?; - - let mut uref = cache.get(name).ok_or(UniformError::NotFound)?; - for component in parts { - uref = uref.field(component)?; - } - - let scalar = uref.leaf_scalar()?; - match scalar.kind { - naga::ScalarKind::Float => { - let values: Vec<f32> = args - .iter() - .map(|a| match a { - OscType::Float(f) => Ok(*f), - OscType::Double(d) => Ok(*d as f32), - OscType::Int(i) => Ok(*i as f32), - OscType::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }), - _ => Err(UniformError::TypeMismatch), - }) - .collect::<Result<_, _>>()?; - uref.set_f32(&values)?; - } - naga::ScalarKind::Sint => { - let values: Vec<i32> = args - .iter() - .map(|a| match a { - OscType::Int(i) => Ok(*i), - OscType::Float(f) => Ok(*f as i32), - OscType::Double(d) => Ok(*d as i32), - OscType::Bool(b) => Ok(if *b { 1 } else { 0 }), - _ => Err(UniformError::TypeMismatch), - }) - .collect::<Result<_, _>>()?; - uref.set_i32(&values)?; - } - naga::ScalarKind::Uint => { - let values: Vec<u32> = args - .iter() - .map(|a| match a { - OscType::Int(i) => Ok(*i as u32), - OscType::Float(f) => Ok(*f as u32), - OscType::Double(d) => Ok(*d as u32), - OscType::Bool(b) => Ok(if *b { 1 } else { 0 }), - _ => Err(UniformError::TypeMismatch), - }) - .collect::<Result<_, _>>()?; - uref.set_u32(&values)?; - } - _ => return Err(UniformError::TypeMismatch), - } - - Ok(()) -} - fn parse_sampler_modes( filter: &str, address: &str, @@ -213,20 +159,3 @@ fn parse_sampler_modes( }; Ok((filter_mode, address_mode)) } - -fn bind_resource( - uniforms: &mut wgsl_view::uniform::UniformCache, - device: &wgpu::Device, - name: &str, - target: &str, -) -> Result<(), String> { - if let Some(id) = target.strip_prefix("/texture/") { - uniforms.bind_texture(device, name, id)?; - Ok(()) - } else if let Some(id) = target.strip_prefix("/sampler/") { - uniforms.bind_sampler(device, name, id)?; - Ok(()) - } else { - Err(format!("invalid binding target '{target}'")) - } -} @@ -3,4 +3,5 @@ pub mod hap; pub mod osc; pub mod renderer; pub mod uniform; +mod wesl; pub mod window; @@ -1,4 +1,4 @@ -use std::{fmt::Display, net::UdpSocket}; +use std::{error::Error, net::UdpSocket}; use rosc::{OscMessage, OscPacket}; @@ -20,9 +20,9 @@ impl OscServer { /// Drains all pending OSC messages, calling `on_message` for each. /// Returns `true` if any messages were dispatched. - pub fn poll<E: Display>( + pub fn poll( &mut self, - mut on_message: impl FnMut(OscMessage) -> Result<(), E>, + mut on_message: impl FnMut(OscMessage) -> Result<(), Box<dyn Error>>, ) -> bool { let mut received = false; loop { @@ -30,19 +30,17 @@ impl OscServer { Ok((size, _addr)) => { let data = &self.buf[..size]; let res = rosc::decoder::decode_udp(data) - .map_err(|e| format!("OSC edecode error: {}", e)) - .and_then(|(_, packet)| { - dispatch(packet, &mut on_message).map_err(|e| e.to_string()) - }); + .map_err(Box::from) + .and_then(|(_, packet)| dispatch(packet, &mut on_message)); if let Err(e) = res { - log::warn!("{}", e); + log::error!("{}", e); }; received = true; } Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break, Err(e) => { - log::warn!("OSC recv error: {}", e); + log::error!("OSC recv error: {}", e); break; } } @@ -51,25 +49,23 @@ impl OscServer { } /// Blocks until at least one OSC message arrives, then drains all pending. - pub fn recv<E: Display>( + pub fn recv( &mut self, - mut on_message: impl FnMut(OscMessage) -> Result<(), E>, + mut on_message: impl FnMut(OscMessage) -> Result<(), Box<dyn Error>>, ) -> bool { self.socket.set_nonblocking(false).expect("set blocking"); match self.socket.recv_from(&mut self.buf) { Ok((size, _addr)) => { let data = &self.buf[..size]; let res = rosc::decoder::decode_udp(data) - .map_err(|e| format!("OSC edecode error: {}", e)) - .and_then(|(_, packet)| { - dispatch(packet, &mut on_message).map_err(|e| e.to_string()) - }); + .map_err(Box::from) + .and_then(|(_, packet)| dispatch(packet, &mut on_message)); if let Err(e) = res { - log::warn!("{}", e); + log::error!("{}", e); }; } Err(e) => { - log::warn!("OSC recv error: {}", e); + log::error!("OSC recv error: {}", e); } } self.socket.set_nonblocking(true).expect("set nonblocking"); diff --git a/src/renderer.rs b/src/renderer.rs index 155c318..c6f1973 100644 --- a/src/renderer.rs +++ b/src/renderer.rs @@ -1,4 +1,11 @@ -use crate::uniform::UniformCache; +use std::error::Error; +use std::str::FromStr; + +use rosc::OscType; +use wesl::{Mangler, ModulePath}; + +use crate::uniform::{UniformCache, UniformError}; +use crate::wesl::ShaderSources; const CANVAS_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8UnormSrgb; @@ -30,6 +37,24 @@ fn fs_main(@location(0) uv: vec2<f32>) -> @location(0) vec4<f32> { } "; +#[derive(Debug, thiserror::Error)] +pub enum ShaderError { + #[error("error resolving module")] + InvalidModulePath, + + #[error("error setting uniform '{0}': {1}")] + UniformError(String, crate::uniform::UniformError), + + #[error("uniform '{0}' not found")] + UniformNotFound(String), + + #[error("error binding resource to '{0}': {1}")] + BindError(String, crate::uniform::BindError), + + #[error("error compiling shader: {0}")] + CompileError(#[from] wesl::Error), +} + pub struct Renderer { canvas: wgpu::Texture, canvas_view: wgpu::TextureView, @@ -37,6 +62,7 @@ pub struct Renderer { vertex_bgl: wgpu::BindGroupLayout, vertex_bg: wgpu::BindGroup, render_pipeline: wgpu::RenderPipeline, + shader_sources: ShaderSources, uniforms: UniformCache, } @@ -102,11 +128,19 @@ impl Renderer { ); let mut uniforms = UniformCache::new(); + let mut shader_sources = ShaderSources::default(); + shader_sources.set_module(wesl::ModulePath::new_root(), DEFAULT_FRAGMENT); + + let initial_fragment = shader_sources + .compile(&wesl::ModulePath::new_root()) + .expect("default shader"); + log::info!("frag source:\n {initial_fragment}"); + let render_pipeline = build_pipeline( device, &vertex_module, &vertex_bgl, - DEFAULT_FRAGMENT, + &initial_fragment, &mut uniforms, ) .expect("default shader"); @@ -118,6 +152,7 @@ impl Renderer { vertex_bgl, vertex_bg, render_pipeline, + shader_sources, uniforms, } } @@ -126,24 +161,115 @@ impl Renderer { &self.canvas } - pub fn canvas_view(&self) -> &wgpu::TextureView { - &self.canvas_view - } - pub fn uniforms(&mut self) -> &mut UniformCache { &mut self.uniforms } - pub fn load_shader(&mut self, device: &wgpu::Device, source: &str) -> Result<(), String> { - let pipeline = build_pipeline( + pub fn set_uniform( + &mut self, + module: &str, + item: &str, + rest: &[&str], + args: &[OscType], + ) -> Result<(), ShaderError> { + let path = ModulePath::from_str(module).map_err(|_| ShaderError::InvalidModulePath)?; + let name = self.shader_sources.mangler().mangle(&path, item); + + let mut uref = self + .uniforms + .get(&name) + .ok_or(ShaderError::UniformNotFound(name))?; + + let lift_err = |e| ShaderError::UniformError(item.to_string(), e); + + for component in rest.iter() { + uref = uref.field(component).map_err(lift_err)?; + } + + let scalar = uref.leaf_scalar().map_err(lift_err)?; + match scalar.kind { + naga::ScalarKind::Float => args + .iter() + .map(|a| match a { + OscType::Float(f) => Ok(*f), + OscType::Double(d) => Ok(*d as f32), + OscType::Int(i) => Ok(*i as f32), + OscType::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }), + _ => Err(UniformError::TypeMismatch), + }) + .collect::<Result<Vec<_>, _>>() + .and_then(|values| uref.set_f32(&values)), + naga::ScalarKind::Sint => args + .iter() + .map(|a| match a { + OscType::Int(i) => Ok(*i), + OscType::Float(f) => Ok(*f as i32), + OscType::Double(d) => Ok(*d as i32), + OscType::Bool(b) => Ok(if *b { 1 } else { 0 }), + _ => Err(UniformError::TypeMismatch), + }) + .collect::<Result<Vec<_>, _>>() + .and_then(|values| uref.set_i32(&values)), + naga::ScalarKind::Uint => args + .iter() + .map(|a| match a { + OscType::Int(i) => Ok(*i as u32), + OscType::Float(f) => Ok(*f as u32), + OscType::Double(d) => Ok(*d as u32), + OscType::Bool(b) => Ok(if *b { 1 } else { 0 }), + _ => Err(UniformError::TypeMismatch), + }) + .collect::<Result<Vec<_>, _>>() + .and_then(|values| uref.set_u32(&values)), + _ => Err(UniformError::TypeMismatch), + } + .map_err(lift_err) + } + + pub fn set_binding( + &mut self, + device: &wgpu::Device, + module: &str, + item: &str, + target: &str, + ) -> Result<(), ShaderError> { + let path = ModulePath::from_str(module).map_err(|_| ShaderError::InvalidModulePath)?; + let name = self.shader_sources.mangler().mangle(&path, item); + + if let Some(id) = target.strip_prefix("/texture/") { + self.uniforms.bind_texture(device, &name, id) + } else if let Some(id) = target.strip_prefix("/sampler/") { + self.uniforms.bind_sampler(device, &name, id) + } else { + Err(crate::uniform::BindError::ResourceNotFound( + target.to_string(), + )) + } + .map_err(|e| ShaderError::BindError(name, e)) + } + + pub fn load_module(&mut self, module_name: &str, source: &str) -> Result<(), ShaderError> { + let path = ModulePath::from_str(module_name).map_err(|_| ShaderError::InvalidModulePath)?; + self.shader_sources.set_module(path, source); + Ok(()) + } + + pub fn compile( + &mut self, + device: &wgpu::Device, + module_name: &str, + ) -> Result<(), Box<dyn Error>> { + let path = ModulePath::from_str(module_name).map_err(|_| ShaderError::InvalidModulePath)?; + let fragment_source = self.shader_sources.compile(&path)?; + log::info!("frag source:\n {fragment_source}"); + + self.render_pipeline = build_pipeline( device, &self.vertex_module, &self.vertex_bgl, - source, + &fragment_source, &mut self.uniforms, )?; - self.render_pipeline = pipeline; - log::info!("shader loaded"); Ok(()) } @@ -189,16 +315,14 @@ fn build_pipeline( vertex_bgl: &wgpu::BindGroupLayout, fragment_source: &str, cache: &mut UniformCache, -) -> Result<wgpu::RenderPipeline, String> { - let module = naga::front::wgsl::parse_str(fragment_source) - .map_err(|e| format!("WGSL parse error: {e}"))?; +) -> Result<wgpu::RenderPipeline, Box<dyn Error>> { + let module = naga::front::wgsl::parse_str(fragment_source)?; naga::valid::Validator::new( naga::valid::ValidationFlags::all(), naga::valid::Capabilities::all(), ) - .validate(&module) - .map_err(|e| format!("WGSL validation error: {e}"))?; + .validate(&module)?; cache.refresh(module, device); cache.rebuild_bind_group(device); @@ -225,13 +349,13 @@ fn build_pipeline( layout: Some(&pipeline_layout), vertex: wgpu::VertexState { module: vertex_module, - entry_point: Some("vs_main"), + entry_point: None, buffers: &[], compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &fragment_module, - entry_point: Some("fs_main"), + entry_point: None, targets: &[Some(wgpu::ColorTargetState { format: CANVAS_FORMAT, blend: Some(wgpu::BlendState::REPLACE), diff --git a/src/uniform.rs b/src/uniform.rs index fefd67f..db01e0d 100644 --- a/src/uniform.rs +++ b/src/uniform.rs @@ -11,29 +11,28 @@ pub struct UniformRef<'a> { pub data: &'a mut [u8], } -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum UniformError { + #[error("can't be indexed (further)")] NotIndexable, - IndexOutOfBounds, - NotFound, + #[error("index '{0}' out of bounds")] + IndexOutOfBounds(usize), + #[error("member '{0}' not found")] + MemberNotFound(String), + #[error("mismatched value types")] TypeMismatch, + #[error("invalid number of OSC values")] SizeMismatch, } -impl std::fmt::Display for UniformError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::NotIndexable => write!(f, "type is not indexable"), - Self::IndexOutOfBounds => write!(f, "index out of bounds"), - Self::NotFound => write!(f, "member not found"), - Self::TypeMismatch => write!(f, "scalar type mismatch"), - Self::SizeMismatch => write!(f, "value count mismatch"), - } - } +#[derive(Debug, thiserror::Error)] +pub enum BindError { + #[error("binding point '{0}' not found")] + NotFound(String), + #[error("bindable resource '{0}' not found")] + ResourceNotFound(String), } -impl std::error::Error for UniformError {} - impl<'a> UniformRef<'a> { /// Navigate by path component: struct member by name, or swizzle/numeric index otherwise. pub fn field(self, name: &str) -> Result<UniformRef<'a>, UniformError> { @@ -51,7 +50,7 @@ impl<'a> UniformRef<'a> { }); } } - return Err(UniformError::NotFound); + return Err(UniformError::MemberNotFound(name.to_string())); } let i = match name { @@ -70,7 +69,7 @@ impl<'a> UniformRef<'a> { match *ty_inner { naga::TypeInner::Vector { size, scalar } => { if i >= size as usize { - return Err(UniformError::IndexOutOfBounds); + return Err(UniformError::IndexOutOfBounds(i)); } let w = scalar.width as usize; let scalar_ty = find_or_expect_scalar_type(self.module, scalar); @@ -87,7 +86,7 @@ impl<'a> UniformRef<'a> { scalar, } => { if i >= columns as usize { - return Err(UniformError::IndexOutOfBounds); + return Err(UniformError::IndexOutOfBounds(i)); } let col_ty = find_or_expect_vector_type(self.module, rows, scalar); let col_layout = self.layouter[col_ty]; @@ -106,7 +105,7 @@ impl<'a> UniformRef<'a> { _ => return Err(UniformError::NotIndexable), }; if i >= len { - return Err(UniformError::IndexOutOfBounds); + return Err(UniformError::IndexOutOfBounds(i)); } let elem_size = self.layouter[base].size as usize; Ok(UniformRef { @@ -733,6 +732,10 @@ impl UniformCache { None => continue, }; + if let Some(ref name) = var.name { + log::info!("found uniform '{name}'"); + } + match var.space { naga::AddressSpace::Uniform => { let layout = new_layouter[var.ty]; @@ -855,6 +858,7 @@ impl UniformCache { // --- Resource pool operations --- pub fn create_texture(&mut self, device: &wgpu::Device, id: &str, tsv_name: &str) { + log::info!("creating texture {id} (tsv {tsv_name})"); self.texture_pool.insert( id.to_string(), TextureResource::new(device, id.to_string(), tsv_name.to_string()), @@ -863,6 +867,7 @@ impl UniformCache { } pub fn destroy_texture(&mut self, device: &wgpu::Device, id: &str) { + log::info!("destroying texture {id}"); let removed = self.texture_pool.remove(id).is_some(); let unbound = self.unbind_texture_resource(id); if removed || unbound { @@ -881,6 +886,7 @@ impl UniformCache { filter: wgpu::FilterMode, address_mode: wgpu::AddressMode, ) { + log::info!("creating sampler {id} ({filter:?} {address_mode:?})"); self.sampler_pool.insert( id.to_string(), SamplerResource::new(device, id.to_string(), filter, address_mode), @@ -889,6 +895,7 @@ impl UniformCache { } pub fn destroy_sampler(&mut self, device: &wgpu::Device, id: &str) { + log::info!("destroying sampler {id}"); let removed = self.sampler_pool.remove(id).is_some(); let unbound = self.unbind_sampler_resource(id); if removed || unbound { @@ -901,15 +908,15 @@ impl UniformCache { device: &wgpu::Device, slot_name: &str, resource_id: &str, - ) -> Result<(), String> { + ) -> Result<(), BindError> { if !self.texture_pool.contains_key(resource_id) { - return Err(format!("texture '{resource_id}' not found")); + return Err(BindError::ResourceNotFound(resource_id.to_string())); } let slot = self .texture_slots .iter_mut() .find(|t| t.name == slot_name) - .ok_or_else(|| format!("texture binding '{slot_name}' not found in shader"))?; + .ok_or(BindError::NotFound(slot_name.to_string()))?; slot.resource_id = Some(resource_id.to_string()); self.build_bind_group(device); Ok(()) @@ -920,15 +927,15 @@ impl UniformCache { device: &wgpu::Device, slot_name: &str, resource_id: &str, - ) -> Result<(), String> { + ) -> Result<(), BindError> { if !self.sampler_pool.contains_key(resource_id) { - return Err(format!("sampler '{resource_id}' not found")); + return Err(BindError::ResourceNotFound(resource_id.to_string())); } let slot = self .sampler_slots .iter_mut() .find(|s| s.name == slot_name) - .ok_or_else(|| format!("sampler binding '{slot_name}' not found in shader"))?; + .ok_or(BindError::NotFound(slot_name.to_string()))?; slot.resource_id = Some(resource_id.to_string()); self.build_bind_group(device); Ok(()) diff --git a/src/wesl.rs b/src/wesl.rs new file mode 100644 index 0000000..446c915 --- /dev/null +++ b/src/wesl.rs @@ -0,0 +1,86 @@ +use std::borrow::Cow; +use std::iter; +use std::path::PathBuf; + +use wesl::{ + syntax::PathOrigin, EscapeMangler, FileResolver, Mangler, ModulePath, ResolveError, Resolver, + VirtualResolver, Wesl, +}; + +pub struct ShaderSources { + virtual_resolver: VirtualResolver<'static>, + file_resolver: Option<FileResolver>, + mangler: EscapeMangler, +} + +impl Default for ShaderSources { + fn default() -> Self { + Self::new(Some("wesl-lib".into())) + } +} + +impl ShaderSources { + pub fn new(lib_dir: Option<PathBuf>) -> Self { + Self { + virtual_resolver: VirtualResolver::new(), + file_resolver: lib_dir.map(FileResolver::new), + mangler: Default::default(), + } + } + + pub fn set_module(&mut self, module: ModulePath, source: impl Into<String>) { + self.virtual_resolver + .add_module(module, Cow::Owned(source.into())); + } + + pub fn compile(&mut self, root_module: &ModulePath) -> Result<String, wesl::Error> { + log::info!("--- all modules ---"); + for (path, text) in self.virtual_resolver.modules() { + log::info!("==== {path}:\n{text}"); + } + log::info!("--- end modules ---"); + + Wesl::new_barebones() + .set_custom_resolver(&*self) + .set_custom_mangler(self.mangler.clone()) + .set_options(wesl::CompileOptions { + mangle_root: true, + ..Default::default() + }) + .compile(root_module) + .map(|compiled| compiled.to_string()) + } + + pub fn mangler(&self) -> &impl Mangler { + &self.mangler + } +} + +impl Resolver for ShaderSources { + fn resolve_source<'a>(&'a self, path: &ModulePath) -> Result<Cow<'a, str>, ResolveError> { + match path.origin { + PathOrigin::Absolute => self.virtual_resolver.resolve_source(path), + PathOrigin::Package(ref pname) => { + if let Some(ref files) = self.file_resolver { + let fpath = ModulePath { + origin: PathOrigin::Absolute, + components: iter::once(pname) + .chain(path.components.iter()) + .cloned() + .collect(), + }; + files.resolve_source(&fpath) + } else { + Err(ResolveError::ModuleNotFound( + path.clone(), + "no library director found".into(), + )) + } + } + _ => Err(ResolveError::ModuleNotFound( + path.clone(), + "can't resolve relative module".into(), + )), + } + } +} |
