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,36 @@ 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
+ /// `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 ,
29
49
watched_paths : WatchedPaths ,
30
- first_result : bool ,
50
+ state : WatcherState ,
31
51
}
32
52
33
53
impl SpirvWatcher {
@@ -63,65 +83,84 @@ impl SpirvWatcher {
63
83
. map_err ( SpirvWatcherError :: NotifyFailed ) ?;
64
84
65
85
Ok ( Self {
66
- watch_path : path_to_crate,
86
+ path_to_crate,
67
87
builder,
68
88
watcher,
69
89
rx,
70
90
watched_paths : HashSet :: new ( ) ,
71
- first_result : false ,
91
+ state : WatcherState :: First ,
72
92
} )
73
93
}
74
94
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 .
77
97
///
78
- /// Result of rebuilding of the crate is then returned to the caller .
98
+ /// See [`Self::try_recv`] for a non-blocking variant .
79
99
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
+ }
87
103
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)
90
111
}
91
112
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
+ }
97
129
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
107
153
}
154
+ WatcherState :: FirstFailed => WatcherState :: FirstFailed ,
155
+ WatcherState :: Watching => WatcherState :: Watching ,
108
156
} ;
109
- self . watcher
110
- . unwatch ( watch_path)
111
- . map_err ( SpirvWatcherError :: NotifyFailed ) ?;
112
- path
157
+ Err ( err)
113
158
}
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
+ }
121
160
}
122
161
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| {
125
164
let path = artifact. to_path ( ) . unwrap ( ) ;
126
165
if self . watched_paths . insert ( path. to_owned ( ) )
127
166
&& let Err ( err) = self . watcher . watch ( path, RecursiveMode :: NonRecursive )
0 commit comments