fix: use futuresunordered instead of bespoke waker system

This commit is contained in:
Jonathan Kelley committed 2022-12-29 19:53:43 -05:00
1 parent ff697d41e3
commit bd720e2268
12 files changed
+308 -272

No files matched your search

+41
View File
@@ -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}" }
})
}
+3 -2
View File
@@ -3,8 +3,9 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_elements::input_data::keyboard_types::Key; use dioxus_elements::input_data::keyboard_types::Key;
fn main() { #[tokio::main]
dioxus_desktop::launch(app); async fn main() {
dioxus_desktop::launch(app).await;
} }
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
+1 -1
View File
@@ -23,7 +23,7 @@ rustc-hash = "1.1.0"
# Used in diffing # Used in diffing
longest-increasing-subsequence = "0.1.0" 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" slab = "0.4"
+4 -5
View File
@@ -1,14 +1,13 @@
use crate::ScopeId; use crate::ScopeId;
use futures_util::stream::FuturesUnordered;
use slab::Slab; use slab::Slab;
mod suspense; mod suspense;
mod task; mod task;
mod wait; mod wait;
mod waker;
pub use suspense::*; pub use suspense::*;
pub use task::*; pub use task::*;
pub use waker::ArcWake;
/// The type of message that can be sent to the scheduler. /// 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<SchedulerMsg>, pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
/// Tasks created with cx.spawn /// Tasks created with cx.spawn
pub tasks: RefCell<Slab<Arc<LocalTask>>>, pub tasks: RefCell<FuturesUnordered<LocalTask>>,
/// Async components /// Async components
pub leaves: RefCell<Slab<Arc<SuspenseLeaf>>>, pub leaves: RefCell<Slab<SuspenseLeaf>>,
} }
impl Scheduler { impl Scheduler {
pub fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> { pub fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
Rc::new(Scheduler { Rc::new(Scheduler {
sender, sender,
tasks: RefCell::new(Slab::new()), tasks: RefCell::new(FuturesUnordered::new()),
leaves: RefCell::new(Slab::new()), leaves: RefCell::new(Slab::new()),
}) })
} }
+10 -3
View File
@@ -1,4 +1,6 @@
use super::{waker::ArcWake, SchedulerMsg}; use futures_util::task::ArcWake;
use super::SchedulerMsg;
use crate::ElementId; use crate::ElementId;
use crate::{innerlude::Mutations, Element, ScopeId}; use crate::{innerlude::Mutations, Element, ScopeId};
use std::future::Future; use std::future::Future;
@@ -40,11 +42,16 @@ pub(crate) struct SuspenseLeaf {
pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>, pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub(crate) notified: Cell<bool>, pub(crate) notified: Cell<bool>,
pub(crate) task: *mut dyn Future<Output = Element<'static>>, pub(crate) task: *mut dyn Future<Output = Element<'static>>,
pub(crate) waker: Arc<SuspenseHandle>,
} }
impl ArcWake for SuspenseLeaf { pub struct SuspenseHandle {
pub(crate) id: SuspenseId,
pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
}
impl ArcWake for SuspenseHandle {
fn wake_by_ref(arc_self: &Arc<Self>) { fn wake_by_ref(arc_self: &Arc<Self>) {
arc_self.notified.set(true);
_ = arc_self _ = arc_self
.tx .tx
.unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id)); .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));
+40 -10
View File
@@ -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 crate::ScopeId;
use std::cell::RefCell; use std::cell::RefCell;
use std::future::Future; use std::future::Future;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Arc; use std::sync::Arc;
use std::task::Waker;
/// A task's unique identifier. /// A task's unique identifier.
/// ///
@@ -19,6 +23,17 @@ pub(crate) struct LocalTask {
pub(super) task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>, pub(super) task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
id: TaskId, id: TaskId,
tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>, tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub waker: Waker,
}
impl Future for LocalTask {
type Output = ();
fn poll(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
self.task.borrow_mut().poll_unpin(cx)
}
} }
impl Scheduler { 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 /// 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. /// will only occur when the VirtuaalDom itself has been dropped.
pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId { pub fn spawn(&self, scope: ScopeId, task: impl Future<Output = ()> + 'static) -> TaskId {
let mut tasks = self.tasks.borrow_mut(); // let entry = tasks.vacant_entry();
let entry = tasks.vacant_entry(); let task_id = TaskId(0);
let task_id = TaskId(entry.key());
entry.insert(Arc::new(LocalTask { let task = LocalTask {
id: task_id, id: task_id,
tx: self.sender.clone(), tx: self.sender.clone(),
task: RefCell::new(Box::pin(task)), task: RefCell::new(Box::pin(task)),
scope, 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 self.sender
.unbounded_send(SchedulerMsg::TaskNotified(task_id)) .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 /// 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) { 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<SchedulerMsg>,
}
impl ArcWake for LocalTaskHandle {
fn wake_by_ref(arc_self: &Arc<Self>) { fn wake_by_ref(arc_self: &Arc<Self>) {
_ = arc_self println!("waking task: {:?} {:p}", arc_self.id, arc_self);
arc_self
.tx .tx
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id)); .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id))
.unwrap();
} }
} }
+70 -64
View File
@@ -1,3 +1,4 @@
use futures_util::task::ArcWake;
use futures_util::FutureExt; use futures_util::FutureExt;
use std::{ use std::{
rc::Rc, rc::Rc,
@@ -10,7 +11,7 @@ use crate::{
ScopeId, TaskId, VNode, VirtualDom, ScopeId, TaskId, VNode, VirtualDom,
}; };
use super::{waker::ArcWake, SuspenseId}; use super::SuspenseId;
impl VirtualDom { impl VirtualDom {
/// Handle notifications by tasks inside the scheduler /// Handle notifications by tasks inside the scheduler
@@ -20,22 +21,27 @@ impl VirtualDom {
pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) { pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
let mut tasks = self.scheduler.tasks.borrow_mut(); let mut tasks = self.scheduler.tasks.borrow_mut();
let task = match tasks.get(id.0) { // let task = match tasks.get(id.0) {
Some(task) => task, // Some(task) => task,
None => return, // 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(&task.waker);
let mut cx = Context::from_waker(&waker);
// If the task completes... // // If the task completes...
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() { // println!("polling task");
// Remove it from the scope so we dont try to double drop it when the scope dropes // if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
self.scopes[task.scope.0].spawned_tasks.remove(&id); // // 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 // // Remove it from the scheduler
tasks.remove(id.0); // 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<SuspenseContext> { pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc<SuspenseContext> {
@@ -45,69 +51,69 @@ impl VirtualDom {
} }
pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
let leaf = self // let leaf = self
.scheduler // .scheduler
.leaves // .leaves
.borrow_mut() // .borrow_mut()
.get(id.0) // .get(id.0)
.unwrap() // .unwrap()
.clone(); // .clone();
let scope_id = leaf.scope_id; // let scope_id = leaf.scope_id;
// todo: cache the waker // // todo: cache the waker
let waker = leaf.waker(); // let waker = leaf.waker();
let mut cx = Context::from_waker(&waker); // let mut cx = Context::from_waker(&waker);
// Safety: the future is always pinned to the bump arena // // Safety: the future is always pinned to the bump arena
let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) }; // let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
let as_pinned_mut = &mut pinned; // let as_pinned_mut = &mut pinned;
// the component finished rendering and gave us nodes // // the component finished rendering and gave us nodes
// we should attach them to that component and then render its children // // we should attach them to that component and then render its children
// continue rendering the tree until we hit yet another suspended component // // continue rendering the tree until we hit yet another suspended component
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) { // if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
let fiber = self.acquire_suspense_boundary(leaf.scope_id); // let fiber = self.acquire_suspense_boundary(leaf.scope_id);
let scope = &mut self.scopes[scope_id.0]; // let scope = &mut self.scopes[scope_id.0];
let arena = scope.current_frame(); // let arena = scope.current_frame();
let ret = arena.bump.alloc(match new_nodes { // let ret = arena.bump.alloc(match new_nodes {
Some(new) => RenderReturn::Ready(new), // Some(new) => RenderReturn::Ready(new),
None => RenderReturn::default(), // 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 { // if let RenderReturn::Ready(template) = ret {
let mutations_ref = &mut fiber.mutations.borrow_mut(); // let mutations_ref = &mut fiber.mutations.borrow_mut();
let mutations = &mut **mutations_ref; // let mutations = &mut **mutations_ref;
let template: &VNode = unsafe { std::mem::transmute(template) }; // let template: &VNode = unsafe { std::mem::transmute(template) };
let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) }; // 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(); // let place_holder_id = scope.placeholder.get().unwrap();
self.scope_stack.push(scope_id); // self.scope_stack.push(scope_id);
let created = self.create(template); // let created = self.create(template);
self.scope_stack.pop(); // self.scope_stack.pop();
mutations.push(Mutation::ReplaceWith { // mutations.push(Mutation::ReplaceWith {
id: place_holder_id, // id: place_holder_id,
m: created, // m: created,
}); // });
for leaf in self.collected_leaves.drain(..) { // for leaf in self.collected_leaves.drain(..) {
fiber.waiting_on.borrow_mut().insert(leaf); // 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() { // if fiber.waiting_on.borrow().is_empty() {
self.finished_fibers.push(fiber.id); // self.finished_fibers.push(fiber.id);
} // }
} // }
} // }
} }
} }
-37
View File
@@ -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<Self>) -> Waker {
unsafe fn rc_vtable<T: ArcWake>() -> &'static RawWakerVTable {
&RawWakerVTable::new(
|data| {
let arc = mem::ManuallyDrop::new(Arc::<T>::from_raw(data.cast::<T>()));
let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
RawWaker::new(data, rc_vtable::<T>())
},
|data| Arc::from_raw(data.cast::<T>()).wake(),
|data| {
let arc = mem::ManuallyDrop::new(Arc::<T>::from_raw(data.cast::<T>()));
ArcWake::wake_by_ref(&arc);
},
|data| drop(Arc::<T>::from_raw(data.cast::<T>())),
)
}
unsafe {
Waker::from_raw(RawWaker::new(
Arc::into_raw(self.clone()).cast(),
rc_vtable::<Self>(),
))
}
}
fn wake_by_ref(arc_self: &Arc<Self>);
fn wake(self: Arc<Self>) {
Self::wake_by_ref(&self)
}
}
+8 -5
View File
@@ -2,9 +2,8 @@ use crate::{
any_props::AnyProps, any_props::AnyProps,
bump_frame::BumpFrame, bump_frame::BumpFrame,
innerlude::DirtyScope, innerlude::DirtyScope,
innerlude::{SuspenseId, SuspenseLeaf}, innerlude::{SuspenseHandle, SuspenseId, SuspenseLeaf},
nodes::RenderReturn, nodes::RenderReturn,
scheduler::ArcWake,
scopes::{ScopeId, ScopeState}, scopes::{ScopeId, ScopeState},
virtual_dom::VirtualDom, virtual_dom::VirtualDom,
}; };
@@ -90,15 +89,19 @@ impl VirtualDom {
let entry = leaves.vacant_entry(); let entry = leaves.vacant_entry();
let suspense_id = SuspenseId(entry.key()); let suspense_id = SuspenseId(entry.key());
let leaf = Arc::new(SuspenseLeaf { let leaf = SuspenseLeaf {
scope_id, scope_id,
task: task.as_mut(), task: task.as_mut(),
id: suspense_id, id: suspense_id,
tx: self.scheduler.sender.clone(), tx: self.scheduler.sender.clone(),
notified: Default::default(), 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); let mut cx = Context::from_waker(&waker);
// safety: the task is already pinned in the bump arena // safety: the task is already pinned in the bump arena
+22 -5
View File
@@ -13,10 +13,21 @@ use crate::{
scopes::{ScopeId, ScopeState}, scopes::{ScopeId, ScopeState},
AttributeValue, Element, Event, Scope, SuspenseContext, 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 rustc_hash::FxHashMap;
use slab::Slab; 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. /// A virtual node system that progresses user events and diffs UI trees.
/// ///
@@ -420,7 +431,7 @@ impl VirtualDom {
loop { loop {
match some_msg.take() { match some_msg.take() {
// If a bunch of messages are ready in a sequence, try to pop them off synchronously // 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::Immediate(id) => self.mark_dirty(id),
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task), SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id), SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
@@ -430,14 +441,20 @@ impl VirtualDom {
None => { None => {
match self.rx.try_next() { match self.rx.try_next() {
Ok(Some(val)) => some_msg = Some(val), Ok(Some(val)) => some_msg = Some(val),
Ok(None) => return, Ok(None) => panic!("Scheduler channel closed"),
Err(_) => { Err(_) => {
// If we have any dirty scopes, or finished fiber trees then we should exit // 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() { if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
return; 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;
} }
} }
} }
+30 -91
View File
@@ -13,6 +13,8 @@ use wry::application::event_loop::EventLoopProxy;
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
use wry::application::platform::ios::WindowExtIOS; use wry::application::platform::ios::WindowExtIOS;
use wry::application::window::Fullscreen as WryFullscreen; use wry::application::window::Fullscreen as WryFullscreen;
use wry::application::window::Window;
use wry::webview::WebView;
use UserWindowEvent::*; use UserWindowEvent::*;
@@ -40,16 +42,32 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
#[derive(Clone)] #[derive(Clone)]
pub struct DesktopContext { pub struct DesktopContext {
/// The wry/tao proxy to the current window /// The wry/tao proxy to the current window
pub webview: Rc<WebView>,
/// The proxy to the event loop
pub proxy: ProxyType, pub proxy: ProxyType,
/// The receiver for eval results since eval is async
pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>, pub(super) eval_reciever: Rc<tokio::sync::Mutex<tokio::sync::mpsc::UnboundedReceiver<Value>>>,
} }
/// 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 { impl DesktopContext {
pub(crate) fn new( pub(crate) fn new(
webview: Rc<WebView>,
proxy: ProxyType, proxy: ProxyType,
eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>, eval_reciever: tokio::sync::mpsc::UnboundedReceiver<Value>,
) -> Self { ) -> Self {
Self { Self {
webview,
proxy, proxy,
eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)), eval_reciever: Rc::new(tokio::sync::Mutex::new(eval_reciever)),
} }
@@ -66,82 +84,21 @@ impl DesktopContext {
pub fn drag(&self) { pub fn drag(&self) {
let _ = self.proxy.send_event(DragWindow); 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 /// toggle window maximize state
pub fn toggle_maximized(&self) { pub fn toggle_maximized(&self) {
let _ = self.proxy.send_event(MaximizeToggle); 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 /// close window
pub fn close(&self) { pub fn close(&self) {
let _ = self.proxy.send_event(CloseWindow); let _ = self.proxy.send_event(CloseWindow);
} }
/// set window to focus
pub fn focus(&self) {
let _ = self.proxy.send_event(FocusWindow);
}
/// change window to fullscreen /// change window to fullscreen
pub fn set_fullscreen(&self, fullscreen: bool) { pub fn set_fullscreen(&self, fullscreen: bool) {
let _ = self.proxy.send_event(Fullscreen(fullscreen)); 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<f64>) {
let _ = self.proxy.send_event(SetInnerSize(logical_size));
}
/// launch print modal /// launch print modal
pub fn print(&self) { pub fn print(&self) {
let _ = self.proxy.send_event(Print); let _ = self.proxy.send_event(Print);
@@ -177,31 +134,20 @@ pub enum UserWindowEvent {
Poll, Poll,
UserEvent(serde_json::Value),
CloseWindow, CloseWindow,
DragWindow, DragWindow,
FocusWindow,
Visible(bool),
Minimize(bool),
Maximize(bool),
MaximizeToggle, MaximizeToggle,
Resizable(bool),
AlwaysOnTop(bool),
Fullscreen(bool), Fullscreen(bool),
CursorVisible(bool),
CursorGrab(bool),
SetTitle(String),
SetDecorations(bool),
SetZoomLevel(f64),
SetInnerSize(LogicalSize<f64>),
Print, Print,
DevTool, DevTool,
Eval(String), Eval(String),
EvalResult(serde_json::Value),
#[cfg(target_os = "ios")] #[cfg(target_os = "ios")]
PushView(objc_id::ShareId<objc::runtime::Object>), PushView(objc_id::ShareId<objc::runtime::Object>),
@@ -236,18 +182,17 @@ impl DesktopController {
// if the drag_window has any errors, we don't do anything // if the drag_window has any errors, we don't do anything
window.fullscreen().is_none().then(|| window.drag_window()); 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()), MaximizeToggle => window.set_maximized(!window.is_maximized()),
Fullscreen(state) => { Fullscreen(state) => {
if let Some(handle) = window.current_monitor() { if let Some(handle) = window.current_monitor() {
window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle)))); window.set_fullscreen(state.then_some(WryFullscreen::Borderless(Some(handle))));
} }
} }
FocusWindow => window.set_focus(),
Resizable(state) => window.set_resizable(state), UserEvent(event) => {
AlwaysOnTop(state) => window.set_always_on_top(state), //
}
Eval(code) => { Eval(code) => {
let script = format!( let script = format!(
@@ -261,17 +206,11 @@ impl DesktopController {
log::warn!("Eval script error: {e}"); log::warn!("Eval script error: {e}");
} }
} }
CursorVisible(state) => window.set_cursor_visible(state),
CursorGrab(state) => { EvalResult(result) => {
let _ = window.set_cursor_grab(state); // 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 => { Poll => {
// todo // todo
} }
+79 -49
View File
@@ -13,6 +13,7 @@ mod protocol;
#[cfg(all(feature = "hot-reload", debug_assertions))] #[cfg(all(feature = "hot-reload", debug_assertions))]
mod hot_reload; mod hot_reload;
use dioxus_html::{a, HtmlEvent};
use futures_util::task::ArcWake; use futures_util::task::ArcWake;
use serde_json::Value; use serde_json::Value;
use std::cell::RefCell; use std::cell::RefCell;
@@ -115,23 +116,18 @@ pub async fn launch_cfg(root: Component, config_builder: Config) {
/// } /// }
/// ``` /// ```
pub async fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) { pub async fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cfg: Config) {
let mut event_loop = EventLoop::with_user_event(); let event_loop = EventLoop::with_user_event();
let is_ready = Arc::new(AtomicBool::new(false)); let mut dom = VirtualDom::new_with_props(root, props);
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 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 { let waker = futures_util::task::waker(Arc::new(DomHandle {
proxy: proxy.clone(), proxy: proxy.clone(),
})); }));
let mut events = Rc::new(RefCell::new(vec![]));
let mut webviews = HashMap::new(); let mut webviews = HashMap::new();
event_loop.run(move |window_event, event_loop, control_flow| { event_loop.run(move |window_event, event_loop, control_flow| {
@@ -139,20 +135,21 @@ pub async fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P,
match window_event { match window_event {
Event::NewEvents(StartCause::Init) => { Event::NewEvents(StartCause::Init) => {
let window = build_webview( let (eval_sender, eval_reciever) = tokio::sync::mpsc::unbounded_channel();
&mut cfg, let window = Rc::new(build_webview(&mut cfg, event_loop, proxy.clone()));
event_loop, let ctx = DesktopContext::new(window.clone(), proxy.clone(), eval_reciever);
is_ready.clone(), dom.base_scope().provide_context(ctx);
proxy.clone(), webviews.insert(window.window().id(), window.clone());
eval_sender.clone(),
events.clone(),
);
webviews.insert(window.window().id(), window); proxy.send_event(UserWindowEvent::Poll).unwrap();
// desktop.start(&mut cfg, event_loop);
} }
Event::MainEventsCleared => {}
Event::Resumed => {}
Event::Suspended => {}
Event::LoopDestroyed => {}
Event::RedrawRequested(_id) => {}
Event::WindowEvent { event, .. } => match event { Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Destroyed { .. } => { WindowEvent::Destroyed { .. } => {
@@ -165,19 +162,65 @@ pub async fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P,
println!("user event: {:?}", user_event); println!("user event: {:?}", user_event);
match user_event { match user_event {
UserWindowEvent::UserEvent(json_value) => {
if let Ok(value) = serde_json::from_value::<HtmlEvent>(json_value) {
let HtmlEvent {
name,
element,
bubbles,
data,
} = value;
dom.handle_event(&name, data.into_any(), element, bubbles);
let 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 => { UserWindowEvent::Poll => {
let mut cx = std::task::Context::from_waker(&waker); let mut cx = std::task::Context::from_waker(&waker);
let render = { println!("polling..");
let fut = dom.wait_for_work();
pin_mut!(fut); loop {
matches!(fut.poll_unpin(&mut cx), std::task::Poll::Ready(_)) {
}; 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(); let edits = dom.render_immediate();
// apply the edits // 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<P: 'static + Send>(root: Component<P>, props: P,
} }
} }
} }
Event::MainEventsCleared => {}
Event::Resumed => {}
Event::Suspended => {}
Event::LoopDestroyed => {}
Event::RedrawRequested(_id) => {}
_ => {} _ => {}
} }
}) })
@@ -223,10 +262,7 @@ impl ArcWake for DomHandle {
fn build_webview( fn build_webview(
cfg: &mut Config, cfg: &mut Config,
event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>, event_loop: &tao::event_loop::EventLoopWindowTarget<UserWindowEvent>,
is_ready: Arc<AtomicBool>,
proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>, proxy: tao::event_loop::EventLoopProxy<UserWindowEvent>,
eval_sender: tokio::sync::mpsc::UnboundedSender<serde_json::Value>,
event_tx: Rc<RefCell<Vec<Value>>>,
) -> wry::webview::WebView { ) -> wry::webview::WebView {
let builder = cfg.window.clone(); let builder = cfg.window.clone();
let window = builder.build(event_loop).unwrap(); let window = builder.build(event_loop).unwrap();
@@ -264,29 +300,23 @@ fn build_webview(
match message.method() { match message.method() {
"eval_result" => { "eval_result" => {
let result = message.params(); let _ = proxy.send_event(UserWindowEvent::EvalResult(message.params()));
eval_sender.send(result).unwrap();
} }
"user_event" => { "user_event" => {
_ = event_tx.borrow_mut().push(message.params()); let _ = proxy.send_event(UserWindowEvent::UserEvent(message.params()));
let _ = proxy.send_event(UserWindowEvent::Poll);
} }
"initialize" => { "initialize" => {
is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
let _ = proxy.send_event(UserWindowEvent::EditsReady); let _ = proxy.send_event(UserWindowEvent::EditsReady);
} }
"browser_open" => { "browser_open" => match message.params().as_object() {
let data = message.params(); Some(temp) if temp.contains_key("href") => {
log::trace!("Open browser: {:?}", data); let open = webbrowser::open(temp["href"].as_str().unwrap());
if let Some(temp) = data.as_object() { if let Err(e) = open {
if temp.contains_key("href") { log::error!("Open Browser error: {:?}", e);
let url = temp.get("href").unwrap().as_str().unwrap();
if let Err(e) = webbrowser::open(url) {
log::error!("Open Browser error: {:?}", e);
}
} }
} }
} _ => (),
},
_ => (), _ => (),
} }
}) })