beet_cli/utils/
fs_app.rs

1use beet::prelude::*;
2use std::num::NonZeroU8;
3use std::ops::ControlFlow;
4use std::time::Duration;
5
6
7/// A bevy app runner that will update when a file is added, changed or removed.
8///
9/// ## Example
10///
11/// ```rust no_run
12/// # use bevy::prelude::*;
13/// # use beet::prelude::*;
14/// # use beet_cli::prelude::*;
15///
16/// App::new()
17/// 	.run_async(FsApp::default().runner());
18///
19/// ```
20pub struct FsApp {
21pub	watcher: FsWatcher,
22}
23
24
25impl Default for FsApp {
26	fn default() -> Self {
27		// let templates_root_dir = WsPathBuf::default();
28
29		Self {
30			watcher: FsWatcher {
31				filter: GlobFilter::default()
32					.with_exclude("*.git*")
33					.with_exclude("*codegen*") // temp until we get fine grained codegen control
34					.with_exclude("*target*"),
35				// avoid short burst refreshing
36				debounce: Duration::from_millis(100),
37				..Default::default()
38			},
39		}
40	}
41}
42
43impl FsApp {
44	/// Update the app whenever a file is created, modified, or deleted.
45	// We dont use [`App::set_runner`] because its an async runner and
46	// we want to be able to call it from inside a tokio
47	pub fn runner(self) -> impl AsyncFnOnce(App) -> AppExit + 'static {
48		async |mut app| {
49			app.init();
50
51			if let Err(err) =
52				Self::on_change(&mut app, WatchEventVec::default())
53			{
54				eprintln!("Error during initial run: {}", err);
55				return AppExit::Error(NonZeroU8::new(1).unwrap());
56			}
57
58			let result: Result<AppExit> = async move {
59				let mut rx = self.watcher.watch()?;
60
61				while let Some(ev) = rx.recv().await? {
62					if ev.has_mutate() {
63						match Self::on_change(&mut app, ev)? {
64							ControlFlow::Continue(_) => {}
65							ControlFlow::Break(exit) => {
66								return Ok(exit);
67							}
68						}
69					}
70				}
71				Ok(AppExit::Success)
72			}
73			.await;
74			result.unwrap_or_else(|err| {
75				// non-bevy results havent printed yet
76				eprintln!("Error during file change: {}", err);
77				AppExit::Error(NonZeroU8::new(1).unwrap())
78			})
79		}
80	}
81
82	/// Updates changed template files
83	fn on_change(
84		app: &mut App,
85		watch_event: WatchEventVec,
86	) -> Result<ControlFlow<AppExit>> {
87		let start = std::time::Instant::now();
88		app.world_mut().send_event_batch(
89			watch_event.take().into_iter().filter(|ev| ev.mutated()),
90		);
91		app.update();
92		let elapsed = start.elapsed();
93		// TODO per-system profiling https://github.com/bevyengine/bevy/blob/main/docs/profiling.md
94		debug!("App updated in {:?}", elapsed);
95		match app.should_exit() {
96			Some(exit) => Ok(ControlFlow::Break(exit)),
97			None => Ok(ControlFlow::Continue(())),
98		}
99	}
100}