mirror of
https://github.com/EmbarkStudios/rust-gpu.git
synced 2024-11-21 22:34:34 +00:00
Clean up the interface of the watching support (#663)
* Clean up the interface of the watching support This allows us to avoid the seperate thread just for looping through the graphics rx to the eventloop, for example In almost all cases, intial results are blocked on the first result, and then needs notifications for later changes * Fix test failures * fmt * Remove the comment again
This commit is contained in:
parent
95bc1914e2
commit
4b2011476b
@ -6,13 +6,15 @@ use rustc_codegen_spirv::CompileResult;
|
||||
use crate::{leaf_deps, SpirvBuilder, SpirvBuilderError};
|
||||
|
||||
impl SpirvBuilder {
|
||||
/// Watches the module for changes using [`notify`](https://crates.io/crates/notify).
|
||||
/// Watches the module for changes using [`notify`](https://crates.io/crates/notify),
|
||||
/// and rebuild it upon changes
|
||||
///
|
||||
/// This is a blocking operation, wand should never return in the happy path
|
||||
/// Returns the result of the first successful compilation, then calls
|
||||
/// `on_compilation_finishes` for each subsequent compilation.
|
||||
pub fn watch(
|
||||
mut self,
|
||||
on_compilation_finishes: impl Fn(CompileResult),
|
||||
) -> Result<(), SpirvBuilderError> {
|
||||
mut on_compilation_finishes: impl FnMut(CompileResult) + Send + 'static,
|
||||
) -> Result<CompileResult, SpirvBuilderError> {
|
||||
self.validate_running_conditions()?;
|
||||
if !matches!(self.print_metadata, crate::MetadataPrintout::None) {
|
||||
return Err(SpirvBuilderError::WatchWithPrintMetadata);
|
||||
@ -53,52 +55,58 @@ impl SpirvBuilder {
|
||||
}
|
||||
};
|
||||
let metadata = self.parse_metadata_file(&metadata_file)?;
|
||||
on_compilation_finishes(metadata);
|
||||
let mut watched_paths = HashSet::new();
|
||||
let (tx, rx) = sync_channel(0);
|
||||
let mut watcher =
|
||||
notify::immediate_watcher(move |event: notify::Result<Event>| match event {
|
||||
Ok(e) => match e.kind {
|
||||
notify::EventKind::Access(_) => (),
|
||||
notify::EventKind::Any
|
||||
| notify::EventKind::Create(_)
|
||||
| notify::EventKind::Modify(_)
|
||||
| notify::EventKind::Remove(_)
|
||||
| notify::EventKind::Other => {
|
||||
let _ = tx.try_send(());
|
||||
}
|
||||
},
|
||||
Err(e) => println!("notify error: {:?}", e),
|
||||
})
|
||||
.expect("Could create watcher");
|
||||
leaf_deps(&metadata_file, |it| {
|
||||
let path = it.to_path().unwrap();
|
||||
if watched_paths.insert(path.to_owned()) {
|
||||
watcher
|
||||
.watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
||||
.expect("Cargo dependencies are valid files");
|
||||
}
|
||||
})
|
||||
.expect("Could read dependencies file");
|
||||
loop {
|
||||
rx.recv().expect("Watcher still alive");
|
||||
let metadata_result = crate::invoke_rustc(&self);
|
||||
if let Ok(file) = metadata_result {
|
||||
// We can bubble this error up because it's an internal error (e.g. rustc_codegen_spirv's version of CompileResult is somehow out of sync)
|
||||
let metadata = self.parse_metadata_file(&file)?;
|
||||
let first_result = metadata;
|
||||
|
||||
leaf_deps(&file, |it| {
|
||||
let path = it.to_path().unwrap();
|
||||
if watched_paths.insert(path.to_owned()) {
|
||||
watcher
|
||||
.watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
||||
.expect("Cargo dependencies are valid files");
|
||||
}
|
||||
let thread = std::thread::spawn(move || {
|
||||
let mut watched_paths = HashSet::new();
|
||||
let (tx, rx) = sync_channel(0);
|
||||
let mut watcher =
|
||||
notify::immediate_watcher(move |event: notify::Result<Event>| match event {
|
||||
Ok(e) => match e.kind {
|
||||
notify::EventKind::Access(_) => (),
|
||||
notify::EventKind::Any
|
||||
| notify::EventKind::Create(_)
|
||||
| notify::EventKind::Modify(_)
|
||||
| notify::EventKind::Remove(_)
|
||||
| notify::EventKind::Other => {
|
||||
let _ = tx.try_send(());
|
||||
}
|
||||
},
|
||||
Err(e) => println!("notify error: {:?}", e),
|
||||
})
|
||||
.expect("Could read dependencies file");
|
||||
.expect("Could create watcher");
|
||||
leaf_deps(&metadata_file, |it| {
|
||||
let path = it.to_path().unwrap();
|
||||
if watched_paths.insert(path.to_owned()) {
|
||||
watcher
|
||||
.watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
||||
.expect("Cargo dependencies are valid files");
|
||||
}
|
||||
})
|
||||
.expect("Could read dependencies file");
|
||||
loop {
|
||||
rx.recv().expect("Watcher still alive");
|
||||
let metadata_result = crate::invoke_rustc(&self);
|
||||
if let Ok(file) = metadata_result {
|
||||
let metadata = self
|
||||
.parse_metadata_file(&file)
|
||||
.expect("Metadata file is correct");
|
||||
|
||||
on_compilation_finishes(metadata);
|
||||
leaf_deps(&file, |it| {
|
||||
let path = it.to_path().unwrap();
|
||||
if watched_paths.insert(path.to_owned()) {
|
||||
watcher
|
||||
.watch(it.to_path().unwrap(), RecursiveMode::NonRecursive)
|
||||
.expect("Cargo dependencies are valid files");
|
||||
}
|
||||
})
|
||||
.expect("Could read dependencies file");
|
||||
|
||||
on_compilation_finishes(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
std::mem::forget(thread);
|
||||
Ok(first_result)
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,7 @@ fn block_on<T>(future: impl Future<Output = T>) -> T {
|
||||
}
|
||||
|
||||
pub fn start(options: &Options) {
|
||||
let rx = crate::maybe_watch(options.shader, true);
|
||||
let shader_binary = rx.recv().expect("Should send one binary");
|
||||
let shader_binary = crate::maybe_watch(options.shader, None);
|
||||
|
||||
block_on(start_internal(options, shader_binary))
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
use std::thread::spawn;
|
||||
|
||||
use crate::maybe_watch;
|
||||
|
||||
use super::Options;
|
||||
@ -310,21 +308,17 @@ fn create_pipeline(
|
||||
|
||||
pub fn start(options: &Options) {
|
||||
// Build the shader before we pop open a window, since it might take a while.
|
||||
let rx = maybe_watch(options.shader, false);
|
||||
let initial_shader = rx.recv().expect("Initial shader is required");
|
||||
|
||||
let event_loop = EventLoop::with_user_event();
|
||||
let proxy = event_loop.create_proxy();
|
||||
let thread = spawn(move || loop {
|
||||
while let Ok(result) = rx.recv() {
|
||||
match proxy.send_event(result) {
|
||||
Ok(()) => {}
|
||||
// If something goes wrong, close this thread
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
});
|
||||
std::mem::forget(thread);
|
||||
let initial_shader = maybe_watch(
|
||||
options.shader,
|
||||
Some(Box::new(move |res| match proxy.send_event(res) {
|
||||
Ok(it) => it,
|
||||
// ShaderModuleDescriptor is not `Debug`, so can't use unwrap/expect
|
||||
Err(_) => panic!("Event loop dead"),
|
||||
})),
|
||||
);
|
||||
|
||||
let window = winit::window::WindowBuilder::new()
|
||||
.with_title("Rust GPU - wgpu")
|
||||
.with_inner_size(winit::dpi::LogicalSize::new(1280.0, 720.0))
|
||||
|
@ -42,8 +42,6 @@
|
||||
rust_2018_idioms
|
||||
)]
|
||||
|
||||
use std::sync::mpsc::{self, Receiver};
|
||||
|
||||
use clap::Clap;
|
||||
use strum::{Display, EnumString};
|
||||
|
||||
@ -60,11 +58,8 @@ pub enum RustGPUShader {
|
||||
|
||||
fn maybe_watch(
|
||||
shader: RustGPUShader,
|
||||
force_no_watch: bool,
|
||||
) -> Receiver<wgpu::ShaderModuleDescriptor<'static>> {
|
||||
// This bound needs to be 1, because in cases where this function is used for direct building (e.g. for the compute example or on android)
|
||||
// we send the value directly in the same thread. This avoids deadlocking in those cases.
|
||||
let (tx, rx) = mpsc::sync_channel(1);
|
||||
on_watch: Option<Box<dyn FnMut(wgpu::ShaderModuleDescriptor<'static>) + Send + 'static>>,
|
||||
) -> wgpu::ShaderModuleDescriptor<'static> {
|
||||
#[cfg(not(any(target_os = "android", target_arch = "wasm32")))]
|
||||
{
|
||||
use spirv_builder::{Capability, CompileResult, MetadataPrintout, SpirvBuilder};
|
||||
@ -94,23 +89,16 @@ fn maybe_watch(
|
||||
for &cap in capabilities {
|
||||
builder = builder.capability(cap);
|
||||
}
|
||||
if force_no_watch {
|
||||
let compile_result = builder.build().unwrap();
|
||||
handle_builder_result(compile_result, &tx);
|
||||
let initial_result = if let Some(mut f) = on_watch {
|
||||
builder
|
||||
.watch(move |compile_result| f(handle_compile_result(compile_result)))
|
||||
.expect("Configuration is correct for watching")
|
||||
} else {
|
||||
let thread = std::thread::spawn(move || {
|
||||
builder
|
||||
.watch(|compile_result| {
|
||||
handle_builder_result(compile_result, &tx);
|
||||
})
|
||||
.expect("Configuration is correct for watching")
|
||||
});
|
||||
std::mem::forget(thread);
|
||||
}
|
||||
fn handle_builder_result(
|
||||
builder.build().unwrap()
|
||||
};
|
||||
fn handle_compile_result(
|
||||
compile_result: CompileResult,
|
||||
tx: &mpsc::SyncSender<wgpu::ShaderModuleDescriptor<'static>>,
|
||||
) {
|
||||
) -> wgpu::ShaderModuleDescriptor<'static> {
|
||||
let module_path = compile_result.module.unwrap_single();
|
||||
let data = std::fs::read(module_path).unwrap();
|
||||
let spirv = wgpu::util::make_spirv(&data);
|
||||
@ -122,25 +110,23 @@ fn maybe_watch(
|
||||
wgpu::ShaderSource::SpirV(Cow::Owned(cow.into_owned()))
|
||||
}
|
||||
};
|
||||
tx.send(wgpu::ShaderModuleDescriptor {
|
||||
wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
source: spirv,
|
||||
flags: wgpu::ShaderFlags::default(),
|
||||
})
|
||||
.expect("Rx is still alive");
|
||||
}
|
||||
}
|
||||
handle_compile_result(initial_result)
|
||||
}
|
||||
#[cfg(any(target_os = "android", target_arch = "wasm32"))]
|
||||
{
|
||||
tx.send(match shader {
|
||||
match shader {
|
||||
RustGPUShader::Simplest => wgpu::include_spirv!(env!("simplest_shader.spv")),
|
||||
RustGPUShader::Sky => wgpu::include_spirv!(env!("sky_shader.spv")),
|
||||
RustGPUShader::Compute => wgpu::include_spirv!(env!("compute_shader.spv")),
|
||||
RustGPUShader::Mouse => wgpu::include_spirv!(env!("mouse_shader.spv")),
|
||||
})
|
||||
.expect("rx to be alive")
|
||||
}
|
||||
}
|
||||
rx
|
||||
}
|
||||
|
||||
fn is_compute_shader(shader: RustGPUShader) -> bool {
|
||||
|
Loading…
Reference in New Issue
Block a user