From ff697d41e39edd9616bc6eef038f5c337c0556a4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 29 Dec 2022 17:11:29 -0500 Subject: [PATCH 01/15] wip: move desktop to main thread and use tokio main --- examples/readme.rs | 5 +- packages/desktop/src/controller.rs | 212 ++++++++++++------------ packages/desktop/src/desktop_context.rs | 10 +- packages/desktop/src/lib.rs | 132 +++++++++++---- 4 files changed, 218 insertions(+), 141 deletions(-) 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); From bd720e2268952d2a33aeff563d22c3b18c132ec1 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 29 Dec 2022 19:53:43 -0500 Subject: [PATCH 02/15] fix: use futuresunordered instead of bespoke waker system --- examples/clock.rs | 41 ++++++++ examples/todomvc.rs | 5 +- packages/core/Cargo.toml | 2 +- packages/core/src/scheduler/mod.rs | 9 +- packages/core/src/scheduler/suspense.rs | 13 ++- packages/core/src/scheduler/task.rs | 50 +++++++-- packages/core/src/scheduler/wait.rs | 134 +++++++++++++----------- packages/core/src/scheduler/waker.rs | 37 ------- packages/core/src/scope_arena.rs | 13 ++- packages/core/src/virtual_dom.rs | 27 ++++- packages/desktop/src/desktop_context.rs | 121 ++++++--------------- packages/desktop/src/lib.rs | 128 +++++++++++++--------- 12 files changed, 308 insertions(+), 272 deletions(-) create mode 100644 examples/clock.rs delete mode 100644 packages/core/src/scheduler/waker.rs diff --git a/examples/clock.rs b/examples/clock.rs new file mode 100644 index 000000000..5df326c1d --- /dev/null +++ b/examples/clock.rs @@ -0,0 +1,41 @@ +//! Example: README.md showcase +//! +//! The example from the README.md. + +use dioxus::prelude::*; +use futures_util::Future; + +#[tokio::main] +async fn main() { + let mut dom = VirtualDom::new(app); + dom.rebuild(); + + loop { + dom.wait_for_work().await; + } + + // dioxus_desktop::launch(app).await; +} + +fn app(cx: Scope) -> Element { + let mut count = use_ref(cx, || 0); + + let mut ct = count.to_owned(); + use_coroutine(cx, |_: UnboundedReceiver<()>| async move { + loop { + tokio::time::sleep(std::time::Duration::from_millis(10)).await; + + *ct.write() += 1; + + let current = *ct.read(); + + println!("current: {}", current); + } + }); + + let mut count = count.read(); + + cx.render(rsx! { + div { "High-Five counter: {count}" } + }) +} diff --git a/examples/todomvc.rs b/examples/todomvc.rs index b5277133e..afd1f36b3 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -3,8 +3,9 @@ use dioxus::prelude::*; use dioxus_elements::input_data::keyboard_types::Key; -fn main() { - dioxus_desktop::launch(app); +#[tokio::main] +async fn main() { + dioxus_desktop::launch(app).await; } #[derive(PartialEq, Eq)] diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index bca8bd031..47652e262 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -23,7 +23,7 @@ rustc-hash = "1.1.0" # Used in diffing longest-increasing-subsequence = "0.1.0" -futures-util = { version = "0.3", default-features = false } +futures-util = { version = "0.3", default-features = false, features = ["alloc"]} slab = "0.4" diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs index 7aed417b1..3996633a2 100644 --- a/packages/core/src/scheduler/mod.rs +++ b/packages/core/src/scheduler/mod.rs @@ -1,14 +1,13 @@ use crate::ScopeId; +use futures_util::stream::FuturesUnordered; use slab::Slab; mod suspense; mod task; mod wait; -mod waker; pub use suspense::*; pub use task::*; -pub use waker::ArcWake; /// The type of message that can be sent to the scheduler. /// @@ -31,17 +30,17 @@ pub(crate) struct Scheduler { pub sender: futures_channel::mpsc::UnboundedSender, /// Tasks created with cx.spawn - pub tasks: RefCell>>, + pub tasks: RefCell>, /// Async components - pub leaves: RefCell>>, + pub leaves: RefCell>, } impl Scheduler { pub fn new(sender: futures_channel::mpsc::UnboundedSender) -> Rc { Rc::new(Scheduler { sender, - tasks: RefCell::new(Slab::new()), + tasks: RefCell::new(FuturesUnordered::new()), leaves: RefCell::new(Slab::new()), }) } diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index c7cd650bb..8449559c8 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -1,4 +1,6 @@ -use super::{waker::ArcWake, SchedulerMsg}; +use futures_util::task::ArcWake; + +use super::SchedulerMsg; use crate::ElementId; use crate::{innerlude::Mutations, Element, ScopeId}; use std::future::Future; @@ -40,11 +42,16 @@ pub(crate) struct SuspenseLeaf { pub(crate) tx: futures_channel::mpsc::UnboundedSender, pub(crate) notified: Cell, pub(crate) task: *mut dyn Future>, + pub(crate) waker: Arc, } -impl ArcWake for SuspenseLeaf { +pub struct SuspenseHandle { + pub(crate) id: SuspenseId, + pub(crate) tx: futures_channel::mpsc::UnboundedSender, +} + +impl ArcWake for SuspenseHandle { fn wake_by_ref(arc_self: &Arc) { - arc_self.notified.set(true); _ = arc_self .tx .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id)); diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index 8c1d7ca76..d79b47cb4 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -1,9 +1,13 @@ -use super::{waker::ArcWake, Scheduler, SchedulerMsg}; +use futures_util::task::ArcWake; +use futures_util::FutureExt; + +use super::{Scheduler, SchedulerMsg}; use crate::ScopeId; use std::cell::RefCell; use std::future::Future; use std::pin::Pin; use std::sync::Arc; +use std::task::Waker; /// A task's unique identifier. /// @@ -19,6 +23,17 @@ pub(crate) struct LocalTask { pub(super) task: RefCell + 'static>>>, id: TaskId, tx: futures_channel::mpsc::UnboundedSender, + pub waker: Waker, +} +impl Future for LocalTask { + type Output = (); + + fn poll( + self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll { + self.task.borrow_mut().poll_unpin(cx) + } } impl Scheduler { @@ -32,16 +47,23 @@ impl Scheduler { /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which /// will only occur when the VirtuaalDom itself has been dropped. pub fn spawn(&self, scope: ScopeId, task: impl Future + 'static) -> TaskId { - let mut tasks = self.tasks.borrow_mut(); - let entry = tasks.vacant_entry(); - let task_id = TaskId(entry.key()); + // let entry = tasks.vacant_entry(); + let task_id = TaskId(0); - entry.insert(Arc::new(LocalTask { + let task = LocalTask { id: task_id, tx: self.sender.clone(), task: RefCell::new(Box::pin(task)), scope, - })); + waker: futures_util::task::waker(Arc::new(LocalTaskHandle { + id: task_id, + tx: self.sender.clone(), + })), + }; + + self.tasks.borrow_mut().push(task); + + // println!("Spawning task: {:?}", task_id); self.sender .unbounded_send(SchedulerMsg::TaskNotified(task_id)) @@ -54,14 +76,22 @@ impl Scheduler { /// /// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you pub fn remove(&self, id: TaskId) { - self.tasks.borrow_mut().remove(id.0); + // self.tasks.borrow_mut().remove(id.0); } } -impl ArcWake for LocalTask { +pub struct LocalTaskHandle { + id: TaskId, + tx: futures_channel::mpsc::UnboundedSender, +} + +impl ArcWake for LocalTaskHandle { fn wake_by_ref(arc_self: &Arc) { - _ = arc_self + println!("waking task: {:?} {:p}", arc_self.id, arc_self); + + arc_self .tx - .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)); + .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)) + .unwrap(); } } diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index 205bd8ea0..deac7fe6f 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,3 +1,4 @@ +use futures_util::task::ArcWake; use futures_util::FutureExt; use std::{ rc::Rc, @@ -10,7 +11,7 @@ use crate::{ ScopeId, TaskId, VNode, VirtualDom, }; -use super::{waker::ArcWake, SuspenseId}; +use super::SuspenseId; impl VirtualDom { /// Handle notifications by tasks inside the scheduler @@ -20,22 +21,27 @@ impl VirtualDom { pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) { let mut tasks = self.scheduler.tasks.borrow_mut(); - let task = match tasks.get(id.0) { - Some(task) => task, - None => return, - }; + // let task = match tasks.get(id.0) { + // Some(task) => task, + // None => { + // // The task was removed from the scheduler, so we can just ignore it + // return; + // } + // }; - let waker = task.waker(); - let mut cx = Context::from_waker(&waker); + // let mut cx = Context::from_waker(&task.waker); - // If the task completes... - if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { - // Remove it from the scope so we dont try to double drop it when the scope dropes - self.scopes[task.scope.0].spawned_tasks.remove(&id); + // // If the task completes... + // println!("polling task"); + // if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { + // // Remove it from the scope so we dont try to double drop it when the scope dropes + // self.scopes[task.scope.0].spawned_tasks.remove(&id); - // Remove it from the scheduler - tasks.remove(id.0); - } + // // Remove it from the scheduler + // tasks.remove(id.0); + // } else { + // println!("task not ready yet, but we gave it a handle to the waker"); + // } } pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc { @@ -45,69 +51,69 @@ impl VirtualDom { } pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { - let leaf = self - .scheduler - .leaves - .borrow_mut() - .get(id.0) - .unwrap() - .clone(); + // let leaf = self + // .scheduler + // .leaves + // .borrow_mut() + // .get(id.0) + // .unwrap() + // .clone(); - let scope_id = leaf.scope_id; + // let scope_id = leaf.scope_id; - // todo: cache the waker - let waker = leaf.waker(); - let mut cx = Context::from_waker(&waker); + // // todo: cache the waker + // let waker = leaf.waker(); + // let mut cx = Context::from_waker(&waker); - // Safety: the future is always pinned to the bump arena - let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) }; - let as_pinned_mut = &mut pinned; + // // Safety: the future is always pinned to the bump arena + // let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) }; + // let as_pinned_mut = &mut pinned; - // the component finished rendering and gave us nodes - // we should attach them to that component and then render its children - // continue rendering the tree until we hit yet another suspended component - if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { - let fiber = self.acquire_suspense_boundary(leaf.scope_id); + // // the component finished rendering and gave us nodes + // // we should attach them to that component and then render its children + // // continue rendering the tree until we hit yet another suspended component + // if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { + // let fiber = self.acquire_suspense_boundary(leaf.scope_id); - let scope = &mut self.scopes[scope_id.0]; - let arena = scope.current_frame(); + // let scope = &mut self.scopes[scope_id.0]; + // let arena = scope.current_frame(); - let ret = arena.bump.alloc(match new_nodes { - Some(new) => RenderReturn::Ready(new), - None => RenderReturn::default(), - }); + // let ret = arena.bump.alloc(match new_nodes { + // Some(new) => RenderReturn::Ready(new), + // None => RenderReturn::default(), + // }); - arena.node.set(ret); + // arena.node.set(ret); - fiber.waiting_on.borrow_mut().remove(&id); + // fiber.waiting_on.borrow_mut().remove(&id); - if let RenderReturn::Ready(template) = ret { - let mutations_ref = &mut fiber.mutations.borrow_mut(); - let mutations = &mut **mutations_ref; - let template: &VNode = unsafe { std::mem::transmute(template) }; - let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) }; + // if let RenderReturn::Ready(template) = ret { + // let mutations_ref = &mut fiber.mutations.borrow_mut(); + // let mutations = &mut **mutations_ref; + // let template: &VNode = unsafe { std::mem::transmute(template) }; + // let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) }; - std::mem::swap(&mut self.mutations, mutations); + // std::mem::swap(&mut self.mutations, mutations); - let place_holder_id = scope.placeholder.get().unwrap(); - self.scope_stack.push(scope_id); - let created = self.create(template); - self.scope_stack.pop(); - mutations.push(Mutation::ReplaceWith { - id: place_holder_id, - m: created, - }); + // let place_holder_id = scope.placeholder.get().unwrap(); + // self.scope_stack.push(scope_id); + // let created = self.create(template); + // self.scope_stack.pop(); + // mutations.push(Mutation::ReplaceWith { + // id: place_holder_id, + // m: created, + // }); - for leaf in self.collected_leaves.drain(..) { - fiber.waiting_on.borrow_mut().insert(leaf); - } + // for leaf in self.collected_leaves.drain(..) { + // fiber.waiting_on.borrow_mut().insert(leaf); + // } - std::mem::swap(&mut self.mutations, mutations); + // std::mem::swap(&mut self.mutations, mutations); - if fiber.waiting_on.borrow().is_empty() { - self.finished_fibers.push(fiber.id); - } - } - } + // if fiber.waiting_on.borrow().is_empty() { + // self.finished_fibers.push(fiber.id); + // } + // } + // } } } diff --git a/packages/core/src/scheduler/waker.rs b/packages/core/src/scheduler/waker.rs deleted file mode 100644 index fc75fe8fd..000000000 --- a/packages/core/src/scheduler/waker.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::mem; -use std::sync::Arc; -use std::task::{RawWaker, RawWakerVTable, Waker}; - -pub trait ArcWake: Sized { - /// Create a waker from this self-wakening object - fn waker(self: &Arc) -> Waker { - unsafe fn rc_vtable() -> &'static RawWakerVTable { - &RawWakerVTable::new( - |data| { - let arc = mem::ManuallyDrop::new(Arc::::from_raw(data.cast::())); - let _rc_clone: mem::ManuallyDrop<_> = arc.clone(); - RawWaker::new(data, rc_vtable::()) - }, - |data| Arc::from_raw(data.cast::()).wake(), - |data| { - let arc = mem::ManuallyDrop::new(Arc::::from_raw(data.cast::())); - ArcWake::wake_by_ref(&arc); - }, - |data| drop(Arc::::from_raw(data.cast::())), - ) - } - - unsafe { - Waker::from_raw(RawWaker::new( - Arc::into_raw(self.clone()).cast(), - rc_vtable::(), - )) - } - } - - fn wake_by_ref(arc_self: &Arc); - - fn wake(self: Arc) { - Self::wake_by_ref(&self) - } -} diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index fbe865021..6d574bf7e 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -2,9 +2,8 @@ use crate::{ any_props::AnyProps, bump_frame::BumpFrame, innerlude::DirtyScope, - innerlude::{SuspenseId, SuspenseLeaf}, + innerlude::{SuspenseHandle, SuspenseId, SuspenseLeaf}, nodes::RenderReturn, - scheduler::ArcWake, scopes::{ScopeId, ScopeState}, virtual_dom::VirtualDom, }; @@ -90,15 +89,19 @@ impl VirtualDom { let entry = leaves.vacant_entry(); let suspense_id = SuspenseId(entry.key()); - let leaf = Arc::new(SuspenseLeaf { + let leaf = SuspenseLeaf { scope_id, task: task.as_mut(), id: suspense_id, tx: self.scheduler.sender.clone(), notified: Default::default(), - }); + waker: Arc::new(SuspenseHandle { + id: suspense_id, + tx: self.scheduler.sender.clone(), + }), + }; - let waker = leaf.waker(); + let waker = futures_util::task::waker(leaf.waker.clone()); let mut cx = Context::from_waker(&waker); // safety: the task is already pinned in the bump arena diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 47cbe7245..2306073cf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -13,10 +13,21 @@ use crate::{ scopes::{ScopeId, ScopeState}, AttributeValue, Element, Event, Scope, SuspenseContext, }; -use futures_util::{pin_mut, StreamExt}; +use futures_util::{ + pin_mut, + stream::{futures_unordered, FuturesUnordered}, + StreamExt, +}; use rustc_hash::FxHashMap; use slab::Slab; -use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc}; +use std::{ + any::Any, + borrow::BorrowMut, + cell::Cell, + collections::{BTreeSet, HashSet}, + future::Future, + rc::Rc, +}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -420,7 +431,7 @@ impl VirtualDom { loop { match some_msg.take() { // If a bunch of messages are ready in a sequence, try to pop them off synchronously - Some(msg) => match msg { + Some(msg) => match dbg!(msg) { SchedulerMsg::Immediate(id) => self.mark_dirty(id), SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task), SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id), @@ -430,14 +441,20 @@ impl VirtualDom { None => { match self.rx.try_next() { Ok(Some(val)) => some_msg = Some(val), - Ok(None) => return, + Ok(None) => panic!("Scheduler channel closed"), Err(_) => { // If we have any dirty scopes, or finished fiber trees then we should exit if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() { return; } - some_msg = self.rx.next().await + println!("Waiting for next message..."); + let mut tasks = self.scheduler.tasks.borrow_mut(); + + let takss = &mut *tasks; + + use futures_util::StreamExt; + takss.next().await; } } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 929f1ce4a..fdc38db3a 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -13,6 +13,8 @@ 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; use UserWindowEvent::*; @@ -40,16 +42,32 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext { #[derive(Clone)] pub struct DesktopContext { /// The wry/tao proxy to the current window + pub webview: Rc, + + /// The proxy to the event loop pub proxy: ProxyType, + + /// The receiver for eval results since eval is async pub(super) eval_reciever: Rc>>, } +/// A smart pointer to the current window. +impl std::ops::Deref for DesktopContext { + type Target = Window; + + fn deref(&self) -> &Self::Target { + &self.webview.window() + } +} + impl DesktopContext { pub(crate) fn new( + webview: Rc, proxy: ProxyType, eval_reciever: tokio::sync::mpsc::UnboundedReceiver, ) -> Self { Self { + webview, proxy, eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)), } @@ -66,82 +84,21 @@ impl DesktopContext { pub fn drag(&self) { let _ = self.proxy.send_event(DragWindow); } - - /// set window minimize state - pub fn set_minimized(&self, minimized: bool) { - let _ = self.proxy.send_event(Minimize(minimized)); - } - - /// set window maximize state - pub fn set_maximized(&self, maximized: bool) { - let _ = self.proxy.send_event(Maximize(maximized)); - } - /// toggle window maximize state pub fn toggle_maximized(&self) { let _ = self.proxy.send_event(MaximizeToggle); } - /// set window visible or not - pub fn set_visible(&self, visible: bool) { - let _ = self.proxy.send_event(Visible(visible)); - } - /// close window pub fn close(&self) { let _ = self.proxy.send_event(CloseWindow); } - /// set window to focus - pub fn focus(&self) { - let _ = self.proxy.send_event(FocusWindow); - } - /// change window to fullscreen pub fn set_fullscreen(&self, fullscreen: bool) { let _ = self.proxy.send_event(Fullscreen(fullscreen)); } - /// set resizable state - pub fn set_resizable(&self, resizable: bool) { - let _ = self.proxy.send_event(Resizable(resizable)); - } - - /// set the window always on top - pub fn set_always_on_top(&self, top: bool) { - let _ = self.proxy.send_event(AlwaysOnTop(top)); - } - - /// set cursor visible or not - pub fn set_cursor_visible(&self, visible: bool) { - let _ = self.proxy.send_event(CursorVisible(visible)); - } - - /// set cursor grab - pub fn set_cursor_grab(&self, grab: bool) { - let _ = self.proxy.send_event(CursorGrab(grab)); - } - - /// set window title - pub fn set_title(&self, title: &str) { - let _ = self.proxy.send_event(SetTitle(String::from(title))); - } - - /// change window to borderless - pub fn set_decorations(&self, decoration: bool) { - let _ = self.proxy.send_event(SetDecorations(decoration)); - } - - /// set window zoom level - pub fn set_zoom_level(&self, scale_factor: f64) { - let _ = self.proxy.send_event(SetZoomLevel(scale_factor)); - } - - /// modifies the inner size of the window - pub fn set_inner_size(&self, logical_size: LogicalSize) { - let _ = self.proxy.send_event(SetInnerSize(logical_size)); - } - /// launch print modal pub fn print(&self) { let _ = self.proxy.send_event(Print); @@ -177,31 +134,20 @@ pub enum UserWindowEvent { Poll, + UserEvent(serde_json::Value), + CloseWindow, DragWindow, - FocusWindow, - Visible(bool), - Minimize(bool), - Maximize(bool), MaximizeToggle, - Resizable(bool), - AlwaysOnTop(bool), + Fullscreen(bool), - CursorVisible(bool), - CursorGrab(bool), - - SetTitle(String), - SetDecorations(bool), - - SetZoomLevel(f64), - SetInnerSize(LogicalSize), - Print, DevTool, Eval(String), + EvalResult(serde_json::Value), #[cfg(target_os = "ios")] PushView(objc_id::ShareId), @@ -236,18 +182,17 @@ impl DesktopController { // if the drag_window has any errors, we don't do anything window.fullscreen().is_none().then(|| window.drag_window()); } - Visible(state) => window.set_visible(state), - Minimize(state) => window.set_minimized(state), - Maximize(state) => window.set_maximized(state), + MaximizeToggle => window.set_maximized(!window.is_maximized()), Fullscreen(state) => { if let Some(handle) = window.current_monitor() { window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); } } - FocusWindow => window.set_focus(), - Resizable(state) => window.set_resizable(state), - AlwaysOnTop(state) => window.set_always_on_top(state), + + UserEvent(event) => { + // + } Eval(code) => { let script = format!( @@ -261,17 +206,11 @@ impl DesktopController { log::warn!("Eval script error: {e}"); } } - CursorVisible(state) => window.set_cursor_visible(state), - CursorGrab(state) => { - let _ = window.set_cursor_grab(state); + + EvalResult(result) => { + // todo } - SetTitle(content) => window.set_title(&content), - SetDecorations(state) => window.set_decorations(state), - - SetZoomLevel(scale_factor) => webview.zoom(scale_factor), - SetInnerSize(logical_size) => window.set_inner_size(logical_size), - Poll => { // todo } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 14ce5f2f7..b620e7857 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -13,6 +13,7 @@ mod protocol; #[cfg(all(feature = "hot-reload", debug_assertions))] mod hot_reload; +use dioxus_html::{a, HtmlEvent}; use futures_util::task::ArcWake; use serde_json::Value; use std::cell::RefCell; @@ -115,23 +116,18 @@ pub async fn launch_cfg(root: Component, config_builder: Config) { /// } /// ``` pub async fn launch_with_props(root: Component

