wavecraft_dev_server/
session.rs1use anyhow::Result;
7use std::path::PathBuf;
8use std::sync::Arc;
9use tokio::sync::{mpsc, watch};
10
11use crate::host::DevServerHost;
12use crate::reload::guard::BuildGuard;
13use crate::reload::rebuild::{RebuildCallbacks, RebuildPipeline};
14use crate::reload::watcher::{FileWatcher, WatchEvent};
15use crate::ws::WsServer;
16
17#[cfg(feature = "audio")]
18use crate::audio::server::AudioHandle;
19
20fn log_rust_file_change(paths: &[PathBuf]) {
21 let timestamp = chrono::Local::now().format("%H:%M:%S");
22 if paths.len() == 1 {
23 println!(
24 "[{}] File changed: {}",
25 timestamp,
26 paths[0].file_name().unwrap_or_default().to_string_lossy()
27 );
28 } else {
29 println!("[{}] {} files changed", timestamp, paths.len());
30 }
31}
32
33fn report_rebuild_result(result: Result<Result<()>, tokio::task::JoinError>) {
34 match result {
35 Ok(Ok(())) => {
36 }
38 Ok(Err(e)) => {
39 eprintln!(" {} Rebuild failed: {:#}", console::style("✗").red(), e);
40 }
41 Err(join_err) => {
42 eprintln!(
43 " {} Hot-reload pipeline panicked: {}\n {} Continuing to watch for changes...",
44 console::style("✗").red(),
45 join_err,
46 console::style("→").cyan()
47 );
48 }
49 }
50}
51
52async fn handle_rust_files_changed(paths: Vec<PathBuf>, pipeline: &Arc<RebuildPipeline>) {
53 log_rust_file_change(&paths);
54
55 let pipeline = Arc::clone(pipeline);
57 let result = tokio::spawn(async move { pipeline.handle_change().await }).await;
58 report_rebuild_result(result);
59}
60
61pub struct DevSession {
68 #[allow(dead_code)] watcher: FileWatcher,
71 #[allow(dead_code)] _pipeline_handle: tokio::task::JoinHandle<()>,
74 #[allow(dead_code)] ws_server: Arc<WsServer<Arc<DevServerHost>>>,
77 #[allow(dead_code)]
79 _shutdown_rx: watch::Receiver<bool>,
80 #[cfg(feature = "audio")]
82 #[allow(dead_code)] _audio_handle: Option<AudioHandle>,
84}
85
86impl DevSession {
87 #[allow(clippy::too_many_arguments)]
102 pub fn new(
103 engine_dir: PathBuf,
104 host: Arc<DevServerHost>,
105 ws_server: Arc<WsServer<Arc<DevServerHost>>>,
106 shutdown_rx: watch::Receiver<bool>,
107 callbacks: RebuildCallbacks,
108 #[cfg(feature = "audio")] audio_handle: Option<AudioHandle>,
109 ) -> Result<Self> {
110 let (watch_tx, mut watch_rx) = mpsc::unbounded_channel::<WatchEvent>();
112
113 let watcher = FileWatcher::new(&engine_dir, watch_tx, shutdown_rx.clone())?;
115
116 let guard = Arc::new(BuildGuard::new());
118 let pipeline = Arc::new(RebuildPipeline::new(
119 guard,
120 engine_dir,
121 Arc::clone(&host),
122 Arc::clone(&ws_server),
123 shutdown_rx.clone(),
124 callbacks,
125 #[cfg(feature = "audio")]
126 None, ));
128
129 let shutdown_rx_for_task = shutdown_rx.clone();
131 let pipeline_handle = tokio::spawn(async move {
132 let mut shutdown_rx = shutdown_rx_for_task.clone();
133 loop {
134 if *shutdown_rx.borrow() {
135 break;
136 }
137
138 tokio::select! {
139 _ = shutdown_rx.changed() => {
140 break;
141 }
142 maybe_event = watch_rx.recv() => {
143 let event = match maybe_event {
144 Some(event) => event,
145 None => break,
146 };
147
148 match event {
149 WatchEvent::RustFilesChanged(paths) => {
150 handle_rust_files_changed(paths, &pipeline).await;
151 }
152 }
153 }
154 }
155 }
156 });
157
158 Ok(Self {
159 watcher,
160 _pipeline_handle: pipeline_handle,
161 ws_server,
162 _shutdown_rx: shutdown_rx,
163 #[cfg(feature = "audio")]
164 _audio_handle: audio_handle,
165 })
166 }
167}