11use crate :: { SpirvBuilder , SpirvBuilderError , leaf_deps} ;
22use notify:: { Event , RecommendedWatcher , RecursiveMode , Watcher } ;
33use rustc_codegen_spirv_types:: CompileResult ;
4- use std:: sync:: mpsc:: TrySendError ;
4+ use std:: path:: Path ;
5+ use std:: sync:: mpsc:: { TryRecvError , TrySendError } ;
56use std:: {
67 collections:: HashSet ,
78 path:: PathBuf ,
@@ -17,17 +18,36 @@ impl SpirvBuilder {
1718
1819type 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 ) ]
2242pub 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
3353impl 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 )
0 commit comments