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`
parameters.
- Redraw requests from `winit` now block the event loop thread until the window
has been repainted.
### Added

View file

@ -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<AppMessage>) {
@ -379,7 +383,7 @@ impl<Message> Windows<Message> {
/// 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<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())
}
@ -441,7 +445,7 @@ impl<Message> Windows<Message> {
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<Message> Windows<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) {
match open_window.sender.try_send(message) {
Ok(()) => {}
@ -469,7 +473,7 @@ impl<Message> Windows<Message> {
}
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()
}

View file

@ -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<Arc<winit::window::Window>> {
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<User> {
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<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
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<WaitForRedraw>) {
(
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<winit::event::WindowEvent> for WindowEvent {
winit::event::WindowEvent::ActivationTokenDone { serial, token } => {
Self::ActivationTokenDone { serial, token }
}
}
},
None,
)
}
}

View file

@ -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<Message> Window<Message> {
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<T> = (Arc<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.
pub struct RunningWindow<AppMessage>
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<Behavior>(&mut self, behavior: &mut Behavior) -> bool
fn process_messages_until_redraw<Behavior>(
&mut self,
behavior: &mut Behavior,
) -> Result<Option<RedrawGuard>, ()>
where
Behavior: self::WindowBehavior<AppMessage>,
{
@ -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<AppMessage::Window>,
behavior: &mut Behavior,
) -> bool
) -> HandleMessageResult
where
Behavior: self::WindowBehavior<AppMessage>,
{
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<AppMessage>, 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");