Skip to content

Commit

Permalink
Remove RedrawEventsCleared + MainEventsCleared, and added AboutToWait
Browse files Browse the repository at this point in the history
The idea that redraw events are dispatched with a specific ordering
that makes it possible to specifically report when we have finished
dispatching redraw events isn't portable and the way in which we
dispatched RedrawEventsCleared was inconsistent across backends.

More generally speaking, there is no inherent relationship between
redrawing and event loop iterations. An event loop may wake up at any
frequency depending on what sources of input events are being listened
to but redrawing is generally throttled and in some way synchronized
with the display frequency.

Similarly there's no inherent relationship between a single event loop
iteration and the dispatching of any specific kind of "main" event.

An event loop wakes up when there are events to read (e.g. input
events or responses from a display server / compositor) and goes back
to waiting when there's nothing else to read.

There isn't really a special kind of "main" event that is dispatched
in order with respect to other events.

What we can do more portably is emit an event when the event loop
is about to block and wait for new events.

In practice this is very similar to how MainEventsCleared was
implemented except it wasn't the very last event previously since
redraw events could be dispatched afterwards.

The main backend where we don't strictly know when we're going to
wait for events is Web (since the real event loop is internal to
the browser). For now we emulate AboutToWait on Web similar to how
MainEventsCleared was dispatched.

In practice most applications almost certainly shouldn't care about
AboutToWait because the frequency of event loop iterations is
essentially arbitrary and usually irrelevant.
  • Loading branch information
