diff --git a/src/platform_impl/web/async/channel.rs b/src/platform_impl/web/async/channel.rs index 6f7cb5be23..3f9c72a156 100644 --- a/src/platform_impl/web/async/channel.rs +++ b/src/platform_impl/web/async/channel.rs @@ -1,5 +1,6 @@ use atomic_waker::AtomicWaker; use std::future; +use std::rc::Rc; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{self, Receiver, RecvError, SendError, Sender, TryRecvError}; use std::sync::{Arc, Mutex}; @@ -21,7 +22,10 @@ pub fn channel() -> (AsyncSender, AsyncReceiver) { sender, inner: Arc::clone(&inner), }; - let receiver = AsyncReceiver { receiver, inner }; + let receiver = AsyncReceiver { + receiver: Rc::new(receiver), + inner, + }; (sender, receiver) } @@ -59,7 +63,7 @@ impl Clone for AsyncSender { } pub struct AsyncReceiver { - receiver: Receiver, + receiver: Rc>, inner: Arc, } @@ -86,6 +90,23 @@ impl AsyncReceiver { }) .await } + + pub fn try_recv(&self) -> Result, RecvError> { + match self.receiver.try_recv() { + Ok(value) => Ok(Some(value)), + Err(TryRecvError::Empty) => Ok(None), + Err(TryRecvError::Disconnected) => Err(RecvError), + } + } +} + +impl Clone for AsyncReceiver { + fn clone(&self) -> Self { + Self { + receiver: Rc::clone(&self.receiver), + inner: Arc::clone(&self.inner), + } + } } struct Inner { diff --git a/src/platform_impl/web/async/dispatcher.rs b/src/platform_impl/web/async/dispatcher.rs index b46874bf78..ea9a2e0c67 100644 --- a/src/platform_impl/web/async/dispatcher.rs +++ b/src/platform_impl/web/async/dispatcher.rs @@ -1,4 +1,4 @@ -use super::{channel, AsyncSender, Wrapper}; +use super::{channel, AsyncReceiver, AsyncSender, Wrapper}; use std::sync::{Arc, Condvar, Mutex}; pub struct Dispatcher(Wrapper>, Closure>); @@ -7,7 +7,7 @@ struct Closure(Box); impl Dispatcher { #[track_caller] - pub fn new(value: T) -> Option { + pub fn new(value: T) -> Option<(Self, DispatchRunner)> { let (sender, receiver) = channel::>(); Wrapper::new( @@ -17,11 +17,14 @@ impl Dispatcher { // funny with it here. See `Self::queue()`. closure(value.read().unwrap().as_ref().unwrap()) }, - move |value| async move { - while let Ok(Closure(closure)) = receiver.next().await { - // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything - // funny with it here. See `Self::queue()`. - closure(value.read().unwrap().as_ref().unwrap()) + { + let receiver = receiver.clone(); + move |value| async move { + while let Ok(Closure(closure)) = receiver.next().await { + // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything + // funny with it here. See `Self::queue()`. + closure(value.read().unwrap().as_ref().unwrap()) + } } }, sender, @@ -31,7 +34,7 @@ impl Dispatcher { sender.send(closure).unwrap() }, ) - .map(Self) + .map(|wrapper| (Self(wrapper.clone()), DispatchRunner { wrapper, receiver })) } pub fn with(&self, f: impl FnOnce(&T) -> R) -> Option { @@ -81,3 +84,26 @@ impl Drop for Dispatcher { self.0.with_sender_data(|sender| sender.close()) } } + +pub struct DispatchRunner { + wrapper: Wrapper>, Closure>, + receiver: AsyncReceiver>, +} + +impl DispatchRunner { + pub fn run(&self) { + while let Some(Closure(closure)) = self + .receiver + .try_recv() + .expect("should only be closed when `Dispatcher` is dropped") + { + self.wrapper + .with(|value| { + // SAFETY: The given `Closure` here isn't really `'static`, so we shouldn't do anything + // funny with it here. See `Self::queue()`. + closure(value) + }) + .expect("don't call this outside the main thread") + } + } +} diff --git a/src/platform_impl/web/async/mod.rs b/src/platform_impl/web/async/mod.rs index 8d6521b629..f1317f6e39 100644 --- a/src/platform_impl/web/async/mod.rs +++ b/src/platform_impl/web/async/mod.rs @@ -3,7 +3,7 @@ mod dispatcher; mod waker; mod wrapper; -use self::channel::{channel, AsyncSender}; -pub use self::dispatcher::Dispatcher; +use self::channel::{channel, AsyncReceiver, AsyncSender}; +pub use self::dispatcher::{DispatchRunner, Dispatcher}; pub use self::waker::{Waker, WakerSpawner}; use self::wrapper::Wrapper; diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index 0d8e056666..068d2893be 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -8,7 +8,8 @@ use crate::event::{ use crate::event_loop::{ControlFlow, DeviceEvents}; use crate::platform::web::PollStrategy; use crate::platform_impl::platform::backend::EventListenerHandle; -use crate::platform_impl::platform::r#async::{Waker, WakerSpawner}; +use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawner}; +use crate::platform_impl::platform::window::Inner; use crate::window::WindowId; use std::{ @@ -47,7 +48,14 @@ pub struct Execution { id: RefCell, window: web_sys::Window, document: Document, - all_canvases: RefCell>)>>, + #[allow(clippy::type_complexity)] + all_canvases: RefCell< + Vec<( + WindowId, + Weak>, + DispatchRunner, + )>, + >, redraw_pending: RefCell>, destroy_pending: RefCell>, page_transition_event_handle: RefCell>, @@ -186,11 +194,13 @@ impl Shared { &self.0.document } - pub fn add_canvas(&self, id: WindowId, canvas: &Rc>) { - self.0 - .all_canvases - .borrow_mut() - .push((id, Rc::downgrade(canvas))); + pub fn add_canvas( + &self, + id: WindowId, + canvas: Weak>, + runner: DispatchRunner, + ) { + self.0.all_canvases.borrow_mut().push((id, canvas, runner)); } pub fn notify_destroy_window(&self, id: WindowId) { @@ -422,7 +432,7 @@ impl Shared { "visibilitychange", Closure::new(move |_| { if !runner.0.suspended.get() { - for (id, canvas) in &*runner.0.all_canvases.borrow() { + for (id, canvas, _) in &*runner.0.all_canvases.borrow() { if let Some(canvas) = canvas.upgrade() { let is_visible = backend::is_visible(runner.document()); // only fire if: @@ -560,7 +570,7 @@ impl Shared { self.0 .all_canvases .borrow_mut() - .retain(|&(item_id, _)| item_id != id); + .retain(|&(item_id, _, _)| item_id != id); self.handle_event(Event::WindowEvent { window_id: id, event: crate::event::WindowEvent::Destroyed, @@ -629,6 +639,15 @@ impl Shared { // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().maybe_runner().is_some() { + // Pre-fetch window commands to avoid having to wait until the next event loop cycle + // and potentially block other threads in the meantime. + for (_, window, runner) in self.0.all_canvases.borrow().iter() { + if let Some(window) = window.upgrade() { + runner.run(); + drop(window) + } + } + // Take an event out of the queue and handle it // Make sure not to let the borrow_mut live during the next handle_event let event = { @@ -712,7 +731,7 @@ impl Shared { // Dropping the `Runner` drops the event handler closure, which will in // turn drop all `Window`s moved into the closure. *self.0.runner.borrow_mut() = RunnerEnum::Destroyed; - for (_, canvas) in all_canvases { + for (_, canvas, _) in all_canvases { // In case any remaining `Window`s are still not dropped, we will need // to explicitly remove the event handlers associated with their canvases. if let Some(canvas) = canvas.upgrade() { @@ -756,23 +775,29 @@ impl Shared { fn device_events(&self) -> bool { match self.0.device_events.get() { DeviceEvents::Always => true, - DeviceEvents::WhenFocused => self.0.all_canvases.borrow().iter().any(|(_, canvas)| { - if let Some(canvas) = canvas.upgrade() { - canvas.borrow().has_focus.get() - } else { - false - } - }), + DeviceEvents::WhenFocused => { + self.0.all_canvases.borrow().iter().any(|(_, canvas, _)| { + if let Some(canvas) = canvas.upgrade() { + canvas.borrow().has_focus.get() + } else { + false + } + }) + } DeviceEvents::Never => false, } } fn transient_activation(&self) { - self.0.all_canvases.borrow().iter().for_each(|(_, canvas)| { - if let Some(canvas) = canvas.upgrade() { - canvas.borrow().transient_activation(); - } - }); + self.0 + .all_canvases + .borrow() + .iter() + .for_each(|(_, canvas, _)| { + if let Some(canvas) = canvas.upgrade() { + canvas.borrow().transient_activation(); + } + }); } pub fn event_loop_recreation(&self, allow: bool) { diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 3690b33a45..09a5cc8b6b 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -81,7 +81,6 @@ impl EventLoopWindowTarget { id: WindowId, prevent_default: bool, ) { - self.runner.add_canvas(RootWindowId(id), canvas); let canvas_clone = canvas.clone(); let mut canvas = canvas.borrow_mut(); canvas.set_attribute("data-raw-handle", &id.0.to_string()); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 608d6d1ff1..b6aa3cb8fc 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -61,9 +61,11 @@ impl Window { inner.set_visible(attr.visible); inner.set_window_icon(attr.window_icon); - Ok(Window { - inner: Dispatcher::new(inner).unwrap(), - }) + let canvas = Rc::downgrade(&inner.canvas); + let (dispatcher, runner) = Dispatcher::new(inner).unwrap(); + target.runner.add_canvas(RootWI(id), canvas, runner); + + Ok(Window { inner: dispatcher }) } pub(crate) fn maybe_queue_on_main(&self, f: impl FnOnce(&Inner) + Send + 'static) {