Web: queue EventLoopProxy::send_event() to microtask

This commit is contained in:
daxpedda
2024-06-12 00:02:26 +02:00
committed by GitHub
parent 9a1ef49dc3
commit 9522670081
3 changed files with 92 additions and 34 deletions

View File

@@ -40,6 +40,16 @@ changelog entry.
## Unreleased ## Unreleased
### Changed
- On Web, let events wake up event loop immediately when using
`ControlFlow::Poll`.
### Fixed
- On Web, fix `EventLoopProxy::send_event()` triggering event loop immediately
when not called from inside the event loop. Now queues a microtask instead.
### Removed ### Removed
- Remove `EventLoop::run`. - Remove `EventLoop::run`.

View File

@@ -1,4 +1,5 @@
use std::future; use std::future;
use std::num::NonZeroUsize;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::Arc; use std::sync::Arc;
use std::task::Poll; use std::task::Poll;
@@ -6,13 +7,13 @@ use std::task::Poll;
use super::super::main_thread::MainThreadMarker; use super::super::main_thread::MainThreadMarker;
use super::{AtomicWaker, Wrapper}; use super::{AtomicWaker, Wrapper};
pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, usize>); pub struct WakerSpawner<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, usize>); pub struct Waker<T: 'static>(Wrapper<Handler<T>, Sender, NonZeroUsize>);
struct Handler<T> { struct Handler<T> {
value: T, value: T,
handler: fn(&T, usize), handler: fn(&T, NonZeroUsize, bool),
} }
#[derive(Clone)] #[derive(Clone)]
@@ -20,7 +21,11 @@ struct Sender(Arc<Inner>);
impl<T> WakerSpawner<T> { impl<T> WakerSpawner<T> {
#[track_caller] #[track_caller]
pub fn new(main_thread: MainThreadMarker, value: T, handler: fn(&T, usize)) -> Option<Self> { pub fn new(
main_thread: MainThreadMarker,
value: T,
handler: fn(&T, NonZeroUsize, bool),
) -> Option<Self> {
let inner = Arc::new(Inner { let inner = Arc::new(Inner {
counter: AtomicUsize::new(0), counter: AtomicUsize::new(0),
waker: AtomicWaker::new(), waker: AtomicWaker::new(),
@@ -37,7 +42,7 @@ impl<T> WakerSpawner<T> {
|handler, count| { |handler, count| {
let handler = handler.borrow(); let handler = handler.borrow();
let handler = handler.as_ref().unwrap(); let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count); (handler.handler)(&handler.value, count, true);
}, },
{ {
let inner = Arc::clone(&inner); let inner = Arc::clone(&inner);
@@ -46,29 +51,31 @@ impl<T> WakerSpawner<T> {
while let Some(count) = future::poll_fn(|cx| { while let Some(count) = future::poll_fn(|cx| {
let count = inner.counter.swap(0, Ordering::Relaxed); let count = inner.counter.swap(0, Ordering::Relaxed);
if count > 0 { match NonZeroUsize::new(count) {
Poll::Ready(Some(count)) Some(count) => Poll::Ready(Some(count)),
} else { None => {
inner.waker.register(cx.waker()); inner.waker.register(cx.waker());
let count = inner.counter.swap(0, Ordering::Relaxed); let count = inner.counter.swap(0, Ordering::Relaxed);
if count > 0 { match NonZeroUsize::new(count) {
Poll::Ready(Some(count)) Some(count) => Poll::Ready(Some(count)),
} else { None => {
if inner.closed.load(Ordering::Relaxed) { if inner.closed.load(Ordering::Relaxed) {
return Poll::Ready(None); return Poll::Ready(None);
}
Poll::Pending
},
} }
},
Poll::Pending
}
} }
}) })
.await .await
{ {
let handler = handler.borrow(); let handler = handler.borrow();
let handler = handler.as_ref().unwrap(); let handler = handler.as_ref().unwrap();
(handler.handler)(&handler.value, count); (handler.handler)(&handler.value, count, false);
} }
} }
}, },
@@ -107,7 +114,7 @@ impl<T> Drop for WakerSpawner<T> {
impl<T> Waker<T> { impl<T> Waker<T> {
pub fn wake(&self) { pub fn wake(&self) {
self.0.send(1) self.0.send(NonZeroUsize::MIN)
} }
} }

View File

@@ -14,12 +14,15 @@ use crate::platform_impl::platform::r#async::{DispatchRunner, Waker, WakerSpawne
use crate::platform_impl::platform::window::Inner; use crate::platform_impl::platform::window::Inner;
use crate::window::WindowId; use crate::window::WindowId;
use js_sys::Function;
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::collections::{HashSet, VecDeque}; use std::collections::{HashSet, VecDeque};
use std::iter; use std::iter;
use std::num::NonZeroUsize;
use std::ops::Deref; use std::ops::Deref;
use std::rc::{Rc, Weak}; use std::rc::{Rc, Weak};
use wasm_bindgen::prelude::Closure; use wasm_bindgen::prelude::{wasm_bindgen, Closure};
use wasm_bindgen::JsCast;
use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent}; use web_sys::{Document, KeyboardEvent, PageTransitionEvent, PointerEvent, WheelEvent};
use web_time::{Duration, Instant}; use web_time::{Duration, Instant};
@@ -133,12 +136,13 @@ impl Shared {
let document = window.document().expect("Failed to obtain document"); let document = window.document().expect("Failed to obtain document");
Shared(Rc::<Execution>::new_cyclic(|weak| { Shared(Rc::<Execution>::new_cyclic(|weak| {
let proxy_spawner = WakerSpawner::new(main_thread, weak.clone(), |runner, count| { let proxy_spawner =
if let Some(runner) = runner.upgrade() { WakerSpawner::new(main_thread, weak.clone(), |runner, count, local| {
Shared(runner).send_events(iter::repeat(Event::UserEvent(())).take(count)) if let Some(runner) = runner.upgrade() {
} Shared(runner).send_user_events(count, local)
}) }
.expect("`EventLoop` has to be created in the main thread"); })
.expect("`EventLoop` has to be created in the main thread");
Execution { Execution {
main_thread, main_thread,
@@ -460,6 +464,48 @@ impl Shared {
self.send_events(iter::once(event)); self.send_events(iter::once(event));
} }
// Add a series of user events to the event loop runner
//
// This will schedule the event loop to wake up instead of waking it up immediately if its not
// running.
pub(crate) fn send_user_events(&self, count: NonZeroUsize, local: bool) {
// If the event loop is closed, it should discard any new events
if self.is_closed() {
return;
}
if local {
// If the loop is not running and triggered locally, queue on next microtick.
if let Ok(RunnerEnum::Running(_)) =
self.0.runner.try_borrow().as_ref().map(Deref::deref)
{
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_name = queueMicrotask)]
fn queue_microtask(task: Function);
}
queue_microtask(
Closure::once_into_js({
let this = Rc::downgrade(&self.0);
move || {
if let Some(shared) = this.upgrade() {
Shared(shared).send_events(
iter::repeat(Event::UserEvent(())).take(count.get()),
)
}
}
})
.unchecked_into(),
);
return;
}
}
self.send_events(iter::repeat(Event::UserEvent(())).take(count.get()))
}
// Add a series of events to the event loop runner // Add a series of events to the event loop runner
// //
// It will determine if the event should be immediately sent to the user or buffered for later // It will determine if the event should be immediately sent to the user or buffered for later
@@ -471,13 +517,8 @@ impl Shared {
// If we can run the event processing right now, or need to queue this and wait for later // If we can run the event processing right now, or need to queue this and wait for later
let mut process_immediately = true; let mut process_immediately = true;
match self.0.runner.try_borrow().as_ref().map(Deref::deref) { match self.0.runner.try_borrow().as_ref().map(Deref::deref) {
Ok(RunnerEnum::Running(ref runner)) => { // If the runner is attached but not running, we always wake it up.
// If we're currently polling, queue this and wait for the poll() method to be Ok(RunnerEnum::Running(_)) => (),
// called
if let State::Poll { .. } = runner.state {
process_immediately = false;
}
},
Ok(RunnerEnum::Pending) => { Ok(RunnerEnum::Pending) => {
// The runner still hasn't been attached: queue this event and wait for it to be // The runner still hasn't been attached: queue this event and wait for it to be
process_immediately = false; process_immediately = false;