waterui_cli/debug/
runner.rs1use std::path::PathBuf;
4
5use futures::FutureExt;
6use smol::Task;
7use smol::channel::{self, Receiver, Sender};
8use target_lexicon::Triple;
9
10use super::file_watcher::FileWatcher;
11use super::hot_reload::{BroadcastMessage, BuildManager, DEFAULT_PORT, HotReloadServer};
12use crate::build::RustBuild;
13use crate::project::Project;
14
15#[derive(Debug, Clone)]
17pub enum HotReloadEvent {
18 ServerStarted {
20 host: String,
22 port: u16,
24 },
25 FileChanged,
27 Rebuilding,
29 Built {
31 path: PathBuf,
33 },
34 BuildFailed {
36 error: String,
38 },
39 Broadcast,
41}
42
43pub struct HotReloadRunner {
45 server: HotReloadServer,
46 event_rx: Receiver<HotReloadEvent>,
47 _runner_task: Task<()>,
48}
49
50impl std::fmt::Debug for HotReloadRunner {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 f.debug_struct("HotReloadRunner")
53 .field("server", &self.server)
54 .finish_non_exhaustive()
55 }
56}
57
58impl HotReloadRunner {
59 pub async fn new(project: &Project, triple: Triple) -> color_eyre::Result<Self> {
68 let server = HotReloadServer::launch(DEFAULT_PORT).await?;
69 let watcher = FileWatcher::new(project.root())?;
70
71 let (event_tx, event_rx) = channel::unbounded();
72
73 let _ = event_tx
75 .send(HotReloadEvent::ServerStarted {
76 host: server.host(),
77 port: server.port(),
78 })
79 .await;
80
81 let rust_build = RustBuild::new(project.root(), triple, true);
82 let file_rx = watcher.receiver().clone();
83 let broadcast_tx = server.broadcast_sender();
84 let crate_name = project.crate_name().replace('-', "_");
85
86 let runner_task = smol::spawn(run_loop(
88 rust_build,
89 file_rx,
90 broadcast_tx,
91 event_tx,
92 watcher,
93 crate_name,
94 ));
95
96 Ok(Self {
97 server,
98 event_rx,
99 _runner_task: runner_task,
100 })
101 }
102
103 #[must_use]
105 pub fn host(&self) -> String {
106 self.server.host()
107 }
108
109 #[must_use]
111 pub const fn port(&self) -> u16 {
112 self.server.port()
113 }
114
115 #[must_use]
117 pub const fn events(&self) -> &Receiver<HotReloadEvent> {
118 &self.event_rx
119 }
120
121 #[must_use]
123 pub fn into_server(self) -> HotReloadServer {
124 self.server
125 }
126}
127
128async fn run_loop(
130 rust_build: RustBuild,
131 file_rx: Receiver<()>,
132 broadcast_tx: Sender<BroadcastMessage>,
133 event_tx: Sender<HotReloadEvent>,
134 _watcher: FileWatcher, crate_name: String,
136) {
137 let mut build_manager = BuildManager::new();
138 let mut reported_change = false;
139
140 loop {
141 futures::select! {
142 _ = file_rx.recv().fuse() => {
144 while file_rx.try_recv().is_ok() {}
145
146 if !reported_change {
147 let _ = event_tx.send(HotReloadEvent::FileChanged).await;
148 reported_change = true;
149 }
150 build_manager.request_rebuild();
151 }
152
153 _ = FutureExt::fuse(smol::Timer::after(std::time::Duration::from_millis(50))) => {
155 if let Some(result) = build_manager.poll_build().await {
156 match result {
157 Ok(lib_dir) => {
158 let lib_name = format!(
159 "{}{}{}",
160 std::env::consts::DLL_PREFIX,
161 crate_name,
162 std::env::consts::DLL_SUFFIX
163 );
164 let dylib_path = lib_dir.join(&lib_name);
165
166 if !dylib_path.exists() {
167 let _ = event_tx.send(HotReloadEvent::BuildFailed {
168 error: format!("Library not found: {}", dylib_path.display()),
169 }).await;
170 reported_change = false;
171 continue;
172 }
173
174 let _ = event_tx.send(HotReloadEvent::Built {
175 path: dylib_path.clone(),
176 }).await;
177
178 match smol::fs::read(&dylib_path).await {
180 Ok(data) => {
181 let _ = broadcast_tx.send(BroadcastMessage::Binary(data)).await;
182 let _ = event_tx.send(HotReloadEvent::Broadcast).await;
183 reported_change = false;
184 }
185 Err(e) => {
186 let _ = event_tx.send(HotReloadEvent::BuildFailed {
187 error: format!("Failed to read library: {e}"),
188 }).await;
189 reported_change = false;
190 }
191 }
192 }
193 Err(e) => {
194 let _ = event_tx.send(HotReloadEvent::BuildFailed {
195 error: e.to_string(),
196 }).await;
197 reported_change = false;
198 }
199 }
200 }
201
202 if build_manager.should_start_build() {
204 let _ = broadcast_tx.send(BroadcastMessage::Text("building".to_string())).await;
206 let _ = event_tx.send(HotReloadEvent::Rebuilding).await;
207 build_manager.start_build(rust_build.clone());
208 }
209 }
210 }
211 }
212}