diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 717819182..5153133b9 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -52,92 +52,7 @@ impl DesktopController { // allow other proccesses to send the new rsx text to the @dioxusin ipc channel and recieve erros on the @dioxusout channel #[cfg(feature = "hot-reload")] - { - use dioxus_rsx_interpreter::{ - error::Error, ErrorHandler, SetManyRsxMessage, RSX_CONTEXT, - }; - use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; - use std::io::{BufRead, BufReader, Write}; - use std::time::Duration; - - fn handle_error( - connection: std::io::Result, - ) -> Option { - connection - .map_err(|error| eprintln!("Incoming connection failed: {}", error)) - .ok() - } - - let latest_in_connection: Arc>>> = - Arc::new(Mutex::new(None)); - let latest_in_connection_handle = latest_in_connection.clone(); - let latest_out_connection: Arc>>> = - Arc::new(Mutex::new(None)); - let latest_out_connection_handle = latest_out_connection.clone(); - - struct DesktopErrorHandler { - latest_connection: Arc>>>, - } - impl ErrorHandler for DesktopErrorHandler { - fn handle_error(&self, err: Error) { - if let Some(conn) = &mut *self.latest_connection.lock().unwrap() { - conn.get_mut() - .write_all( - (serde_json::to_string(&err).unwrap() + "\n").as_bytes(), - ) - .unwrap(); - } else { - panic!("{}", err); - } - } - } - - RSX_CONTEXT.set_error_handler(DesktopErrorHandler { - latest_connection: latest_out_connection_handle, - }); - RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel()); - - // connect to processes for incoming data - std::thread::spawn(move || { - if let Ok(listener) = LocalSocketListener::bind("@dioxusin") { - for conn in listener.incoming().filter_map(handle_error) { - *latest_in_connection_handle.lock().unwrap() = - Some(BufReader::new(conn)); - } - } - }); - - // connect to processes for outgoing errors - std::thread::spawn(move || { - if let Ok(listener) = LocalSocketListener::bind("@dioxusout") { - for conn in listener.incoming().filter_map(handle_error) { - *latest_out_connection.lock().unwrap() = Some(BufReader::new(conn)); - } - } - }); - - std::thread::spawn(move || { - loop { - if let Some(conn) = &mut *latest_in_connection.lock().unwrap() { - let mut buf = String::new(); - match conn.read_line(&mut buf) { - Ok(_) => { - let msgs: SetManyRsxMessage = - serde_json::from_str(&buf).unwrap(); - RSX_CONTEXT.extend(msgs); - } - Err(err) => { - if err.kind() != std::io::ErrorKind::WouldBlock { - break; - } - } - } - } - // give the error handler time to take the mutex - std::thread::sleep(Duration::from_millis(100)); - } - }); - } + crate::hot_reload::init(&dom); let edits = dom.rebuild(); diff --git a/packages/desktop/src/hot_reload.rs b/packages/desktop/src/hot_reload.rs new file mode 100644 index 000000000..2f63162c1 --- /dev/null +++ b/packages/desktop/src/hot_reload.rs @@ -0,0 +1,80 @@ +use dioxus_core::VirtualDom; +use dioxus_rsx_interpreter::{error::Error, ErrorHandler, SetManyRsxMessage, RSX_CONTEXT}; +use interprocess::local_socket::{LocalSocketListener, LocalSocketStream}; +use std::io::{BufRead, BufReader, Write}; +use std::time::Duration; +use std::{sync::Arc, sync::Mutex}; + +fn handle_error(connection: std::io::Result) -> Option { + connection + .map_err(|error| eprintln!("Incoming connection failed: {}", error)) + .ok() +} + +pub(crate) fn init(dom: &VirtualDom) { + let latest_in_connection: Arc>>> = + Arc::new(Mutex::new(None)); + let latest_in_connection_handle = latest_in_connection.clone(); + let latest_out_connection: Arc>>> = + Arc::new(Mutex::new(None)); + let latest_out_connection_handle = latest_out_connection.clone(); + + struct DesktopErrorHandler { + latest_connection: Arc>>>, + } + impl ErrorHandler for DesktopErrorHandler { + fn handle_error(&self, err: Error) { + if let Some(conn) = &mut *self.latest_connection.lock().unwrap() { + conn.get_mut() + .write_all((serde_json::to_string(&err).unwrap() + "\n").as_bytes()) + .unwrap(); + } else { + panic!("{}", err); + } + } + } + + RSX_CONTEXT.set_error_handler(DesktopErrorHandler { + latest_connection: latest_out_connection_handle, + }); + RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel()); + + // connect to processes for incoming data + std::thread::spawn(move || { + if let Ok(listener) = LocalSocketListener::bind("@dioxusin") { + for conn in listener.incoming().filter_map(handle_error) { + *latest_in_connection_handle.lock().unwrap() = Some(BufReader::new(conn)); + } + } + }); + + // connect to processes for outgoing errors + std::thread::spawn(move || { + if let Ok(listener) = LocalSocketListener::bind("@dioxusout") { + for conn in listener.incoming().filter_map(handle_error) { + *latest_out_connection.lock().unwrap() = Some(BufReader::new(conn)); + } + } + }); + + std::thread::spawn(move || { + loop { + if let Some(conn) = &mut *latest_in_connection.lock().unwrap() { + let mut buf = String::new(); + match conn.read_line(&mut buf) { + Ok(_) => { + let msgs: SetManyRsxMessage = serde_json::from_str(&buf).unwrap(); + RSX_CONTEXT.extend(msgs); + } + Err(err) => { + if err.kind() != std::io::ErrorKind::WouldBlock { + break; + } + } + } + } + // give the error handler time to take the mutex + std::thread::sleep(Duration::from_millis(100)); + } + }); +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 160ed7d43..edf8211a1 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -8,6 +8,8 @@ mod controller; mod desktop_context; mod escape; mod events; +#[cfg(feature = "hot-reload")] +mod hot_reload; mod protocol; use desktop_context::UserWindowEvent; diff --git a/packages/web/src/hot_reload.rs b/packages/web/src/hot_reload.rs new file mode 100644 index 000000000..0fe30d2f2 --- /dev/null +++ b/packages/web/src/hot_reload.rs @@ -0,0 +1,68 @@ +use dioxus_core::VirtualDom; +use dioxus_rsx_interpreter::error::Error; +use dioxus_rsx_interpreter::{ErrorHandler, SetManyRsxMessage, RSX_CONTEXT}; +use futures_channel::mpsc::unbounded; +use futures_channel::mpsc::UnboundedSender; +use futures_util::StreamExt; +use wasm_bindgen::closure::Closure; +use wasm_bindgen::JsCast; +use web_sys::{console, MessageEvent, WebSocket}; + +pub(crate) fn init(dom: &VirtualDom) { + let window = web_sys::window().unwrap(); + + let protocol = if window.location().protocol().unwrap() == "https:" { + "wss:" + } else { + "ws:" + }; + + let url = format!( + "{protocol}//{}/_dioxus/hot_reload", + window.location().host().unwrap() + ); + + let ws = WebSocket::new(&url).unwrap(); + + // change the rsx when new data is received + let cl = Closure::wrap(Box::new(|e: MessageEvent| { + if let Ok(text) = e.data().dyn_into::() { + let msgs: SetManyRsxMessage = serde_json::from_str(&format!("{text}")).unwrap(); + RSX_CONTEXT.extend(msgs); + } + }) as Box); + + ws.set_onmessage(Some(cl.as_ref().unchecked_ref())); + cl.forget(); + + let (error_channel_sender, mut error_channel_receiver) = unbounded(); + + struct WebErrorHandler { + sender: UnboundedSender, + } + + impl ErrorHandler for WebErrorHandler { + fn handle_error(&self, err: dioxus_rsx_interpreter::error::Error) { + self.sender.unbounded_send(err).unwrap(); + } + } + + RSX_CONTEXT.set_error_handler(WebErrorHandler { + sender: error_channel_sender, + }); + + RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel()); + + // forward stream to the websocket + dom.base_scope().spawn_forever(async move { + while let Some(err) = error_channel_receiver.next().await { + if ws.ready_state() == WebSocket::OPEN { + ws.send_with_str(serde_json::to_string(&err).unwrap().as_str()) + .unwrap(); + } else { + console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into()); + panic!("{}", err); + } + } + }); +} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index e7a4d098d..3a68de7a0 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -62,11 +62,12 @@ use dioxus_core::prelude::Component; use dioxus_core::SchedulerMsg; use dioxus_core::VirtualDom; use futures_util::FutureExt; -use web_sys::console; mod cache; mod cfg; mod dom; +#[cfg(feature = "hot-reload")] +mod hot_reload; mod rehydrate; mod ric_raf; mod util; @@ -174,73 +175,7 @@ pub async fn run_with_props(root: Component, root_props: T let mut dom = VirtualDom::new_with_props(root, root_props); #[cfg(feature = "hot-reload")] - { - use dioxus_rsx_interpreter::error::Error; - use dioxus_rsx_interpreter::{ErrorHandler, SetManyRsxMessage, RSX_CONTEXT}; - use futures_channel::mpsc::unbounded; - use futures_channel::mpsc::UnboundedSender; - use futures_util::StreamExt; - use wasm_bindgen::closure::Closure; - use wasm_bindgen::JsCast; - use web_sys::{MessageEvent, WebSocket}; - - let window = web_sys::window().unwrap(); - - let protocol = if window.location().protocol().unwrap() == "https:" { - "wss:" - } else { - "ws:" - }; - - let url = format!( - "{protocol}//{}/_dioxus/hot_reload", - window.location().host().unwrap() - ); - - let ws = WebSocket::new(&url).unwrap(); - - // change the rsx when new data is received - let cl = Closure::wrap(Box::new(|e: MessageEvent| { - if let Ok(text) = e.data().dyn_into::() { - let msgs: SetManyRsxMessage = serde_json::from_str(&format!("{text}")).unwrap(); - RSX_CONTEXT.extend(msgs); - } - }) as Box); - - ws.set_onmessage(Some(cl.as_ref().unchecked_ref())); - cl.forget(); - - let (error_channel_sender, mut error_channel_receiver) = unbounded(); - - struct WebErrorHandler { - sender: UnboundedSender, - } - - impl ErrorHandler for WebErrorHandler { - fn handle_error(&self, err: dioxus_rsx_interpreter::error::Error) { - self.sender.unbounded_send(err).unwrap(); - } - } - - RSX_CONTEXT.set_error_handler(WebErrorHandler { - sender: error_channel_sender, - }); - - RSX_CONTEXT.provide_scheduler_channel(dom.get_scheduler_channel()); - - // forward stream to the websocket - dom.base_scope().spawn_forever(async move { - while let Some(err) = error_channel_receiver.next().await { - if ws.ready_state() == WebSocket::OPEN { - ws.send_with_str(serde_json::to_string(&err).unwrap().as_str()) - .unwrap(); - } else { - console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into()); - panic!("{}", err); - } - } - }); - } + hot_reload::init(&dom); for s in crate::cache::BUILTIN_INTERNED_STRINGS { wasm_bindgen::intern(s);