Skip to content

Commit a0e2601

Browse files
committed
Consistently deliver a Resumed event on all platforms
To be more consistent with mobile platforms this updates the Windows, macOS, Wayland, X11 and Web backends to all emit a Resumed event immediately after the initial `NewEvents(StartCause::Init)` event. The documentation for Suspended and Resumed has also been updated to provide general recommendations for how to handle Suspended and Resumed events in portable applications as well as providing Android and iOS specific details. This consistency makes it possible to write applications that lazily initialize their graphics state when the application resumes without any platform-specific knowledge. Previously, applications that wanted to run on Android and other systems would have to maintain two, mutually-exclusive, initialization paths. Note: This patch does nothing to guarantee that Suspended events will be delivered. It's still reasonable to say that most OSs without a formal lifecycle for applications will simply never "suspend" your application. There are currently no known portability issues caused by not delivering `Suspended` events consistently and technically it's not possible to guarantee the delivery of `Suspended` events if the OS doesn't define an application lifecycle. (app can always be terminated without any kind of clean up notification on most non-mobile OSs) Resolves: rust-windowing#2185
1 parent 1cd0e94 commit a0e2601

File tree

7 files changed

+125
-2
lines changed

7 files changed

+125
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ And please only add new entries to the top of this list, right below the `# Unre
7474
- On Windows, fix focus events being sent to inactive windows.
7575
- **Breaking**, update `raw-window-handle` to `v0.5` and implement `HasRawDisplayHandle` for `Window` and `EventLoopWindowTarget`.
7676
- On X11, add function `register_xlib_error_hook` into `winit::platform::unix` to subscribe for errors comming from Xlib.
77+
- All platforms can now be relied on to emit a `Resumed` event. Applications are recommended to lazily initialize graphics state and windows on first resume for portability.
7778

7879
# 0.26.1 (2022-01-05)
7980

src/event.rs

+98
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,107 @@ pub enum Event<'a, T: 'static> {
7474
UserEvent(T),
7575

7676
/// Emitted when the application has been suspended.
77+
///
78+
/// # Portability
79+
///
80+
/// Not all platforms support the notion of suspending applications, and there may be no
81+
/// technical way to guarantee being able to emit a `Suspended` event if the OS has
82+
/// no formal application lifecycle (currently only Android and iOS do). For this reason
83+
/// Winit does not currently try to emit pseudo `Suspended` events before the application
84+
/// quits on platforms without an application lifecycle.
85+
///
86+
/// Considering that the implementation of `Suspended` and [`Resumed`] events may be internally
87+
/// driven by multiple platform-specific events, and there may be subtle differences across
88+
/// platforms with how these internal events are delivered, it's recommended that applications
89+
/// be able to gracefully handle redundant (i.e. back-to-back) `Suspended` or [`Resumed`] events.
90+
///
91+
/// Also see [`Resumed`] notes.
92+
///
93+
/// ## Android
94+
///
95+
/// On Android, the `Suspended` event is only sent when the application's associated
96+
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
97+
/// lifecycle event but there may technically be a discrepancy.
98+
///
99+
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
100+
///
101+
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
102+
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
103+
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
104+
///
105+
/// After being `Suspended` on Android applications must drop all render surfaces before
106+
/// the event callback completes, which may be re-created when the application is next [`Resumed`].
107+
///
108+
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
109+
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
110+
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
111+
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
112+
///
113+
/// ## iOS
114+
///
115+
/// On iOS the `Suspended` event is currently emitted in response to an
116+
/// [`applicationWillResignActive`] callback which means that the application is
117+
/// about to transition from the active to inactive state (according to the
118+
/// [iOS application lifecycle]).
119+
///
120+
/// [`applicationWillResignActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive
121+
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
122+
///
123+
/// [`Resumed`]: Self::Resumed
77124
Suspended,
78125

