Skip to content

Commit

Permalink
Add EventLoop::waker
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Jan 25, 2024
1 parent b36d8d1 commit 28f339f
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 122 deletions.
2 changes: 1 addition & 1 deletion examples/child_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ fn main() -> Result<(), impl std::error::Error> {

println!("parent window: {parent_window:?})");

event_loop.run(move |event: Event<()>, elwt| {
event_loop.run(move |event: Event, elwt| {
if let Event::WindowEvent { event, window_id } = event {
match event {
WindowEvent::CloseRequested => {
Expand Down
27 changes: 17 additions & 10 deletions examples/custom_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#[cfg(not(web_platform))]
fn main() -> Result<(), impl std::error::Error> {
use std::sync::mpsc;

use simple_logger::SimpleLogger;
use winit::{
event::{Event, WindowEvent},
Expand All @@ -18,30 +20,35 @@ fn main() -> Result<(), impl std::error::Error> {
}

SimpleLogger::new().init().unwrap();
let event_loop = EventLoopBuilder::<CustomEvent>::with_user_event()
.build()
.unwrap();
let event_loop = EventLoopBuilder::new().build().unwrap();

let window = WindowBuilder::new()
.with_title("A fantastic window!")
.build(&event_loop)
.unwrap();

// `EventLoopProxy` allows you to dispatch custom events to the main Winit event
// loop from any thread.
let event_loop_proxy = event_loop.create_proxy();
let waker = event_loop.create_proxy().waker();

let (sender, receiver) = mpsc::channel();

std::thread::spawn(move || {
// Wake up the `event_loop` once every second and dispatch a custom event
// from a different thread.
// Dispatch a custom event from a different thread once every second,
// and wake the `event_loop` so that it can handle it.
loop {
std::thread::sleep(std::time::Duration::from_secs(1));
event_loop_proxy.send_event(CustomEvent::Timer).ok();
if sender.send(CustomEvent::Timer).is_err() {
break;
}
waker.wake_by_ref();
}
});

event_loop.run(move |event, elwt| match event {
Event::UserEvent(event) => println!("user event: {event:?}"),
Event::NewEvents(_) => {
for event in receiver.iter() {
println!("user event: {event:?}");
}
}
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
Expand Down
2 changes: 1 addition & 1 deletion examples/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ mod wasm {
log_list
}

pub fn log_event(log_list: &web_sys::Element, event: &Event<()>) {
pub fn log_event(log_list: &web_sys::Element, event: &Event) {
log::debug!("{:?}", event);

// Getting access to browser logs requires a lot of setup on mobile devices.
Expand Down
7 changes: 6 additions & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use crate::{
///
/// See the module-level docs for more information on the event loop manages each event.
#[derive(Debug, Clone, PartialEq)]
pub enum Event<T: 'static> {
pub enum Event<T: 'static = ()> {
/// Emitted when new events arrive from the OS to be processed.
///
/// This event type is useful as a place to put code that should be done before you start
Expand All @@ -80,6 +80,7 @@ pub enum Event<T: 'static> {
},

/// Emitted when an event is sent from [`EventLoopProxy::send_event`](crate::event_loop::EventLoopProxy::send_event)
#[deprecated = "use `EventLoopProxy::waker`, and listen for wakeups in `NewEvents`"]
UserEvent(T),

/// Emitted when the application has been suspended.
Expand Down Expand Up @@ -257,9 +258,11 @@ pub enum Event<T: 'static> {

impl<T> Event<T> {
#[allow(clippy::result_large_err)]
#[deprecated = "Event::UserEvent is deprecated, so there is no need for this any more"]
pub fn map_nonuser_event<U>(self) -> Result<Event<U>, Event<T>> {
use self::Event::*;
match self {
#[allow(deprecated)]
UserEvent(_) => Err(self),
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
Expand Down Expand Up @@ -1164,6 +1167,7 @@ mod tests {

// Mainline events.
let wid = unsafe { WindowId::dummy() };
#[allow(deprecated)]
x(UserEvent(()));
x(NewEvents(event::StartCause::Init));
x(AboutToWait);
Expand Down Expand Up @@ -1278,6 +1282,7 @@ mod tests {
}

#[test]
#[allow(deprecated)]
fn test_map_nonuser_event() {
foreach_event!(|event: event::Event<()>| {
let is_user = matches!(event, event::Event::UserEvent(()));
Expand Down
38 changes: 35 additions & 3 deletions src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::ops::Deref;
#[cfg(any(x11_platform, wayland_platform))]
use std::os::unix::io::{AsFd, AsRawFd, BorrowedFd, RawFd};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::task::Waker;
use std::{error, fmt};

#[cfg(not(web_platform))]
Expand Down Expand Up @@ -67,7 +68,10 @@ impl EventLoopBuilder<()> {
/// Start building a new event loop.
#[inline]
pub fn new() -> Self {
Self::with_user_event()
Self {
platform_specific: Default::default(),
_p: PhantomData,
}
}
}

Expand All @@ -77,6 +81,7 @@ impl<T> EventLoopBuilder<T> {
/// Start building a new event loop, with the given type as the user event
/// type.
#[inline]
#[deprecated = "use `EventLoopProxy` with your own event channel instead"]
pub fn with_user_event() -> Self {
Self {
platform_specific: Default::default(),
Expand Down Expand Up @@ -205,6 +210,7 @@ impl EventLoop<()> {
impl<T> EventLoop<T> {
#[deprecated = "Use `EventLoopBuilder::<T>::with_user_event().build()` instead."]
pub fn with_user_event() -> Result<EventLoop<T>, EventLoopError> {
#[allow(deprecated)]
EventLoopBuilder::<T>::with_user_event().build()
}

Expand Down Expand Up @@ -246,7 +252,8 @@ impl<T> EventLoop<T> {
self.event_loop.run(event_handler)
}

/// Creates an [`EventLoopProxy`] that can be used to dispatch user events to the main event loop.
/// Creates an [`EventLoopProxy`] that can be used to control the event
/// loop from a different thread.
pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy {
event_loop_proxy: self.event_loop.create_proxy(),
Expand Down Expand Up @@ -442,7 +449,10 @@ unsafe impl rwh_05::HasRawDisplayHandle for OwnedDisplayHandle {
}

/// Used to send custom events to [`EventLoop`].
pub struct EventLoopProxy<T: 'static> {
///
/// This has a generic type `T` that represents a user event, but that is
/// deprecated.
pub struct EventLoopProxy<T: 'static = ()> {
event_loop_proxy: platform_impl::EventLoopProxy<T>,
}

Expand All @@ -462,9 +472,31 @@ impl<T: 'static> EventLoopProxy<T> {
/// Returns an `Err` if the associated [`EventLoop`] no longer exists.
///
/// [`UserEvent(event)`]: Event::UserEvent
#[deprecated = "get a `waker` and wake that instead"]
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.event_loop_proxy.send_event(event)
}

/// Create a [`Waker`] that can wake the event loop from any thread.
///
/// This is usually used in combination with [`mpsc::channel`] to send the
/// data that should be processed by the event loop.
///
/// If you do use a channel, the data will be accessible from inside
/// [`Event::NewEvents`].
///
/// [`mpsc::channel`]: std::sync::mpsc::channel
pub fn waker(self) -> Waker {
self.event_loop_proxy.waker()
}
}

// Note: This does not have the generic from `EventLoopProxy<T>`, since this
// is the new API that shouldn't need it.
impl From<EventLoopProxy> for Waker {
fn from(proxy: EventLoopProxy) -> Self {
proxy.waker()
}
}

impl<T: 'static> fmt::Debug for EventLoopProxy<T> {
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,7 @@ impl<T: 'static> EventLoop<T> {
// Empty the user event buffer
{
while let Ok(event) = self.user_events_receiver.try_recv() {
#[allow(deprecated)]
callback(crate::event::Event::UserEvent(event), self.window_target());
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/platform_impl/ios/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
drop(this);

#[allow(deprecated)]
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));

loop {
Expand Down Expand Up @@ -745,6 +746,7 @@ fn handle_user_events(mtm: MainThreadMarker) {
}
}

#[allow(deprecated)]
handler.handle_event(Event::UserEvent(HandlePendingUserEvents));
}
}
Expand Down
121 changes: 74 additions & 47 deletions src/platform_impl/ios/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
marker::PhantomData,
ptr,
sync::mpsc::{self, Receiver, Sender},
task::{RawWaker, RawWakerVTable, Waker},
};

use core_foundation::base::{CFIndex, CFRelease};
Expand Down Expand Up @@ -108,6 +109,7 @@ impl OwnedDisplayHandle {
}
}

#[allow(deprecated)]
fn map_user_event<T: 'static>(
mut handler: impl FnMut(Event<T>, &RootEventLoopWindowTarget),
receiver: mpsc::Receiver<T>,
Expand Down Expand Up @@ -208,7 +210,10 @@ impl<T: 'static> EventLoop<T> {
}

pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
EventLoopProxy {
sender: self.sender.clone(),
waker: waker(),
}
}

pub fn window_target(&self) -> &RootEventLoopWindowTarget {
Expand All @@ -223,70 +228,92 @@ impl<T: 'static> EventLoop<T> {
}
}

pub struct EventLoopProxy<T> {
sender: Sender<T>,
source: CFRunLoopSourceRef,
}
pub fn waker() -> Waker {
fn new_raw_waker() -> RawWaker {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}

// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = unsafe { CFRunLoopGetMain() };
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source = unsafe {
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context)
};
unsafe { CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes) };
unsafe { CFRunLoopWakeUp(rl) };
RawWaker::new(
source as *const (),
&RawWakerVTable::new(clone_waker, wake, wake_by_ref, drop_waker),
)
}

unsafe impl<T: Send> Send for EventLoopProxy<T> {}
unsafe fn clone_waker(waker: *const ()) -> RawWaker {
let _source = waker as CFRunLoopSourceRef;
new_raw_waker()
}

impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.sender.clone())
unsafe fn wake(waker: *const ()) {
unsafe { wake_by_ref(waker) };
unsafe { drop_waker(waker) };
}
}

impl<T> Drop for EventLoopProxy<T> {
fn drop(&mut self) {
unsafe fn wake_by_ref(waker: *const ()) {
let source = waker as CFRunLoopSourceRef;
unsafe {
CFRunLoopSourceInvalidate(self.source);
CFRelease(self.source as _);
// let the main thread know there's a new event
CFRunLoopSourceSignal(source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
}
}

impl<T> EventLoopProxy<T> {
fn new(sender: Sender<T>) -> EventLoopProxy<T> {
unsafe {
// just wake up the eventloop
extern "C" fn event_loop_proxy_handler(_: *const c_void) {}
unsafe fn drop_waker(waker: *const ()) {
let source = waker as CFRunLoopSourceRef;
unsafe { CFRunLoopSourceInvalidate(source) };
unsafe { CFRelease(source as _) };
}

// adding a Source to the main CFRunLoop lets us wake it up and
// process user events through the normal OS EventLoop mechanisms.
let rl = CFRunLoopGetMain();
let mut context = CFRunLoopSourceContext {
version: 0,
info: ptr::null_mut(),
retain: None,
release: None,
copyDescription: None,
equal: None,
hash: None,
schedule: None,
cancel: None,
perform: event_loop_proxy_handler,
};
let source =
CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context);
CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes);
CFRunLoopWakeUp(rl);
unsafe { Waker::from_raw(new_raw_waker()) }
}

EventLoopProxy { sender, source }
pub struct EventLoopProxy<T> {
sender: Sender<T>,
waker: Waker,
}

impl<T> Clone for EventLoopProxy<T> {
fn clone(&self) -> Self {
Self {
sender: self.sender.clone(),
waker: self.waker.clone(),
}
}
}

impl<T> EventLoopProxy<T> {
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
self.sender
.send(event)
.map_err(|::std::sync::mpsc::SendError(x)| EventLoopClosed(x))?;
unsafe {
// let the main thread know there's a new event
CFRunLoopSourceSignal(self.source);
let rl = CFRunLoopGetMain();
CFRunLoopWakeUp(rl);
}
.map_err(|mpsc::SendError(x)| EventLoopClosed(x))?;
self.waker.wake_by_ref();
Ok(())
}

pub fn waker(self) -> Waker {
self.waker
}
}

fn setup_control_flow_observers() {
Expand Down

0 comments on commit 28f339f

Please sign in to comment.