Ver código fonte

wip: move desktop to main thread and use tokio main

Jonathan Kelley 2 anos atrás
pai
commit
ff697d41e3

+ 3 - 2
examples/readme.rs

@@ -4,8 +4,9 @@
 
 use dioxus::prelude::*;
 
-fn main() {
-    dioxus_desktop::launch(app);
+#[tokio::main]
+async fn main() {
+    dioxus_desktop::launch(app).await;
 }
 
 fn app(cx: Scope) -> Element {

+ 106 - 106
packages/desktop/src/controller.rs

@@ -21,7 +21,7 @@ use wry::{
 pub(super) struct DesktopController {
     pub(super) webviews: HashMap<WindowId, WebView>,
     pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender<Value>,
-    pub(super) pending_edits: Arc<Mutex<Vec<String>>>,
+
     pub(super) quit_app_on_close: bool,
     pub(super) is_ready: Arc<AtomicBool>,
     pub(super) proxy: EventLoopProxy<UserWindowEvent>,
@@ -35,97 +35,6 @@ pub(super) struct DesktopController {
 }
 
 impl DesktopController {
-    // 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![],
-        }
-    }
-
     pub(super) fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
         self.webviews.remove(&window_id);
 
@@ -134,21 +43,112 @@ impl DesktopController {
         }
     }
 
-    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();
+    // 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 mut queue = self.pending_edits.lock().unwrap();
+    //             std::mem::swap(&mut new_queue, &mut *queue);
+    //         }
 
-            let (_id, view) = self.webviews.iter_mut().next().unwrap();
+    //         let (_id, view) = self.webviews.iter_mut().next().unwrap();
 
-            for edit in new_queue.drain(..) {
-                view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
-                    .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![],
+//         }
+//     }

+ 9 - 1
packages/desktop/src/desktop_context.rs

@@ -175,6 +175,8 @@ pub enum UserWindowEvent {
     EditsReady,
     Initialize,
 
+    Poll,
+
     CloseWindow,
     DragWindow,
     FocusWindow,
@@ -226,7 +228,9 @@ impl DesktopController {
         let window = webview.window();
 
         match user_event {
-            Initialize | EditsReady => self.try_load_ready_webviews(),
+            Initialize | EditsReady => {
+                // self.try_load_ready_webviews();
+            }
             CloseWindow => *control_flow = ControlFlow::Exit,
             DragWindow => {
                 // if the drag_window has any errors, we don't do anything
@@ -268,6 +272,10 @@ impl DesktopController {
             SetZoomLevel(scale_factor) => webview.zoom(scale_factor),
             SetInnerSize(logical_size) => window.set_inner_size(logical_size),
 
+            Poll => {
+                // todo
+            }
+
             Print => {
                 if let Err(e) = webview.print() {
                     // we can't panic this error.

+ 101 - 33
packages/desktop/src/lib.rs

@@ -13,12 +13,20 @@ mod protocol;
 #[cfg(all(feature = "hot-reload", debug_assertions))]
 mod hot_reload;
 
+use futures_util::task::ArcWake;
+use serde_json::Value;
+use std::cell::RefCell;
+use std::collections::HashMap;
+use std::rc::Rc;
 use std::sync::atomic::AtomicBool;
 use std::sync::Arc;
+use std::task::Waker;
 
 use desktop_context::UserWindowEvent;
 pub use desktop_context::{use_eval, use_window, DesktopContext, EvalResult};
 use futures_channel::mpsc::UnboundedSender;
+use futures_util::future::poll_fn;
+use futures_util::{pin_mut, FutureExt};
 pub use wry;
 pub use wry::application as tao;
 
@@ -33,6 +41,8 @@ use tao::{
     event_loop::{ControlFlow, EventLoop},
     window::Window,
 };
+use wry::application::event_loop::EventLoopProxy;
+use wry::application::platform::run_return::EventLoopExtRunReturn;
 use wry::webview::WebViewBuilder;
 
 /// Launch the WebView and run the event loop.
@@ -52,8 +62,8 @@ use wry::webview::WebViewBuilder;
 ///     })
 /// }
 /// ```
-pub fn launch(root: Component) {
-    launch_with_props(root, (), Config::default())
+pub async fn launch(root: Component) {
+    launch_with_props(root, (), Config::default()).await
 }
 
 /// Launch the WebView and run the event loop, with configuration.
@@ -75,12 +85,14 @@ pub fn launch(root: Component) {
 ///     })
 /// }
 /// ```
-pub fn launch_cfg(root: Component, config_builder: Config) {
-    launch_with_props(root, (), config_builder)
+pub async fn launch_cfg(root: Component, config_builder: Config) {
+    launch_with_props(root, (), config_builder).await
 }
 
 /// Launch the WebView and run the event loop, with configuration and root props.
 ///
+/// THIS WILL BLOCK THE CURRENT THREAD
+///
 /// This function will start a multithreaded Tokio runtime as well the WebView event loop.
 ///
 /// You can configure the WebView window with a configuration closure
@@ -102,28 +114,92 @@ 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) {
-    let event_loop = EventLoop::with_user_event();
-    let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
+pub async fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
+    let mut event_loop = EventLoop::with_user_event();
+
+    let is_ready = Arc::new(AtomicBool::new(false));
+    let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel();
+
+    let mut dom = VirtualDom::new_with_props(root, props).with_root_context(DesktopContext::new(
+        event_loop.create_proxy(),
+        eval_reciever,
+    ));
+
+    let proxy = event_loop.create_proxy();
 
-    #[cfg(debug_assertions)]
-    hot_reload::init(desktop.templates_tx.clone());
+    let waker = futures_util::task::waker(Arc::new(DomHandle {
+        proxy: proxy.clone(),
+    }));
+
+    let mut events = Rc::new(RefCell::new(vec![]));
+    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) => desktop.start(&mut cfg, event_loop),
+            Event::NewEvents(StartCause::Init) => {
+                let window = build_webview(
+                    &mut cfg,
+                    event_loop,
+                    is_ready.clone(),
+                    proxy.clone(),
+                    eval_sender.clone(),
+                    events.clone(),
+                );
+
+                webviews.insert(window.window().id(), window);
 
-            Event::WindowEvent {
-                event, window_id, ..
-            } => match event {
+                // desktop.start(&mut cfg, event_loop);
+            }
+
+            Event::WindowEvent { event, .. } => match event {
                 WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                WindowEvent::Destroyed { .. } => desktop.close_window(window_id, control_flow),
+                WindowEvent::Destroyed { .. } => {
+                    // desktop.close_window(window_id, control_flow);
+                }
                 _ => {}
             },
 
-            Event::UserEvent(user_event) => desktop.handle_event(user_event, control_flow),
+            Event::UserEvent(user_event) => {
+                println!("user event: {:?}", user_event);
+
+                match user_event {
+                    UserWindowEvent::Poll => {
+                        let mut cx = std::task::Context::from_waker(&waker);
+
+                        let render = {
+                            let fut = dom.wait_for_work();
+                            pin_mut!(fut);
+                            matches!(fut.poll_unpin(&mut cx), std::task::Poll::Ready(_))
+                        };
+
+                        if render {
+                            let edits = dom.render_immediate();
+
+                            // apply the edits
+                        }
+                    }
+
+                    UserWindowEvent::EditsReady => {
+                        let edits = dom.rebuild();
+
+                        let (_id, view) = webviews.iter_mut().next().unwrap();
+
+                        let serialized = serde_json::to_string(&edits).unwrap();
+
+                        view.evaluate_script(&format!(
+                            "window.interpreter.handleEdits({})",
+                            serialized
+                        ))
+                        .unwrap();
+                    }
+
+                    other => {
+                        // desktop.handle_event(user_event, control_flow);
+                    }
+                }
+            }
             Event::MainEventsCleared => {}
             Event::Resumed => {}
             Event::Suspended => {}
@@ -134,22 +210,13 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
     })
 }
 
-impl DesktopController {
-    fn start(
-        &mut self,
-        cfg: &mut Config,
-        event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
-    ) {
-        let webview = build_webview(
-            cfg,
-            event_loop,
-            self.is_ready.clone(),
-            self.proxy.clone(),
-            self.eval_sender.clone(),
-            self.event_tx.clone(),
-        );
-
-        self.webviews.insert(webview.window().id(), webview);
+struct DomHandle {
+    proxy: EventLoopProxy<UserWindowEvent>,
+}
+
+impl ArcWake for DomHandle {
+    fn wake_by_ref(arc_self: &Arc<Self>) {
+        arc_self.proxy.send_event(UserWindowEvent::Poll).unwrap();
     }
 }
 
@@ -159,7 +226,7 @@ fn build_webview(
     is_ready: Arc<AtomicBool>,
     proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
     eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
-    event_tx: UnboundedSender<serde_json::Value>,
+    event_tx: Rc<RefCell<Vec<Value>>>,
 ) -> wry::webview::WebView {
     let builder = cfg.window.clone();
     let window = builder.build(event_loop).unwrap();
@@ -201,7 +268,8 @@ fn build_webview(
                     eval_sender.send(result).unwrap();
                 }
                 "user_event" => {
-                    _ = event_tx.unbounded_send(message.params());
+                    _ = event_tx.borrow_mut().push(message.params());
+                    let _ = proxy.send_event(UserWindowEvent::Poll);
                 }
                 "initialize" => {
                     is_ready.store(true, std::sync::atomic::Ordering::Relaxed);