From 1e03e3946dd106d2a6c428611d136ea4cbd343dc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 14 Aug 2024 23:55:04 +0200 Subject: [PATCH] queue hot reload changes for future clients (#2843) --- packages/cli/src/serve/mod.rs | 15 +++++++-- packages/cli/src/serve/server.rs | 11 +++++-- packages/cli/src/serve/watcher.rs | 52 +++++++++++++++++++++++++++++-- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 1e1505090..43c27bde7 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -96,6 +96,9 @@ pub async fn serve_all( // We're going to kick off a new build, interrupting the current build if it's ongoing builder.build(); + // Clear the hot reload changes + watcher.clear_hot_reload_changes(); + // Tell the server to show a loading page for any new requests server.start_build().await; } @@ -105,8 +108,16 @@ pub async fn serve_all( msg = server.wait() => { // Run the server in the background // Waiting for updates here lets us tap into when clients are added/removed - if let Some(msg) = msg { - screen.new_ws_message(TargetPlatform::Web, msg); + match msg { + Some(ServerUpdate::NewConnection) => { + if let Some(msg) = watcher.applied_hot_reload_changes() { + server.send_hotreload(msg).await; + } + } + Some(ServerUpdate::Message(msg)) => { + screen.new_ws_message(TargetPlatform::Web, msg); + } + None => {} } } diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 1cbb943be..ec0c8f338 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -44,6 +44,11 @@ use tower_http::{ ServiceBuilderExt, }; +pub enum ServerUpdate { + NewConnection, + Message(Message), +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "type", content = "data")] enum Status { @@ -231,7 +236,7 @@ impl Server { } /// Wait for new clients to be connected and then save them - pub async fn wait(&mut self) -> Option { + pub async fn wait(&mut self) -> Option { let mut new_hot_reload_socket = self.new_hot_reload_sockets.next(); let mut new_build_status_socket = self.new_build_status_sockets.next(); let mut new_message = self @@ -247,7 +252,7 @@ impl Server { if let Some(new_socket) = new_hot_reload_socket { drop(new_message); self.hot_reload_sockets.push(new_socket); - return None; + return Some(ServerUpdate::NewConnection); } else { panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use"); } @@ -269,7 +274,7 @@ impl Server { } (idx, message) = next_new_message => { match message { - Some(Ok(message)) => return Some(message), + Some(Ok(message)) => return Some(ServerUpdate::Message(message)), _ => { drop(new_message); _ = self.hot_reload_sockets.remove(idx); diff --git a/packages/cli/src/serve/watcher.rs b/packages/cli/src/serve/watcher.rs index e296ae6ea..7e8cc85c1 100644 --- a/packages/cli/src/serve/watcher.rs +++ b/packages/cli/src/serve/watcher.rs @@ -1,3 +1,4 @@ +use std::collections::{HashMap, HashSet}; use std::{fs, path::PathBuf, time::Duration}; use crate::serve::hot_reloading_file_map::FileMap; @@ -24,6 +25,7 @@ pub struct Watcher { queued_events: Vec, file_map: FileMap, ignore: Gitignore, + applied_hot_reload_message: Option, } impl Watcher { @@ -132,6 +134,7 @@ impl Watcher { ignore, queued_events: Vec::new(), _last_update_time: chrono::Local::now().timestamp(), + applied_hot_reload_message: None, } } @@ -251,11 +254,56 @@ impl Watcher { templates.extend(hotreloaded_templates); } - Some(HotReloadMsg { + let msg = HotReloadMsg { templates, assets, unknown_files, - }) + }; + + self.add_hot_reload_message(&msg); + + Some(msg) + } + + /// Get any hot reload changes that have been applied since the last full rebuild + pub fn applied_hot_reload_changes(&mut self) -> Option { + self.applied_hot_reload_message.clone() + } + + /// Clear the hot reload changes. This should be called any time a new build is starting + pub fn clear_hot_reload_changes(&mut self) { + self.applied_hot_reload_message.take(); + } + + /// Store the hot reload changes for any future clients that connect + fn add_hot_reload_message(&mut self, msg: &HotReloadMsg) { + match &mut self.applied_hot_reload_message { + Some(applied) => { + // Merge the assets, unknown files, and templates + // We keep the newer change if there is both a old and new change + let mut templates: HashMap = std::mem::take(&mut applied.templates) + .into_iter() + .map(|template| (template.location.clone(), template)) + .collect(); + let mut assets: HashSet = + std::mem::take(&mut applied.assets).into_iter().collect(); + let mut unknown_files: HashSet = + std::mem::take(&mut applied.unknown_files) + .into_iter() + .collect(); + for template in &msg.templates { + templates.insert(template.location.clone(), template.clone()); + } + assets.extend(msg.assets.iter().cloned()); + unknown_files.extend(msg.unknown_files.iter().cloned()); + applied.templates = templates.into_values().collect(); + applied.assets = assets.into_iter().collect(); + applied.unknown_files = unknown_files.into_iter().collect(); + } + None => { + self.applied_hot_reload_message = Some(msg.clone()); + } + } } /// Ensure the changes we've received from the queue are actually legit changes to either assets or