Quellcode durchsuchen

chore: clean up msg in desktop even more

Jonathan Kelley vor 2 Jahren
Ursprung
Commit
531f7c6d3f

+ 0 - 151
packages/desktop/src/controller.rs

@@ -1,151 +0,0 @@
-use crate::desktop_context::{DesktopContext, UserWindowEvent};
-use dioxus_core::*;
-use dioxus_html::HtmlEvent;
-use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
-use futures_util::StreamExt;
-#[cfg(target_os = "ios")]
-use objc::runtime::Object;
-use serde_json::Value;
-use std::{
-    collections::HashMap,
-    sync::Arc,
-    sync::{atomic::AtomicBool, Mutex},
-    time::Duration,
-};
-use wry::{
-    self,
-    application::{event_loop::ControlFlow, event_loop::EventLoopProxy, window::WindowId},
-    webview::WebView,
-};
-
-pub(super) struct DesktopController {
-    pub(super) webviews: HashMap<WindowId, WebView>,
-    pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender<Value>,
-
-    pub(super) quit_app_on_close: bool,
-    pub(super) is_ready: Arc<AtomicBool>,
-    pub(super) proxy: EventLoopProxy<UserWindowEvent>,
-
-    pub(super) event_tx: UnboundedSender<serde_json::Value>,
-    #[cfg(debug_assertions)]
-    pub(super) templates_tx: UnboundedSender<Template<'static>>,
-}
-
-impl DesktopController {
-    pub(super) fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
-        self.webviews.remove(&window_id);
-
-        if self.webviews.is_empty() && self.quit_app_on_close {
-            *control_flow = ControlFlow::Exit;
-        }
-    }
-
-    // pub(super) fn try_load_ready_webviews(&mut self) {
-    //     if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
-    //         let mut new_queue = Vec::new();
-
-    //         {
-    //             let mut queue = self.pending_edits.lock().unwrap();
-    //             std::mem::swap(&mut new_queue, &mut *queue);
-    //         }
-
-    //         let (_id, view) = self.webviews.iter_mut().next().unwrap();
-
-    //         for edit in new_queue.drain(..) {
-    //             view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
-    //                 .unwrap();
-    //         }
-    //     }
-    // }
-}
-
-// // Launch the virtualdom on its own thread managed by tokio
-//     // returns the desktop state
-//     pub(super) fn new_on_tokio<P: Send + 'static>(
-//         root: Component<P>,
-//         props: P,
-//         proxy: EventLoopProxy<UserWindowEvent>,
-//     ) -> Self {
-//         let edit_queue = Arc::new(Mutex::new(Vec::new()));
-//         let (event_tx, mut event_rx) = unbounded();
-//         let (templates_tx, mut templates_rx) = unbounded();
-//         let proxy2 = proxy.clone();
-
-//         let pending_edits = edit_queue.clone();
-//         let desktop_context_proxy = proxy.clone();
-//         let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel::<Value>();
-
-//         std::thread::spawn(move || {
-//             // We create the runtime as multithreaded, so you can still "tokio::spawn" onto multiple threads
-//             // I'd personally not require tokio to be built-in to Dioxus-Desktop, but the DX is worse without it
-
-//             let runtime = tokio::runtime::Builder::new_multi_thread()
-//                 .enable_all()
-//                 .build()
-//                 .unwrap();
-
-//             runtime.block_on(async move {
-//                 let mut dom = VirtualDom::new_with_props(root, props)
-//                     .with_root_context(DesktopContext::new(desktop_context_proxy, eval_reciever));
-//                 {
-//                     let edits = dom.rebuild();
-//                     let mut queue = edit_queue.lock().unwrap();
-//                     queue.push(serde_json::to_string(&edits).unwrap());
-//                     proxy.send_event(UserWindowEvent::EditsReady).unwrap();
-//                 }
-
-//                 loop {
-//                     tokio::select! {
-//                         template = {
-//                             #[allow(unused)]
-//                             fn maybe_future<'a>(templates_rx: &'a mut UnboundedReceiver<Template<'static>>) -> impl Future<Output = dioxus_core::Template<'static>> + 'a {
-//                                 #[cfg(debug_assertions)]
-//                                 return templates_rx.select_next_some();
-//                                 #[cfg(not(debug_assertions))]
-//                                 return std::future::pending();
-//                             }
-//                             maybe_future(&mut templates_rx)
-//                         } => {
-//                             dom.replace_template(template);
-//                         }
-//                         _ = dom.wait_for_work() => {}
-//                         Some(json_value) = event_rx.next() => {
-//                             if let Ok(value) = serde_json::from_value::<HtmlEvent>(json_value) {
-//                                 let HtmlEvent {
-//                                     name,
-//                                     element,
-//                                     bubbles,
-//                                     data
-//                                 } = value;
-//                                 dom.handle_event(&name,  data.into_any(), element,  bubbles);
-//                             }
-//                         }
-//                     }
-
-//                     let muts = dom
-//                         .render_with_deadline(tokio::time::sleep(Duration::from_millis(16)))
-//                         .await;
-
-//                     edit_queue
-//                         .lock()
-//                         .unwrap()
-//                         .push(serde_json::to_string(&muts).unwrap());
-//                     let _ = proxy.send_event(UserWindowEvent::EditsReady);
-//                 }
-//             })
-//         });
-
-//         Self {
-//             pending_edits,
-//             eval_sender,
-//             webviews: HashMap::new(),
-//             is_ready: Arc::new(AtomicBool::new(false)),
-//             quit_app_on_close: true,
-//             proxy: proxy2,
-//             event_tx,
-//             #[cfg(debug_assertions)]
-//             templates_tx,
-//             #[cfg(target_os = "ios")]
-//             views: vec![],
-//         }
-//     }

