Compare commits

..

1 Commits

Author SHA1 Message Date
Mads Marquart
2a391b348a Add EventLoopProxy::into_waker 2025-09-05 02:00:44 +02:00
6 changed files with 99 additions and 37 deletions

View File

@@ -1,9 +1,12 @@
use std::os::raw::c_void;
use std::ptr::NonNull;
use std::sync::Arc;
use std::task::{RawWaker, RawWakerVTable, Waker};
use objc2::MainThreadMarker;
use objc2_core_foundation::{
kCFRunLoopCommonModes, CFIndex, CFRetained, CFRunLoop, CFRunLoopSource, CFRunLoopSourceContext,
Type,
};
use winit_core::event_loop::EventLoopProxyProvider;
@@ -119,4 +122,54 @@ impl EventLoopProxyProvider for EventLoopProxy {
// main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
self.main_loop.wake_up();
}
fn waker(&self) -> Waker {
const VTABLE: RawWakerVTable =
RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker);
unsafe fn clone_waker(data: *const ()) -> RawWaker {
// SAFETY: The poiner came from `CFRunLoopSource` and is valid and non-null.
let source = unsafe { &*data.cast::<CFRunLoopSource>() };
// Increment reference count.
let source = source.retain();
// Pass ownership to the raw waker.
let data: *const CFRunLoopSource = CFRetained::into_raw(source).as_ptr();
RawWaker::new(data.cast(), &VTABLE)
}
unsafe fn wake(data: *const ()) {
unsafe { wake_by_ref(data) };
unsafe { drop_waker(data) };
}
unsafe fn wake_by_ref(data: *const ()) {
// SAFETY: The poiner came from `CFRunLoopSource` and is valid and non-null.
let source = unsafe { &*data.cast::<CFRunLoopSource>() };
// Signal the source, which ends up later invoking `perform` on the main thread.
//
// Multiple signals in quick succession are automatically coalesced into a single
// signal.
source.signal();
let main_loop = CFRunLoop::main().unwrap();
// Let the main thread know there's a new event.
//
// This is required since we may be (probably are) running on a different thread, and
// the main loop may be sleeping (and `CFRunLoopSourceSignal` won't wake it).
main_loop.wake_up();
}
unsafe fn drop_waker(data: *const ()) {
let source = data.cast::<CFRunLoopSource>().cast_mut();
// SAFETY: The poiner came from `CFRunLoopSource` and is valid and non-null.
// We take ownership of a retain count here.
let _source = unsafe { CFRetained::from_raw(NonNull::new_unchecked(source)) };
}
let data: *const CFRunLoopSource = CFRetained::into_raw(self.source.clone()).as_ptr();
unsafe { Waker::from_raw(RawWaker::new(data.cast(), &VTABLE)) }
}
}

View File

