From aa5fcc53bb978d1dd09a5ed70f02c43a857d47d5 Mon Sep 17 00:00:00 2001 From: Robert Bragg Date: Thu, 10 Nov 2022 22:23:41 +0000 Subject: [PATCH] Improve docs This removes the indirection of the `StateSaver` and `StateLoader` type aliases (which resulted in no documentation for these interfaces) and we now simply re-export the types from the backend implementation. --- README.md | 1 + android-activity/src/config.rs | 7 ++ android-activity/src/game_activity/mod.rs | 3 - android-activity/src/lib.rs | 92 +++++++++++++++++--- android-activity/src/native_activity/glue.rs | 3 - android-activity/src/native_activity/mod.rs | 20 ++++- 6 files changed, 107 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index d369fb3..65ef70d 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,7 @@ fn android_main(app: AndroidApp) { app.input_events(|event| { info!("Input Event: {event:?}"); + InputStatus::Unhandled }); }); } diff --git a/android-activity/src/config.rs b/android-activity/src/config.rs index 5b12ddc..7d818ec 100644 --- a/android-activity/src/config.rs +++ b/android-activity/src/config.rs @@ -6,6 +6,13 @@ use ndk::configuration::{ ScreenSize, Touchscreen, UiModeNight, UiModeType, }; +/// A (cheaply clonable) reference to this application's [`ndk::configuration::Configuration`] +/// +/// This provides a thread-safe way to access the latest configuration state for +/// an application without deeply copying the large [`ndk::configuration::Configuration`] struct. +/// +/// If the application is notified of configuration changes then those changes +/// will become visible via pre-existing configuration references. #[derive(Clone)] pub struct ConfigurationRef { config: Arc>, diff --git a/android-activity/src/game_activity/mod.rs b/android-activity/src/game_activity/mod.rs index 54beb45..a5bbccd 100644 --- a/android-activity/src/game_activity/mod.rs +++ b/android-activity/src/game_activity/mod.rs @@ -655,9 +655,6 @@ pub unsafe extern "C" fn _rust_glue_entry(app: *mut ffi::android_app) { // code to look up non-standard Java classes. android_main(app); - // Since this is a newly spawned thread then the JVM hasn't been attached - // to the thread yet. Attach before calling the applications main function - // so they can safely make JNI calls if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { detach_current_thread(jvm); } diff --git a/android-activity/src/lib.rs b/android-activity/src/lib.rs index ed10481..70d0cfd 100644 --- a/android-activity/src/lib.rs +++ b/android-activity/src/lib.rs @@ -1,3 +1,62 @@ +//! A glue layer for building standalone, Rust applications on Android +//! +//! This crate provides a "glue" layer for building native Rust +//! applications on Android, supporting multiple [`Activity`] base classes. +//! It's comparable to [`android_native_app_glue.c`][ndk_concepts] +//! for C/C++ applications. +//! +//! Currently the crate supports two `Activity` base classes: +//! 1. [`NativeActivity`] - Built in to Android, this doesn't require compiling any Java or Kotlin code. +//! 2. [`GameActivity`] - From the Android Game Development Kit, it has more +//! sophisticated input handling support than `NativeActivity`. `GameActivity` +//! is also based on the `AndroidAppCompat` class which can help with supporting +//! a wider range of devices. +//! +//! Standalone applications based on this crate need to be built as `cdylib` libraries, like: +//! ``` +//! [lib] +//! crate_type=["cdylib"] +//! ``` +//! +//! and implement a `#[no_mangle]` `android_main` entry point like this: +//! ```rust +//! #[no_mangle] +//! fn android_main(app: AndroidApp) { +//! +//! } +//! ``` +//! +//! Once your application's `Activity` class has loaded and it calls `onCreate` then +//! `android-activity` will spawn a dedicated thread to run your `android_main` function, +//! separate from the Java thread that created the corresponding `Activity`. +//! +//! [`AndroidApp`] provides an interface to query state for the application as +//! well as monitor events, such as lifecycle and input events, that are +//! marshalled between the Java thread that owns the `Activity` and the native +//! thread that runs the `android_main()` code. +//! +//! # Main Thread Initialization +//! +//! Before `android_main()` is called, the following application state +//! is also initialized: +//! +//! 1. An I/O thread is spawned that will handle redirecting standard input +//! and output to the Android log, visible via `logcat`. +//! 2. A `JavaVM` and `Activity` instance will be associated with the [`ndk_context`] crate +//! so that other, independent, Rust crates are able to find a JavaVM +//! for making JNI calls. +//! 3. The `JavaVM` will be attached to the native thread +//! 4. A [Looper] is attached to the Rust native thread. +//! +//! +//! These are undone after `android_main()` returns +//! +//! [`Activity`]: https://developer.android.com/reference/android/app/Activity +//! [`NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity +//! [ndk_concepts]: https://developer.android.com/ndk/guides/concepts#naa +//! [`GameActivity`]: https://developer.android.com/games/agdk/integrate-game-activity +//! [Looper]: https://developer.android.com/reference/android/os/Looper + use std::hash::Hash; use std::sync::Arc; use std::sync::RwLock; @@ -54,13 +113,7 @@ pub use config::ConfigurationRef; mod util; -// Note: unlike in ndk-glue this has signed components (consistent -// with Android's ARect) which generally allows for representing -// rectangles with a negative/off-screen origin. Even though this -// is currently just used to represent the content rect (that probably -// wouldn't have any negative components) we keep the generality -// since this is a primitive type that could potentially be used -// for more things in the future. +/// A rectangle with integer edge coordinates. Used to represent window insets, for example. #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct Rect { pub left: i32, @@ -103,9 +156,11 @@ impl From for Rect { } } -pub type StateSaver<'a> = activity_impl::StateSaver<'a>; -pub type StateLoader<'a> = activity_impl::StateLoader<'a>; +pub use activity_impl::StateSaver; +pub use activity_impl::StateLoader; + +/// An application event delivered during [`AndroidApp::poll_events`] #[non_exhaustive] #[derive(Debug)] pub enum MainEvent<'a> { @@ -197,6 +252,7 @@ pub enum MainEvent<'a> { InsetsChanged {}, } +/// An event delivered during [`AndroidApp::poll_events`] #[derive(Debug)] #[non_exhaustive] pub enum PollEvent<'a> { @@ -205,6 +261,9 @@ pub enum PollEvent<'a> { Main(MainEvent<'a>), } +/// Indicates whether an application has handled or ignored an event +/// +/// If an event is not handled by an application then some default handling may happen. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InputStatus { Handled, @@ -380,6 +439,13 @@ bitflags! { } } +/// The top-level state and interface for a native Rust application +/// +/// `AndroidApp` provides an interface to query state for the application as +/// well as monitor events, such as lifecycle and input events, that are +/// marshalled between the Java thread that owns the `Activity` and the native +/// thread that runs the `android_main()` code. +/// #[derive(Debug, Clone)] pub struct AndroidApp { pub(crate) inner: Arc>, @@ -398,6 +464,7 @@ impl Hash for AndroidApp { } } + impl AndroidApp { /// Queries the current [`NativeWindow`] for the application. /// @@ -408,7 +475,7 @@ impl AndroidApp { self.inner.read().unwrap().native_window() } - /// Polls for any events associated with this AndroidApp and processes those events + /// Polls for any events associated with this [AndroidApp] and processes those events /// (such as lifecycle events) via the given `callback`. /// /// It's important to use this API for polling, and not call [`ALooper_pollAll`] directly since @@ -420,6 +487,11 @@ impl AndroidApp { /// main thread. The [`MainEvent::SaveState`] event is also synchronized with the /// Java main thread. /// + /// # Panics + /// + /// This must only be called from your `android_main()` thread and it may panic if called + /// from another thread. + /// /// [`ALooper_pollAll`]: ndk::looper::ThreadLooper::poll_all pub fn poll_events(&self, timeout: Option, callback: F) where diff --git a/android-activity/src/native_activity/glue.rs b/android-activity/src/native_activity/glue.rs index 3d97f0f..36844d0 100644 --- a/android-activity/src/native_activity/glue.rs +++ b/android-activity/src/native_activity/glue.rs @@ -816,9 +816,6 @@ extern "C" fn ANativeActivity_onCreate( // code to look up non-standard Java classes. android_main(app); - // Since this is a newly spawned thread then the JVM hasn't been attached - // to the thread yet. Attach before calling the applications main function - // so they can safely make JNI calls if let Some(detach_current_thread) = (*(*jvm)).DetachCurrentThread { detach_current_thread(jvm); } diff --git a/android-activity/src/native_activity/mod.rs b/android-activity/src/native_activity/mod.rs index 0712d68..473dc0d 100644 --- a/android-activity/src/native_activity/mod.rs +++ b/android-activity/src/native_activity/mod.rs @@ -42,30 +42,37 @@ pub const LOOPER_ID_MAIN: libc::c_int = 1; pub const LOOPER_ID_INPUT: libc::c_int = 2; //pub const LOOPER_ID_USER: ::std::os::raw::c_uint = 3; -// The only time it's safe to update the saved_state pointer is -// while handling a SaveState event, so this API is only exposed for those -// events +/// An interface for saving application state during [MainEvent::SaveState] events +/// +/// This interface is only available temporarily while handling a [MainEvent::SaveState] event. #[derive(Debug)] pub struct StateSaver<'a> { app: &'a AndroidAppInner, } impl<'a> StateSaver<'a> { + /// Stores the given `state` such that it will be available to load the next + /// time that the application resumes. pub fn store(&self, state: &'a [u8]) { self.app.native_activity.set_saved_state(state); } } +/// An interface for loading application state during [MainEvent::Resume] events +/// +/// This interface is only available temporarily while handling a [MainEvent::Resume] event. #[derive(Debug)] pub struct StateLoader<'a> { app: &'a AndroidAppInner, } impl<'a> StateLoader<'a> { + /// Returns whatever state was saved during the last [MainEvent::SaveState] event or `None` pub fn load(&self) -> Option> { self.app.native_activity.saved_state() } } +/// A means to wake up the main thread while it is blocked waiting for I/O #[derive(Clone)] pub struct AndroidAppWaker { // The looper pointer is owned by the android_app and effectively @@ -77,6 +84,13 @@ unsafe impl Send for AndroidAppWaker {} unsafe impl Sync for AndroidAppWaker {} impl AndroidAppWaker { + + /// Interrupts the main thread if it is blocked within [`AndroidApp::poll_events()`] + /// + /// If [`AndroidApp::poll_events()`] is interrupted it will invoke the poll + /// callback with a [PollEvent::Wake][wake_event] event. + /// + /// [wake_event]: crate::PollEvent::Wake pub fn wake(&self) { unsafe { ALooper_wake(self.looper.as_ptr());