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:
Daniel McNab 2021-06-14 08:19:41 +01:00 committed by GitHub
parent 95bc1914e2
commit 4b2011476b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 93 deletions

View File

@ -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)
}
}

View File

@ -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))
}

View File

@ -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))

View File

@ -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 {