79126
/// Emitted when the application has been resumed.
127+
///
128+
/// For consistency all platforms emit a `Resumed` event even if they don't themselves have a
129+
/// formal suspend/resume lifecycle. For systems without a standard suspend/resume lifecycle
130+
/// the `Resumed` event is always emitted after the [`NewEvents(StartCause::Init)`][StartCause::Init]
131+
/// event.
132+
///
133+
/// # Portability
134+
///
135+
/// It's recommended that applications should only initialize their graphics context and create
136+
/// a window after they have received their first `Resumed` event. Some systems
137+
/// (specifically Android) won't allow applications to create a render surface until they are
138+
/// resumed.
139+
///
140+
/// Considering that the implementation of [`Suspended`] and `Resumed` events may be internally
141+
/// driven by multiple platform-specific events, and there may be subtle differences across
142+
/// platforms with how these internal events are delivered, it's recommended that applications
143+
/// be able to gracefully handle redundant (i.e. back-to-back) [`Suspended`] or `Resumed` events.
144+
///
145+
/// Also see [`Suspended`] notes.
146+
///
147+
/// ## Android
148+
///
149+
/// On Android the `Resumed` event is sent when a new [`SurfaceView`] has been created. This is
150+
/// expected to closely correlate with the [`onResume`] lifecycle event but there may technically
151+
/// be a discrepancy.
152+
///
153+
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
154+
///
155+
/// Applications that need to run on Android must wait until they have been `Resumed`
156+
/// before they will be able to create a render surface (such as an `EGLSurface`,
157+
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]) which depend on having a
158+
/// [`SurfaceView`]. Applications must also assume that if they are [`Suspended`] then their
159+
/// render surfaces are invalid and should be dropped.
160+
///
161+
/// Also see [`Suspended`] notes.
162+
///
163+
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
164+
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
165+
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
166+
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
167+
///
168+
/// ## iOS
169+
///
170+
/// On iOS the `Resumed` event is emitted in response to an [`applicationDidBecomeActive`]
171+
/// callback which means the application is "active" (according to the
172+
/// [iOS application lifecycle]).
173+
///
174+
/// [`applicationDidBecomeActive`]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622956-applicationdidbecomeactive
175+
/// [iOS application lifecycle]: https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle
176+
///
177+
/// [`Suspended`]: Self::Suspended
80178
Resumed,
81179

82180
/// Emitted when all of the event loop's input events have been processed and redraw processing

src/platform_impl/linux/wayland/event_loop/mod.rs

+4
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ impl<T: 'static> EventLoop<T> {
232232
&mut control_flow,
233233
);
234234

235+
// NB: For consistency all platforms must emit a 'resumed' event even though Wayland
236+
// applications don't themselves have a formal suspend/resume lifecycle.
237+
callback(Event::Resumed, &self.window_target, &mut control_flow);
238+
235239
let mut window_updates: Vec<(WindowId, WindowUpdate)> = Vec::new();
236240
let mut event_sink_back_buffer = Vec::new();
237241

src/platform_impl/linux/x11/mod.rs

+11
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,17 @@ impl<T: 'static> EventLoop<T> {
333333
callback,
334334
);
335335

336+
// NB: For consistency all platforms must emit a 'resumed' event even though X11
337+
// applications don't themselves have a formal suspend/resume lifecycle.
338+
if *cause == StartCause::Init {
339+
sticky_exit_callback(
340+
crate::event::Event::Resumed,
341+
&this.target,
342+
control_flow,
343+
callback,
344+
);
345+
}
346+
336347
// Process all pending events
337348
this.drain_events(callback, control_flow);
338349

src/platform_impl/macos/app_state.rs

+3
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ impl AppState {
302302
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(
303303
StartCause::Init,
304304
)));
305+
// NB: For consistency all platforms must emit a 'resumed' event even though macOS
306+
// applications don't themselves have a formal suspend/resume lifecycle.
307+
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed));
305308
HANDLER.set_in_callback(false);
306309
}
307310

src/platform_impl/web/event_loop/runner.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -158,8 +158,9 @@ impl<T: 'static> Shared<T> {
158158
}
159159

160160
pub fn init(&self) {
161-
let start_cause = Event::NewEvents(StartCause::Init);
162-
self.run_until_cleared(iter::once(start_cause));
161+
// NB: For consistency all platforms must emit a 'resumed' event even though web
162+
// applications don't themselves have a formal suspend/resume lifecycle.
163+
self.run_until_cleared([Event::NewEvents(StartCause::Init), Event::Resumed].into_iter());
163164
}
164165

165166
// Run the polling logic for the Poll ControlFlow, which involves clearing the queue

src/platform_impl/windows/event_loop/runner.rs

+5
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,11 @@ impl<T> EventLoopRunner<T> {
393393
}
394394
};
395395
self.call_event_handler(Event::NewEvents(start_cause));
396+
// NB: For consistency all platforms must emit a 'resumed' event even though Windows
397+
// applications don't themselves have a formal suspend/resume lifecycle.
398+
if init {
399+
self.call_event_handler(Event::Resumed);
400+
}
396401
self.dispatch_buffered_events();
397402
RedrawWindow(self.thread_msg_target, ptr::null(), 0, RDW_INTERNALPAINT);
398403
}

0 commit comments

Comments
 (0)