use std::path::PathBuf;
use futures::FutureExt;
use smol::Task;
use smol::channel::{self, Receiver, Sender};
use target_lexicon::Triple;
use super::file_watcher::FileWatcher;
use super::hot_reload::{BroadcastMessage, BuildManager, DEFAULT_PORT, HotReloadServer};
use crate::build::RustBuild;
use crate::project::Project;
#[derive(Debug, Clone)]
pub enum HotReloadEvent {
ServerStarted {
host: String,
port: u16,
},
FileChanged,
Rebuilding,
Built {
path: PathBuf,
},
BuildFailed {
error: String,
},
Broadcast,
}
pub struct HotReloadRunner {
server: HotReloadServer,
event_rx: Receiver<HotReloadEvent>,
_runner_task: Task<()>,
}
impl std::fmt::Debug for HotReloadRunner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HotReloadRunner")
.field("server", &self.server)
.finish_non_exhaustive()
}
}
impl HotReloadRunner {
pub async fn new(project: &Project, triple: Triple) -> color_eyre::Result<Self> {
let server = HotReloadServer::launch(DEFAULT_PORT).await?;
let watcher = FileWatcher::new(project.root())?;
let (event_tx, event_rx) = channel::unbounded();
let _ = event_tx
.send(HotReloadEvent::ServerStarted {
host: server.host(),
port: server.port(),
})
.await;
let rust_build = RustBuild::new(project.root(), triple, true);
let file_rx = watcher.receiver().clone();
let broadcast_tx = server.broadcast_sender();
let crate_name = project.crate_name().replace('-', "_");
let runner_task = smol::spawn(run_loop(
rust_build,
file_rx,
broadcast_tx,
event_tx,
watcher,
crate_name,
));
Ok(Self {
server,
event_rx,
_runner_task: runner_task,
})
}
#[must_use]
pub fn host(&self) -> String {
self.server.host()
}
#[must_use]
pub const fn port(&self) -> u16 {
self.server.port()
}
#[must_use]
pub const fn events(&self) -> &Receiver<HotReloadEvent> {
&self.event_rx
}
#[must_use]
pub fn into_server(self) -> HotReloadServer {
self.server
}
}
async fn run_loop(
rust_build: RustBuild,
file_rx: Receiver<()>,
broadcast_tx: Sender<BroadcastMessage>,
event_tx: Sender<HotReloadEvent>,
_watcher: FileWatcher, crate_name: String,
) {
let mut build_manager = BuildManager::new();
let mut reported_change = false;
loop {
futures::select! {
_ = file_rx.recv().fuse() => {
while file_rx.try_recv().is_ok() {}
if !reported_change {
let _ = event_tx.send(HotReloadEvent::FileChanged).await;
reported_change = true;
}
build_manager.request_rebuild();
}
_ = FutureExt::fuse(smol::Timer::after(std::time::Duration::from_millis(50))) => {
if let Some(result) = build_manager.poll_build().await {
match result {
Ok(lib_dir) => {
let lib_name = format!(
"{}{}{}",
std::env::consts::DLL_PREFIX,
crate_name,
std::env::consts::DLL_SUFFIX
);
let dylib_path = lib_dir.join(&lib_name);
if !dylib_path.exists() {
let _ = event_tx.send(HotReloadEvent::BuildFailed {
error: format!("Library not found: {}", dylib_path.display()),
}).await;
reported_change = false;
continue;
}
let _ = event_tx.send(HotReloadEvent::Built {
path: dylib_path.clone(),
}).await;
match smol::fs::read(&dylib_path).await {
Ok(data) => {
let _ = broadcast_tx.send(BroadcastMessage::Binary(data)).await;
let _ = event_tx.send(HotReloadEvent::Broadcast).await;
reported_change = false;
}
Err(e) => {
let _ = event_tx.send(HotReloadEvent::BuildFailed {
error: format!("Failed to read library: {e}"),
}).await;
reported_change = false;
}
}
}
Err(e) => {
let _ = event_tx.send(HotReloadEvent::BuildFailed {
error: e.to_string(),
}).await;
reported_change = false;
}
}
}
if build_manager.should_start_build() {
let _ = broadcast_tx.send(BroadcastMessage::Text("building".to_string())).await;
let _ = event_tx.send(HotReloadEvent::Rebuilding).await;
build_manager.start_build(rust_build.clone());
}
}
}
}
}