Skip to content

Commit

Permalink
android: Forward suspended() and resumed() events and patch up pl…
Browse files Browse the repository at this point in the history
…atform-specific documentation

Key them off of `onStop()` and `onStart()` which seems to match the
other backends most closely.  These [Android Activity lifecycle] events
denote when the application is visible on-screen, and recommend that any
heavy lifting for startup and shutdown happens here, as the application
may be demoted to the background and later shut down entirely unless the
user navigates back to it.

[Android Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
  • Loading branch information
MarijnS95 committed Oct 8, 2024
1 parent 32cd1ad commit 9a488fb
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 60 deletions.
86 changes: 52 additions & 34 deletions src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,21 @@ pub trait ApplicationHandler {
/// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event
/// [`bfcache`]: https://web.dev/bfcache/
///
/// ### Android
///
/// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a
/// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing
/// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method.
///
/// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart()
///
/// ### Others
///
/// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported.
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`resumed()`]: Self::resumed
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
Expand All @@ -71,26 +81,24 @@ pub trait ApplicationHandler {
///
/// ### Android
///
/// On Android, the [`can_create_surfaces()`] method is called when a new [`SurfaceView`] has
/// been created. This is expected to closely correlate with the [`onResume`] lifecycle
/// event but there may technically be a discrepancy.
///
/// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume()
/// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`]
/// (native [`Surface`]) is created which backs the application window. This is expected to
/// closely correlate with the [`onStart`] lifecycle event which typically results in a surface
/// to be created after the app becomes visible.
///
/// Applications that need to run on Android must wait until they have been "resumed" before
/// Applications that need to run on Android must wait until they have received a surface before
/// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`]
/// or [`wgpu::Surface`]) which depend on having a [`SurfaceView`]. Applications must also
/// assume that if they are [suspended], then their render surfaces are invalid and should
/// be dropped.
/// or [`wgpu::Surface`]) which depend on having a [`NativeWindow`]. Applications must handle
/// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped.
///
/// [suspended]: Self::destroy_surfaces
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces
/// [`destroy_surfaces()`]: Self::destroy_surfaces
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop);

/// Called after a wake up is requested using [`EventLoopProxy::wake_up()`].
Expand Down Expand Up @@ -235,18 +243,30 @@ pub trait ApplicationHandler {
/// ### Web
///
/// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the
/// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache that
/// page is being moved/stored in the [`bfcache`] (back/forward cache) - an in-memory cache that
/// stores a complete snapshot of a page (including the JavaScript heap) as the user is
/// navigating away.
///
/// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event
/// [`bfcache`]: https://web.dev/bfcache/
///
/// ### Android
///
/// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible to
/// the user. This is a good place to stop refreshing UI, running animations and other visual
/// things. It is driven by Android's [`onStop()`] method.
///
/// After this event the application either receives [`resumed()`] again, or [`exiting()`].
///
/// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop()
///
/// ### Others
///
/// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported.
/// **macOS / Orbital / Wayland / Windows / X11:** Unsupported.
///
/// [`suspended()`]: Self::suspended
/// [`resumed()`]: Self::resumed()
/// [`suspended()`]: Self::suspended()
/// [`exiting()`]: Self::exiting()
fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
Expand All @@ -259,33 +279,31 @@ pub trait ApplicationHandler {
///
/// ### Android
///
/// On Android, the [`destroy_surfaces()`] method is called when the application's associated
/// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`]
/// lifecycle event but there may technically be a discrepancy.
///
/// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause()
/// On Android, the [`destroy_surfaces()`] method is called when the application's
/// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate
/// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed
/// after the app becomes invisible.
///
/// Applications that need to run on Android should assume their [`SurfaceView`] has been
/// Applications that need to run on Android should assume their [`NativeWindow`] has been
/// destroyed, which indirectly invalidates any existing render surfaces that may have been
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]).
///
/// After being [suspended] on Android applications must drop all render surfaces before
/// the event callback completes, which may be re-created when the application is next
/// [resumed].
/// When receiving [`destroy_surfaces()`] Android applications must drop all render surfaces
/// before the event callback completes, which may be re-created when the application next
/// receives [`can_create_surfaces()`].
///
/// [suspended]: Self::destroy_surfaces
/// [resumed]: Self::can_create_surfaces
/// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView
/// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle
/// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window
/// [`Surface`]: https://developer.android.com/reference/android/view/Surface
/// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop()
/// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html
/// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html
///
/// ### Others
///
/// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported.
///
/// [`can_create_surfaces()`]: Self::can_create_surfaces
/// [`destroy_surfaces()`]: Self::destroy_surfaces
/// [`can_create_surfaces()`]: Self::can_create_surfaces()
/// [`destroy_surfaces()`]: Self::destroy_surfaces()
fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let _ = event_loop;
}
Expand Down
6 changes: 3 additions & 3 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,14 +84,14 @@ changelog entry.
- Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now
only wakes up the loop.
- On X11, implement smooth resizing through the sync extension API.
- `ApplicationHandler::create|destroy_surfaces()` was split off from
- `ApplicationHandler::can_create|destroy_surfaces()` was split off from
`ApplicationHandler::resumed/suspended()`.