rib authored Jul 28, 2023
1 parent 935146d commit ae7497e
Show file tree
Hide file tree
Showing 24 changed files with 76 additions and 138 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ And please only add new entries to the top of this list, right below the `# Unre
- `RedrawRequested` is no longer guaranteed to be emitted after `MainEventsCleared`, it is now platform-specific when the event is emitted after being requested via `redraw_request()`.
- On Windows, `RedrawRequested` is now driven by `WM_PAINT` messages which are requested via `redraw_request()`
- **Breaking** `LoopDestroyed` renamed to `LoopExiting` ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `RedrawEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- **Breaking** `MainEventsCleared` removed ([#2900](https://github.com/rust-windowing/winit/issues/2900))
- Added `AboutToWait` event which is emitted when the event loop is about to block and wait for new events ([#2900](https://github.com/rust-windowing/winit/issues/2900))

# 0.29.0-beta.0

Expand Down
18 changes: 9 additions & 9 deletions examples/control_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,18 +95,11 @@ fn main() -> Result<(), impl std::error::Error> {
},
_ => (),
},
Event::MainEventsCleared => {
Event::AboutToWait => {
if request_redraw && !wait_cancelled && !close_requested {
window.request_redraw();
}
if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
Event::RedrawEventsCleared => {

match mode {
Mode::Wait => control_flow.set_wait(),
Mode::WaitUntil => {
Expand All @@ -119,6 +112,13 @@ fn main() -> Result<(), impl std::error::Error> {
control_flow.set_poll();
}
};

if close_requested {
control_flow.set_exit();
}
}
Event::RedrawRequested(_window_id) => {
fill::fill_window(&window);
}
_ => (),
}
Expand Down
2 changes: 1 addition & 1 deletion examples/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn main() -> Result<(), impl std::error::Error> {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::WindowEvent {
Expand Down
2 changes: 1 addition & 1 deletion examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fn main() -> Result<(), impl std::error::Error> {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/window_ondemand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fn main() -> Result<(), impl std::error::Error> {
println!("--------------------------------------------------------- Window {idx} CloseRequested");
app.window = None;
}
Event::MainEventsCleared => window.request_redraw(),
Event::AboutToWait => window.request_redraw(),
Event::RedrawRequested(_) => {
fill::fill_window(window);
}
Expand Down
2 changes: 1 addition & 1 deletion examples/window_option_as_alt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ fn main() -> Result<(), impl std::error::Error> {
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
_ => (),
},
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/window_pump_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn main() -> std::process::ExitCode {
event: WindowEvent::CloseRequested,
window_id,
} if window_id == window.id() => control_flow.set_exit(),
Event::MainEventsCleared => {
Event::AboutToWait => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
Expand Down
2 changes: 1 addition & 1 deletion examples/window_resize_increments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fn main() -> Result<(), impl std::error::Error> {
debug!("Had increments: {}", new_increments.is_none());
window.set_resize_increments(new_increments);
}
Event::MainEventsCleared => window.request_redraw(),
Event::AboutToWait => window.request_redraw(),
Event::RedrawRequested(_) => {
fill::fill_window(&window);
}
Expand Down
63 changes: 18 additions & 45 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
//! for e in (window events, user events, device events) {
//! event_handler(e, ..., &mut control_flow);
//! }
//! event_handler(MainEventsCleared, ..., &mut control_flow);
//!
//! for w in (redraw windows) {
//! event_handler(RedrawRequested(w), ..., &mut control_flow);
//! }
//! event_handler(RedrawEventsCleared, ..., &mut control_flow);
//!
//! event_handler(AboutToWait, ..., &mut control_flow);
//! start_cause = wait_if_necessary(control_flow);
//! }
//!
Expand Down Expand Up @@ -207,53 +206,30 @@ pub enum Event<'a, T: 'static> {
/// [`Suspended`]: Self::Suspended
Resumed,

/// Emitted when all of the event loop's input events have been processed and redraw processing
/// is about to begin.
/// Emitted when the event loop is about to block and wait for new events.
///
/// This event is useful as a place to put your code that should be run after all
/// state-changing events have been handled and you want to do stuff (updating state, performing
/// calculations, etc) that happens as the "main body" of your event loop. If your program only draws
/// graphics when something changes, it's usually better to do it in response to
/// [`Event::RedrawRequested`](crate::event::Event::RedrawRequested), which gets emitted
/// immediately after this event. Programs that draw graphics continuously, like most games,
/// can render here unconditionally for simplicity.
MainEventsCleared,
/// Most applications shouldn't need to hook into this event since there is no real relationship
/// between how often the event loop needs to wake up and the dispatching of any specific events.
///
/// High frequency event sources, such as input devices could potentially lead to lots of wake
/// ups and also lots of corresponding `AboutToWait` events.
///
/// This is not an ideal event to drive application rendering from and instead applications
/// should render in response to [`Event::RedrawRequested`](crate::event::Event::RedrawRequested)
/// events.
AboutToWait,

/// Emitted after [`MainEventsCleared`] when a window should be redrawn.
/// Emitted when a window should be redrawn.
///
/// This gets triggered in two scenarios:
/// - The OS has performed an operation that's invalidated the window's contents (such as
/// resizing the window).
/// - The application has explicitly requested a redraw via [`Window::request_redraw`].
///
/// During each iteration of the event loop, Winit will aggregate duplicate redraw requests
/// into a single event, to help avoid duplicating rendering work.
///
/// Mainly of interest to applications with mostly-static graphics that avoid redrawing unless
/// something changes, like most non-game GUIs.
///
///
/// ## Platform-specific
///
/// - **macOS / iOS:** Due to implementation difficulties, this will often, but not always, be
/// emitted directly inside `drawRect:`, with neither a preceding [`MainEventsCleared`] nor
/// subsequent `RedrawEventsCleared`. See [#2640] for work on this.
///
/// [`MainEventsCleared`]: Self::MainEventsCleared
/// [`RedrawEventsCleared`]: Self::RedrawEventsCleared
/// [#2640]: https://github.com/rust-windowing/winit/issues/2640
/// Winit will aggregate duplicate redraw requests into a single event, to
/// help avoid duplicating rendering work.
RedrawRequested(WindowId),

/// Emitted after all [`RedrawRequested`] events have been processed and control flow is about to
/// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
/// immediately after `MainEventsCleared`.
///
/// This event is useful for doing any cleanup or bookkeeping work after all the rendering
/// tasks have been completed.
///
/// [`RedrawRequested`]: Self::RedrawRequested
RedrawEventsCleared,

/// Emitted when the event loop is being shut down.
///
/// This is irreversible - if this event is emitted, it is guaranteed to be the last event that
Expand All @@ -275,9 +251,8 @@ impl<T: Clone> Clone for Event<'static, T> {
event: event.clone(),
},
NewEvents(cause) => NewEvents(*cause),
MainEventsCleared => MainEventsCleared,
AboutToWait => AboutToWait,
RedrawRequested(wid) => RedrawRequested(*wid),
RedrawEventsCleared => RedrawEventsCleared,
LoopExiting => LoopExiting,
Suspended => Suspended,
Resumed => Resumed,
Expand All @@ -294,9 +269,8 @@ impl<'a, T> Event<'a, T> {
WindowEvent { window_id, event } => Ok(WindowEvent { window_id, event }),
DeviceEvent { device_id, event } => Ok(DeviceEvent { device_id, event }),
NewEvents(cause) => Ok(NewEvents(cause)),
MainEventsCleared => Ok(MainEventsCleared),
AboutToWait => Ok(AboutToWait),
RedrawRequested(wid) => Ok(RedrawRequested(wid)),
RedrawEventsCleared => Ok(RedrawEventsCleared),
LoopExiting => Ok(LoopExiting),
Suspended => Ok(Suspended),
Resumed => Ok(Resumed),
Expand All @@ -314,9 +288,8 @@ impl<'a, T> Event<'a, T> {
UserEvent(event) => Some(UserEvent(event)),
DeviceEvent { device_id, event } => Some(DeviceEvent { device_id, event }),
NewEvents(cause) => Some(NewEvents(cause)),
MainEventsCleared => Some(MainEventsCleared),
AboutToWait => Some(AboutToWait),
RedrawRequested(wid) => Some(RedrawRequested(wid)),
RedrawEventsCleared => Some(RedrawEventsCleared),
LoopExiting => Some(LoopExiting),
Suspended => Some(Suspended),
Resumed => Some(Resumed),
Expand Down
2 changes: 1 addition & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ impl<T> fmt::Debug for EventLoopWindowTarget<T> {

/// Set by the user callback given to the [`EventLoop::run`] method.
///
/// Indicates the desired behavior of the event loop after [`Event::RedrawEventsCleared`] is emitted.
/// Indicates the desired behavior of the event loop after [`Event::AboutToWait`] is emitted.
///
/// Defaults to [`Poll`].
///
Expand Down
4 changes: 2 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
//! println!("The close button was pressed; stopping");
//! control_flow.set_exit();
//! },
//! Event::MainEventsCleared => {
//! Event::AboutToWait => {
//! // Application update code.
//!
//! // Queue a RedrawRequested event.
Expand All @@ -83,7 +83,7 @@
//! // Redraw the application.
//! //
//! // It's preferable for applications that do not render continuously to render in
//! // this event rather than in MainEventsCleared, since rendering in here allows
//! // this event rather than in AboutToWait, since rendering in here allows
//! // the program to gracefully handle redraws requested by the OS.
//! },
//! _ => ()
Expand Down
2 changes: 1 addition & 1 deletion src/platform/pump_events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ pub trait EventLoopExtPumpEvents {
/// event: WindowEvent::CloseRequested,
/// window_id,
/// } if window_id == window.id() => control_flow.set_exit(),
/// Event::MainEventsCleared => {
/// Event::AboutToWait => {
/// window.request_redraw();
/// }
/// _ => (),
Expand Down
10 changes: 2 additions & 8 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -484,13 +484,6 @@ impl<T: 'static> EventLoop<T> {
}
}

sticky_exit_callback(
event::Event::MainEventsCleared,
self.window_target(),
&mut control_flow,
callback,
);

if self.running {
if resized {
let size = if let Some(native_window) = self.android_app.native_window().as_ref() {
Expand All @@ -515,8 +508,9 @@ impl<T: 'static> EventLoop<T> {
}
}

// This is always the last event we dispatch before poll again
sticky_exit_callback(
event::Event::RedrawEventsCleared,
event::Event::AboutToWait,
self.window_target(),
&mut control_flow,
callback,
Expand Down
7 changes: 2 additions & 5 deletions src/platform_impl/ios/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -753,21 +753,18 @@ pub unsafe fn handle_main_events_cleared() {
};
drop(this);

// User events are always sent out at the end of the "MainEventLoop"
handle_user_events();
handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));

let mut this = AppState::get_mut();
let mut redraw_events: Vec<EventWrapper> = this
let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition()
.into_iter()
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
.collect();

redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
drop(this);

handle_nonuser_events(redraw_events);
handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
}

// requires main thread
Expand Down
4 changes: 2 additions & 2 deletions src/platform_impl/ios/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,10 @@ fn setup_control_flow_observers() {

// Core Animation registers its `CFRunLoopObserver` that performs drawing operations in
// `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end
// priority to be 0, in order to send MainEventsCleared before RedrawRequested. This value was
// priority to be 0, in order to send AboutToWait before RedrawRequested. This value was
// chosen conservatively to guard against apple using different priorities for their redraw
// observers in different OS's or on different devices. If it so happens that it's too
// conservative, the main symptom would be non-redraw events coming in after `MainEventsCleared`.
// conservative, the main symptom would be non-redraw events coming in after `AboutToWait`.
//
// The value of `0x1e8480` was determined by inspecting stack traces and the associated
// registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4.
Expand Down
11 changes: 3 additions & 8 deletions src/platform_impl/ios/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,9 @@ declare_class!(
fn draw_rect(&self, rect: CGRect) {
let window = self.window().unwrap();
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared,
))),
);
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.id()),
)));
}
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
}
Expand Down
12 changes: 2 additions & 10 deletions src/platform_impl/linux/wayland/event_loop/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,14 +475,6 @@ impl<T: 'static> EventLoop<T> {
sticky_exit_callback(event, &self.window_target, &mut control_flow, &mut callback);
}

// Send events cleared.
sticky_exit_callback(
Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
&mut callback,
);

// Collect the window ids
self.with_state(|state| {
window_ids.extend(state.window_requests.get_mut().keys());
Expand Down Expand Up @@ -525,9 +517,9 @@ impl<T: 'static> EventLoop<T> {
}
}

// Send RedrawEventCleared.
// This is always the last event we dispatch before poll again
sticky_exit_callback(
Event::RedrawEventsCleared,
Event::AboutToWait,
&self.window_target,
&mut control_flow,
&mut callback,
Expand Down
15 changes: 4 additions & 11 deletions src/platform_impl/linux/x11/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -659,15 +659,7 @@ impl<T: 'static> EventLoop<T> {
);
}
}
// send MainEventsCleared
{
sticky_exit_callback(
crate::event::Event::MainEventsCleared,
&self.target,
&mut control_flow,
callback,
);
}

// Empty the redraw requests
{
let mut windows = HashSet::new();
Expand All @@ -686,10 +678,11 @@ impl<T: 'static> EventLoop<T> {
);
}
}
// send RedrawEventsCleared

// This is always the last event we dispatch before poll again
{
sticky_exit_callback(
crate::event::Event::RedrawEventsCleared,
crate::event::Event::AboutToWait,
&self.target,
&mut control_flow,
callback,
Expand Down
Loading

0 comments on commit ae7497e

Please sign in to comment.