1
1
use crate :: { SpirvBuilder , SpirvBuilderError , leaf_deps} ;
2
2
use notify:: { Event , RecommendedWatcher , RecursiveMode , Watcher } ;
3
3
use rustc_codegen_spirv_types:: CompileResult ;
4
- use std:: sync:: mpsc:: TrySendError ;
4
+ use std:: path:: Path ;
5
+ use std:: sync:: mpsc:: { TryRecvError , TrySendError } ;
5
6
use std:: {
6
7
collections:: HashSet ,
7
8
path:: PathBuf ,
@@ -17,17 +18,34 @@ impl SpirvBuilder {
17
18
18
19
type WatchedPaths = HashSet < PathBuf > ;
19
20
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
+
20
40
/// Watcher of a crate which rebuilds it on changes.
21
41
#[ derive( Debug ) ]
22
42
pub struct SpirvWatcher {
23
43
builder : SpirvBuilder ,
24
44
watcher : RecommendedWatcher ,
25
45
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 ,
29
47
watched_paths : WatchedPaths ,
30
- first_result : bool ,
48
+ state : WatcherState ,
31
49
}
32
50
33
51
impl SpirvWatcher {
@@ -63,65 +81,84 @@ impl SpirvWatcher {
63
81
. map_err ( SpirvWatcherError :: NotifyFailed ) ?;
64
82
65
83
Ok ( Self {
66
- watch_path : path_to_crate,
84
+ path_to_crate,
67
85
builder,
68
86
watcher,
69
87
rx,
70
88
watched_paths : HashSet :: new ( ) ,
71
- first_result : false ,
89
+ state : WatcherState :: First ,
72
90
} )
73
91
}
74
92
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 .
77
95
///
78
- /// Result of rebuilding of the crate is then returned to the caller .
96
+ /// See [`Self::try_recv`] for a non-blocking variant .
79
97
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
+ }
87
101
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)
90
109
}
91
110
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
+ }
97
127
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
107
151
}
152
+ WatcherState :: FirstFailed => WatcherState :: FirstFailed ,
153
+ WatcherState :: Watching => WatcherState :: Watching ,
108
154
} ;
109
- self . watcher
110
- . unwatch ( watch_path)
111
- . map_err ( SpirvWatcherError :: NotifyFailed ) ?;
112
- path
155
+ Err ( err)
113
156
}
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
+ }
121
158
}
122
159
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| {
125
162
let path = artifact. to_path ( ) . unwrap ( ) ;
126
163
if self . watched_paths . insert ( path. to_owned ( ) )
127
164
&& let Err ( err) = self . watcher . watch ( path, RecursiveMode :: NonRecursive )
0 commit comments