`ApplicationHandler::can_create_surfaces()` should, for portability reasons
to Android, be the only place to create render surfaces.

`ApplicationHandler::resumed/suspended()` are now only emitted by iOS and Web
and now signify actually resuming/suspending the application.
`ApplicationHandler::resumed/suspended()` are now only emitted by iOS, Web
and Android, and now signify actually resuming/suspending the application.
- Rename `platform::web::*ExtWebSys` to `*ExtWeb`.
- Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and
`EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly,
Expand Down
36 changes: 18 additions & 18 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,51 +63,51 @@ use crate::window::{ActivationToken, Theme, WindowId};
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Event {
/// See [`ApplicationHandler::new_events`] for details.
/// See [`ApplicationHandler::new_events()`] for details.
///
/// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events
/// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events()
NewEvents(StartCause),

/// See [`ApplicationHandler::window_event`] for details.
/// See [`ApplicationHandler::window_event()`] for details.
///
/// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event
/// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event()
#[allow(clippy::enum_variant_names)]
WindowEvent { window_id: WindowId, event: WindowEvent },

/// See [`ApplicationHandler::device_event`] for details.
/// See [`ApplicationHandler::device_event()`] for details.
///
/// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event
/// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event()
#[allow(clippy::enum_variant_names)]
DeviceEvent { device_id: Option<DeviceId>, event: DeviceEvent },

/// See [`ApplicationHandler::suspended`] for details.
/// See [`ApplicationHandler::suspended()`] for details.
///
/// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended
/// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended()
Suspended,

/// See [`ApplicationHandler::can_create_surfaces`] for details.
/// See [`ApplicationHandler::can_create_surfaces()`] for details.
///
/// [`ApplicationHandler::can_create_surfaces`]: crate::application::ApplicationHandler::can_create_surfaces
/// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces()
CreateSurfaces,

/// See [`ApplicationHandler::resumed`] for details.
/// See [`ApplicationHandler::resumed()`] for details.
///
/// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed
/// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed()
Resumed,

/// See [`ApplicationHandler::about_to_wait`] for details.
/// See [`ApplicationHandler::about_to_wait()`] for details.
///
/// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait
/// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait()
AboutToWait,

/// See [`ApplicationHandler::exiting`] for details.
/// See [`ApplicationHandler::exiting()`] for details.
///
/// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting
/// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting()
LoopExiting,

/// See [`ApplicationHandler::memory_warning`] for details.
/// See [`ApplicationHandler::memory_warning()`] for details.
///
/// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning
/// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning()
MemoryWarning,

/// User requested a wake up.
Expand Down
2 changes: 1 addition & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ impl rwh_06::HasDisplayHandle for dyn ActiveEventLoop + '_ {

/// A proxy for the underlying display handle.
///
/// The purpose of this type is to provide a cheaply clonable handle to the underlying
/// The purpose of this type is to provide a cheaply cloneable handle to the underlying
/// display handle. This is often used by graphics APIs to connect to the underlying APIs.
/// It is difficult to keep a handle to the [`EventLoop`] type or the [`ActiveEventLoop`]
/// type. In contrast, this type involves no lifetimes and can be persisted for as long as
Expand Down
8 changes: 4 additions & 4 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -217,11 +217,11 @@ impl EventLoop {
app.memory_warning(&self.window_target);
},
MainEvent::Start => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStart notification to application");
app.resumed(self.window_target());
},
MainEvent::Resume { .. } => {
debug!("App Resumed - is running");
// TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897
self.running = true;
},
MainEvent::SaveState { .. } => {
Expand All @@ -231,11 +231,11 @@ impl EventLoop {
},
MainEvent::Pause => {
debug!("App Paused - stopped running");
// TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897
self.running = false;
},
MainEvent::Stop => {
// XXX: how to forward this state to applications?
warn!("TODO: forward onStop notification to application");
app.suspended(self.window_target());
},
MainEvent::Destroy => {
// XXX: maybe exit mainloop to drop things before being
Expand Down

0 comments on commit 9a488fb

Please sign in to comment.