Skip to content

Commit e0b5e1c

Browse files
committed
watch: add try_recv, a non-blocking variant
1 parent f178189 commit e0b5e1c

File tree

2 files changed

+89
-47
lines changed

2 files changed

+89
-47
lines changed

crates/spirv-builder/src/watch.rs

Lines changed: 83 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::{SpirvBuilder, SpirvBuilderError, leaf_deps};
22
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher};
33
use rustc_codegen_spirv_types::CompileResult;
4-
use std::sync::mpsc::TrySendError;
4+
use std::path::Path;
5+
use std::sync::mpsc::{TryRecvError, TrySendError};
56
use std::{
67
collections::HashSet,
78
path::PathBuf,
@@ -17,17 +18,34 @@ impl SpirvBuilder {
1718

1819
type WatchedPaths = HashSet<PathBuf>;
1920

21+
#[derive(Copy, Clone, Debug)]
22+
enum WatcherState {
23+
/// upcoming compile is the first compile:
24+
/// * always recompile regardless of file watches
25+
/// * success: go to [`Self::Watching`]
26+
/// * fail: go to [`Self::FirstFailed`]
27+
First,
28+
/// the first compile (and all consecutive ones) failed:
29+
/// * only recompile when watcher notifies us
30+
/// * the whole project dir is being watched, remove that watch
31+
/// * success: go to [`Self::Watching`]
32+
/// * fail: stay in [`Self::FirstFailed`]
33+
FirstFailed,
34+
/// at least one compile finished and has set up the proper file watches:
35+
/// * only recompile when watcher notifies us
36+
/// * always stays in [`Self::Watching`]
37+
Watching,
38+
}
39+
2040
/// Watcher of a crate which rebuilds it on changes.
2141
#[derive(Debug)]
2242
pub struct SpirvWatcher {
2343
builder: SpirvBuilder,
2444
watcher: RecommendedWatcher,
2545
rx: Receiver<()>,
26-
/// `!first_result`: the path to the crate
27-
/// `first_result`: the path to our metadata file with entry point names and file paths
28-
watch_path: PathBuf,
46+
path_to_crate: PathBuf,
2947
watched_paths: WatchedPaths,
30-
first_result: bool,
48+
state: WatcherState,
3149
}
3250

3351
impl SpirvWatcher {
@@ -63,65 +81,84 @@ impl SpirvWatcher {
6381
.map_err(SpirvWatcherError::NotifyFailed)?;
6482

6583
Ok(Self {
66-
watch_path: path_to_crate,
84+
path_to_crate,
6785
builder,
6886
watcher,
6987
rx,
7088
watched_paths: HashSet::new(),
71-
first_result: false,
89+
state: WatcherState::First,
7290
})
7391
}
7492

75-
/// Blocks the current thread until a change is detected
76-
/// and the crate is rebuilt.
93+
/// Blocks the current thread until a change is detected, rebuilds the crate and returns the [`CompileResult`] or
94+
/// an [`SpirvBuilderError`]. Always builds once when called for the first time.
7795
///
78-
/// Result of rebuilding of the crate is then returned to the caller.
96+
/// See [`Self::try_recv`] for a non-blocking variant.
7997
pub fn recv(&mut self) -> Result<CompileResult, SpirvBuilderError> {
80-
if !self.first_result {
81-
return self.recv_first_result();
82-
}
83-
84-
self.rx.recv().map_err(|_| SpirvWatcherError::WatcherDied)?;
85-
let metadata_file = crate::invoke_rustc(&self.builder)?;
86-
let result = self.builder.parse_metadata_file(&metadata_file)?;
98+
self.recv_inner(|rx| rx.recv().map_err(|err| TryRecvError::from(err)))
99+
.map(|result| result.unwrap())
100+
}
87101

88-
self.watch_leaf_deps()?;
89-
Ok(result)
102+
/// If a change is detected or this is the first invocation, builds the crate and returns the [`CompileResult`]
103+
/// (wrapped in `Some`) or an [`SpirvBuilderError`]. If no change has been detected, returns `Ok(None)` without
104+
/// blocking.
105+
///
106+
/// See [`Self::recv`] for a blocking variant.
107+
pub fn try_recv(&mut self) -> Result<Option<CompileResult>, SpirvBuilderError> {
108+
self.recv_inner(Receiver::try_recv)
90109
}
91110

92-
fn recv_first_result(&mut self) -> Result<CompileResult, SpirvBuilderError> {
93-
let metadata_file = match crate::invoke_rustc(&self.builder) {
94-
Ok(path) => path,
95-
Err(err) => {
96-
log::error!("{err}");
111+
#[inline]
112+
fn recv_inner(
113+
&mut self,
114+
recv: impl FnOnce(&Receiver<()>) -> Result<(), TryRecvError>,
115+
) -> Result<Option<CompileResult>, SpirvBuilderError> {
116+
let received = match self.state {
117+
// always compile on first invocation
118+
// file watches have yet to be setup, so recv channel is empty and must not be cleared
119+
WatcherState::First => Ok(()),
120+
WatcherState::FirstFailed | WatcherState::Watching => recv(&self.rx),
121+
};
122+
match received {
123+
Ok(_) => (),
124+
Err(TryRecvError::Empty) => return Ok(None),
125+
Err(TryRecvError::Disconnected) => return Err(SpirvWatcherError::WatcherDied.into()),
126+
}
97127

98-
let watch_path = self.watch_path.as_ref();
99-
self.watcher
100-
.watch(watch_path, RecursiveMode::Recursive)
101-
.map_err(SpirvWatcherError::NotifyFailed)?;
102-
let path = loop {
103-
self.rx.recv().map_err(|_| SpirvWatcherError::WatcherDied)?;
104-
match crate::invoke_rustc(&self.builder) {
105-
Ok(path) => break path,
106-
Err(err) => log::error!("{err}"),
128+
let result = (|| {
129+
let metadata_file = crate::invoke_rustc(&self.builder)?;
130+
let result = self.builder.parse_metadata_file(&metadata_file)?;
131+
self.watch_leaf_deps(&metadata_file)?;
132+
Ok(result)
133+
})();
134+
match result {
135+
Ok(result) => {
136+
if matches!(self.state, WatcherState::FirstFailed) {
137+
self.watcher
138+
.unwatch(&self.path_to_crate)
139+
.map_err(SpirvWatcherError::NotifyFailed)?;
140+
}
141+
self.state = WatcherState::Watching;
142+
Ok(Some(result))
143+
}
144+
Err(err) => {
145+
self.state = match self.state {
146+
WatcherState::First => {
147+
self.watcher
148+
.watch(&self.path_to_crate, RecursiveMode::Recursive)
149+
.map_err(SpirvWatcherError::NotifyFailed)?;
150+
WatcherState::FirstFailed
107151
}
152+
WatcherState::FirstFailed => WatcherState::FirstFailed,
153+
WatcherState::Watching => WatcherState::Watching,
108154
};
109-
self.watcher
110-
.unwatch(watch_path)
111-
.map_err(SpirvWatcherError::NotifyFailed)?;
112-
path
155+
Err(err)
113156
}
114-
};
115-
let result = self.builder.parse_metadata_file(&metadata_file)?;
116-
117-
self.watch_path = metadata_file;
118-
self.first_result = true;
119-
self.watch_leaf_deps()?;
120-
Ok(result)
157+
}
121158
}
122159

123-
fn watch_leaf_deps(&mut self) -> Result<(), SpirvBuilderError> {
124-
leaf_deps(&self.watch_path, |artifact| {
160+
fn watch_leaf_deps(&mut self, watch_path: &Path) -> Result<(), SpirvBuilderError> {
161+
leaf_deps(watch_path, |artifact| {
125162
let path = artifact.to_path().unwrap();
126163
if self.watched_paths.insert(path.to_owned())
127164
&& let Err(err) = self.watcher.watch(path, RecursiveMode::NonRecursive)

examples/runners/wgpu/src/lib.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,12 @@ fn maybe_watch(
178178

179179
if let Some(mut f) = on_watch {
180180
let mut watcher = builder.watch().unwrap();
181-
let first_compile = watcher.recv().unwrap();
181+
let first_compile = loop {
182+
match watcher.recv() {
183+
Ok(e) => break e,
184+
Err(e) => println!("Shader compiling failed: {e}"),
185+
}
186+
};
182187

183188
let shader_modules = handle_compile_result(first_compile);
184189
std::thread::spawn(move || {

0 commit comments

Comments
 (0)