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 if msg.for_build_id == Some(dioxus_cli_config::build_id()) {
39 let our_pid = if cfg!(target_family = "wasm") {
40 None
41 } else {
42 Some(std::process::id())
43 };
44
45 if msg.for_pid == our_pid {
46 unsafe { subsecond::apply_patch(jump_table) }?;
47 dom.runtime().force_all_dirty();
48 ctx.clear::<Signal<Option<HotReloadedTemplate>>>();
49 }
50 }
51 }
52
53 Ok(())
54 })
55}
56
57#[cfg(not(target_family = "wasm"))]
61pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) {
62 let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else {
63 return;
64 };
65
66 connect_at(endpoint, callback);
67}
68
69#[cfg(not(target_family = "wasm"))]
76pub fn connect_subsecond() {
77 connect(|msg| {
78 if let DevserverMsg::HotReload(hot_reload_msg) = msg {
79 if let Some(jumptable) = hot_reload_msg.jump_table {
80 if hot_reload_msg.for_pid == Some(std::process::id()) {
81 unsafe { subsecond::apply_patch(jumptable).unwrap() };
82 }
83 }
84 }
85 });
86}
87
88#[cfg(not(target_family = "wasm"))]
89pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) {
90 std::thread::spawn(move || {
91 let uri = format!(
92 "{endpoint}?aslr_reference={}&build_id={}&pid={}",
93 subsecond::aslr_reference(),
94 dioxus_cli_config::build_id(),
95 std::process::id()
96 );
97
98 let (mut websocket, _req) = match tungstenite::connect(uri) {
99 Ok((websocket, req)) => (websocket, req),
100 Err(_) => return,
101 };
102
103 while let Ok(msg) = websocket.read() {
104 if let tungstenite::Message::Text(text) = msg {
105 if let Ok(msg) = serde_json::from_str(&text) {
106 callback(msg);
107 }
108 }
109 }
110 });
111}
112
113#[cfg(feature = "serve")]
142#[cfg(not(target_family = "wasm"))]
143pub async fn serve_subsecond<O, F>(mut callback: impl FnMut() -> F)
144where
145 F: std::future::Future<Output = O> + 'static,
146{
147 serve_subsecond_with_args((), move |_args| callback()).await
148}
149
150#[cfg(feature = "serve")]
177pub async fn serve_subsecond_with_args<A: Clone, O, F>(args: A, mut callback: impl FnMut(A) -> F)
178where
179 F: std::future::Future<Output = O> + 'static,
180{
181 let (tx, mut rx) = futures_channel::mpsc::unbounded();
182
183 connect(move |msg| {
184 if let DevserverMsg::HotReload(hot_reload_msg) = msg {
185 if let Some(jumptable) = hot_reload_msg.jump_table {
186 if hot_reload_msg.for_pid == Some(std::process::id()) {
187 unsafe { subsecond::apply_patch(jumptable).unwrap() };
188 tx.unbounded_send(()).unwrap();
189 }
190 }
191 }
192 });
193
194 let wrapped = move |args| -> std::pin::Pin<Box<dyn std::future::Future<Output = O>>> {
195 Box::pin(callback(args))
196 };
197
198 let mut hotfn = subsecond::HotFn::current(wrapped);
199 let mut cur_future = hotfn.call((args.clone(),));
200
201 loop {
202 use futures_util::StreamExt;
203 let res = futures_util::future::select(cur_future, rx.next()).await;
204
205 match res {
206 futures_util::future::Either::Left(_completed) => _ = rx.next().await,
207 futures_util::future::Either::Right(_reload) => {}
208 }
209
210 cur_future = hotfn.call((args.clone(),));
211 }
212}