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,34 @@ 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+ path_to_crate : PathBuf ,
2947 watched_paths : WatchedPaths ,
30- first_result : bool ,
48+ state : WatcherState ,
3149}
3250
3351impl 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 )
0 commit comments