From 699b85762ba3cf3757c23ed9aa698d8ba3711e49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20Ram=C3=B3n=20Jim=C3=A9nez?= Date: Tue, 10 Jun 2025 02:32:22 +0200 Subject: [PATCH] Try to detect stale type changes when hotpatching --- debug/src/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++++++--- devtools/src/lib.rs | 31 ++++++++++++++++--------- 2 files changed, 72 insertions(+), 14 deletions(-) diff --git a/debug/src/lib.rs b/debug/src/lib.rs index 7d64cb60..99def674 100644 --- a/debug/src/lib.rs +++ b/debug/src/lib.rs @@ -121,6 +121,10 @@ pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) { internal::on_hotpatch(f) } +pub fn is_stale() -> bool { + internal::is_stale() +} + #[cfg(all(feature = "enable", not(target_arch = "wasm32")))] mod internal { use crate::core::theme; @@ -136,8 +140,16 @@ mod internal { use beacon::span; use beacon::span::present; + use std::collections::BTreeSet; use std::sync::atomic::{self, AtomicBool, AtomicUsize}; - use std::sync::{Arc, LazyLock, RwLock}; + use std::sync::{Arc, LazyLock, Mutex, OnceLock, RwLock}; + + static IS_STALE: AtomicBool = AtomicBool::new(false); + + static HOT_FUNCTIONS_PENDING: Mutex> = + Mutex::new(BTreeSet::new()); + + static HOT_FUNCTIONS: OnceLock> = OnceLock::new(); pub fn init(metadata: Metadata) { let name = metadata.name.split("::").next().unwrap_or(metadata.name); @@ -150,6 +162,20 @@ mod internal { }; cargo_hot::connect(); + + cargo_hot::subsecond::register_handler(Arc::new(|| { + if HOT_FUNCTIONS.get().is_none() { + HOT_FUNCTIONS + .set(std::mem::take( + &mut HOT_FUNCTIONS_PENDING + .lock() + .expect("Lock hot functions"), + )) + .expect("Set hot functions"); + } + + IS_STALE.store(false, atomic::Ordering::Relaxed); + })); } pub fn quit() -> bool { @@ -286,15 +312,34 @@ mod internal { // The `move` here is important. Hotpatching will not work // otherwise. - cargo_hot::subsecond::call(move || { + let mut f = cargo_hot::subsecond::HotFn::current(move || { f.take().expect("Hot function is stale")() - }) + }); + + let address = f.ptr_address().0; + + if let Some(hot_functions) = HOT_FUNCTIONS.get() { + if hot_functions.contains(&address) { + IS_STALE.store(true, atomic::Ordering::Relaxed); + } + } else { + let _ = HOT_FUNCTIONS_PENDING + .lock() + .expect("Lock hot functions") + .insert(address); + } + + f.call(()) } pub fn on_hotpatch(f: impl Fn() + Send + Sync + 'static) { cargo_hot::subsecond::register_handler(Arc::new(f)); } + pub fn is_stale() -> bool { + IS_STALE.load(atomic::Ordering::Relaxed) + } + fn span(span: span::Stage) -> Span { log(client::Event::SpanStarted(span.clone())); @@ -428,4 +473,8 @@ mod internal { } pub fn on_hotpatch(_f: impl Fn() + Send + Sync + 'static) {} + + pub fn is_stale() -> bool { + false + } } diff --git a/devtools/src/lib.rs b/devtools/src/lib.rs index e4c2e4d4..45409267 100644 --- a/devtools/src/lib.rs +++ b/devtools/src/lib.rs @@ -343,21 +343,30 @@ where themer(derive_theme(), Element::from(mode).map(Event::Message)) }); - let notification = self.show_notification.then(|| { - themer( - derive_theme(), - bottom_right(opaque( - container(text("Press F12 to open debug metrics")) - .padding(10) - .style(container::dark), - )), - ) - }); + let notification = self + .show_notification + .then(|| text("Press F12 to open debug metrics")) + .or_else(|| { + debug::is_stale().then(|| { + text( + "Types have changed. Restart to re-enable hotpatching.", + ) + }) + }); stack![view] .height(Fill) .push_maybe(mode.map(opaque)) - .push_maybe(notification) + .push_maybe(notification.map(|notification| { + themer( + derive_theme(), + bottom_right(opaque( + container(notification) + .padding(10) + .style(container::dark), + )), + ) + })) .into() }