, props: P, mut cfg: Config) { - let mut event_loop = EventLoop::with_user_event(); + let 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 mut dom = VirtualDom::new_with_props(root, props); let proxy = event_loop.create_proxy(); + // We want to poll the virtualdom and the event loop at the same time + // So the waker will be connected to both 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| { @@ -139,20 +135,21 @@ pub async fn launch_with_props(root: Component

, props: P, match window_event { Event::NewEvents(StartCause::Init) => { - let window = build_webview( - &mut cfg, - event_loop, - is_ready.clone(), - proxy.clone(), - eval_sender.clone(), - events.clone(), - ); + let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel(); + let window = Rc::new(build_webview(&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()); - webviews.insert(window.window().id(), window); - - // desktop.start(&mut cfg, event_loop); + proxy.send_event(UserWindowEvent::Poll).unwrap(); } + Event::MainEventsCleared => {} + Event::Resumed => {} + Event::Suspended => {} + Event::LoopDestroyed => {} + Event::RedrawRequested(_id) => {} + Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Destroyed { .. } => { @@ -165,19 +162,65 @@ pub async fn launch_with_props(root: Component

, props: P, println!("user event: {:?}", user_event); match user_event { + UserWindowEvent::UserEvent(json_value) => { + 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 edits = dom.render_immediate(); + + let serialized = serde_json::to_string(&edits).unwrap(); + + let (_id, view) = webviews.iter_mut().next().unwrap(); + + view.evaluate_script(&format!( + "window.interpreter.handleEdits({})", + serialized + )) + .unwrap(); + } + } + 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(_)) - }; + println!("polling.."); + + loop { + { + println!("wait for next work"); + + let fut = dom.wait_for_work(); + pin_mut!(fut); + + match fut.poll_unpin(&mut cx) { + std::task::Poll::Ready(_) => { + println!("work ready"); + } + std::task::Poll::Pending => break, + } + } + + println!("rendering.."); - if render { let edits = dom.render_immediate(); // apply the edits + let serialized = serde_json::to_string(&edits).unwrap(); + + let (_id, view) = webviews.iter_mut().next().unwrap(); + + view.evaluate_script(&format!( + "window.interpreter.handleEdits({})", + serialized + )) + .unwrap(); } } @@ -200,11 +243,7 @@ pub async fn launch_with_props(root: Component

, props: P, } } } - Event::MainEventsCleared => {} - Event::Resumed => {} - Event::Suspended => {} - Event::LoopDestroyed => {} - Event::RedrawRequested(_id) => {} + _ => {} } }) @@ -223,10 +262,7 @@ impl ArcWake for DomHandle { fn build_webview( cfg: &mut Config, event_loop: &tao::event_loop::EventLoopWindowTarget, - is_ready: Arc, proxy: tao::event_loop::EventLoopProxy, - eval_sender: tokio::sync::mpsc::UnboundedSender, - event_tx: Rc>>, ) -> wry::webview::WebView { let builder = cfg.window.clone(); let window = builder.build(event_loop).unwrap(); @@ -264,29 +300,23 @@ fn build_webview( match message.method() { "eval_result" => { - let result = message.params(); - eval_sender.send(result).unwrap(); + let _ = proxy.send_event(UserWindowEvent::EvalResult(message.params())); } "user_event" => { - _ = event_tx.borrow_mut().push(message.params()); - let _ = proxy.send_event(UserWindowEvent::Poll); + let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params())); } "initialize" => { - is_ready.store(true, std::sync::atomic::Ordering::Relaxed); let _ = proxy.send_event(UserWindowEvent::EditsReady); } - "browser_open" => { - let data = message.params(); - log::trace!("Open browser: {:?}", data); - if let Some(temp) = data.as_object() { - if temp.contains_key("href") { - let url = temp.get("href").unwrap().as_str().unwrap(); - if let Err(e) = webbrowser::open(url) { - log::error!("Open Browser error: {:?}", e); - } + "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); } } - } + _ => (), + }, _ => (), } }) From 7bc8150da738176bf910999a62bf5bb49de95628 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 29 Dec 2022 20:39:22 -0500 Subject: [PATCH 03/15] fix desktop to poll without budget --- examples/clock.rs | 12 +---- examples/todomvc.rs | 5 +- packages/core/src/virtual_dom.rs | 34 +++++-------- packages/desktop/src/desktop_context.rs | 5 +- packages/desktop/src/lib.rs | 67 +++++++++++-------------- 5 files changed, 48 insertions(+), 75 deletions(-) diff --git a/examples/clock.rs b/examples/clock.rs index 5df326c1d..c76c49faf 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -5,16 +5,8 @@ use dioxus::prelude::*; use futures_util::Future; -#[tokio::main] -async fn main() { - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - loop { - dom.wait_for_work().await; - } - - // dioxus_desktop::launch(app).await; +fn main() { + dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/todomvc.rs b/examples/todomvc.rs index afd1f36b3..b5277133e 100644 --- a/examples/todomvc.rs +++ b/examples/todomvc.rs @@ -3,9 +3,8 @@ use dioxus::prelude::*; use dioxus_elements::input_data::keyboard_types::Key; -#[tokio::main] -async fn main() { - dioxus_desktop::launch(app).await; +fn main() { + dioxus_desktop::launch(app); } #[derive(PartialEq, Eq)] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 2306073cf..435ccea3d 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -13,21 +13,10 @@ use crate::{ scopes::{ScopeId, ScopeState}, AttributeValue, Element, Event, Scope, SuspenseContext, }; -use futures_util::{ - pin_mut, - stream::{futures_unordered, FuturesUnordered}, - StreamExt, -}; +use futures_util::{pin_mut, StreamExt}; use rustc_hash::FxHashMap; use slab::Slab; -use std::{ - any::Any, - borrow::BorrowMut, - cell::Cell, - collections::{BTreeSet, HashSet}, - future::Future, - rc::Rc, -}; +use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future::Future, rc::Rc}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -431,7 +420,7 @@ impl VirtualDom { loop { match some_msg.take() { // If a bunch of messages are ready in a sequence, try to pop them off synchronously - Some(msg) => match dbg!(msg) { + Some(msg) => match msg { SchedulerMsg::Immediate(id) => self.mark_dirty(id), SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task), SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id), @@ -441,20 +430,23 @@ impl VirtualDom { None => { match self.rx.try_next() { Ok(Some(val)) => some_msg = Some(val), - Ok(None) => panic!("Scheduler channel closed"), + Ok(None) => return, Err(_) => { // If we have any dirty scopes, or finished fiber trees then we should exit if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() { return; } - println!("Waiting for next message..."); - let mut tasks = self.scheduler.tasks.borrow_mut(); + let res = (&mut *self.scheduler.tasks.borrow_mut()).next().await; - let takss = &mut *tasks; - - use futures_util::StreamExt; - takss.next().await; + if res.is_none() { + // If we have no tasks, then we should wait for a message + if let Some(msg) = self.rx.next().await { + some_msg = Some(msg); + } else { + return; + } + } } } } diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index fdc38db3a..f6f7df32d 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -82,7 +82,7 @@ impl DesktopContext { /// onmousedown: move |_| { desktop.drag_window(); } /// ``` pub fn drag(&self) { - let _ = self.proxy.send_event(DragWindow); + let _ = self.webview.window().drag_window(); } /// toggle window maximize state pub fn toggle_maximized(&self) { @@ -129,7 +129,6 @@ impl DesktopContext { #[derive(Debug)] pub enum UserWindowEvent { - EditsReady, Initialize, Poll, @@ -174,7 +173,7 @@ impl DesktopController { let window = webview.window(); match user_event { - Initialize | EditsReady => { + EditsReady => { // self.try_load_ready_webviews(); } CloseWindow => *control_flow = ControlFlow::Exit, diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index b620e7857..a7b47109b 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -13,28 +13,17 @@ mod protocol; #[cfg(all(feature = "hot-reload", debug_assertions))] mod hot_reload; -use dioxus_html::{a, HtmlEvent}; -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; - +pub use cfg::Config; 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; - -pub use cfg::Config; -use controller::DesktopController; use dioxus_core::*; +use dioxus_html::HtmlEvent; use events::parse_ipc_message; +use futures_util::task::ArcWake; +use futures_util::{pin_mut, FutureExt}; +use std::collections::HashMap; +use std::rc::Rc; +use std::sync::Arc; pub use tao::dpi::{LogicalSize, PhysicalSize}; pub use tao::window::WindowBuilder; use tao::{ @@ -42,8 +31,9 @@ use tao::{ event_loop::{ControlFlow, EventLoop}, window::Window, }; +pub use wry; +pub use wry::application as tao; 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. @@ -63,8 +53,8 @@ use wry::webview::WebViewBuilder; /// }) /// } /// ``` -pub async fn launch(root: Component) { - launch_with_props(root, (), Config::default()).await +pub fn launch(root: Component) { + launch_with_props(root, (), Config::default()) } /// Launch the WebView and run the event loop, with configuration. @@ -86,8 +76,8 @@ pub async fn launch(root: Component) { /// }) /// } /// ``` -pub async fn launch_cfg(root: Component, config_builder: Config) { - launch_with_props(root, (), config_builder).await +pub fn launch_cfg(root: Component, config_builder: Config) { + launch_with_props(root, (), config_builder) } /// Launch the WebView and run the event loop, with configuration and root props. @@ -115,21 +105,27 @@ pub async fn launch_cfg(root: Component, config_builder: Config) { /// }) /// } /// ``` -pub async fn launch_with_props(root: Component

, props: P, mut cfg: Config) { +pub fn launch_with_props(root: Component

, props: P, mut cfg: Config) { let event_loop = EventLoop::with_user_event(); let mut dom = VirtualDom::new_with_props(root, props); let proxy = event_loop.create_proxy(); + let mut webviews = HashMap::new(); + + // todo: make this configurable + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + // We want to poll the virtualdom and the event loop at the same time // So the waker will be connected to both let waker = futures_util::task::waker(Arc::new(DomHandle { proxy: proxy.clone(), })); - let mut webviews = HashMap::new(); - event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -150,6 +146,8 @@ pub async fn launch_with_props(root: Component

, props: P, Event::LoopDestroyed => {} Event::RedrawRequested(_id) => {} + Event::NewEvents(cause) => {} + Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::Destroyed { .. } => { @@ -159,8 +157,6 @@ pub async fn launch_with_props(root: Component

, props: P, }, Event::UserEvent(user_event) => { - println!("user event: {:?}", user_event); - match user_event { UserWindowEvent::UserEvent(json_value) => { if let Ok(value) = serde_json::from_value::(json_value) { @@ -190,25 +186,20 @@ pub async fn launch_with_props(root: Component

, props: P, UserWindowEvent::Poll => { let mut cx = std::task::Context::from_waker(&waker); - println!("polling.."); + // using this will reset the budget for the task that we're blocking the main thread with + let _guard = rt.enter(); loop { { - println!("wait for next work"); - let fut = dom.wait_for_work(); pin_mut!(fut); match fut.poll_unpin(&mut cx) { - std::task::Poll::Ready(_) => { - println!("work ready"); - } + std::task::Poll::Ready(_) => {} std::task::Poll::Pending => break, } } - println!("rendering.."); - let edits = dom.render_immediate(); // apply the edits @@ -224,7 +215,7 @@ pub async fn launch_with_props(root: Component

, props: P, } } - UserWindowEvent::EditsReady => { + UserWindowEvent::Initialize => { let edits = dom.rebuild(); let (_id, view) = webviews.iter_mut().next().unwrap(); @@ -306,7 +297,7 @@ fn build_webview( let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params())); } "initialize" => { - let _ = proxy.send_event(UserWindowEvent::EditsReady); + let _ = proxy.send_event(UserWindowEvent::Initialize); } "browser_open" => match message.params().as_object() { Some(temp) if temp.contains_key("href") => { From 633bf1f834bd4afbe7a6dd2e4eb84d40a5432127 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 01:08:25 -0500 Subject: [PATCH 04/15] aggressively clean up desktop with new inline poll --- packages/desktop/Cargo.toml | 1 + packages/desktop/src/controller.rs | 3 - packages/desktop/src/desktop_context.rs | 255 ++++++++--------------- packages/desktop/src/eval.rs | 50 +++++ packages/desktop/src/lib.rs | 265 ++++++------------------ packages/desktop/src/waker.rs | 22 ++ packages/desktop/src/webview.rs | 111 ++++++++++ 7 files changed, 325 insertions(+), 382 deletions(-) create mode 100644 packages/desktop/src/eval.rs create mode 100644 packages/desktop/src/waker.rs create mode 100644 packages/desktop/src/webview.rs diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 4aaae6c64..85dd02bd0 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -35,6 +35,7 @@ dunce = "1.0.2" interprocess = { version = "1.1.1", optional = true} futures-util = "0.3.25" +sledgehammer = "0.2.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.7" diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 0d198da21..109985ca8 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -29,9 +29,6 @@ pub(super) struct DesktopController { pub(super) event_tx: UnboundedSender, #[cfg(debug_assertions)] pub(super) templates_tx: UnboundedSender>, - - #[cfg(target_os = "ios")] - pub(super) views: Vec<*mut Object>, } impl DesktopController { diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index f6f7df32d..b7a4405fd 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,13 +1,10 @@ +use std::cell::RefCell; use std::rc::Rc; use crate::controller::DesktopController; +use crate::eval::EvalResult; 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")] @@ -16,8 +13,6 @@ use wry::application::window::Fullscreen as WryFullscreen; use wry::application::window::Window; use wry::webview::WebView; -use UserWindowEvent::*; - pub type ProxyType = EventLoopProxy; /// Get an imperative handle to the current window @@ -48,7 +43,10 @@ pub struct DesktopContext { pub proxy: ProxyType, /// The receiver for eval results since eval is async - pub(super) eval_reciever: Rc>>, + pub(super) eval_reciever: Rc>>, + + #[cfg(target_os = "ios")] + pub(crate) views: Rc>>>, } /// A smart pointer to the current window. @@ -56,7 +54,7 @@ impl std::ops::Deref for DesktopContext { type Target = Window; fn deref(&self) -> &Self::Target { - &self.webview.window() + self.webview.window() } } @@ -69,7 +67,7 @@ impl DesktopContext { Self { webview, proxy, - eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)), + eval_reciever: Rc::new(RefCell::new(eval_reciever)), } } @@ -82,48 +80,108 @@ impl DesktopContext { /// onmousedown: move |_| { desktop.drag_window(); } /// ``` pub fn drag(&self) { - let _ = self.webview.window().drag_window(); + let window = self.webview.window(); + + // if the drag_window has any errors, we don't do anything + window.fullscreen().is_none().then(|| window.drag_window()); } - /// toggle window maximize state + + /// Toggle whether the window is maximized or not pub fn toggle_maximized(&self) { - let _ = self.proxy.send_event(MaximizeToggle); + let window = self.webview.window(); + + window.set_maximized(!window.is_maximized()) } /// close window pub fn close(&self) { - let _ = self.proxy.send_event(CloseWindow); + let _ = self.proxy.send_event(UserWindowEvent::CloseWindow); } /// change window to fullscreen pub fn set_fullscreen(&self, fullscreen: bool) { - let _ = self.proxy.send_event(Fullscreen(fullscreen)); + if let Some(handle) = self.webview.window().current_monitor() { + self.webview + .window() + .set_fullscreen(fullscreen.then_some(WryFullscreen::Borderless(Some(handle)))); + } } /// launch print modal pub fn print(&self) { - let _ = self.proxy.send_event(Print); + if let Err(e) = self.webview.print() { + log::warn!("Open print modal failed: {e}"); + } } /// opens DevTool window pub fn devtool(&self) { - let _ = self.proxy.send_event(DevTool); + #[cfg(debug_assertions)] + self.webview.open_devtools(); + + #[cfg(not(debug_assertions))] + log::warn!("Devtools are disabled in release builds"); } - /// run (evaluate) a script in the WebView context - pub fn eval(&self, script: impl std::string::ToString) { - let _ = self.proxy.send_event(Eval(script.to_string())); + /// Evaluate a javascript expression + pub fn eval(&self, code: &str) -> EvalResult { + // Embed the return of the eval in a function so we can send it back to the main thread + let script = format!( + r#" + window.ipc.postMessage( + JSON.stringify({{ + "method":"eval_result", + "params": ( + function(){{ + {} + }} + )() + }}) + ); + "#, + code + ); + + if let Err(e) = self.webview.evaluate_script(&script) { + // send an error to the eval receiver + log::warn!("Eval script error: {e}"); + } + + EvalResult { + reciever: self.eval_reciever.clone(), + } } - /// Push view + /// Push an objc view to the window #[cfg(target_os = "ios")] pub fn push_view(&self, view: objc_id::ShareId) { - let _ = self.proxy.send_event(PushView(view)); + unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + let ui_view = window.ui_view() as *mut Object; + let ui_view_frame: *mut Object = msg_send![ui_view, frame]; + let _: () = msg_send![view, setFrame: ui_view_frame]; + let _: () = msg_send![view, setAutoresizingMask: 31]; + + let ui_view_controller = window.ui_view_controller() as *mut Object; + let _: () = msg_send![ui_view_controller, setView: view]; + self.views.borrow_mut().push(ui_view); + } } - /// Push view + /// Pop an objc view from the window #[cfg(target_os = "ios")] pub fn pop_view(&self) { - let _ = self.proxy.send_event(PopView); + unsafe { + use objc::runtime::Object; + use objc::*; + assert!(is_main_thread()); + if let Some(view) = self.views.borrow_mut().pop() { + let ui_view_controller = window.ui_view_controller() as *mut Object; + let _: () = msg_send![ui_view_controller, setView: view]; + } + } } } @@ -136,157 +194,8 @@ pub enum UserWindowEvent { UserEvent(serde_json::Value), CloseWindow, - DragWindow, - MaximizeToggle, - - Fullscreen(bool), - - Print, - DevTool, - - Eval(String), EvalResult(serde_json::Value), - - #[cfg(target_os = "ios")] - PushView(objc_id::ShareId), - #[cfg(target_os = "ios")] - PopView, -} - -impl DesktopController { - pub(super) fn handle_event( - &mut self, - user_event: UserWindowEvent, - control_flow: &mut ControlFlow, - ) { - // currently dioxus-desktop supports a single window only, - // so we can grab the only webview from the map; - // on wayland it is possible that a user event is emitted - // before the webview is initialized. ignore the event. - let webview = if let Some(webview) = self.webviews.values().next() { - webview - } else { - return; - }; - - let window = webview.window(); - - match user_event { - EditsReady => { - // self.try_load_ready_webviews(); - } - CloseWindow => *control_flow = ControlFlow::Exit, - DragWindow => { - // if the drag_window has any errors, we don't do anything - window.fullscreen().is_none().then(|| window.drag_window()); - } - - MaximizeToggle => window.set_maximized(!window.is_maximized()), - Fullscreen(state) => { - if let Some(handle) = window.current_monitor() { - window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); - } - } - - UserEvent(event) => { - // - } - - Eval(code) => { - let script = format!( - r#"window.ipc.postMessage(JSON.stringify({{"method":"eval_result", params: (function(){{ - {} - }})()}}));"#, - code - ); - if let Err(e) = webview.evaluate_script(&script) { - // we can't panic this error. - log::warn!("Eval script error: {e}"); - } - } - - EvalResult(result) => { - // todo - } - - Poll => { - // todo - } - - Print => { - if let Err(e) = webview.print() { - // we can't panic this error. - log::warn!("Open print modal failed: {e}"); - } - } - DevTool => { - #[cfg(debug_assertions)] - webview.open_devtools(); - #[cfg(not(debug_assertions))] - log::warn!("Devtools are disabled in release builds"); - } - - #[cfg(target_os = "ios")] - PushView(view) => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - let ui_view = window.ui_view() as *mut Object; - let ui_view_frame: *mut Object = msg_send![ui_view, frame]; - let _: () = msg_send![view, setFrame: ui_view_frame]; - let _: () = msg_send![view, setAutoresizingMask: 31]; - - let ui_view_controller = window.ui_view_controller() as *mut Object; - let _: () = msg_send![ui_view_controller, setView: view]; - self.views.push(ui_view); - }, - - #[cfg(target_os = "ios")] - PopView => unsafe { - use objc::runtime::Object; - use objc::*; - assert!(is_main_thread()); - if let Some(view) = self.views.pop() { - let ui_view_controller = window.ui_view_controller() as *mut Object; - let _: () = msg_send![ui_view_controller, setView: view]; - } - }, - } - } -} - -/// Get a closure that executes any JavaScript in the WebView context. -pub fn use_eval(cx: &ScopeState) -> &dyn Fn(S) -> EvalResult { - let desktop = use_window(cx).clone(); - cx.use_hook(|| { - move |script| { - desktop.eval(script); - let recv = desktop.eval_reciever.clone(); - EvalResult { reciever: recv } - } - }) -} - -/// A future that resolves to the result of a JavaScript evaluation. -pub struct EvalResult { - reciever: Rc>>, -} - -impl IntoFuture for EvalResult { - type Output = Result; - - type IntoFuture = Pin>>>; - - fn into_future(self) -> Self::IntoFuture { - Box::pin(async move { - let mut reciever = self.reciever.lock().await; - match reciever.recv().await { - Some(result) => Ok(result), - None => Err(serde_json::Error::custom("No result returned")), - } - }) as Pin>>> - } } #[cfg(target_os = "ios")] diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs new file mode 100644 index 000000000..358825fa3 --- /dev/null +++ b/packages/desktop/src/eval.rs @@ -0,0 +1,50 @@ +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 { + pub(crate) reciever: Rc>>, +} + +impl IntoFuture for EvalResult { + type Output = Result; + + type IntoFuture = Pin>>>; + + fn into_future(self) -> Self::IntoFuture { + Box::pin(async move { + let mut reciever = self.reciever.borrow_mut(); + match reciever.recv().await { + Some(result) => Ok(result), + None => Err(serde_json::Error::custom("No result returned")), + } + }) as Pin>>> + } +} + +/// Get a closure that executes any JavaScript in the WebView context. +pub fn use_eval(cx: &ScopeState) -> &Rc EvalResult> { + let desktop = use_window(cx); + &*cx.use_hook(|| { + let desktop = desktop.clone(); + + Rc::new(move |script: String| desktop.eval(&script)) as Rc EvalResult> + }) +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index a7b47109b..7dda7e679 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -13,28 +13,27 @@ mod protocol; #[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_eval, use_window, DesktopContext, EvalResult}; +pub use desktop_context::{use_window, DesktopContext}; use dioxus_core::*; use dioxus_html::HtmlEvent; -use events::parse_ipc_message; -use futures_util::task::ArcWake; use futures_util::{pin_mut, FutureExt}; use std::collections::HashMap; use std::rc::Rc; -use std::sync::Arc; +use std::task::Waker; pub use tao::dpi::{LogicalSize, PhysicalSize}; pub use tao::window::WindowBuilder; use tao::{ event::{Event, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, - window::Window, }; pub use wry; pub use wry::application as tao; -use wry::application::event_loop::EventLoopProxy; -use wry::webview::WebViewBuilder; /// Launch the WebView and run the event loop. /// @@ -82,9 +81,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// 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. +/// This function will start a multithreaded Tokio runtime as well the WebView event loop. This will block the current thread. /// /// You can configure the WebView window with a configuration closure /// @@ -106,25 +103,27 @@ 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 dom = VirtualDom::new_with_props(root, props); + let event_loop = EventLoop::with_user_event(); + let proxy = event_loop.create_proxy(); - let mut webviews = HashMap::new(); - - // todo: make this configurable + // We start the tokio runtime *on this thread* + // Any future we poll later will use this runtime to spawn tasks and for IO let rt = tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap(); - // We want to poll the virtualdom and the event loop at the same time - // So the waker will be connected to both - let waker = futures_util::task::waker(Arc::new(DomHandle { - proxy: proxy.clone(), - })); + // We enter the runtime but we poll futures manually, circumventing the per-task runtime budget + let _guard = rt.enter(); + + // We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both + let waker = waker::tao_waker(&proxy); + + // We only have one webview right now, but we'll have more later + let mut webviews = HashMap::new(); event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -132,11 +131,10 @@ pub fn launch_with_props(root: Component

, props: P, mut cf match window_event { Event::NewEvents(StartCause::Init) => { let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel(); - let window = Rc::new(build_webview(&mut cfg, event_loop, proxy.clone())); + 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(); } @@ -156,200 +154,55 @@ pub fn launch_with_props(root: Component

, props: P, mut cf _ => {} }, - Event::UserEvent(user_event) => { - match user_event { - UserWindowEvent::UserEvent(json_value) => { - 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 edits = dom.render_immediate(); - - let serialized = serde_json::to_string(&edits).unwrap(); - - let (_id, view) = webviews.iter_mut().next().unwrap(); - - view.evaluate_script(&format!( - "window.interpreter.handleEdits({})", - serialized - )) - .unwrap(); - } - } - - UserWindowEvent::Poll => { - let mut cx = std::task::Context::from_waker(&waker); - - // using this will reset the budget for the task that we're blocking the main thread with - let _guard = rt.enter(); - - loop { - { - let fut = dom.wait_for_work(); - pin_mut!(fut); - - match fut.poll_unpin(&mut cx) { - std::task::Poll::Ready(_) => {} - std::task::Poll::Pending => break, - } - } - - let edits = dom.render_immediate(); - - // apply the edits - let serialized = serde_json::to_string(&edits).unwrap(); - - let (_id, view) = webviews.iter_mut().next().unwrap(); - - view.evaluate_script(&format!( - "window.interpreter.handleEdits({})", - serialized - )) - .unwrap(); - } - } - - UserWindowEvent::Initialize => { - 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::UserEvent(UserWindowEvent::Initialize) => { + send_edits(dom.rebuild(), &mut webviews); } - _ => {} + Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit, + + Event::UserEvent(UserWindowEvent::EvalResult(_)) => todo!(), + + Event::UserEvent(UserWindowEvent::UserEvent(json_value)) => { + let evt = match serde_json::from_value::(json_value) { + Ok(value) => value, + Err(_) => return, + }; + + dom.handle_event(&evt.name, evt.data.into_any(), evt.element, evt.bubbles); + + send_edits(dom.render_immediate(), &mut webviews); + } + + Event::UserEvent(UserWindowEvent::Poll) => { + poll_vdom(&waker, &mut dom, &mut webviews); + } + + _ => todo!(), } }) } -struct DomHandle { - proxy: EventLoopProxy, -} +type Webviews = HashMap>; -impl ArcWake for DomHandle { - fn wake_by_ref(arc_self: &Arc) { - arc_self.proxy.send_event(UserWindowEvent::Poll).unwrap(); - } -} +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(); + pin_mut!(fut); -fn build_webview( - cfg: &mut Config, - event_loop: &tao::event_loop::EventLoopWindowTarget, - proxy: tao::event_loop::EventLoopProxy, -) -> wry::webview::WebView { - let builder = cfg.window.clone(); - let window = builder.build(event_loop).unwrap(); - let file_handler = cfg.file_drop_handler.take(); - let custom_head = cfg.custom_head.clone(); - let resource_dir = cfg.resource_dir.clone(); - let index_file = cfg.custom_index.clone(); - let root_name = cfg.root_name.clone(); - - // We assume that if the icon is None in cfg, then the user just didnt set it - if cfg.window.window.window_icon.is_none() { - window.set_window_icon(Some( - tao::window::Icon::from_rgba( - include_bytes!("./assets/default_icon.bin").to_vec(), - 460, - 460, - ) - .expect("image parse failed"), - )); - } - - let mut webview = WebViewBuilder::new(window) - .unwrap() - .with_transparent(cfg.window.window.transparent) - .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); - } - } - _ => (), - }, - _ => (), + match fut.poll_unpin(&mut cx) { + std::task::Poll::Ready(_) => {} + std::task::Poll::Pending => break, } - }) - .with_custom_protocol(String::from("dioxus"), move |r| { - protocol::desktop_handler( - r, - resource_dir.clone(), - custom_head.clone(), - index_file.clone(), - &root_name, - ) - }) - .with_file_drop_handler(move |window, evet| { - file_handler - .as_ref() - .map(|handler| handler(window, evet)) - .unwrap_or_default() - }); + } - for (name, handler) in cfg.protocols.drain(..) { - webview = webview.with_custom_protocol(name, handler) + send_edits(dom.render_immediate(), webviews); } +} - if cfg.disable_context_menu { - // in release mode, we don't want to show the dev tool or reload menus - webview = webview.with_initialization_script( - r#" - if (document.addEventListener) { - document.addEventListener('contextmenu', function(e) { - e.preventDefault(); - }, false); - } else { - document.attachEvent('oncontextmenu', function() { - window.event.returnValue = false; - }); - } - "#, - ) - } else { - // in debug, we are okay with the reload menu showing and dev tool - webview = webview.with_devtools(true); - } - - webview.build().unwrap() +fn send_edits(edits: Mutations, webviews: &mut Webviews) { + let serialized = serde_json::to_string(&edits).unwrap(); + let (_id, view) = webviews.iter_mut().next().unwrap(); + _ = view.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized)); } diff --git a/packages/desktop/src/waker.rs b/packages/desktop/src/waker.rs new file mode 100644 index 000000000..a08c93bd3 --- /dev/null +++ b/packages/desktop/src/waker.rs @@ -0,0 +1,22 @@ +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. +/// +/// All other IO lives in the Tokio runtime, +pub fn tao_waker(proxy: &EventLoopProxy) -> std::task::Waker { + struct DomHandle(EventLoopProxy); + + impl ArcWake for DomHandle { + fn wake_by_ref(arc_self: &Arc) { + arc_self.0.send_event(UserWindowEvent::Poll).unwrap(); + } + } + + futures_util::task::waker(Arc::new(DomHandle(proxy.clone()))) +} diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs new file mode 100644 index 000000000..5cb80e41b --- /dev/null +++ b/packages/desktop/src/webview.rs @@ -0,0 +1,111 @@ +use crate::events::parse_ipc_message; +use crate::protocol; +use crate::{desktop_context::UserWindowEvent, Config}; +pub use wry; +pub use wry::application as tao; +use wry::application::window::Window; +use wry::webview::WebViewBuilder; + +pub fn build( + cfg: &mut Config, + event_loop: &tao::event_loop::EventLoopWindowTarget, + proxy: tao::event_loop::EventLoopProxy, +) -> wry::webview::WebView { + let builder = cfg.window.clone(); + let window = builder.build(event_loop).unwrap(); + let file_handler = cfg.file_drop_handler.take(); + let custom_head = cfg.custom_head.clone(); + let resource_dir = cfg.resource_dir.clone(); + let index_file = cfg.custom_index.clone(); + let root_name = cfg.root_name.clone(); + + // We assume that if the icon is None in cfg, then the user just didnt set it + if cfg.window.window.window_icon.is_none() { + window.set_window_icon(Some( + tao::window::Icon::from_rgba( + include_bytes!("./assets/default_icon.bin").to_vec(), + 460, + 460, + ) + .expect("image parse failed"), + )); + } + + let mut webview = WebViewBuilder::new(window) + .unwrap() + .with_transparent(cfg.window.window.transparent) + .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); + } + } + _ => (), + }, + _ => (), + } + }) + .with_custom_protocol(String::from("dioxus"), move |r| { + protocol::desktop_handler( + r, + resource_dir.clone(), + custom_head.clone(), + index_file.clone(), + &root_name, + ) + }) + .with_file_drop_handler(move |window, evet| { + file_handler + .as_ref() + .map(|handler| handler(window, evet)) + .unwrap_or_default() + }); + + for (name, handler) in cfg.protocols.drain(..) { + webview = webview.with_custom_protocol(name, handler) + } + + if cfg.disable_context_menu { + // in release mode, we don't want to show the dev tool or reload menus + webview = webview.with_initialization_script( + r#" + if (document.addEventListener) { + document.addEventListener('contextmenu', function(e) { + e.preventDefault(); + }, false); + } else { + document.attachEvent('oncontextmenu', function() { + window.event.returnValue = false; + }); + } + "#, + ) + } else { + // in debug, we are okay with the reload menu showing and dev tool + webview = webview.with_devtools(true); + } + + webview.build().unwrap() +} From 531f7c6d3fb483767efde7008988739036c113a8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 01:52:54 -0500 Subject: [PATCH 05/15] chore: clean up msg in desktop even more --- packages/desktop/src/controller.rs | 151 ------------------------ packages/desktop/src/desktop_context.rs | 30 +++-- packages/desktop/src/eval.rs | 10 -- packages/desktop/src/events.rs | 4 +- packages/desktop/src/lib.rs | 102 ++++++++++------ packages/desktop/src/waker.rs | 10 +- packages/desktop/src/webview.rs | 44 ++----- 7 files changed, 104 insertions(+), 247 deletions(-) delete mode 100644 packages/desktop/src/controller.rs diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs deleted file mode 100644 index 109985ca8..000000000 --- a/packages/desktop/src/controller.rs +++ /dev/null @@ -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, - pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender, - - pub(super) quit_app_on_close: bool, - pub(super) is_ready: Arc, - pub(super) proxy: EventLoopProxy, - - pub(super) event_tx: UnboundedSender, - #[cfg(debug_assertions)] - pub(super) templates_tx: UnboundedSender>, -} - -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( -// 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 b7a4405fd..75a458cb7 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/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, + /// The receiver for eval results since eval is async pub(super) eval_reciever: Rc>>, #[cfg(target_os = "ios")] - pub(crate) views: Rc>>>, + pub(crate) views: Rc>>, } /// 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, - proxy: ProxyType, - eval_reciever: tokio::sync::mpsc::UnboundedReceiver, - ) -> Self { + pub(crate) fn new(webview: Rc, 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) { + 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")] diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs index 358825fa3..80baba2d4 100644 --- a/packages/desktop/src/eval.rs +++ b/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 { diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index f9b3fe5cb..ae4d77c68 100644 --- a/packages/desktop/src/events.rs +++ b/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, } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 7dda7e679..9d10f36f5 100644 --- a/packages/desktop/src/lib.rs +++ b/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(root: Component

, props: P, mut cfg: Config) { +pub fn launch_with_props(root: Component

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

, 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::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit, - Event::MainEventsCleared => {} - Event::Resumed => {} - Event::Suspended => {} - Event::LoopDestroyed => {} - Event::RedrawRequested(_id) => {} - - Event::NewEvents(cause) => {} - - 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()); + + dom.base_scope() + .provide_context(DesktopContext::new(window.clone(), proxy.clone())); + + webviews.insert(window.window().id(), window); + + _ = proxy.send_event(UserWindowEvent::Poll); } - Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit, + Event::UserEvent(UserWindowEvent::Poll) => { + poll_vdom(&waker, &mut dom, &mut webviews); + } - Event::UserEvent(UserWindowEvent::EvalResult(_)) => todo!(), - - Event::UserEvent(UserWindowEvent::UserEvent(json_value)) => { - let evt = match serde_json::from_value::(json_value) { + Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "user_event" => { + let evt = match serde_json::from_value::(msg.params()) { Ok(value) => value, Err(_) => return, }; @@ -173,19 +171,49 @@ pub fn launch_with_props(root: Component

, 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); } - _ => todo!(), + // 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::() + .unwrap() + .eval_sender + .send(msg.params()) + .unwrap(); + } + + 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>; +/// 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)); } diff --git a/packages/desktop/src/waker.rs b/packages/desktop/src/waker.rs index a08c93bd3..a2586ffed 100644 --- a/packages/desktop/src/waker.rs +++ b/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) -> std::task::Waker { struct DomHandle(EventLoopProxy); + // 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) { - arc_self.0.send_event(UserWindowEvent::Poll).unwrap(); + _ = arc_self.0.send_event(UserWindowEvent::Poll); } } diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 5cb80e41b..6c286c35b 100644 --- a/packages/desktop/src/webview.rs +++ b/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, - proxy: tao::event_loop::EventLoopProxy, -) -> wry::webview::WebView { + event_loop: &EventLoopWindowTarget, + proxy: EventLoopProxy, +) -> Rc { 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()) } From d160a5c8ff81cc1678309518e80585ab75be236b Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 01:54:12 -0500 Subject: [PATCH 06/15] chore: remove unnecessary parse method --- packages/desktop/src/events.rs | 10 ---------- packages/desktop/src/webview.rs | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index ae4d77c68..50d3fbf3d 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -17,13 +17,3 @@ impl IpcMessage { self.params } } - -pub(crate) fn parse_ipc_message(payload: &str) -> Option { - match serde_json::from_str(payload) { - Ok(message) => Some(message), - Err(e) => { - log::error!("could not parse IPC message, error: {}", e); - None - } - } -} diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 6c286c35b..4a270bfab 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -1,6 +1,5 @@ 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}; @@ -41,7 +40,7 @@ pub fn build( .unwrap() .with_ipc_handler(move |_window: &Window, payload: String| { // defer the event to the main thread - if let Some(message) = parse_ipc_message(&payload) { + if let Ok(message) = serde_json::from_str(&payload) { _ = proxy.send_event(UserWindowEvent::Ipc(message)); } }) From 6dfe84834d2c42e3b3220df08a1e26875540c504 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:06:33 -0500 Subject: [PATCH 07/15] fix eval example --- examples/eval.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/examples/eval.rs b/examples/eval.rs index 22e036b9c..3a16b774e 100644 --- a/examples/eval.rs +++ b/examples/eval.rs @@ -1,25 +1,17 @@ use dioxus::prelude::*; -use dioxus_desktop::EvalResult; fn main() { dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { - let script = use_state(cx, String::new); let eval = dioxus_desktop::use_eval(cx); - let future: &UseRef> = use_ref(cx, || None); - if future.read().is_some() { - let future_clone = future.clone(); - cx.spawn(async move { - if let Some(fut) = future_clone.with_mut(|o| o.take()) { - println!("{:?}", fut.await) - } - }); - } + let script = use_state(cx, String::new); + let output = use_state(cx, String::new); cx.render(rsx! { div { + p { "Output: {output}" } input { placeholder: "Enter an expression", value: "{script}", @@ -27,8 +19,12 @@ fn app(cx: Scope) -> Element { } button { onclick: move |_| { - let fut = eval(script); - future.set(Some(fut)); + to_owned![script, eval, output]; + cx.spawn(async move { + if let Ok(res) = eval(script.to_string()).await { + output.set(res.to_string()); + } + }); }, "Execute" } From 7f418bbfeefe81717a2d51e3be6fcc7ef76eb62f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:09:02 -0500 Subject: [PATCH 08/15] fix: window zoom --- examples/readme.rs | 5 ++--- examples/window_zoom.rs | 4 ++-- packages/desktop/src/desktop_context.rs | 5 +++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/readme.rs b/examples/readme.rs index 7e574904f..f4668488b 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -4,9 +4,8 @@ use dioxus::prelude::*; -#[tokio::main] -async fn main() { - dioxus_desktop::launch(app).await; +fn main() { + dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index b7c190896..98786c75b 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -7,8 +7,8 @@ fn main() { fn app(cx: Scope) -> Element { let window = use_window(cx); - let level = use_state(cx, || 1.0); + cx.render(rsx! { input { r#type: "number", @@ -16,7 +16,7 @@ fn app(cx: Scope) -> Element { oninput: |e| { let num = e.value.parse::().unwrap_or(1.0); level.set(num); - window.set_zoom_level(num); + window.webview.zoom(num); } } }) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 75a458cb7..09bed730e 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -118,6 +118,11 @@ impl DesktopContext { } } + /// Set the zoom level of the webview + pub fn set_zoom_level(&self, level: f64) { + self.webview.zoom(level); + } + /// opens DevTool window pub fn devtool(&self) { #[cfg(debug_assertions)] From a13b69788bececfeea16d876fc1080e7799d4931 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:11:43 -0500 Subject: [PATCH 09/15] chore: rename zoom --- examples/window_zoom.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index 98786c75b..facff0b40 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -14,9 +14,9 @@ fn app(cx: Scope) -> Element { r#type: "number", value: "{level}", oninput: |e| { - let num = e.value.parse::().unwrap_or(1.0); - level.set(num); - window.webview.zoom(num); + let new_zoom = e.value.parse::().unwrap_or(1.0); + level.set(new_zoom); + window.webview.zoom(new_zoom); } } }) From 972084ee3233505ecf3c9ab06b461311c079b158 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:32:53 -0500 Subject: [PATCH 10/15] chore: clean up zoom example --- examples/window_zoom.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/window_zoom.rs b/examples/window_zoom.rs index facff0b40..e45d71be2 100644 --- a/examples/window_zoom.rs +++ b/examples/window_zoom.rs @@ -14,9 +14,10 @@ fn app(cx: Scope) -> Element { r#type: "number", value: "{level}", oninput: |e| { - let new_zoom = e.value.parse::().unwrap_or(1.0); - level.set(new_zoom); - window.webview.zoom(new_zoom); + if let Ok(new_zoom) = e.value.parse::() { + level.set(new_zoom); + window.webview.zoom(new_zoom); + } } } }) From daa5449b6bb5b87381627ec8d89b6f7e0ffed9bb Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:39:47 -0500 Subject: [PATCH 11/15] chore: use existing scheduler --- packages/core/src/scheduler/mod.rs | 7 +- packages/core/src/scheduler/suspense.rs | 5 +- packages/core/src/scheduler/task.rs | 27 ++--- packages/core/src/scheduler/wait.rs | 129 +++++++++++------------- packages/core/src/scope_arena.rs | 9 +- packages/core/src/virtual_dom.rs | 20 ++-- 6 files changed, 85 insertions(+), 112 deletions(-) diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs index 3996633a2..8fb476fbe 100644 --- a/packages/core/src/scheduler/mod.rs +++ b/packages/core/src/scheduler/mod.rs @@ -1,5 +1,4 @@ use crate::ScopeId; -use futures_util::stream::FuturesUnordered; use slab::Slab; mod suspense; @@ -24,13 +23,13 @@ pub(crate) enum SchedulerMsg { SuspenseNotified(SuspenseId), } -use std::{cell::RefCell, rc::Rc, sync::Arc}; +use std::{cell::RefCell, rc::Rc}; pub(crate) struct Scheduler { pub sender: futures_channel::mpsc::UnboundedSender, /// Tasks created with cx.spawn - pub tasks: RefCell>, + pub tasks: RefCell>, /// Async components pub leaves: RefCell>, @@ -40,7 +39,7 @@ impl Scheduler { pub fn new(sender: futures_channel::mpsc::UnboundedSender) -> Rc { Rc::new(Scheduler { sender, - tasks: RefCell::new(FuturesUnordered::new()), + tasks: RefCell::new(Slab::new()), leaves: RefCell::new(Slab::new()), }) } diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs index 8449559c8..39e404198 100644 --- a/packages/core/src/scheduler/suspense.rs +++ b/packages/core/src/scheduler/suspense.rs @@ -5,6 +5,7 @@ use crate::ElementId; use crate::{innerlude::Mutations, Element, ScopeId}; use std::future::Future; use std::sync::Arc; +use std::task::Waker; use std::{ cell::{Cell, RefCell}, collections::HashSet, @@ -37,12 +38,10 @@ impl SuspenseContext { } pub(crate) struct SuspenseLeaf { - pub(crate) id: SuspenseId, pub(crate) scope_id: ScopeId, - pub(crate) tx: futures_channel::mpsc::UnboundedSender, pub(crate) notified: Cell, pub(crate) task: *mut dyn Future>, - pub(crate) waker: Arc, + pub(crate) waker: Waker, } pub struct SuspenseHandle { diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index d79b47cb4..a905cf993 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -1,5 +1,4 @@ use futures_util::task::ArcWake; -use futures_util::FutureExt; use super::{Scheduler, SchedulerMsg}; use crate::ScopeId; @@ -21,20 +20,8 @@ pub struct TaskId(pub usize); pub(crate) struct LocalTask { pub scope: ScopeId, pub(super) task: RefCell + 'static>>>, - id: TaskId, - tx: futures_channel::mpsc::UnboundedSender, pub waker: Waker, } -impl Future for LocalTask { - type Output = (); - - fn poll( - self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - self.task.borrow_mut().poll_unpin(cx) - } -} impl Scheduler { /// Start a new future on the same thread as the rest of the VirtualDom. @@ -47,12 +34,12 @@ impl Scheduler { /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which /// will only occur when the VirtuaalDom itself has been dropped. pub fn spawn(&self, scope: ScopeId, task: impl Future + 'static) -> TaskId { - // let entry = tasks.vacant_entry(); - let task_id = TaskId(0); + let mut tasks = self.tasks.borrow_mut(); + + let entry = tasks.vacant_entry(); + let task_id = TaskId(entry.key()); let task = LocalTask { - id: task_id, - tx: self.sender.clone(), task: RefCell::new(Box::pin(task)), scope, waker: futures_util::task::waker(Arc::new(LocalTaskHandle { @@ -61,9 +48,7 @@ impl Scheduler { })), }; - self.tasks.borrow_mut().push(task); - - // println!("Spawning task: {:?}", task_id); + entry.insert(task); self.sender .unbounded_send(SchedulerMsg::TaskNotified(task_id)) @@ -76,7 +61,7 @@ impl Scheduler { /// /// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you pub fn remove(&self, id: TaskId) { - // self.tasks.borrow_mut().remove(id.0); + self.tasks.borrow_mut().remove(id.0); } } diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index deac7fe6f..9268cfeb0 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,4 +1,3 @@ -use futures_util::task::ArcWake; use futures_util::FutureExt; use std::{ rc::Rc, @@ -21,27 +20,22 @@ impl VirtualDom { pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) { let mut tasks = self.scheduler.tasks.borrow_mut(); - // let task = match tasks.get(id.0) { - // Some(task) => task, - // None => { - // // The task was removed from the scheduler, so we can just ignore it - // return; - // } - // }; + let task = match tasks.get(id.0) { + Some(task) => task, + // The task was removed from the scheduler, so we can just ignore it + None => return, + }; - // let mut cx = Context::from_waker(&task.waker); + let mut cx = Context::from_waker(&task.waker); - // // If the task completes... - // println!("polling task"); - // if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { - // // Remove it from the scope so we dont try to double drop it when the scope dropes - // self.scopes[task.scope.0].spawned_tasks.remove(&id); + // If the task completes... + if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { + // Remove it from the scope so we dont try to double drop it when the scope dropes + self.scopes[task.scope.0].spawned_tasks.remove(&id); - // // Remove it from the scheduler - // tasks.remove(id.0); - // } else { - // println!("task not ready yet, but we gave it a handle to the waker"); - // } + // Remove it from the scheduler + tasks.remove(id.0); + } } pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc { @@ -51,69 +45,66 @@ impl VirtualDom { } pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { - // let leaf = self - // .scheduler - // .leaves - // .borrow_mut() - // .get(id.0) - // .unwrap() - // .clone(); + let leaves = self.scheduler.leaves.borrow_mut(); + let leaf = leaves.get(id.0).unwrap(); - // let scope_id = leaf.scope_id; + let scope_id = leaf.scope_id; - // // todo: cache the waker - // let waker = leaf.waker(); - // let mut cx = Context::from_waker(&waker); + // todo: cache the waker + let mut cx = Context::from_waker(&leaf.waker); - // // Safety: the future is always pinned to the bump arena - // let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) }; - // let as_pinned_mut = &mut pinned; + // Safety: the future is always pinned to the bump arena + let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) }; + let as_pinned_mut = &mut pinned; - // // the component finished rendering and gave us nodes - // // we should attach them to that component and then render its children - // // continue rendering the tree until we hit yet another suspended component - // if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { - // let fiber = self.acquire_suspense_boundary(leaf.scope_id); + // the component finished rendering and gave us nodes + // we should attach them to that component and then render its children + // continue rendering the tree until we hit yet another suspended component + if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { + let fiber = self.acquire_suspense_boundary(leaf.scope_id); - // let scope = &mut self.scopes[scope_id.0]; - // let arena = scope.current_frame(); + let scope = &mut self.scopes[scope_id.0]; + let arena = scope.current_frame(); - // let ret = arena.bump.alloc(match new_nodes { - // Some(new) => RenderReturn::Ready(new), - // None => RenderReturn::default(), - // }); + let ret = arena.bump.alloc(match new_nodes { + Some(new) => RenderReturn::Ready(new), + None => RenderReturn::default(), + }); - // arena.node.set(ret); + arena.node.set(ret); - // fiber.waiting_on.borrow_mut().remove(&id); + fiber.waiting_on.borrow_mut().remove(&id); - // if let RenderReturn::Ready(template) = ret { - // let mutations_ref = &mut fiber.mutations.borrow_mut(); - // let mutations = &mut **mutations_ref; - // let template: &VNode = unsafe { std::mem::transmute(template) }; - // let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) }; + if let RenderReturn::Ready(template) = ret { + let mutations_ref = &mut fiber.mutations.borrow_mut(); + let mutations = &mut **mutations_ref; + let template: &VNode = unsafe { std::mem::transmute(template) }; + let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) }; - // std::mem::swap(&mut self.mutations, mutations); + std::mem::swap(&mut self.mutations, mutations); - // let place_holder_id = scope.placeholder.get().unwrap(); - // self.scope_stack.push(scope_id); - // let created = self.create(template); - // self.scope_stack.pop(); - // mutations.push(Mutation::ReplaceWith { - // id: place_holder_id, - // m: created, - // }); + let place_holder_id = scope.placeholder.get().unwrap(); + self.scope_stack.push(scope_id); - // for leaf in self.collected_leaves.drain(..) { - // fiber.waiting_on.borrow_mut().insert(leaf); - // } + drop(leaves); - // std::mem::swap(&mut self.mutations, mutations); + let created = self.create(template); + self.scope_stack.pop(); + mutations.push(Mutation::ReplaceWith { + id: place_holder_id, + m: created, + }); - // if fiber.waiting_on.borrow().is_empty() { - // self.finished_fibers.push(fiber.id); - // } - // } - // } + for leaf in self.collected_leaves.drain(..) { + fiber.waiting_on.borrow_mut().insert(leaf); + } + + std::mem::swap(&mut self.mutations, mutations); + + if fiber.waiting_on.borrow().is_empty() { + self.finished_fibers.push(fiber.id); + } + } + } } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 6d574bf7e..8d3de2b45 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -92,17 +92,14 @@ impl VirtualDom { let leaf = SuspenseLeaf { scope_id, task: task.as_mut(), - id: suspense_id, - tx: self.scheduler.sender.clone(), notified: Default::default(), - waker: Arc::new(SuspenseHandle { + waker: futures_util::task::waker(Arc::new(SuspenseHandle { id: suspense_id, tx: self.scheduler.sender.clone(), - }), + })), }; - let waker = futures_util::task::waker(leaf.waker.clone()); - let mut cx = Context::from_waker(&waker); + let mut cx = Context::from_waker(&leaf.waker); // safety: the task is already pinned in the bump arena let mut pinned = unsafe { Pin::new_unchecked(task.as_mut()) }; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 435ccea3d..4450a5170 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -437,16 +437,18 @@ impl VirtualDom { return; } - let res = (&mut *self.scheduler.tasks.borrow_mut()).next().await; + some_msg = self.rx.next().await - if res.is_none() { - // If we have no tasks, then we should wait for a message - if let Some(msg) = self.rx.next().await { - some_msg = Some(msg); - } else { - return; - } - } + // let res = (&mut *self.scheduler.tasks.borrow_mut()).next().await; + + // if res.is_none() { + // // If we have no tasks, then we should wait for a message + // if let Some(msg) = self.rx.next().await { + // some_msg = Some(msg); + // } else { + // return; + // } + // } } } } From ba20bc08b866c4681b1710ac406c9f6bff84cd16 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:41:09 -0500 Subject: [PATCH 12/15] chore: clean up clock --- examples/clock.rs | 7 +++---- packages/core/src/scheduler/task.rs | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/clock.rs b/examples/clock.rs index c76c49faf..a159ef73a 100644 --- a/examples/clock.rs +++ b/examples/clock.rs @@ -3,16 +3,15 @@ //! The example from the README.md. use dioxus::prelude::*; -use futures_util::Future; fn main() { dioxus_desktop::launch(app); } fn app(cx: Scope) -> Element { - let mut count = use_ref(cx, || 0); + let count = use_ref(cx, || 0); - let mut ct = count.to_owned(); + let ct = count.to_owned(); use_coroutine(cx, |_: UnboundedReceiver<()>| async move { loop { tokio::time::sleep(std::time::Duration::from_millis(10)).await; @@ -25,7 +24,7 @@ fn app(cx: Scope) -> Element { } }); - let mut count = count.read(); + let count = count.read(); cx.render(rsx! { div { "High-Five counter: {count}" } diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs index a905cf993..39c770a06 100644 --- a/packages/core/src/scheduler/task.rs +++ b/packages/core/src/scheduler/task.rs @@ -72,8 +72,6 @@ pub struct LocalTaskHandle { impl ArcWake for LocalTaskHandle { fn wake_by_ref(arc_self: &Arc) { - println!("waking task: {:?} {:p}", arc_self.id, arc_self); - arc_self .tx .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)) From 9db69cc61331b4b71b3a719ff2d343fab58042b3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:43:00 -0500 Subject: [PATCH 13/15] chore: remove comments --- packages/core/src/virtual_dom.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 4450a5170..47cbe7245 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -438,17 +438,6 @@ impl VirtualDom { } some_msg = self.rx.next().await - - // let res = (&mut *self.scheduler.tasks.borrow_mut()).next().await; - - // if res.is_none() { - // // If we have no tasks, then we should wait for a message - // if let Some(msg) = self.rx.next().await { - // some_msg = Some(msg); - // } else { - // return; - // } - // } } } } From 1518f07d7e2de0feece7f566dcad4e31f6270b70 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:44:37 -0500 Subject: [PATCH 14/15] chore: remove sledgehammer --- packages/desktop/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index 85dd02bd0..8e8cc1a90 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -33,9 +33,8 @@ webbrowser = "0.8.0" infer = "0.11.0" dunce = "1.0.2" -interprocess = { version = "1.1.1", optional = true} +interprocess = { version = "1.1.1", optional = true } futures-util = "0.3.25" -sledgehammer = "0.2.0" [target.'cfg(target_os = "ios")'.dependencies] objc = "0.2.7" From 819bd3fc24219352c93af7eead40bfee734d7c3d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 30 Dec 2022 02:56:10 -0500 Subject: [PATCH 15/15] chore: use broadcast instead of receiver --- packages/desktop/src/desktop_context.rs | 16 +++------------- packages/desktop/src/eval.rs | 15 ++++++++++----- packages/desktop/src/lib.rs | 2 +- packages/html/src/events/drag.rs | 2 +- packages/html/src/events/mouse.rs | 2 +- 5 files changed, 16 insertions(+), 21 deletions(-) diff --git a/packages/desktop/src/desktop_context.rs b/packages/desktop/src/desktop_context.rs index 09bed730e..cb5a62635 100644 --- a/packages/desktop/src/desktop_context.rs +++ b/packages/desktop/src/desktop_context.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::rc::Rc; use crate::eval::EvalResult; @@ -42,10 +41,7 @@ pub struct DesktopContext { pub proxy: ProxyType, /// The receiver for eval results since eval is async - pub(super) eval_sender: tokio::sync::mpsc::UnboundedSender, - - /// The receiver for eval results since eval is async - pub(super) eval_reciever: Rc>>, + pub(super) eval: tokio::sync::broadcast::Sender, #[cfg(target_os = "ios")] pub(crate) views: Rc>>, @@ -62,14 +58,10 @@ impl std::ops::Deref for DesktopContext { impl DesktopContext { pub(crate) fn new(webview: Rc, 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, - + eval: tokio::sync::broadcast::channel(8).0, #[cfg(target_os = "ios")] views: Default::default(), } @@ -156,9 +148,7 @@ impl DesktopContext { log::warn!("Eval script error: {e}"); } - EvalResult { - reciever: self.eval_reciever.clone(), - } + EvalResult::new(self.eval.clone()) } /// Push an objc view to the window diff --git a/packages/desktop/src/eval.rs b/packages/desktop/src/eval.rs index 80baba2d4..7a92bedf5 100644 --- a/packages/desktop/src/eval.rs +++ b/packages/desktop/src/eval.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::rc::Rc; use crate::use_window; @@ -10,7 +9,13 @@ use std::pin::Pin; /// A future that resolves to the result of a JavaScript evaluation. pub struct EvalResult { - pub(crate) reciever: Rc>>, + pub(crate) broadcast: tokio::sync::broadcast::Sender, +} + +impl EvalResult { + pub(crate) fn new(sender: tokio::sync::broadcast::Sender) -> Self { + Self { broadcast: sender } + } } impl IntoFuture for EvalResult { @@ -20,10 +25,10 @@ impl IntoFuture for EvalResult { fn into_future(self) -> Self::IntoFuture { Box::pin(async move { - let mut reciever = self.reciever.borrow_mut(); + let mut reciever = self.broadcast.subscribe(); match reciever.recv().await { - Some(result) => Ok(result), - None => Err(serde_json::Error::custom("No result returned")), + Ok(result) => Ok(result), + Err(_) => Err(serde_json::Error::custom("No result returned")), } }) as Pin>>> } diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 9d10f36f5..e3002f539 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -183,7 +183,7 @@ pub fn launch_with_props(root: Component

, props: P, mut cfg: Conf dom.base_scope() .consume_context::() .unwrap() - .eval_sender + .eval .send(msg.params()) .unwrap(); } diff --git a/packages/html/src/events/drag.rs b/packages/html/src/events/drag.rs index f8ee6ff92..fa3219631 100644 --- a/packages/html/src/events/drag.rs +++ b/packages/html/src/events/drag.rs @@ -9,7 +9,7 @@ pub type DragEvent = Event; /// (such as another DOM element). Applications are free to interpret a drag and drop interaction in an /// application-specific way. #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct DragData { /// Inherit mouse data pub mouse: MouseData, diff --git a/packages/html/src/events/mouse.rs b/packages/html/src/events/mouse.rs index f8446b7d1..7899393ff 100644 --- a/packages/html/src/events/mouse.rs +++ b/packages/html/src/events/mouse.rs @@ -10,7 +10,7 @@ pub type MouseEvent = Event; /// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone, Default, PartialEq)] +#[derive(Clone, Default, PartialEq, Eq)] /// Data associated with a mouse event /// /// Do not use the deprecated fields; they may change or become private in the future.