1use dioxus_core::internal::HotReloadedTemplate;
2use dioxus_core::{ScopeId, VirtualDom};
3use dioxus_signals::{GlobalKey, Signal, WritableExt};
4
5pub use dioxus_devtools_types::*;
6pub use subsecond;
7use subsecond::PatchError;
8
9pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) {
13 try_apply_changes(dom, msg).unwrap()
14}
15
16pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), PatchError> {
20 dom.runtime().in_scope(ScopeId::ROOT, || {
21 let ctx = dioxus_signals::get_global_context();
23 for template in &msg.templates {
24 let value = template.template.clone();
25 let key = GlobalKey::File {
26 file: template.key.file.as_str(),
27 line: template.key.line as _,
28 column: template.key.column as _,
29 index: template.key.index as _,
30 };
31 if let Some(mut signal) = ctx.get_signal_with_key(key.clone()) {
32 signal.set(Some(value));
33 }
34 }
35
36 if let Some(jump_table) = msg.jump_table.as_ref().cloned()
38 && msg.for_build_id == Some(dioxus_cli_config::build_id())
39 {
40 let our_pid = if cfg!(target_family = "wasm") {
41 None
42 } else {
43 Some(std::process::id())
44 };
45
46 if msg.for_pid == our_pid {
47 tracing::trace!(
48 "Applying hot-patch jump table with {} entries ({} templates in message)",
49 jump_table.map.len(),
50 msg.templates.len()
51 );
52 unsafe { subsecond::apply_patch(jump_table) }?;
53 dom.runtime().force_all_dirty();
54 ctx.clear::<Signal<Option<HotReloadedTemplate>>>();
55 } else {
56 tracing::warn!(
57 "Ignoring hot-patch intended for pid {:?} (this process is pid {:?})",
58 msg.for_pid,
59 our_pid
60 );
61 }
62 }
63
64 Ok(())
65 })
66}
67
68#[cfg(not(target_family = "wasm"))]
72pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
73 let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
74 return;
75 };
76
77 connect_at(endpoint, callback);
78}
79
80#[cfg(not(target_family = "wasm"))]
87pub fn connect_subsecond() {
88 connect(|msg| {
89 if let DevserverMsg::HotReload(hot_reload_msg) = msg
90 && let Some(jumptable) = hot_reload_msg.jump_table
91 && hot_reload_msg.for_pid == Some(std::process::id())
92 {
93 unsafe { subsecond::apply_patch(jumptable).unwrap() };
94 }
95 });
96}
97
98#[cfg(not(target_family = "wasm"))]
99pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
100 std::thread::spawn(move || {
101 let uri = format!(
102 "{endpoint}?aslr_reference={}&build_id={}&pid={}",
103 subsecond::aslr_reference(),
104 dioxus_cli_config::build_id(),
105 std::process::id()
106 );
107
108 let (mut websocket, _req) = match tungstenite::connect(uri) {
109 Ok((websocket, req)) => (websocket, req),
110 Err(err) => {
111 tracing::warn!("Failed to connect to devserver websocket: {err}");
112 return;
113 }
114 };
115
116 loop {
117 match websocket.read() {
118 Ok(tungstenite::Message::Text(text)) => match serde_json::from_str(&text) {
119 Ok(msg) => callback(msg),
120 Err(err) => {
121 tracing::warn!("Failed to deserialize devserver message: {err}");
122 }
123 },
124 Ok(_) => {}
125 Err(tungstenite::Error::ConnectionClosed) => {
126 tracing::debug!("Devserver websocket closed");
127 return;
128 }
129 Err(err) => {
130 tracing::warn!(
131 "Devserver websocket closed - no more hot-reloads or hot-patches will be received: {err}"
132 );
133 return;
134 }
135 }
136 }
137 });
138}
139
140#[cfg(feature = "serve")]
169#[cfg(not(target_family = "wasm"))]
170pub async fn serve_subsecond<O, F>(mut callback: impl FnMut() -> F)
171where
172 F: std::future::Future<Output = O> + 'static,
173{
174 serve_subsecond_with_args((), move |_args| callback()).await
175}
176
177#[cfg(feature = "serve")]
204pub async fn serve_subsecond_with_args<A: Clone, O, F>(args: A, mut callback: impl FnMut(A) -> F)
205where
206 F: std::future::Future<Output = O> + 'static,
207{
208 let (tx, mut rx) = futures_channel::mpsc::unbounded();
209
210 connect(move |msg| {
211 if let DevserverMsg::HotReload(hot_reload_msg) = msg
212 && let Some(jumptable) = hot_reload_msg.jump_table
213 && hot_reload_msg.for_pid == Some(std::process::id())
214 {
215 unsafe { subsecond::apply_patch(jumptable).unwrap() };
216 tx.unbounded_send(()).unwrap();
217 }
218 });
219
220 let wrapped = move |args| -> std::pin::Pin<Box<dyn std::future::Future<Output = O>>> {
221 Box::pin(callback(args))
222 };
223
224 let mut hotfn = subsecond::HotFn::current(wrapped);
225 let mut cur_future = hotfn.call((args.clone(),));
226
227 loop {
228 use futures_util::StreamExt;
229 let res = futures_util::future::select(cur_future, rx.next()).await;
230
231 match res {
232 futures_util::future::Either::Left(_completed) => _ = rx.next().await,
233 futures_util::future::Either::Right((None, callback)) => {
234 callback.await;
239 return;
240 }
241 futures_util::future::Either::Right((Some(_), _)) => {}
242 }
243
244 cur_future = hotfn.call((args.clone(),));
245 }
246}