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.
This commit is contained in:
Jonathan Johnson 2024-05-12 06:29:20 -07:00
parent 36b8c1fc8b
commit ee4528cab2
No known key found for this signature in database
GPG key ID: A66D6A34D6620579
4 changed files with 96 additions and 30 deletions

View file

@ -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` - All `&Appplication` bounds now are `?Sized`, enabling `&dyn Application`
parameters. parameters.
- Redraw requests from `winit` now block the event loop thread until the window
has been repainted.
### Added ### Added

View file

@ -10,9 +10,10 @@ mod window;
use std::collections::HashMap; use std::collections::HashMap;
use std::process::exit; use std::process::exit;
use std::sync::{mpsc, Arc, Mutex, PoisonError}; use std::sync::{mpsc, Arc, Mutex, PoisonError};
use std::time::Duration;
use private::{OpenedWindow, WindowSpawner}; use private::{OpenedWindow, WindowSpawner};
pub use window::{RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder}; pub use window::{Run, RunningWindow, Window, WindowAttributes, WindowBehavior, WindowBuilder};
pub use winit; pub use winit;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
use winit::error::{EventLoopError, OsError}; use winit::error::{EventLoopError, OsError};
@ -149,10 +150,13 @@ where
window_id: WindowId, window_id: WindowId,
event: winit::event::WindowEvent, event: winit::event::WindowEvent,
) { ) {
let event = WindowEvent::from(event); let (event, waiter) = WindowEvent::from_winit(event);
self.running self.running
.windows .windows
.send(window_id, WindowMessage::Event(event)); .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<AppMessage>) { fn user_event(&mut self, event_loop: &ActiveEventLoop, message: EventLoopMessage<AppMessage>) {
@ -379,7 +383,7 @@ impl<Message> Windows<Message> {
/// Gets an instance of the winit window for the given window id, if it has /// Gets an instance of the winit window for the given window id, if it has
/// been opened and is still open. /// been opened and is still open.
pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> { pub fn get(&self, id: WindowId) -> Option<Arc<winit::window::Window>> {
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()) windows.get(&id).and_then(|w| w.winit.winit())
} }
@ -441,7 +445,7 @@ impl<Message> Windows<Message> {
let winit = Arc::new(target.create_window(builder)?); let winit = Arc::new(target.create_window(builder)?);
let id = winit.id(); let id = winit.id();
let winit = OpenedWindow(Arc::new(Mutex::new(Some(winit)))); 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( windows.insert(
id, id,
OpenWindow { OpenWindow {
@ -453,7 +457,7 @@ impl<Message> Windows<Message> {
} }
fn send(&self, window: WindowId, message: WindowMessage<Message>) { fn send(&self, window: WindowId, message: WindowMessage<Message>) {
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) { if let Some(open_window) = data.get(&window) {
match open_window.sender.try_send(message) { match open_window.sender.try_send(message) {
Ok(()) => {} Ok(()) => {}
@ -469,7 +473,7 @@ impl<Message> Windows<Message> {
} }
fn close(&self, window: WindowId) -> bool { 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.remove(&window);
data.is_empty() data.is_empty()
} }

View file

@ -1,5 +1,6 @@
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::{mpsc, Arc, Mutex, PoisonError}; use std::sync::{mpsc, Arc, Mutex, PoisonError};
use std::time::Duration;
use winit::dpi::{PhysicalPosition, PhysicalSize}; use winit::dpi::{PhysicalPosition, PhysicalSize};
use winit::error::OsError; use winit::error::OsError;
@ -34,7 +35,7 @@ impl OpenedWindow {
pub fn winit(&self) -> Option<Arc<winit::window::Window>> { pub fn winit(&self) -> Option<Arc<winit::window::Window>> {
self.0 self.0
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .unwrap_or_else(PoisonError::into_inner)
.clone() .clone()
} }
} }
@ -63,9 +64,33 @@ pub enum WindowMessage<User> {
Event(WindowEvent), 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)] #[derive(Debug)]
pub enum WindowEvent { pub enum WindowEvent {
RedrawRequested, RedrawRequested(RedrawGuard),
/// The size of the window has changed. Contains the client area's new dimensions. /// The size of the window has changed. Contains the client area's new dimensions.
Resized(PhysicalSize<u32>), Resized(PhysicalSize<u32>),
@ -262,11 +287,15 @@ pub enum WindowEvent {
}, },
} }
impl From<winit::event::WindowEvent> for WindowEvent { impl WindowEvent {
#[allow(clippy::too_many_lines)] // it's a match statement #[allow(clippy::too_many_lines)] // it's a match statement
fn from(event: winit::event::WindowEvent) -> Self { pub fn from_winit(event: winit::event::WindowEvent) -> (Self, Option<WaitForRedraw>) {
match event { (
winit::event::WindowEvent::RedrawRequested => Self::RedrawRequested, 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::Resized(size) => Self::Resized(size),
winit::event::WindowEvent::Moved(pos) => Self::Moved(pos), winit::event::WindowEvent::Moved(pos) => Self::Moved(pos),
winit::event::WindowEvent::CloseRequested => Self::CloseRequested, winit::event::WindowEvent::CloseRequested => Self::CloseRequested,
@ -381,6 +410,8 @@ impl From<winit::event::WindowEvent> for WindowEvent {
winit::event::WindowEvent::ActivationTokenDone { serial, token } => { winit::event::WindowEvent::ActivationTokenDone { serial, token } => {
Self::ActivationTokenDone { serial, token } Self::ActivationTokenDone { serial, token }
} }
} },
None,
)
} }
} }

View file

@ -15,7 +15,7 @@ use winit::event::{
use winit::keyboard::PhysicalKey; use winit::keyboard::PhysicalKey;
use winit::window::{Fullscreen, Icon, Theme, WindowButtons, WindowId, WindowLevel}; 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::{ use crate::{
App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows, App, Application, AsApplication, EventLoopMessage, Message, PendingApp, WindowMessage, Windows,
}; };
@ -34,7 +34,7 @@ impl<Message> Window<Message> {
self.opened self.opened
.0 .0
.lock() .lock()
.map_or_else(PoisonError::into_inner, |g| g) .unwrap_or_else(PoisonError::into_inner)
.as_ref() .as_ref()
.map(|w| w.id()) .map(|w| w.id())
} }
@ -264,6 +264,12 @@ where
type SyncArcChannel<T> = (Arc<mpsc::SyncSender<T>>, mpsc::Receiver<T>); type SyncArcChannel<T> = (Arc<mpsc::SyncSender<T>>, mpsc::Receiver<T>);
type SyncChannel<T> = (mpsc::SyncSender<T>, mpsc::Receiver<T>); type SyncChannel<T> = (mpsc::SyncSender<T>, mpsc::Receiver<T>);
enum HandleMessageResult {
Ok,
RedrawRequired(RedrawGuard),
Destroyed,
}
/// A window that is running in its own thread. /// A window that is running in its own thread.
pub struct RunningWindow<AppMessage> pub struct RunningWindow<AppMessage>
where where
@ -442,9 +448,15 @@ where
// the entire app panics or not. // the entire app panics or not.
let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || { let possible_panic = std::panic::catch_unwind(AssertUnwindSafe(move || {
let mut behavior = Behavior::initialize(&mut self, context); let mut behavior = Behavior::initialize(&mut self, context);
while !self.close && self.process_messages_until_redraw(&mut behavior) { while !self.close {
self.next_redraw_target = None; match self.process_messages_until_redraw(&mut behavior) {
behavior.redraw(&mut self); 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 // Do not notify the main thread to close the window until after the
// behavior is dropped. This upholds the requirement for RawWindowHandle // behavior is dropped. This upholds the requirement for RawWindowHandle
@ -460,7 +472,10 @@ where
} }
} }
fn process_messages_until_redraw<Behavior>(&mut self, behavior: &mut Behavior) -> bool fn process_messages_until_redraw<Behavior>(
&mut self,
behavior: &mut Behavior,
) -> Result<Option<RedrawGuard>, ()>
where where
Behavior: self::WindowBehavior<AppMessage>, Behavior: self::WindowBehavior<AppMessage>,
{ {
@ -471,27 +486,29 @@ where
// block. // block.
TimeUntilRedraw::None => match self.messages.1.try_recv() { TimeUntilRedraw::None => match self.messages.1.try_recv() {
Ok(message) => message, Ok(message) => message,
Err(mpsc::TryRecvError::Disconnected) => return false, Err(mpsc::TryRecvError::Disconnected) => return Err(()),
Err(mpsc::TryRecvError::Empty) => return true, Err(mpsc::TryRecvError::Empty) => return Ok(None),
}, },
// We have a scheduled time for the next frame, and it hasn't // We have a scheduled time for the next frame, and it hasn't
// elapsed yet. // elapsed yet.
TimeUntilRedraw::Some(duration_remaining) => { TimeUntilRedraw::Some(duration_remaining) => {
match self.messages.1.recv_timeout(duration_remaining) { match self.messages.1.recv_timeout(duration_remaining) {
Ok(message) => message, Ok(message) => message,
Err(mpsc::RecvTimeoutError::Timeout) => return true, Err(mpsc::RecvTimeoutError::Timeout) => return Ok(None),
Err(mpsc::RecvTimeoutError::Disconnected) => return false, Err(mpsc::RecvTimeoutError::Disconnected) => return Err(()),
} }
} }
// No scheduled redraw time, sleep until the next message. // No scheduled redraw time, sleep until the next message.
TimeUntilRedraw::Indefinite => match self.messages.1.recv() { TimeUntilRedraw::Indefinite => match self.messages.1.recv() {
Ok(message) => message, Ok(message) => message,
Err(_) => return false, Err(_) => return Err(()),
}, },
}; };
if !self.handle_message(message, behavior) { match self.handle_message(message, behavior) {
break false; HandleMessageResult::Ok => {}
HandleMessageResult::RedrawRequired(guard) => return Ok(Some(guard)),
HandleMessageResult::Destroyed => return Err(()),
} }
} }
} }
@ -501,15 +518,16 @@ where
&mut self, &mut self,
message: WindowMessage<AppMessage::Window>, message: WindowMessage<AppMessage::Window>,
behavior: &mut Behavior, behavior: &mut Behavior,
) -> bool ) -> HandleMessageResult
where where
Behavior: self::WindowBehavior<AppMessage>, Behavior: self::WindowBehavior<AppMessage>,
{ {
match message { match message {
WindowMessage::User(user) => behavior.event(self, user), WindowMessage::User(user) => behavior.event(self, user),
WindowMessage::Event(evt) => match evt { WindowMessage::Event(evt) => match evt {
WindowEvent::RedrawRequested => { WindowEvent::RedrawRequested(guard) => {
self.set_needs_redraw(); self.set_needs_redraw();
return HandleMessageResult::RedrawRequired(guard);
} }
WindowEvent::CloseRequested => { WindowEvent::CloseRequested => {
if behavior.close_requested(self) { if behavior.close_requested(self) {
@ -546,7 +564,7 @@ where
self.position = position; self.position = position;
} }
WindowEvent::Destroyed => { WindowEvent::Destroyed => {
return false; return HandleMessageResult::Destroyed;
} }
WindowEvent::ThemeChanged(theme) => { WindowEvent::ThemeChanged(theme) => {
self.theme = theme; self.theme = theme;
@ -667,7 +685,7 @@ where
}, },
} }
true HandleMessageResult::Ok
} }
/// Sets this window to close as soon as possible. /// Sets this window to close as soon as possible.
@ -1090,12 +1108,18 @@ where
fn event(&mut self, window: &mut RunningWindow<AppMessage>, event: AppMessage::Window) {} fn event(&mut self, window: &mut RunningWindow<AppMessage>, event: AppMessage::Window) {}
} }
/// A runnable window.
pub trait Run: WindowBehavior<()> { pub trait Run: WindowBehavior<()> {
/// Runs a window with a default instance of this behavior's /// Runs a window with a default instance of this behavior's
/// [`Context`](Self::Context). /// [`Context`](Self::Context).
/// ///
/// This function is shorthand for creating a [`PendingApp`], opening this /// This function is shorthand for creating a [`PendingApp`], opening this
/// window inside of it, and running the pending app. /// 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> fn run() -> Result<(), EventLoopError>
where where
Self::Context: Default, Self::Context: Default,
@ -1109,6 +1133,11 @@ pub trait Run: WindowBehavior<()> {
/// ///
/// This function is shorthand for creating a [`PendingApp`], opening this /// This function is shorthand for creating a [`PendingApp`], opening this
/// window inside of it, and running the pending app. /// 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> { fn run_with(context: Self::Context) -> Result<(), EventLoopError> {
let mut app = PendingApp::new(); let mut app = PendingApp::new();
Self::open_with(&mut app, context).expect("error opening initial window"); Self::open_with(&mut app, context).expect("error opening initial window");