Remove RedrawEventsCleared + MainEventsCleared, and added AboutToWait

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.
This commit is contained in:
Robert Bragg 2023-07-28 17:37:56 +01:00 committed by GitHub
parent 935146d299
commit ae7497e18f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 76 additions and 138 deletions

View file

@ -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() {
@ -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,

View file

@ -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

View file

@ -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.

View file

@ -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] };
}

View file

@ -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());
@ -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,

View file

@ -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();
@ -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,

View file

@ -615,13 +615,13 @@ impl AppState {
for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::MainEventsCleared));
for window_id in HANDLER.should_redraw() {
HANDLER
.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawRequested(window_id)));
}
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait));
HANDLER.set_in_callback(false);
if HANDLER.should_exit() {

View file

@ -64,7 +64,7 @@ extern "C" fn control_flow_begin_handler(
}
// end is queued with the lowest priority to ensure it is processed after other observers
// without that, LoopExiting would get sent after MainEventsCleared
// without that, LoopExiting would get sent after AboutToWait
extern "C" fn control_flow_end_handler(
_: CFRunLoopObserverRef,
activity: CFRunLoopActivity,

View file

@ -594,12 +594,6 @@ impl<T: 'static> EventLoop<T> {
);
}
event_handler(
event::Event::MainEventsCleared,
&self.window_target,
&mut control_flow,
);
// To avoid deadlocks the redraws lock is not held during event processing.
while let Some(window_id) = {
let mut redraws = self.window_target.p.redraws.lock().unwrap();
@ -613,7 +607,7 @@ impl<T: 'static> EventLoop<T> {
}
event_handler(
event::Event::RedrawEventsCleared,
event::Event::AboutToWait,
&self.window_target,
&mut control_flow,
);

View file

@ -535,7 +535,7 @@ impl<T: 'static> Shared<T> {
}
// Process the destroy-pending windows. This should only be called from
// `run_until_cleared`, somewhere between emitting `NewEvents` and `MainEventsCleared`.
// `run_until_cleared`, somewhere between emitting `NewEvents` and `AboutToWait`.
fn process_destroy_pending_windows(&self, control: &mut ControlFlow) {
while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() {
self.0
@ -563,14 +563,14 @@ impl<T: 'static> Shared<T> {
self.handle_event(event.into(), &mut control);
}
self.process_destroy_pending_windows(&mut control);
self.handle_event(Event::MainEventsCleared, &mut control);
// Collect all of the redraw events to avoid double-locking the RefCell
let redraw_events: Vec<WindowId> = self.0.redraw_pending.borrow_mut().drain().collect();
for window_id in redraw_events {
self.handle_event(Event::RedrawRequested(window_id), &mut control);
}
self.handle_event(Event::RedrawEventsCleared, &mut control);
self.handle_event(Event::AboutToWait, &mut control);
self.apply_control_flow(control);
// If the event loop is closed, it has been closed this iteration and now the closing

View file

@ -403,7 +403,7 @@ impl<T: 'static> EventLoop<T> {
}
// We aim to be consistent with the MacOS backend which has a RunLoop
// observer that will dispatch MainEventsCleared when about to wait for
// observer that will dispatch AboutToWait when about to wait for
// events, and NewEvents after the RunLoop wakes up.
//
// We emulate similar behaviour by treating `GetMessage` as our wait

View file

@ -52,7 +52,7 @@ pub(crate) enum RunnerState {
/// The event loop is idling.
Idle,
/// The event loop is handling the OS's events and sending them to the user's callback.
/// `NewEvents` has been sent, and `MainEventsCleared` hasn't.
/// `NewEvents` has been sent, and `AboutToWait` hasn't.
HandlingMainEvents,
/// The event loop has been destroyed. No other events will be emitted.
Destroyed,
@ -250,8 +250,9 @@ impl<T> EventLoopRunner<T> {
}
}
/// Dispatch control flow events (`NewEvents`, `MainEventsCleared`, `RedrawEventsCleared`, and
/// `LoopExiting`) as necessary to bring the internal `RunnerState` to the new runner state.
/// Dispatch control flow events (`NewEvents`, `AboutToWait`, and
/// `LoopExiting`) as necessary to bring the internal `RunnerState` to the
/// new runner state.
///
/// The state transitions are defined as follows:
///
@ -291,13 +292,13 @@ impl<T> EventLoopRunner<T> {
}
(Uninitialized, Idle) => {
self.call_new_events(true);
self.call_event_handler(Event::MainEventsCleared);
self.call_redraw_events_cleared();
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
}
(Uninitialized, Destroyed) => {
self.call_new_events(true);
self.call_event_handler(Event::MainEventsCleared);
self.call_redraw_events_cleared();
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
self.call_event_handler(Event::LoopExiting);
}
(_, Uninitialized) => panic!("cannot move state to Uninitialized"),
@ -311,12 +312,13 @@ impl<T> EventLoopRunner<T> {
}
(HandlingMainEvents, Idle) => {
self.call_event_handler(Event::MainEventsCleared);
self.call_redraw_events_cleared();
// This is always the last event we dispatch before waiting for new events
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
}
(HandlingMainEvents, Destroyed) => {
self.call_event_handler(Event::MainEventsCleared);
self.call_redraw_events_cleared();
self.call_event_handler(Event::AboutToWait);
self.last_events_cleared.set(Instant::now());
self.call_event_handler(Event::LoopExiting);
}
@ -356,11 +358,6 @@ impl<T> EventLoopRunner<T> {
}
self.dispatch_buffered_events();
}
fn call_redraw_events_cleared(&self) {
self.call_event_handler(Event::RedrawEventsCleared);
self.last_events_cleared.set(Instant::now());
}
}
impl<T> BufferedEvent<T> {