11#[ cfg( feature = "filesystem_watcher" ) ]
22use crate :: { filesystem_watcher:: FilesystemWatcher , AssetServer } ;
3- use crate :: { AssetIo , AssetIoError , Metadata } ;
3+ use crate :: { AssetIo , AssetIoError , ChangeWatcher , Metadata } ;
44use anyhow:: Result ;
55#[ cfg( feature = "filesystem_watcher" ) ]
6- use bevy_ecs:: system:: Res ;
6+ use bevy_ecs:: system:: { Local , Res } ;
77use bevy_utils:: BoxedFuture ;
88#[ cfg( feature = "filesystem_watcher" ) ]
9- use bevy_utils:: { default, HashSet } ;
9+ use bevy_utils:: { default, HashMap , Instant } ;
1010#[ cfg( feature = "filesystem_watcher" ) ]
1111use crossbeam_channel:: TryRecvError ;
1212use fs:: File ;
@@ -35,13 +35,13 @@ impl FileAssetIo {
3535 /// watching for changes.
3636 ///
3737 /// See `get_base_path` below.
38- pub fn new < P : AsRef < Path > > ( path : P , watch_for_changes : bool ) -> Self {
38+ pub fn new < P : AsRef < Path > > ( path : P , watch_for_changes : & Option < ChangeWatcher > ) -> Self {
3939 let file_asset_io = FileAssetIo {
4040 #[ cfg( feature = "filesystem_watcher" ) ]
4141 filesystem_watcher : default ( ) ,
4242 root_path : Self :: get_base_path ( ) . join ( path. as_ref ( ) ) ,
4343 } ;
44- if watch_for_changes {
44+ if let Some ( configuration ) = watch_for_changes {
4545 #[ cfg( any(
4646 not( feature = "filesystem_watcher" ) ,
4747 target_arch = "wasm32" ,
@@ -52,7 +52,7 @@ impl FileAssetIo {
5252 wasm32 / android targets"
5353 ) ;
5454 #[ cfg( feature = "filesystem_watcher" ) ]
55- file_asset_io. watch_for_changes ( ) . unwrap ( ) ;
55+ file_asset_io. watch_for_changes ( configuration ) . unwrap ( ) ;
5656 }
5757 file_asset_io
5858 }
@@ -143,10 +143,10 @@ impl AssetIo for FileAssetIo {
143143 Ok ( ( ) )
144144 }
145145
146- fn watch_for_changes ( & self ) -> Result < ( ) , AssetIoError > {
146+ fn watch_for_changes ( & self , configuration : & ChangeWatcher ) -> Result < ( ) , AssetIoError > {
147147 #[ cfg( feature = "filesystem_watcher" ) ]
148148 {
149- * self . filesystem_watcher . write ( ) = Some ( default ( ) ) ;
149+ * self . filesystem_watcher . write ( ) = Some ( FilesystemWatcher :: new ( configuration ) ) ;
150150 }
151151 #[ cfg( not( feature = "filesystem_watcher" ) ) ]
152152 bevy_log:: warn!( "Watching for changes is not supported when the `filesystem_watcher` feature is disabled" ) ;
@@ -174,22 +174,26 @@ impl AssetIo for FileAssetIo {
174174 feature = "filesystem_watcher" ,
175175 all( not( target_arch = "wasm32" ) , not( target_os = "android" ) )
176176) ) ]
177- pub fn filesystem_watcher_system ( asset_server : Res < AssetServer > ) {
177+ pub fn filesystem_watcher_system (
178+ asset_server : Res < AssetServer > ,
179+ mut changed : Local < HashMap < PathBuf , Instant > > ,
180+ ) {
178181 let asset_io =
179182 if let Some ( asset_io) = asset_server. server . asset_io . downcast_ref :: < FileAssetIo > ( ) {
180183 asset_io
181184 } else {
182185 return ;
183186 } ;
184187 let watcher = asset_io. filesystem_watcher . read ( ) ;
188+
185189 if let Some ( ref watcher) = * watcher {
186- let mut changed = HashSet :: < & PathBuf > :: default ( ) ;
187190 loop {
188191 let event = match watcher. receiver . try_recv ( ) {
189192 Ok ( result) => result. unwrap ( ) ,
190193 Err ( TryRecvError :: Empty ) => break ,
191194 Err ( TryRecvError :: Disconnected ) => panic ! ( "FilesystemWatcher disconnected." ) ,
192195 } ;
196+
193197 if let notify:: event:: Event {
194198 kind : notify:: event:: EventKind :: Modify ( _) ,
195199 paths,
@@ -199,13 +203,22 @@ pub fn filesystem_watcher_system(asset_server: Res<AssetServer>) {
199203 for path in & paths {
200204 let Some ( set) = watcher. path_map . get ( path) else { continue } ;
201205 for to_reload in set {
202- if !changed. contains ( to_reload) {
203- changed. insert ( to_reload) ;
204- let _ = asset_server. load_untracked ( to_reload. as_path ( ) . into ( ) , true ) ;
205- }
206+ // When an asset is modified, note down the timestamp (overriding any previous modification events)
207+ changed. insert ( to_reload. to_owned ( ) , Instant :: now ( ) ) ;
206208 }
207209 }
208210 }
209211 }
212+
213+ // Reload all assets whose last modification was at least 50ms ago.
214+ //
215+ // When changing and then saving a shader, several modification events are sent in short succession.
216+ // Unless we wait until we are sure the shader is finished being modified (and that there will be no more events coming),
217+ // we will sometimes get a crash when trying to reload a partially-modified shader.
218+ for ( to_reload, _) in
219+ changed. drain_filter ( |_, last_modified| last_modified. elapsed ( ) >= watcher. delay )
220+ {
221+ let _ = asset_server. load_untracked ( to_reload. as_path ( ) . into ( ) , true ) ;
222+ }
210223 }
211224}
0 commit comments