From ee4528cab24ce619fa8c8c0c859c838f73da211f Mon Sep 17 00:00:00 2001 From: Jonathan Johnson Date: Sun, 12 May 2024 06:29:20 -0700 Subject: [PATCH] Attempt to make resizes repaint in time This introduces a RedrawGuard concept, which allows the event loop to block until the window has been repainted. This hopefully will resolve khonsulabs/cushy#114. --- CHANGELOG.md | 2 ++ src/lib.rs | 16 ++++++++----- src/private.rs | 45 ++++++++++++++++++++++++++++++------ src/window.rs | 63 ++++++++++++++++++++++++++++++++++++-------------- 4 files changed, 96 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f028f..4761f91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - All `&Appplication` bounds now are `?Sized`, enabling `&dyn Application` parameters. +- Redraw requests from `winit` now block the event loop thread until the window + has been repainted. ### Added diff --git a/src/lib.rs b/src/lib.rs index 55622be..3b2ac27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,9 +10,10 @@ mod window; use std::collections::HashMap; use std::process::exit; use std::sync::{mpsc, Arc, Mutex, PoisonError}; +use std::time::Duration; use private::{OpenedWindow, WindowSpawner}; -pub use window::{RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder}; +pub use window::{Run, RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder}; pub use winit; use winit::application::ApplicationHandler; use winit::error::{EventLoopError, OsError}; @@ -149,10 +150,13 @@ where window_id: WindowId, event: winit::event::WindowEvent, ) { - let event = WindowEvent::from(event); + let (event, waiter) = WindowEvent::from_winit(event); self.running .windows .send(window_id, WindowMessage::Event(event)); + if let Some(waiter) = waiter { + waiter.wait(Duration::from_millis(16)); + } } fn user_event(&mut self, event_loop: &ActiveEventLoop, message: EventLoopMessage) { @@ -379,7 +383,7 @@ impl Windows { /// Gets an instance of the winit window for the given window id, if it has /// been opened and is still open. pub fn get(&self, id: WindowId) -> Option> { - let windows = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + let windows = self.data.lock().unwrap_or_else(PoisonError::into_inner); windows.get(&id).and_then(|w| w.winit.winit()) } @@ -441,7 +445,7 @@ impl Windows { let winit = Arc::new(target.create_window(builder)?); let id = winit.id(); let winit = OpenedWindow(Arc::new(Mutex::new(Some(winit)))); - let mut windows = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + let mut windows = self.data.lock().unwrap_or_else(PoisonError::into_inner); windows.insert( id, OpenWindow { @@ -453,7 +457,7 @@ impl Windows { } fn send(&self, window: WindowId, message: WindowMessage) { - let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner); if let Some(open_window) = data.get(&window) { match open_window.sender.try_send(message) { Ok(()) => {} @@ -469,7 +473,7 @@ impl Windows { } fn close(&self, window: WindowId) -> bool { - let mut data = self.data.lock().map_or_else(PoisonError::into_inner, |g| g); + let mut data = self.data.lock().unwrap_or_else(PoisonError::into_inner); data.remove(&window); data.is_empty() } diff --git a/src/private.rs b/src/private.rs index 0b41a42..47fe587 100644 --- a/src/private.rs +++ b/src/private.rs @@ -1,5 +1,6 @@ use std::path::PathBuf; use std::sync::{mpsc, Arc, Mutex, PoisonError}; +use std::time::Duration; use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::error::OsError; @@ -34,7 +35,7 @@ impl OpenedWindow { pub fn winit(&self) -> Option> { self.0 .lock() - .map_or_else(PoisonError::into_inner, |g| g) + .unwrap_or_else(PoisonError::into_inner) .clone() } } @@ -63,9 +64,33 @@ pub enum WindowMessage { Event(WindowEvent), } +#[derive(Debug)] +pub struct RedrawGuard(mpsc::SyncSender<()>); + +impl Drop for RedrawGuard { + fn drop(&mut self) { + let _ignored = self.0.send(()); + } +} + +pub struct WaitForRedraw(mpsc::Receiver<()>); + +impl WaitForRedraw { + pub fn wait(self, timeout: Duration) { + let _result = self.0.recv_timeout(timeout); + } +} + +impl RedrawGuard { + pub fn new() -> (Self, WaitForRedraw) { + let (sender, receiver) = mpsc::sync_channel(1); + (Self(sender), WaitForRedraw(receiver)) + } +} + #[derive(Debug)] pub enum WindowEvent { - RedrawRequested, + RedrawRequested(RedrawGuard), /// The size of the window has changed. Contains the client area's new dimensions. Resized(PhysicalSize), @@ -262,11 +287,15 @@ pub enum WindowEvent { }, } -impl From for WindowEvent { +impl WindowEvent { #[allow(clippy::too_many_lines)] // it's a match statement - fn from(event: winit::event::WindowEvent) -> Self { - match event { - winit::event::WindowEvent::RedrawRequested => Self::RedrawRequested, + pub fn from_winit(event: winit::event::WindowEvent) -> (Self, Option) { + ( + match event { + winit::event::WindowEvent::RedrawRequested => { + let (guard, wait) = RedrawGuard::new(); + return (Self::RedrawRequested(guard), Some(wait)) + }, winit::event::WindowEvent::Resized(size) => Self::Resized(size), winit::event::WindowEvent::Moved(pos) => Self::Moved(pos), winit::event::WindowEvent::CloseRequested => Self::CloseRequested, @@ -381,6 +410,8 @@ impl From for WindowEvent { winit::event::WindowEvent::ActivationTokenDone { serial, token } => { Self::ActivationTokenDone { serial, token } } - } + }, + None, + ) } } diff --git a/src/window.rs b/src/window.rs index 3139afb..89ce8ae 100644 --- a/src/window.rs +++ b/src/window.rs @@ -15,7 +15,7 @@ use winit::event::{ use winit::keyboard::PhysicalKey; use winit::window::{Fullscreen, Icon, Theme, WindowButtons, WindowId, WindowLevel}; -use crate::private::{self, OpenedWindow, WindowEvent, WindowSpawner}; +use crate::private::{self, OpenedWindow, RedrawGuard, WindowEvent, WindowSpawner}; use crate::{ App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows, }; @@ -34,7 +34,7 @@ impl Window { self.opened .0 .lock() - .map_or_else(PoisonError::into_inner, |g| g) + .unwrap_or_else(PoisonError::into_inner) .as_ref() .map(|w| w.id()) } @@ -264,6 +264,12 @@ where type SyncArcChannel = (Arc>, mpsc::Receiver); type SyncChannel = (mpsc::SyncSender, mpsc::Receiver); +enum HandleMessageResult { + Ok, + RedrawRequired(RedrawGuard), + Destroyed, +} + /// A window that is running in its own thread. pub struct RunningWindow where @@ -442,9 +448,15 @@ where // the entire app panics or not. let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || { let mut behavior = Behavior::initialize(&mut self, context); - while !self.close && self.process_messages_until_redraw(&mut behavior) { - self.next_redraw_target = None; - behavior.redraw(&mut self); + while !self.close { + match self.process_messages_until_redraw(&mut behavior) { + Ok(guard) => { + self.next_redraw_target = None; + behavior.redraw(&mut self); + drop(guard); + } + Err(()) => break, + } } // Do not notify the main thread to close the window until after the // behavior is dropped. This upholds the requirement for RawWindowHandle @@ -460,7 +472,10 @@ where } } - fn process_messages_until_redraw(&mut self, behavior: &mut Behavior) -> bool + fn process_messages_until_redraw( + &mut self, + behavior: &mut Behavior, + ) -> Result, ()> where Behavior: self::WindowBehavior, { @@ -471,27 +486,29 @@ where // block. TimeUntilRedraw::None => match self.messages.1.try_recv() { Ok(message) => message, - Err(mpsc::TryRecvError::Disconnected) => return false, - Err(mpsc::TryRecvError::Empty) => return true, + Err(mpsc::TryRecvError::Disconnected) => return Err(()), + Err(mpsc::TryRecvError::Empty) => return Ok(None), }, // We have a scheduled time for the next frame, and it hasn't // elapsed yet. TimeUntilRedraw::Some(duration_remaining) => { match self.messages.1.recv_timeout(duration_remaining) { Ok(message) => message, - Err(mpsc::RecvTimeoutError::Timeout) => return true, - Err(mpsc::RecvTimeoutError::Disconnected) => return false, + Err(mpsc::RecvTimeoutError::Timeout) => return Ok(None), + Err(mpsc::RecvTimeoutError::Disconnected) => return Err(()), } } // No scheduled redraw time, sleep until the next message. TimeUntilRedraw::Indefinite => match self.messages.1.recv() { Ok(message) => message, - Err(_) => return false, + Err(_) => return Err(()), }, }; - if !self.handle_message(message, behavior) { - break false; + match self.handle_message(message, behavior) { + HandleMessageResult::Ok => {} + HandleMessageResult::RedrawRequired(guard) => return Ok(Some(guard)), + HandleMessageResult::Destroyed => return Err(()), } } } @@ -501,15 +518,16 @@ where &mut self, message: WindowMessage, behavior: &mut Behavior, - ) -> bool + ) -> HandleMessageResult where Behavior: self::WindowBehavior, { match message { WindowMessage::User(user) => behavior.event(self, user), WindowMessage::Event(evt) => match evt { - WindowEvent::RedrawRequested => { + WindowEvent::RedrawRequested(guard) => { self.set_needs_redraw(); + return HandleMessageResult::RedrawRequired(guard); } WindowEvent::CloseRequested => { if behavior.close_requested(self) { @@ -546,7 +564,7 @@ where self.position = position; } WindowEvent::Destroyed => { - return false; + return HandleMessageResult::Destroyed; } WindowEvent::ThemeChanged(theme) => { self.theme = theme; @@ -667,7 +685,7 @@ where }, } - true + HandleMessageResult::Ok } /// Sets this window to close as soon as possible. @@ -1090,12 +1108,18 @@ where fn event(&mut self, window: &mut RunningWindow, event: AppMessage::Window) {} } +/// A runnable window. pub trait Run: WindowBehavior<()> { /// Runs a window with a default instance of this behavior's /// [`Context`](Self::Context). /// /// This function is shorthand for creating a [`PendingApp`], opening this /// window inside of it, and running the pending app. + /// + /// # Errors + /// + /// Returns an [`EventLoopError`] upon the loop exiting due to an error. See + /// [`EventLoop::run`] for more information. fn run() -> Result<(), EventLoopError> where Self::Context: Default, @@ -1109,6 +1133,11 @@ pub trait Run: WindowBehavior<()> { /// /// This function is shorthand for creating a [`PendingApp`], opening this /// window inside of it, and running the pending app. + /// + /// # Errors + /// + /// Returns an [`EventLoopError`] upon the loop exiting due to an error. See + /// [`EventLoop::run`] for more information. fn run_with(context: Self::Context) -> Result<(), EventLoopError> { let mut app = PendingApp::new(); Self::open_with(&mut app, context).expect("error opening initial window");