use dioxus_core::internal::HotReloadedTemplate;
use dioxus_core::{ScopeId, VirtualDom};
use dioxus_signals::{GlobalKey, Signal, WritableExt};
pub use dioxus_devtools_types::*;
pub use subsecond;
use subsecond::PatchError;
pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) {
try_apply_changes(dom, msg).unwrap()
}
pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), PatchError> {
dom.runtime().in_scope(ScopeId::ROOT, || {
let ctx = dioxus_signals::get_global_context();
for template in &msg.templates {
let value = template.template.clone();
let key = GlobalKey::File {
file: template.key.file.as_str(),
line: template.key.line as _,
column: template.key.column as _,
index: template.key.index as _,
};
if let Some(mut signal) = ctx.get_signal_with_key(key.clone()) {
signal.set(Some(value));
}
}
if let Some(jump_table) = msg.jump_table.as_ref().cloned() {
if msg.for_build_id == Some(dioxus_cli_config::build_id()) {
let our_pid = if cfg!(target_family = "wasm") {
None
} else {
Some(std::process::id())
};
if msg.for_pid == our_pid {
unsafe { subsecond::apply_patch(jump_table) }?;
dom.runtime().force_all_dirty();
ctx.clear::<Signal<Option<HotReloadedTemplate>>>();
}
}
}
Ok(())
})
}
#[cfg(not(target_family = "wasm"))]
pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
return;
};
connect_at(endpoint, callback);
}
#[cfg(not(target_family = "wasm"))]
pub fn connect_subsecond() {
connect(|msg| {
if let DevserverMsg::HotReload(hot_reload_msg) = msg {
if let Some(jumptable) = hot_reload_msg.jump_table {
if hot_reload_msg.for_pid == Some(std::process::id()) {
unsafe { subsecond::apply_patch(jumptable).unwrap() };
}
}
}
});
}
#[cfg(not(target_family = "wasm"))]
pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
std::thread::spawn(move || {
let uri = format!(
"{endpoint}?aslr_reference={}&build_id={}&pid={}",
subsecond::aslr_reference(),
dioxus_cli_config::build_id(),
std::process::id()
);
let (mut websocket, _req) = match tungstenite::connect(uri) {
Ok((websocket, req)) => (websocket, req),
Err(_) => return,
};
while let Ok(msg) = websocket.read() {
if let tungstenite::Message::Text(text) = msg {
if let Ok(msg) = serde_json::from_str(&text) {
callback(msg);
}
}
}
});
}
#[cfg(feature = "serve")]
#[cfg(not(target_family = "wasm"))]
pub async fn serve_subsecond<O, F>(mut callback: impl FnMut() -> F)
where
F: std::future::Future<Output = O> + 'static,
{
serve_subsecond_with_args((), move |_args| callback()).await
}
#[cfg(feature = "serve")]
pub async fn serve_subsecond_with_args<A: Clone, O, F>(args: A, mut callback: impl FnMut(A) -> F)
where
F: std::future::Future<Output = O> + 'static,
{
let (tx, mut rx) = futures_channel::mpsc::unbounded();
connect(move |msg| {
if let DevserverMsg::HotReload(hot_reload_msg) = msg {
if let Some(jumptable) = hot_reload_msg.jump_table {
if hot_reload_msg.for_pid == Some(std::process::id()) {
unsafe { subsecond::apply_patch(jumptable).unwrap() };
tx.unbounded_send(()).unwrap();
}
}
}
});
let wrapped = move |args| -> std::pin::Pin<Box<dyn std::future::Future<Output = O>>> {
Box::pin(callback(args))
};
let mut hotfn = subsecond::HotFn::current(wrapped);
let mut cur_future = hotfn.call((args.clone(),));
loop {
use futures_util::StreamExt;
let res = futures_util::future::select(cur_future, rx.next()).await;
match res {
futures_util::future::Either::Left(_completed) => _ = rx.next().await,
futures_util::future::Either::Right((None, callback)) => {
callback.await;
return;
}
futures_util::future::Either::Right((Some(_), _)) => {}
}
cur_future = hotfn.call((args.clone(),));
}
}