On Wayland, reduce amount of spurious wakeups

Mark it as breaking, since some clients relied on that behavior, simply
because dispatching clients queue always woke up a winit, meaning that
they won't be able to use user events for this sake.
This commit is contained in:
Kirill Chibisov 2023-08-06 01:09:59 +04:00 committed by GitHub
parent 3c3a863cc9
commit cad3277550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 155 additions and 95 deletions

View file

@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased # Unreleased
- **Breaking:** on Wayland, dispatching user created wayland queue won't wake up the loop unless winit has event to send back.
- Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead: - Removed platform-specific extensions that should be retrieved through `raw-window-handle` trait implementations instead:
- `platform::windows::HINSTANCE`. - `platform::windows::HINSTANCE`.
- `WindowExtWindows::hinstance`. - `WindowExtWindows::hinstance`.

View file

@ -90,8 +90,15 @@ impl<T: 'static> EventLoop<T> {
// Register Wayland source. // Register Wayland source.
let wayland_source = WaylandSource::new(event_queue)?; let wayland_source = WaylandSource::new(event_queue)?;
let wayland_dispatcher = let wayland_dispatcher =
calloop::Dispatcher::new(wayland_source, |_, queue, winit_state| { calloop::Dispatcher::new(wayland_source, |_, queue, winit_state: &mut WinitState| {
queue.dispatch_pending(winit_state) let result = queue.dispatch_pending(winit_state);
if result.is_ok()
&& (!winit_state.events_sink.is_empty()
|| !winit_state.window_compositor_updates.is_empty())
{
winit_state.dispatched_events = true;
}
result
}); });
event_loop event_loop
@ -102,21 +109,25 @@ impl<T: 'static> EventLoop<T> {
let pending_user_events = Rc::new(RefCell::new(Vec::new())); let pending_user_events = Rc::new(RefCell::new(Vec::new()));
let pending_user_events_clone = pending_user_events.clone(); let pending_user_events_clone = pending_user_events.clone();
let (user_events_sender, user_events_channel) = calloop::channel::channel(); let (user_events_sender, user_events_channel) = calloop::channel::channel();
event_loop event_loop.handle().insert_source(
.handle() user_events_channel,
.insert_source(user_events_channel, move |event, _, _| { move |event, _, winit_state: &mut WinitState| {
if let calloop::channel::Event::Msg(msg) = event { if let calloop::channel::Event::Msg(msg) = event {
winit_state.dispatched_events = true;
pending_user_events_clone.borrow_mut().push(msg); pending_user_events_clone.borrow_mut().push(msg);
} }
})?; },
)?;
// An event's loop awakener to wake up for window events from winit's windows. // An event's loop awakener to wake up for window events from winit's windows.
let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?; let (event_loop_awakener, event_loop_awakener_source) = calloop::ping::make_ping()?;
event_loop event_loop.handle().insert_source(
.handle() event_loop_awakener_source,
.insert_source(event_loop_awakener_source, move |_, _, _| { move |_, _, winit_state: &mut WinitState| {
// No extra handling is required, we just need to wake-up. // No extra handling is required, we just need to wake-up.
})?; winit_state.dispatched_events = true;
},
)?;
let window_target = EventLoopWindowTarget { let window_target = EventLoopWindowTarget {
connection: connection.clone(), connection: connection.clone(),
@ -220,6 +231,7 @@ impl<T: 'static> EventLoop<T> {
where where
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{ {
let cause = loop {
let start = Instant::now(); let start = Instant::now();
// TODO(rib): remove this workaround and instead make sure that the calloop // TODO(rib): remove this workaround and instead make sure that the calloop
@ -246,7 +258,11 @@ impl<T: 'static> EventLoop<T> {
}; };
match queue.dispatch_pending(state) { match queue.dispatch_pending(state) {
Ok(dispatched) => dispatched > 0, Ok(dispatched) => {
state.dispatched_events |= !state.events_sink.is_empty()
|| !state.window_compositor_updates.is_empty();
dispatched > 0
}
Err(error) => { Err(error) => {
error!("Error dispatching wayland queue: {}", error); error!("Error dispatching wayland queue: {}", error);
self.control_flow = ControlFlow::ExitWithCode(1); self.control_flow = ControlFlow::ExitWithCode(1);
@ -318,6 +334,15 @@ impl<T: 'static> EventLoop<T> {
ControlFlow::ExitWithCode(_code) => unreachable!(), ControlFlow::ExitWithCode(_code) => unreachable!(),
}; };
// Reduce spurious wake-ups.
let dispatched_events = self.with_state(|state| state.dispatched_events);
if matches!(cause, StartCause::WaitCancelled { .. }) && !dispatched_events {
continue;
}
break cause;
};
self.single_iteration(&mut callback, cause); self.single_iteration(&mut callback, cause);
} }
@ -531,6 +556,11 @@ impl<T: 'static> EventLoop<T> {
} }
} }
// Reset the hint that we've dispatched events.
self.with_state(|state| {
state.dispatched_events = false;
});
// This is always the last event we dispatch before poll again // This is always the last event we dispatch before poll again
sticky_exit_callback( sticky_exit_callback(
Event::AboutToWait, Event::AboutToWait,

View file

@ -20,6 +20,12 @@ impl EventSink {
Default::default() Default::default()
} }
/// Return `true` if there're pending events.
#[inline]
pub fn is_empty(&self) -> bool {
self.window_events.is_empty()
}
/// Add new device event to a queue. /// Add new device event to a queue.
#[inline] #[inline]
pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) { pub fn push_device_event(&mut self, event: DeviceEvent, device_id: DeviceId) {

View file

@ -1,5 +1,6 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::error::Error; use std::error::Error;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use fnv::FnvHashMap; use fnv::FnvHashMap;
@ -104,6 +105,10 @@ pub struct WinitState {
/// Loop handle to re-register event sources, such as keyboard repeat. /// Loop handle to re-register event sources, such as keyboard repeat.
pub loop_handle: LoopHandle<'static, Self>, pub loop_handle: LoopHandle<'static, Self>,
/// Whether we have dispatched events to the user thus we want to
/// send `AboutToWait` and normally wakeup the user.
pub dispatched_events: bool,
} }
impl WinitState { impl WinitState {
@ -167,6 +172,8 @@ impl WinitState {
monitors: Arc::new(Mutex::new(monitors)), monitors: Arc::new(Mutex::new(monitors)),
events_sink: EventSink::new(), events_sink: EventSink::new(),
loop_handle, loop_handle,
// Make it true by default.
dispatched_events: true,
}) })
} }
@ -328,6 +335,18 @@ impl CompositorHandler for WinitState {
None => return, None => return,
}; };
// In case we have a redraw requested we must indicate the wake up.
if self
.window_requests
.get_mut()
.get(&window_id)
.unwrap()
.redraw_requested
.load(Ordering::Relaxed)
{
self.dispatched_events = true;
}
window.lock().unwrap().frame_callback_received(); window.lock().unwrap().frame_callback_received();
} }
} }

View file

@ -287,11 +287,15 @@ impl Window {
#[inline] #[inline]
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
self.window_requests if self
.window_requests
.redraw_requested .redraw_requested
.store(true, Ordering::Relaxed); .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
.is_ok()
{
self.event_loop_awakener.ping(); self.event_loop_awakener.ping();
} }
}
#[inline] #[inline]
pub fn pre_present_notify(&self) { pub fn pre_present_notify(&self) {