From 515595153db46d36137e237ce53735c371f86076 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Wed, 27 Sep 2017 16:31:46 +0200 Subject: [PATCH] Wayland: rework the event loop & expose readiness signal (#298) * wayland: don't create a second event_queue As each EventsLoop has its own context, this is no longer necessary. * wayland: buffer events rather than direct dispatch Changes the behavior of the event loop to first internally buffer the events generated by the wayland handlers, and then dispatch them to the client's closure. - It simplifies the event loop logic - It makes it possible for the user to call window methods such as `set_title()` or `set_inner_size()` without causing a deadlock * wayland: add is_ready() & fix protocol errors Adds a `is_ready()` method to the windows to advertize when it is legal to start drawing, and fix a few wayland protocol mishandling in the process. --- CHANGELOG.md | 2 + src/os/unix.rs | 19 +++ src/platform/linux/mod.rs | 2 +- src/platform/linux/wayland/context.rs | 19 ++- src/platform/linux/wayland/event_loop.rs | 167 ++++++++--------------- src/platform/linux/wayland/window.rs | 62 ++++++--- 6 files changed, 130 insertions(+), 141 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a931f63d..c08efbce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased - Uniformize keyboard scancode values accross Wayland and X11 (#297). +- Internal rework of the wayland event loop +- Added method `os::linux::WindowExt::is_ready` # Version 0.8.1 (2017-09-22) diff --git a/src/os/unix.rs b/src/os/unix.rs index 166126e5..e743e426 100644 --- a/src/os/unix.rs +++ b/src/os/unix.rs @@ -112,6 +112,17 @@ pub trait WindowExt { /// /// The pointer will become invalid when the glutin `Window` is destroyed. fn get_wayland_display(&self) -> Option<*mut libc::c_void>; + + /// Check if the window is ready for drawing + /// + /// On wayland, drawing on a surface before the server has configured + /// it using a special event is illegal. As a result, you should wait + /// until this method returns `true`. + /// + /// Once it starts returning `true`, it can never return `false` again. + /// + /// If the window is X11-based, this will just always return `true`. + fn is_ready(&self) -> bool; } impl WindowExt for Window { @@ -175,6 +186,14 @@ impl WindowExt for Window { _ => None } } + + #[inline] + fn is_ready(&self) -> bool { + match self.window { + LinuxWindow::Wayland(ref w) => w.is_ready(), + LinuxWindow::X(_) => true + } + } } /// Additional methods on `WindowBuilder` that are specific to Unix. diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs index 553cde80..ba147f12 100644 --- a/src/platform/linux/mod.rs +++ b/src/platform/linux/mod.rs @@ -325,7 +325,7 @@ impl EventsLoop { pub fn new_wayland() -> Result { wayland::WaylandContext::init() - .map(|ctx| EventsLoop::Wayland(wayland::EventsLoop::new(Arc::new(ctx)))) + .map(|ctx| EventsLoop::Wayland(wayland::EventsLoop::new(ctx))) .ok_or(()) } diff --git a/src/platform/linux/wayland/context.rs b/src/platform/linux/wayland/context.rs index 1c5ae5d6..ca399fce 100644 --- a/src/platform/linux/wayland/context.rs +++ b/src/platform/linux/wayland/context.rs @@ -114,7 +114,6 @@ impl wl_registry::Handler for WaylandEnv { { if interface == wl_output::WlOutput::interface_name() { // intercept outputs - // this "expect" cannot trigger (see https://github.com/vberger/wayland-client-rs/issues/69) let output = self.registry.bind::(1, name); evqh.register::<_, WaylandEnv>(&output, self.my_id); self.monitors.push(OutputInfo::new(output, name)); @@ -201,7 +200,7 @@ declare_handler!(WaylandEnv, wl_output::Handler, wl_output::WlOutput); pub struct WaylandContext { pub display: wl_display::WlDisplay, - evq: Mutex, + pub evq: Mutex, env_id: usize, } @@ -237,6 +236,14 @@ impl WaylandContext { }) } + pub fn read_events(&self) { + let evq_guard = self.evq.lock().unwrap(); + // read some events from the socket if some are waiting & queue is empty + if let Some(guard) = evq_guard.prepare_read() { + guard.read_events().expect("Wayland connection unexpectedly lost"); + } + } + pub fn dispatch_pending(&self) { let mut guard = self.evq.lock().unwrap(); guard.dispatch_pending().expect("Wayland connection unexpectedly lost"); @@ -293,8 +300,8 @@ impl WaylandContext { evq.register::<_, InitialBufferHandler>(&buffer, initial_buffer_handler_id); } - pub fn create_window(&self, width: u32, height: u32) - -> (Arc, wayland_window::DecoratedSurface) + pub fn create_window(&self, width: u32, height: u32, decorated: bool) + -> (Arc, wayland_window::DecoratedSurface, bool) { let mut guard = self.evq.lock().unwrap(); let (surface, decorated, xdg) = { @@ -308,7 +315,7 @@ impl WaylandContext { &env.inner.shm, env.get_shell(), env.get_seat(), - false + decorated ).expect("Failed to create a tmpfile buffer."); let xdg = match env.get_shell() { &Shell::Xdg(_) => true, @@ -325,7 +332,7 @@ impl WaylandContext { // from the compositor self.blank_surface(&surface, &mut *guard, width as i32, height as i32); } - (surface, decorated) + (surface, decorated, xdg) } } diff --git a/src/platform/linux/wayland/event_loop.rs b/src/platform/linux/wayland/event_loop.rs index 4bea66e1..bab2aeda 100644 --- a/src/platform/linux/wayland/event_loop.rs +++ b/src/platform/linux/wayland/event_loop.rs @@ -26,19 +26,15 @@ use super::keyboard::KbdHandler; /// /// Failure to do so is unsafeā„¢ pub struct EventsLoopSink { - callback: Box + buffer: VecDeque<::Event> } unsafe impl Send for EventsLoopSink { } impl EventsLoopSink { - pub fn new() -> (EventsLoopSink, Arc>>) { - let buffer = Arc::new(Mutex::new(VecDeque::new())); - let buffer_clone = buffer.clone(); - let sink = EventsLoopSink { - callback: Box::new(move |evt| { println!("TEMP: {:?}", evt); buffer.lock().unwrap().push_back(evt)}), - }; - (sink, buffer_clone) + pub fn new() -> EventsLoopSink{EventsLoopSink { + buffer: VecDeque::new() + } } pub fn send_event(&mut self, evt: ::WindowEvent, wid: WindowId) { @@ -46,37 +42,27 @@ impl EventsLoopSink { event: evt, window_id: ::WindowId(::platform::WindowId::Wayland(wid)) }; - (self.callback)(evt) + self.buffer.push_back(evt); } - // This function is only safe of the set callback is unset before exclusive - // access to the wayland EventQueue is finished. - // - // The callback also cannot be used any longer as long as it has not been - // cleared from the Sink. - unsafe fn set_callback(&mut self, cb: Box) -> Box { - ::std::mem::replace(&mut self.callback, cb) + pub fn send_raw_event(&mut self, evt: ::Event) { + self.buffer.push_back(evt); } - fn with_callback(&mut self, f: F) - where F: FnOnce(&mut FnMut(::Event)), - { - f(&mut *self.callback) + fn empty_with(&mut self, callback: &mut F) where F: FnMut(::Event) { + for evt in self.buffer.drain(..) { + callback(evt) + } } } pub struct EventsLoop { - // the global wayland context + // the wayland context ctxt: Arc, - // our EventQueue - evq: Arc>, // ids of the DecoratedHandlers of the surfaces we know decorated_ids: Mutex)>>, - // our sink, receiver of callbacks, shared with some handlers + // our sink, shared with some handlers, buffering the events sink: Arc>, - // a buffer in which events that were dispatched internally are stored - // until the user next dispatches events - buffer: Arc>>, // trigger cleanup of the dead surfaces cleanup_needed: Arc, // Whether or not there is a pending `Awakened` event to be emitted. @@ -114,17 +100,16 @@ impl EventsLoopProxy { } impl EventsLoop { - pub fn new(ctxt: Arc) -> EventsLoop { - let mut evq = ctxt.display.create_event_queue(); - let (sink, buffer) = EventsLoopSink::new(); - let sink = Arc::new(Mutex::new(sink)); - let hid = evq.add_handler_with_init(InputHandler::new(&ctxt, sink.clone())); + pub fn new(mut ctxt: WaylandContext) -> EventsLoop { + let sink = Arc::new(Mutex::new(EventsLoopSink::new())); + let inputh = InputHandler::new(&ctxt, sink.clone()); + let hid = ctxt.evq.get_mut().unwrap().add_handler_with_init(inputh); + let ctxt = Arc::new(ctxt); + EventsLoop { ctxt: ctxt, - evq: Arc::new(Mutex::new(evq)), decorated_ids: Mutex::new(Vec::new()), sink: sink, - buffer: buffer, pending_wakeup: Arc::new(AtomicBool::new(false)), cleanup_needed: Arc::new(AtomicBool::new(false)), hid: hid @@ -144,41 +129,39 @@ impl EventsLoop { } // some internals that Window needs access to - pub fn get_window_init(&self) -> (Arc>, Arc) { - (self.evq.clone(), self.cleanup_needed.clone()) + pub fn get_window_init(&self) -> Arc { + self.cleanup_needed.clone() } pub fn register_window(&self, decorated_id: usize, surface: Arc) { - self.buffer.lock().unwrap().push_back(::Event::WindowEvent { - window_id: ::WindowId(::platform::WindowId::Wayland(make_wid(&surface))), - event: ::WindowEvent::Refresh - }); self.decorated_ids.lock().unwrap().push((decorated_id, surface.clone())); - let mut guard = self.evq.lock().unwrap(); + let mut guard = self.ctxt.evq.lock().unwrap(); let mut state = guard.state(); state.get_mut_handler::(self.hid).windows.push(surface); } - fn process_resize(evq: &mut EventQueue, ids: &[(usize, Arc)], callback: &mut FnMut(::Event)) + fn process_resize(evq: &mut EventQueue, ids: &[(usize, Arc)], sink: &mut EventsLoopSink) { let mut state = evq.state(); for &(decorated_id, ref window) in ids { let decorated = state.get_mut_handler::>(decorated_id); if let Some((w, h)) = decorated.handler().as_mut().and_then(|h| h.take_newsize()) { decorated.resize(w as i32, h as i32); - callback( - ::Event::WindowEvent { - window_id: ::WindowId(::platform::WindowId::Wayland(make_wid(&window))), - event: ::WindowEvent::Resized(w,h) - } + sink.send_event( + ::WindowEvent::Resized(w,h), + make_wid(&window) + ); + } + if decorated.handler().as_mut().map(|h| h.take_refresh()).unwrap_or(false) { + sink.send_event( + ::WindowEvent::Refresh, + make_wid(&window) ); } if decorated.handler().as_ref().map(|h| h.is_closed()).unwrap_or(false) { - callback( - ::Event::WindowEvent { - window_id: ::WindowId(::platform::WindowId::Wayland(make_wid(&window))), - event: ::WindowEvent::Closed - } + sink.send_event( + ::WindowEvent::Closed, + make_wid(&window) ); } @@ -187,7 +170,7 @@ impl EventsLoop { fn prune_dead_windows(&self) { self.decorated_ids.lock().unwrap().retain(|&(_, ref w)| w.status() == Liveness::Alive); - let mut evq_guard = self.evq.lock().unwrap(); + let mut evq_guard = self.ctxt.evq.lock().unwrap(); let mut state = evq_guard.state(); let handler = state.get_mut_handler::(self.hid); handler.windows.retain(|w| w.status() == Liveness::Alive); @@ -198,11 +181,12 @@ impl EventsLoop { } } - fn empty_buffer(&self, callback: &mut F) where F: FnMut(::Event) { - let mut guard = self.buffer.lock().unwrap(); - for evt in guard.drain(..) { - callback(evt) - } + fn post_dispatch_triggers(&self) { + let mut evq_guard = self.ctxt.evq.lock().unwrap(); + let mut sink_guard = self.sink.lock().unwrap(); + let ids_guard = self.decorated_ids.lock().unwrap(); + self.emit_pending_wakeup(&mut sink_guard); + Self::process_resize(&mut evq_guard, &ids_guard, &mut sink_guard); } pub fn poll_events(&mut self, mut callback: F) @@ -211,41 +195,15 @@ impl EventsLoop { // send pending requests to the server... self.ctxt.flush(); - // first of all, get exclusive access to this event queue - let mut evq_guard = self.evq.lock().unwrap(); + // dispatch any pre-buffered events + self.sink.lock().unwrap().empty_with(&mut callback); - // dispatch pre-buffered events - self.empty_buffer(&mut callback); - - // read some events from the socket if some are waiting & queue is empty - if let Some(guard) = evq_guard.prepare_read() { - guard.read_events().expect("Wayland connection unexpectedly lost"); - } - - // set the callback into the sink - // we extend the lifetime of the closure to 'static to be able to put it in - // the sink, but we'll explicitly drop it at the end of this function, so it's fine - let static_cb = unsafe { ::std::mem::transmute(Box::new(callback) as Box) }; - let old_cb = unsafe { self.sink.lock().unwrap().set_callback(static_cb) }; - - // then do the actual dispatching + // try dispatching events without blocking + self.ctxt.read_events(); self.ctxt.dispatch_pending(); - evq_guard.dispatch_pending().expect("Wayland connection unexpectedly lost"); + self.post_dispatch_triggers(); - self.emit_pending_wakeup(); - - { - let mut sink_guard = self.sink.lock().unwrap(); - - // events where probably dispatched, process resize - let ids_guard = self.decorated_ids.lock().unwrap(); - sink_guard.with_callback( - |cb| Self::process_resize(&mut evq_guard, &ids_guard, cb) - ); - - // replace the old noop callback - unsafe { sink_guard.set_callback(old_cb) }; - } + self.sink.lock().unwrap().empty_with(&mut callback); if self.cleanup_needed.swap(false, atomic::Ordering::Relaxed) { self.prune_dead_windows() @@ -258,9 +216,6 @@ impl EventsLoop { // send pending requests to the server... self.ctxt.flush(); - // first of all, get exclusive access to this event queue - let mut evq_guard = self.evq.lock().unwrap(); - // Check for control flow by wrapping the callback. let control_flow = ::std::cell::Cell::new(ControlFlow::Continue); let mut callback = |event| if let ControlFlow::Break = callback(event) { @@ -268,24 +223,15 @@ impl EventsLoop { }; // dispatch pre-buffered events - self.empty_buffer(&mut callback); - - // set the callback into the sink - // we extend the lifetime of the closure to 'static to be able to put it in - // the sink, but we'll explicitly drop it at the end of this function, so it's fine - let static_cb = unsafe { ::std::mem::transmute(Box::new(callback) as Box) }; - let old_cb = unsafe { self.sink.lock().unwrap().set_callback(static_cb) }; + self.sink.lock().unwrap().empty_with(&mut callback); loop { + // dispatch events blocking if needed self.ctxt.dispatch(); - evq_guard.dispatch_pending().expect("Wayland connection unexpectedly lost"); + self.post_dispatch_triggers(); - self.emit_pending_wakeup(); - - let ids_guard = self.decorated_ids.lock().unwrap(); - self.sink.lock().unwrap() - .with_callback(|cb| Self::process_resize(&mut evq_guard, &ids_guard, cb)); - self.ctxt.flush(); + // empty buffer of events + self.sink.lock().unwrap().empty_with(&mut callback); if self.cleanup_needed.swap(false, atomic::Ordering::Relaxed) { self.prune_dead_windows() @@ -295,15 +241,12 @@ impl EventsLoop { break; } } - - // replace the old noop callback - unsafe { self.sink.lock().unwrap().set_callback(old_cb) }; } // If an `EventsLoopProxy` has signalled a wakeup, emit an event and reset the flag. - fn emit_pending_wakeup(&self) { + fn emit_pending_wakeup(&self, sink: &mut EventsLoopSink) { if self.pending_wakeup.load(atomic::Ordering::Relaxed) { - self.sink.lock().unwrap().with_callback(|cb| cb(::Event::Awakened)); + sink.send_raw_event(::Event::Awakened); self.pending_wakeup.store(false, atomic::Ordering::Relaxed); } } diff --git a/src/platform/linux/wayland/window.rs b/src/platform/linux/wayland/window.rs index 0061711e..d57dfec3 100644 --- a/src/platform/linux/wayland/window.rs +++ b/src/platform/linux/wayland/window.rs @@ -1,8 +1,8 @@ use std::sync::{Arc, Mutex}; -use std::sync::atomic::AtomicBool; +use std::sync::atomic::{Ordering, AtomicBool}; use std::cmp; -use wayland_client::{EventQueue, EventQueueHandle, Proxy}; +use wayland_client::{EventQueueHandle, Proxy}; use wayland_client::protocol::{wl_display,wl_surface}; use {CreationError, MouseCursor, CursorState, WindowAttributes}; @@ -18,8 +18,6 @@ use super::wayland_window::DecoratedSurface; pub struct Window { // the global wayland context ctxt: Arc, - // the EventQueue of our EventsLoop - evq: Arc>, // signal to advertize the EventsLoop when we are destroyed cleanup_signal: Arc, // our wayland surface @@ -44,27 +42,31 @@ impl Window { let ctxt = evlp.context().clone(); let (width, height) = attributes.dimensions.unwrap_or((800,600)); - let (surface, decorated) = ctxt.create_window::(width, height); + let (surface, decorated, xdg) = ctxt.create_window::(width, height, attributes.decorations); // init DecoratedSurface - let (evq, cleanup_signal) = evlp.get_window_init(); + let cleanup_signal = evlp.get_window_init(); + + let mut fullscreen_monitor = None; + if let Some(RootMonitorId { inner: PlatformMonitorId::Wayland(ref monitor_id) }) = attributes.fullscreen { + ctxt.with_output(monitor_id.clone(), |output| { + fullscreen_monitor = output.clone(); + }); + } + let decorated_id = { - let mut evq_guard = evq.lock().unwrap(); + let mut evq_guard = ctxt.evq.lock().unwrap(); // store the DecoratedSurface handler let decorated_id = evq_guard.add_handler_with_init(decorated); { let mut state = evq_guard.state(); // initialize the DecoratedHandler let decorated = state.get_mut_handler::>(decorated_id); - *(decorated.handler()) = Some(DecoratedHandler::new()); + *(decorated.handler()) = Some(DecoratedHandler::new(!xdg)); // set fullscreen if necessary - if let Some(RootMonitorId { inner: PlatformMonitorId::Wayland(ref monitor_id) }) = attributes.fullscreen { - ctxt.with_output(monitor_id.clone(), |output| { - decorated.set_fullscreen(Some(output)) - }); - } else if attributes.decorations { - decorated.set_decorate(true); + if let Some(output) = fullscreen_monitor { + decorated.set_fullscreen(Some(&output)); } // Finally, set the decorations size decorated.resize(width as i32, height as i32); @@ -76,7 +78,6 @@ impl Window { }; let me = Window { ctxt: ctxt, - evq: evq, cleanup_signal: cleanup_signal, surface: surface, size: Mutex::new((width, height)), @@ -95,7 +96,7 @@ impl Window { } pub fn set_title(&self, title: &str) { - let mut guard = self.evq.lock().unwrap(); + let mut guard = self.ctxt.evq.lock().unwrap(); let mut state = guard.state(); let decorated = state.get_mut_handler::>(self.decorated_id); decorated.set_title(title.into()) @@ -136,7 +137,7 @@ impl Window { #[inline] // NOTE: This will only resize the borders, the contents must be updated by the user pub fn set_inner_size(&self, x: u32, y: u32) { - let mut guard = self.evq.lock().unwrap(); + let mut guard = self.ctxt.evq.lock().unwrap(); let mut state = guard.state(); let mut decorated = state.get_mut_handler::>(self.decorated_id); decorated.resize(x as i32, y as i32); @@ -213,25 +214,36 @@ impl Window { find } + + pub fn is_ready(&self) -> bool { + let mut guard = self.ctxt.evq.lock().unwrap(); + let mut state = guard.state(); + let mut decorated = state.get_mut_handler::>(self.decorated_id); + decorated.handler().as_ref().unwrap().configured + } } impl Drop for Window { fn drop(&mut self) { self.surface.destroy(); - self.cleanup_signal.store(true, ::std::sync::atomic::Ordering::Relaxed); + self.cleanup_signal.store(true, Ordering::Relaxed); } } pub struct DecoratedHandler { newsize: Option<(u32, u32)>, + refresh: bool, closed: bool, + configured: bool } impl DecoratedHandler { - fn new() -> DecoratedHandler { + fn new(configured: bool) -> DecoratedHandler { DecoratedHandler { newsize: None, + refresh: false, closed: false, + configured: configured } } @@ -239,6 +251,12 @@ impl DecoratedHandler { self.newsize.take() } + pub fn take_refresh(&mut self) -> bool { + let refresh = self.refresh; + self.refresh = false; + refresh + } + pub fn is_closed(&self) -> bool { self.closed } } @@ -248,9 +266,9 @@ impl wayland_window::Handler for DecoratedHandler { _cfg: wayland_window::Configure, newsize: Option<(i32, i32)>) { - if let Some((w, h)) = newsize { - self.newsize = Some((w as u32, h as u32)); - } + self.newsize = newsize.map(|(w, h)| (w as u32, h as u32)); + self.refresh = true; + self.configured = true; } fn close(&mut self, _: &mut EventQueueHandle) {