diff --git a/examples/readme.rs b/examples/readme.rs index f4668488b..7e574904f 100644 --- a/examples/readme.rs +++ b/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 { diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 2d08ad79a..0d198da21 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -21,7 +21,7 @@ use wry::{ pub(super) struct DesktopController { pub(super) webviews: HashMap, pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender, - pub(super) pending_edits: Arc>>, + pub(super) quit_app_on_close: bool, pub(super) is_ready: Arc, pub(super) proxy: EventLoopProxy, @@ -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( - root: Component

, - props: P, - proxy: EventLoopProxy, - ) -> 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::(); - - 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>) -> impl Future> + '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::(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( +// root: Component

, +// props: P, +// proxy: EventLoopProxy, +// ) -> 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::(); + +// 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>) -> impl Future> + '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::(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![], +// } +// } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 943fbf91e..929f1ce4a 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/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. diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 337a9485c..14ce5f2f7 100644 --- a/packages/desktop/src/lib.rs +++ b/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(root: Component

, 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(root: Component

, props: P, mut cfg: Config) { + let mut event_loop = EventLoop::with_user_event(); - #[cfg(debug_assertions)] - hot_reload::init(desktop.templates_tx.clone()); + 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(); + + 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(), + ); - Event::WindowEvent { - event, window_id, .. - } => match event { + webviews.insert(window.window().id(), window); + + // 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(root: Component

, props: P, mut cf }) } -impl DesktopController { - fn start( - &mut self, - cfg: &mut Config, - event_loop: &tao::event_loop::EventLoopWindowTarget, - ) { - let webview = build_webview( - cfg, - event_loop, - self.is_ready.clone(), - self.proxy.clone(), - self.eval_sender.clone(), - self.event_tx.clone(), - ); +struct DomHandle { + proxy: EventLoopProxy, +} - self.webviews.insert(webview.window().id(), webview); +impl ArcWake for DomHandle { + fn wake_by_ref(arc_self: &Arc) { + arc_self.proxy.send_event(UserWindowEvent::Poll).unwrap(); } } @@ -159,7 +226,7 @@ fn build_webview( is_ready: Arc, proxy: tao::event_loop::EventLoopProxy, eval_sender: tokio::sync::mpsc::UnboundedSender, - event_tx: UnboundedSender, + event_tx: Rc>>, ) -> 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);