@@ -4,6 +4,7 @@ pub mod run_on_demand;
use std::fmt::{self, Debug};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
use std::task::Waker;
use std::time::Duration;
use rwh_06::{DisplayHandle, HandleError, HasDisplayHandle};
@@ -140,10 +141,23 @@ impl EventLoopProxy {
///
/// [`proxy_wake_up`]: crate::application::ApplicationHandler::proxy_wake_up
/// [`ApplicationHandler::proxy_wake_up()`]: crate::application::ApplicationHandler::proxy_wake_up
///
/// # Platform-specific
///
/// - **Windows**: The wake-up may be ignored under high contention, see [#3687].
///
/// [#3687]: https://github.com/rust-windowing/winit/pull/3687
pub fn wake_up(&self) {
self.proxy.wake_up();
}
/// Get a [`Waker`] that calls [`wake_up`] on the proxy when awoken.
///
/// This may be useful to `async` code or otherwise interoperating with `std`.
pub fn waker(&self) -> Waker {
self.proxy.waker()
}
pub fn new(proxy: Arc<dyn EventLoopProxyProvider>) -> Self {
Self { proxy }
}
@@ -152,6 +166,9 @@ impl EventLoopProxy {
pub trait EventLoopProxyProvider: Send + Sync + Debug {
/// See [`EventLoopProxy::wake_up`] for details.
fn wake_up(&self);
/// See [`EventLoopProxy::waker`] for details.
fn waker(&self) -> Waker;
}
/// A proxy for the underlying display handle.

View File

@@ -2,6 +2,7 @@ use std::cell::Cell;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::task::Waker;
use std::time::Instant;
use std::{iter, mem, slice};
@@ -276,7 +277,6 @@ impl EventState {
pub struct EventLoop {
windows: Vec<(Arc<RedoxSocket>, EventState)>,
window_target: ActiveEventLoop,
user_events_receiver: mpsc::Receiver<()>,
}
impl EventLoop {
@@ -577,7 +577,7 @@ impl EventLoop {
i += 1;
}
while self.user_events_receiver.try_recv().is_ok() {
if self.wake_up.swap(false, Ordering::Relaxed) {
app.proxy_wake_up(&self.window_target);
}
@@ -666,17 +666,26 @@ impl EventLoop {
#[derive(Debug)]
pub struct EventLoopProxy {
user_events_sender: mpsc::SyncSender<()>,
pub(super) wake_socket: TimeSocket,
waker: Waker,
}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
// When we fail to send the event it means that we haven't woken up to read the previous
// event.
if self.user_events_sender.try_send(()).is_ok() {
self.wake_socket.wake().unwrap();
}
self.waker.wake_by_ref();
}
fn into_waker(self) -> Waker {
self.waker
}
}
impl std::task::Wake for TimeSocket {
fn wake(self: Arc<TimeSocket>) {
TimeSocket::wake(&*self).unwrap();
}
fn wake_by_ref(self: &Arc<TimeSocket>) {
TimeSocket::wake(&**self).unwrap();
}
}

View File

@@ -416,10 +416,7 @@ impl ActiveEventLoop {
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> RootEventLoopProxy {
let event_loop_proxy = EventLoopProxy {
has_sent_wakeup_msg: self.0.has_sent_wakeup_msg.clone(),
target_window: self.0.thread_msg_target,
};
let event_loop_proxy = EventLoopProxy { target_window: self.0.thread_msg_target };
RootEventLoopProxy::new(Arc::new(event_loop_proxy))
}
@@ -769,7 +766,6 @@ type ThreadExecFn = Box<Box<dyn FnMut()>>;
#[derive(Debug)]
pub struct EventLoopProxy {
has_sent_wakeup_msg: Arc<AtomicBool>,
target_window: HWND,
}
@@ -778,18 +774,7 @@ unsafe impl Sync for EventLoopProxy {}
impl EventLoopProxyProvider for EventLoopProxy {
fn wake_up(&self) {
if self.has_sent_wakeup_msg.swap(true, Ordering::AcqRel) {
// Do not send a wakeup event if one has already been sent, but hasn't been processed
// yet. This prevents errors when the internal message queue fills up, and effectively
// coalesces wakeups.
tracing::trace!("avoided sending wake up, previous wake-up has yet to be processed");
return;
}
if unsafe { PostMessageW(self.target_window, PROXY_WAKEUP_MSG_ID.get(), 0, 0) } == 0 {
// _can_ technically fail, but realistically won't, since we've prevented the most
// common case (queue full) above.
tracing::error!("failed waking event loop: {}", std::io::Error::last_os_error());
}
unsafe { PostMessageW(self.target_window, USER_EVENT_MSG_ID.get(), 0, 0) };
}
}
@@ -845,7 +830,7 @@ impl LazyMessageId {
// Message sent by the `EventLoopProxy` when we want to wake up the thread.
// WPARAM and LPARAM are unused.
static PROXY_WAKEUP_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0");
static USER_EVENT_MSG_ID: LazyMessageId = LazyMessageId::new("Winit::WakeupMsg\0");
// Message sent when we want to execute a closure in the thread.
// WPARAM contains a Box<Box<dyn FnMut()>> that must be retrieved with `Box::from_raw`,
// and LPARAM is unused.
@@ -2486,9 +2471,12 @@ unsafe extern "system" fn thread_event_target_callback(
unsafe { DefWindowProcW(window, msg, wparam, lparam) }
},
_ if msg == PROXY_WAKEUP_MSG_ID.get() => {
// Reset the sent state, allowing new `PROXY_WAKEUP_MSG_ID` messages to be sent.
userdata.event_loop_runner.has_sent_wakeup_msg.store(false, Ordering::Release);
_ if msg == USER_EVENT_MSG_ID.get() => {
// synthesis a placeholder UserEvent, so that if the callback is
// re-entered it can be buffered for later delivery. the real
// user event is still in the mpsc channel and will be pulled
// once the placeholder event is delivered to the wrapper
// `event_handler`
userdata.send_wakeup();
0
},

View File

@@ -2,7 +2,6 @@ use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use std::{fmt, mem, panic};
@@ -31,8 +30,6 @@ pub(crate) struct EventLoopRunner {
// can't stall an external loop beyond a frame
pub(super) interrupt_msg_dispatch: Cell<bool>,
pub(super) has_sent_wakeup_msg: Arc<AtomicBool>,
control_flow: Cell<ControlFlow>,
exit: Cell<Option<i32>>,
runner_state: Cell<RunnerState>,
@@ -72,6 +69,8 @@ pub(crate) enum Event {
Device { device_id: DeviceId, event: DeviceEvent },
Window { window_id: WindowId, event: WindowEvent },
BufferedScaleFactorChanged(HWND, f64, PhysicalSize<u32>),
// FIXME(madsmtm): Coalesce these into a flag (or similar) instead of handling them as events.
// https://github.com/rust-windowing/winit/pull/3687
WakeUp,
}
@@ -81,7 +80,6 @@ impl EventLoopRunner {
thread_id,
thread_msg_target,
interrupt_msg_dispatch: Cell::new(false),
has_sent_wakeup_msg: Arc::new(AtomicBool::new(false)),
runner_state: Cell::new(RunnerState::Uninitialized),
control_flow: Cell::new(ControlFlow::default()),
exit: Cell::new(None),
@@ -133,7 +131,6 @@ impl EventLoopRunner {
thread_id: _,
thread_msg_target: _,
interrupt_msg_dispatch,
has_sent_wakeup_msg,
runner_state,
panic_error,
control_flow: _,
@@ -143,7 +140,6 @@ impl EventLoopRunner {
event_buffer: _,
} = self;
interrupt_msg_dispatch.set(false);
has_sent_wakeup_msg.store(false, Ordering::Release);
runner_state.set(RunnerState::Uninitialized);
panic_error.set(None);
exit.set(None);

View File

@@ -260,4 +260,3 @@ changelog entry.
- On Windows, `Window::theme` will return the correct theme after setting it through `Window::set_theme`.
- On Windows, `Window::set_theme` will change the title bar color immediately now.
- On Windows 11, prevent incorrect shifting when dragging window onto a monitor with different DPI.
- On Windows, coalesce wake-up events to avoid filling the message queue.