+ 17 - 13
packages/desktop/src/desktop_context.rs

@@ -1,11 +1,10 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use crate::controller::DesktopController;
 use crate::eval::EvalResult;
+use crate::events::IpcMessage;
 use dioxus_core::ScopeState;
 use serde_json::Value;
-use wry::application::event_loop::ControlFlow;
 use wry::application::event_loop::EventLoopProxy;
 #[cfg(target_os = "ios")]
 use wry::application::platform::ios::WindowExtIOS;
@@ -42,11 +41,14 @@ pub struct DesktopContext {
     /// The proxy to the event loop
     pub proxy: ProxyType,
 
+    /// The receiver for eval results since eval is async
+    pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender<Value>,
+
     /// The receiver for eval results since eval is async
     pub(super) eval_reciever: Rc<RefCell<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
 
     #[cfg(target_os = "ios")]
-    pub(crate) views: Rc<RefCell<Vec<Vec<*mut Object>>>>,
+    pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
 }
 
 /// A smart pointer to the current window.
@@ -59,15 +61,17 @@ impl std::ops::Deref for DesktopContext {
 }
 
 impl DesktopContext {
-    pub(crate) fn new(
-        webview: Rc<WebView>,
-        proxy: ProxyType,
-        eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
-    ) -> Self {
+    pub(crate) fn new(webview: Rc<WebView>, proxy: ProxyType) -> Self {
+        let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel();
+
         Self {
             webview,
             proxy,
             eval_reciever: Rc::new(RefCell::new(eval_reciever)),
+            eval_sender,
+
+            #[cfg(target_os = "ios")]
+            views: Default::default(),
         }
     }
 
@@ -155,6 +159,8 @@ impl DesktopContext {
     /// Push an objc view to the window
     #[cfg(target_os = "ios")]
     pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
+        let window = self.webview.window();
+
         unsafe {
             use objc::runtime::Object;
             use objc::*;
@@ -173,6 +179,8 @@ impl DesktopContext {
     /// Pop an objc view from the window
     #[cfg(target_os = "ios")]
     pub fn pop_view(&self) {
+        let window = self.webview.window();
+
         unsafe {
             use objc::runtime::Object;
             use objc::*;
@@ -187,15 +195,11 @@ impl DesktopContext {
 
 #[derive(Debug)]
 pub enum UserWindowEvent {
-    Initialize,
-
     Poll,
 
-    UserEvent(serde_json::Value),
+    Ipc(IpcMessage),
 
     CloseWindow,
-
-    EvalResult(serde_json::Value),
 }
 
 #[cfg(target_os = "ios")]

+ 0 - 10
packages/desktop/src/eval.rs

@@ -1,22 +1,12 @@
 use std::cell::RefCell;
 use std::rc::Rc;
 
-use crate::controller::DesktopController;
 use crate::use_window;
 use dioxus_core::ScopeState;
 use serde::de::Error;
-use serde_json::Value;
 use std::future::Future;
 use std::future::IntoFuture;
 use std::pin::Pin;
-use wry::application::dpi::LogicalSize;
-use wry::application::event_loop::ControlFlow;
-use wry::application::event_loop::EventLoopProxy;
-#[cfg(target_os = "ios")]
-use wry::application::platform::ios::WindowExtIOS;
-use wry::application::window::Fullscreen as WryFullscreen;
-use wry::application::window::Window;
-use wry::webview::WebView;
 
 /// A future that resolves to the result of a JavaScript evaluation.
 pub struct EvalResult {

+ 2 - 2
packages/desktop/src/events.rs

@@ -2,8 +2,8 @@
 
 use serde::{Deserialize, Serialize};
 
-#[derive(Deserialize, Serialize)]
-pub(crate) struct IpcMessage {
+#[derive(Deserialize, Serialize, Debug)]
+pub struct IpcMessage {
     method: String,
     params: serde_json::Value,
 }

+ 67 - 35
packages/desktop/src/lib.rs

@@ -4,24 +4,23 @@
 #![deny(missing_docs)]
 
 mod cfg;
-mod controller;
 mod desktop_context;
 mod escape;
+mod eval;
 mod events;
 mod protocol;
+mod waker;
+mod webview;
 
 #[cfg(all(feature = "hot-reload", debug_assertions))]
 mod hot_reload;
 
-mod eval;
-mod waker;
-mod webview;
-
 pub use cfg::Config;
 use desktop_context::UserWindowEvent;
 pub use desktop_context::{use_window, DesktopContext};
 use dioxus_core::*;
 use dioxus_html::HtmlEvent;
+pub use eval::{use_eval, EvalResult};
 use futures_util::{pin_mut, FutureExt};
 use std::collections::HashMap;
 use std::rc::Rc;
@@ -89,7 +88,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
 /// use dioxus::prelude::*;
 ///
 /// fn main() {
-///     dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, |c| c);
+///     dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default());
 /// }
 ///
 /// struct AppProps {
@@ -102,7 +101,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
 ///     })
 /// }
 /// ```
-pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
+pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, mut cfg: Config) {
     let mut dom = VirtualDom::new_with_props(root, props);
 
     let event_loop = EventLoop::with_user_event();
@@ -123,47 +122,46 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
     let waker = waker::tao_waker(&proxy);
 
     // We only have one webview right now, but we'll have more later
+    // Store them in a hashmap so we can remove them when they're closed
     let mut webviews = HashMap::new();
 
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
         match window_event {
-            Event::NewEvents(StartCause::Init) => {
-                let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel();
-                let window = Rc::new(webview::build(&mut cfg, event_loop, proxy.clone()));
-                let ctx = DesktopContext::new(window.clone(), proxy.clone(), eval_reciever);
-                dom.base_scope().provide_context(ctx);
-                webviews.insert(window.window().id(), window.clone());
-                proxy.send_event(UserWindowEvent::Poll).unwrap();
-            }
-
-            Event::MainEventsCleared => {}
-            Event::Resumed => {}
-            Event::Suspended => {}
-            Event::LoopDestroyed => {}
-            Event::RedrawRequested(_id) => {}
-
-            Event::NewEvents(cause) => {}
+            Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit,
 
-            Event::WindowEvent { event, .. } => match event {
+            Event::WindowEvent {
+                event, window_id, ..
+            } => match event {
                 WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
                 WindowEvent::Destroyed { .. } => {
-                    // desktop.close_window(window_id, control_flow);
+                    webviews.remove(&window_id);
+
+                    if webviews.is_empty() {
+                        *control_flow = ControlFlow::Exit;
+                    }
                 }
                 _ => {}
             },
 
-            Event::UserEvent(UserWindowEvent::Initialize) => {
-                send_edits(dom.rebuild(), &mut webviews);
-            }
+            Event::NewEvents(StartCause::Init) => {
+                let window = webview::build(&mut cfg, event_loop, proxy.clone());
 
-            Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit,
+                dom.base_scope()
+                    .provide_context(DesktopContext::new(window.clone(), proxy.clone()));
+
+                webviews.insert(window.window().id(), window);
 
-            Event::UserEvent(UserWindowEvent::EvalResult(_)) => todo!(),
+                _ = proxy.send_event(UserWindowEvent::Poll);
+            }
+
+            Event::UserEvent(UserWindowEvent::Poll) => {
+                poll_vdom(&waker, &mut dom, &mut webviews);
+            }
 
-            Event::UserEvent(UserWindowEvent::UserEvent(json_value)) => {
-                let evt = match serde_json::from_value::<HtmlEvent>(json_value) {
+            Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "user_event" => {
+                let evt = match serde_json::from_value::<HtmlEvent>(msg.params()) {
                     Ok(value) => value,
                     Err(_) => return,
                 };
@@ -173,19 +171,49 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
                 send_edits(dom.render_immediate(), &mut webviews);
             }
 
-            Event::UserEvent(UserWindowEvent::Poll) => {
-                poll_vdom(&waker, &mut dom, &mut webviews);
+            Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "initialize" => {
+                send_edits(dom.rebuild(), &mut webviews);
+            }
+
+            // When the webview chirps back with the result of the eval, we send it to the active receiver
+            //
+            // This currently doesn't perform any targeting to the callsite, so if you eval multiple times at once,
+            // you might the wrong result. This should be fixed
+            Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "eval_result" => {
+                dom.base_scope()
+                    .consume_context::<DesktopContext>()
+                    .unwrap()
+                    .eval_sender
+                    .send(msg.params())
+                    .unwrap();
             }
 
-            _ => todo!(),
+            Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "browser_open" => {
+                if let Some(temp) = msg.params().as_object() {
+                    if temp.contains_key("href") {
+                        let open = webbrowser::open(temp["href"].as_str().unwrap());
+                        if let Err(e) = open {
+                            log::error!("Open Browser error: {:?}", e);
+                        }
+                    }
+                }
+            }
+
+            _ => {}
         }
     })
 }
 
 type Webviews = HashMap<tao::window::WindowId, Rc<wry::webview::WebView>>;
 
+/// Poll the virtualdom until it's pending
+///
+/// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
+///
+/// All IO is done on the tokio runtime we started earlier
 fn poll_vdom(waker: &Waker, dom: &mut VirtualDom, webviews: &mut Webviews) {
     let mut cx = std::task::Context::from_waker(waker);
+
     loop {
         {
             let fut = dom.wait_for_work();
@@ -201,8 +229,12 @@ fn poll_vdom(waker: &Waker, dom: &mut VirtualDom, webviews: &mut Webviews) {
     }
 }
 
+/// Send a list of mutations to the webview
 fn send_edits(edits: Mutations, webviews: &mut Webviews) {
     let serialized = serde_json::to_string(&edits).unwrap();
+
     let (_id, view) = webviews.iter_mut().next().unwrap();
+
+    // todo: use SSE and binary data to send the edits with lower overhead
     _ = view.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized));
 }

+ 7 - 3
packages/desktop/src/waker.rs

@@ -1,9 +1,8 @@
+use crate::desktop_context::UserWindowEvent;
 use futures_util::task::ArcWake;
 use std::sync::Arc;
 use wry::application::event_loop::EventLoopProxy;
 
-use crate::desktop_context::UserWindowEvent;
-
 /// Create a waker that will send a poll event to the event loop.
 ///
 /// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView.
@@ -12,9 +11,14 @@ use crate::desktop_context::UserWindowEvent;
 pub fn tao_waker(proxy: &EventLoopProxy<UserWindowEvent>) -> std::task::Waker {
     struct DomHandle(EventLoopProxy<UserWindowEvent>);
 
+    // this should be implemented by most platforms, but ios is missing this until
+    // https://github.com/tauri-apps/wry/issues/830 is resolved
+    unsafe impl Send for DomHandle {}
+    unsafe impl Sync for DomHandle {}
+
     impl ArcWake for DomHandle {
         fn wake_by_ref(arc_self: &Arc<Self>) {
-            arc_self.0.send_event(UserWindowEvent::Poll).unwrap();
+            _ = arc_self.0.send_event(UserWindowEvent::Poll);
         }
     }
 

+ 11 - 33
packages/desktop/src/webview.rs

@@ -1,16 +1,19 @@
+use std::rc::Rc;
+
 use crate::events::parse_ipc_message;
 use crate::protocol;
 use crate::{desktop_context::UserWindowEvent, Config};
+use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
 pub use wry;
 pub use wry::application as tao;
 use wry::application::window::Window;
-use wry::webview::WebViewBuilder;
+use wry::webview::{WebView, WebViewBuilder};
 
 pub fn build(
     cfg: &mut Config,
-    event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
-    proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
-) -> wry::webview::WebView {
+    event_loop: &EventLoopWindowTarget<UserWindowEvent>,
+    proxy: EventLoopProxy<UserWindowEvent>,
+) -> Rc<WebView> {
     let builder = cfg.window.clone();
     let window = builder.build(event_loop).unwrap();
     let file_handler = cfg.file_drop_handler.take();
@@ -37,34 +40,9 @@ pub fn build(
         .with_url("dioxus://index.html/")
         .unwrap()
         .with_ipc_handler(move |_window: &Window, payload: String| {
-            let message = match parse_ipc_message(&payload) {
-                Some(message) => message,
-                None => {
-                    log::error!("Failed to parse IPC message: {}", payload);
-                    return;
-                }
-            };
-
-            match message.method() {
-                "eval_result" => {
-                    let _ = proxy.send_event(UserWindowEvent::EvalResult(message.params()));
-                }
-                "user_event" => {
-                    let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params()));
-                }
-                "initialize" => {
-                    let _ = proxy.send_event(UserWindowEvent::Initialize);
-                }
-                "browser_open" => match message.params().as_object() {
-                    Some(temp) if temp.contains_key("href") => {
-                        let open = webbrowser::open(temp["href"].as_str().unwrap());
-                        if let Err(e) = open {
-                            log::error!("Open Browser error: {:?}", e);
-                        }
-                    }
-                    _ => (),
-                },
-                _ => (),
+            // defer the event to the main thread
+            if let Some(message) = parse_ipc_message(&payload) {
+                _ = proxy.send_event(UserWindowEvent::Ipc(message));
             }
         })
         .with_custom_protocol(String::from("dioxus"), move |r| {
@@ -107,5 +85,5 @@ pub fn build(
         webview = webview.with_devtools(true);
     }
 
-    webview.build().unwrap()
+    Rc::new(webview.build().unwrap())
 }