Skip to content

Commit a46522e

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

File tree

2 files changed

+91
-47
lines changed

2 files changed

+91
-47
lines changed

crates/spirv-builder/src/watch.rs

Lines changed: 85 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,36 @@ 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+
/// `First | FirstFailed`: the path to the crate
47+
/// `Watching`: the path to our metadata file with entry point names and file paths
48+
path_to_crate: PathBuf,
2949
watched_paths: WatchedPaths,
30-
first_result: bool,
50+
state: WatcherState,
3151
}
3252

3353
impl SpirvWatcher {
@@ -63,65 +83,84 @@ impl SpirvWatcher {
6383
.map_err(SpirvWatcherError::NotifyFailed)?;
6484

6585
Ok(Self {
66-
watch_path: path_to_crate,
86+
path_to_crate,
6787
builder,
6888
watcher,
6989
rx,
7090
watched_paths: HashSet::new(),
71-
first_result: false,
91+
state: WatcherState::First,
7292
})
7393
}
7494

75-
/// Blocks the current thread until a change is detected
76-
/// and the crate is rebuilt.
95+
/// Blocks the current thread until a change is detected, rebuilds the crate and returns the [`CompileResult`] or
96+
/// an [`SpirvBuilderError`]. Always builds once when called for the first time.
7797
///
78-
/// Result of rebuilding of the crate is then returned to the caller.
98+
/// See [`Self::try_recv`] for a non-blocking variant.
7999
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)?;
100+
self.recv_inner(|rx| rx.recv().map_err(|err| TryRecvError::from(err)))
101+
.map(|result| result.unwrap())
102+
}
87103

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

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}");
113+
#[inline]
114+
fn recv_inner(
115+
&mut self,
116+
recv: impl FnOnce(&Receiver<()>) -> Result<(), TryRecvError>,
117+
) -> Result<Option<CompileResult>, SpirvBuilderError> {
118+
let received = match self.state {
119+
// always compile on first invocation
120+
// file watches have yet to be setup, so recv channel is empty and must not be cleared
121+
WatcherState::First => Ok(()),
122+
WatcherState::FirstFailed | WatcherState::Watching => recv(&self.rx),
123+
};
124+
match received {
125+
Ok(_) => (),
126+
Err(TryRecvError::Empty) => return Ok(None),
127+
Err(TryRecvError::Disconnected) => return Err(SpirvWatcherError::WatcherDied.into()),
128+
}
97129

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}"),
130+
let result = (|| {
131+
let metadata_file = crate::invoke_rustc(&self.builder)?;
132+
let result = self.builder.parse_metadata_file(&metadata_file)?;
133+
self.watch_leaf_deps(&metadata_file)?;
134+
Ok(result)
135+
})();
136+
match result {
137+
Ok(result) => {
138+
if matches!(self.state, WatcherState::FirstFailed) {
139+
self.watcher
140+
.unwatch(&self.path_to_crate)
141+
.map_err(SpirvWatcherError::NotifyFailed)?;
142+
}
143+
self.state = WatcherState::Watching;
144+
Ok(Some(result))
145+
}
146+
Err(err) => {
147+
self.state = match self.state {
148+
WatcherState::First => {
149+
self.watcher
150+
.watch(&self.path_to_crate, RecursiveMode::Recursive)
151+
.map_err(SpirvWatcherError::NotifyFailed)?;
152+
WatcherState::FirstFailed
107153
}
154+
WatcherState::FirstFailed => WatcherState::FirstFailed,
155+
WatcherState::Watching => WatcherState::Watching,
108156
};
109-
self.watcher
110-
.unwatch(watch_path)
111-
.map_err(SpirvWatcherError::NotifyFailed)?;
112-
path
157+
Err(err)
113158
}
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)
159+
}
121160
}
122161

123-
fn watch_leaf_deps(&mut self) -> Result<(), SpirvBuilderError> {
124-
leaf_deps(&self.watch_path, |artifact| {
162+
fn watch_leaf_deps(&mut self, watch_path: &Path) -> Result<(), SpirvBuilderError> {
163+
leaf_deps(watch_path, |artifact| {
125164
let path = artifact.to_path().unwrap();
126165
if self.watched_paths.insert(path.to_owned())
127166
&& 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)