Expose the application Configuration by reference

This provides an API to access `Configuration` state for the application
without having to make deep copies of the large `Configuration` struct.

This should avoid the need for Winit to create a global static copy of
the Configuration whenever it changes - and instead it can just get
a `ConfigurationRef` which will always reflect the latest config for
the application.

Fixes: #5
This commit is contained in:
Robert Bragg
2022-08-14 02:01:52 +01:00
parent b161b24ce4
commit a654f72f62
8 changed files with 184 additions and 39 deletions
+1
View File
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `AndroidApp` is now `Send` + `Sync`
### Changed
- *Breaking*: updates to `ndk 0.7` and `ndk-sys 0.4`
- *Breaking*: `AndroidApp::config()` now returns a clonable `ConfigurationRef` instead of a deep `Configuration` copy
## [0.1.1] - 2022-07-04
### Changed
+144
View File
@@ -0,0 +1,144 @@
use core::fmt;
use std::sync::{Arc, RwLock};
use ndk::configuration::{
Configuration, Keyboard, KeysHidden, LayoutDir, NavHidden, Navigation, Orientation, ScreenLong,
ScreenSize, Touchscreen, UiModeNight, UiModeType,
};
#[derive(Clone)]
pub struct ConfigurationRef {
config: Arc<RwLock<Configuration>>,
}
impl PartialEq for ConfigurationRef {
fn eq(&self, other: &Self) -> bool {
if Arc::ptr_eq(&self.config, &other.config) {
true
} else {
let other_guard = other.config.read().unwrap();
self.config.read().unwrap().eq(&*other_guard)
}
}
}
impl Eq for ConfigurationRef {}
unsafe impl Send for ConfigurationRef {}
unsafe impl Sync for ConfigurationRef {}
impl fmt::Debug for ConfigurationRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.config.read().unwrap().fmt(f)
}
}
impl ConfigurationRef {
pub(crate) fn new(config: Configuration) -> Self {
Self {
config: Arc::new(RwLock::new(config)),
}
}
pub(crate) fn replace(&self, src: Configuration) {
self.config.write().unwrap().copy(&src);
}
// Returns a deep copy of the full application configuration
pub fn copy(&self) -> Configuration {
let mut dest = Configuration::new();
dest.copy(&self.config.read().unwrap());
dest
}
/// Returns the country code, as a [`String`] of two characters, if set
pub fn country(&self) -> Option<String> {
self.config.read().unwrap().country()
}
/// Returns the screen density in dpi.
///
/// On some devices it can return values outside of the density enum.
pub fn density(&self) -> Option<u32> {
self.config.read().unwrap().density()
}
/// Returns the keyboard type.
pub fn keyboard(&self) -> Keyboard {
self.config.read().unwrap().keyboard()
}
/// Returns keyboard visibility/availability.
pub fn keys_hidden(&self) -> KeysHidden {
self.config.read().unwrap().keys_hidden()
}
/// Returns the language, as a `String` of two characters, if a language is set
pub fn language(&self) -> Option<String> {
self.config.read().unwrap().language()
}
/// Returns the layout direction
pub fn layout_direction(&self) -> LayoutDir {
self.config.read().unwrap().layout_direction()
}
/// Returns the mobile country code.
pub fn mcc(&self) -> i32 {
self.config.read().unwrap().mcc()
}
/// Returns the mobile network code, if one is defined
pub fn mnc(&self) -> Option<i32> {
self.config.read().unwrap().mnc()
}
pub fn nav_hidden(&self) -> NavHidden {
self.config.read().unwrap().nav_hidden()
}
pub fn navigation(&self) -> Navigation {
self.config.read().unwrap().navigation()
}
pub fn orientation(&self) -> Orientation {
self.config.read().unwrap().orientation()
}
pub fn screen_height_dp(&self) -> Option<i32> {
self.config.read().unwrap().screen_height_dp()
}
pub fn screen_width_dp(&self) -> Option<i32> {
self.config.read().unwrap().screen_width_dp()
}
pub fn screen_long(&self) -> ScreenLong {
self.config.read().unwrap().screen_long()
}
#[cfg(feature = "api-level-30")]
pub fn screen_round(&self) -> ScreenRound {
self.config.read().unwrap().screen_round()
}
pub fn screen_size(&self) -> ScreenSize {
self.config.read().unwrap().screen_size()
}
pub fn sdk_version(&self) -> i32 {
self.config.read().unwrap().sdk_version()
}
pub fn smallest_screen_width_dp(&self) -> Option<i32> {
self.config.read().unwrap().smallest_screen_width_dp()
}
pub fn touchscreen(&self) -> Touchscreen {
self.config.read().unwrap().touchscreen()
}
pub fn ui_mode_night(&self) -> UiModeNight {
self.config.read().unwrap().ui_mode_night()
}
pub fn ui_mode_type(&self) -> UiModeType {
self.config.read().unwrap().ui_mode_type()
}
}
+11 -16
View File
@@ -8,8 +8,7 @@ use std::ops::Deref;
use std::os::raw;
use std::os::unix::prelude::*;
use std::ptr::NonNull;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use std::{ptr, thread};
@@ -25,7 +24,7 @@ use ndk::configuration::Configuration;
use ndk::looper::FdEvent;
use ndk::native_window::NativeWindow;
use crate::{util, AndroidApp, MainEvent, NativeWindowRef, PollEvent, Rect};
use crate::{util, AndroidApp, ConfigurationRef, MainEvent, NativeWindowRef, PollEvent, Rect};
mod ffi;
@@ -121,15 +120,12 @@ impl AndroidApp {
// Note: we don't use from_ptr since we don't own the android_app.config
// and need to keep in mind that the Drop handler is going to call
// AConfiguration_delete()
//
// Whenever we get a ConfigChanged notification we synchronize this
// config state with a deep copy.
let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
Self {
inner: Arc::new(RwLock::new(AndroidAppInner {
native_app: NativeAppGlue { ptr },
config: RwLock::new(config),
config: ConfigurationRef::new(config),
native_window: Default::default(),
})),
}
@@ -153,7 +149,7 @@ unsafe impl Sync for NativeAppGlue {}
#[derive(Debug)]
pub struct AndroidAppInner {
native_app: NativeAppGlue,
config: RwLock<Configuration>,
config: ConfigurationRef,
native_window: RwLock<Option<NativeWindow>>,
}
@@ -254,7 +250,7 @@ impl AndroidAppInner {
MainEvent::LostFocus
}
ffi::NativeAppGlueAppCmd_APP_CMD_CONFIG_CHANGED => {
MainEvent::ConfigChanged
MainEvent::ConfigChanged {}
}
ffi::NativeAppGlueAppCmd_APP_CMD_LOW_MEMORY => {
MainEvent::LowMemory
@@ -282,11 +278,10 @@ impl AndroidAppInner {
trace!("Calling android_app_pre_exec_cmd({cmd_i})");
ffi::android_app_pre_exec_cmd(native_app.as_ptr(), cmd_i);
match cmd {
MainEvent::ConfigChanged => {
*self.config.write().unwrap() =
Configuration::clone_from_ptr(NonNull::new_unchecked(
(*native_app.as_ptr()).config,
));
MainEvent::ConfigChanged { .. } => {
self.config.replace(Configuration::clone_from_ptr(
NonNull::new_unchecked((*native_app.as_ptr()).config),
));
}
MainEvent::InitWindow { .. } => {
let win_ptr = (*native_app.as_ptr()).window;
@@ -354,8 +349,8 @@ impl AndroidAppInner {
}
}
pub fn config(&self) -> Configuration {
self.config.read().unwrap().clone()
pub fn config(&self) -> ConfigurationRef {
self.config.clone()
}
pub fn content_rect(&self) -> Rect {
+7 -4
View File
@@ -5,7 +5,6 @@ use std::time::Duration;
use std::{os::unix::prelude::RawFd, sync::Arc};
use ndk::asset::AssetManager;
use ndk::configuration::Configuration;
// TODO: import FdEvent and avoid depending on ndk Looper abstraction in case we want to
// support using epoll directly in the future.
use ndk::looper::FdEvent;
@@ -36,6 +35,9 @@ use game_activity as activity_impl;
pub use activity_impl::input;
mod config;
pub use config::ConfigurationRef;
mod util;
// Note: unlike in ndk-glue this has signed components (consistent
@@ -148,7 +150,8 @@ pub enum MainEvent<'a> {
/// Command from main thread: the current device configuration has changed.
/// You can get a copy of the latest [Configuration] by calling
/// [`AndroidApp::config()`]
ConfigChanged,
#[non_exhaustive]
ConfigChanged {},
/// Command from main thread: the system is running low on memory.
/// Try to reduce your memory use.
@@ -267,8 +270,8 @@ impl AndroidApp {
self.inner.read().unwrap().create_waker()
}
/// Returns a deep copy of this application's [`Configuration`]
pub fn config(&self) -> Configuration {
/// Returns a (cheaply clonable) reference to this application's [`Configuration`]
pub fn config(&self) -> ConfigurationRef {
self.inner.read().unwrap().config()
}
+15 -18
View File
@@ -7,8 +7,7 @@ use std::ops::Deref;
use std::os::raw;
use std::os::unix::prelude::*;
use std::ptr::NonNull;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use std::{ptr, thread};
@@ -23,7 +22,7 @@ use ndk::input_queue::InputQueue;
use ndk::looper::FdEvent;
use ndk::native_window::NativeWindow;
use crate::{util, AndroidApp, MainEvent, NativeWindowRef, PollEvent, Rect};
use crate::{util, AndroidApp, ConfigurationRef, MainEvent, NativeWindowRef, PollEvent, Rect};
mod ffi;
@@ -123,15 +122,12 @@ impl AndroidApp {
// Note: we don't use from_ptr since we don't own the android_app.config
// and need to keep in mind that the Drop handler is going to call
// AConfiguration_delete()
//
// Whenever we get a ConfigChanged notification we synchronize this
// config state with a deep copy.
let config = Configuration::clone_from_ptr(NonNull::new_unchecked((*ptr.as_ptr()).config));
AndroidApp {
inner: Arc::new(RwLock::new(AndroidAppInner {
native_app: NativeAppGlue { ptr },
config: RwLock::new(config),
config: ConfigurationRef::new(config),
native_window: Default::default(),
})),
}
@@ -155,7 +151,7 @@ unsafe impl Sync for NativeAppGlue {}
#[derive(Debug)]
pub(crate) struct AndroidAppInner {
native_app: NativeAppGlue,
config: RwLock<Configuration>,
config: ConfigurationRef,
native_window: RwLock<Option<NativeWindow>>,
}
@@ -253,7 +249,9 @@ impl AndroidAppInner {
}
ffi::APP_CMD_GAINED_FOCUS => Some(MainEvent::GainedFocus),
ffi::APP_CMD_LOST_FOCUS => Some(MainEvent::LostFocus),
ffi::APP_CMD_CONFIG_CHANGED => Some(MainEvent::ConfigChanged),
ffi::APP_CMD_CONFIG_CHANGED => {
Some(MainEvent::ConfigChanged {})
}
ffi::APP_CMD_LOW_MEMORY => Some(MainEvent::LowMemory),
ffi::APP_CMD_START => Some(MainEvent::Start),
ffi::APP_CMD_RESUME => Some(MainEvent::Resume {
@@ -276,13 +274,12 @@ impl AndroidAppInner {
if let Some(cmd) = cmd {
trace!("Read ID_MAIN command {cmd_i} = {cmd:?}");
match cmd {
MainEvent::ConfigChanged => {
*self.config.write().unwrap() =
Configuration::clone_from_ptr(
NonNull::new_unchecked(
(*native_app.as_ptr()).config,
),
);
MainEvent::ConfigChanged { .. } => {
self.config.replace(Configuration::clone_from_ptr(
NonNull::new_unchecked(
(*native_app.as_ptr()).config,
),
));
}
MainEvent::InitWindow { .. } => {
let win_ptr = (*native_app.as_ptr()).window;
@@ -353,8 +350,8 @@ impl AndroidAppInner {
}
}
pub fn config(&self) -> Configuration {
self.config.read().unwrap().clone()
pub fn config(&self) -> ConfigurationRef {
self.config.clone()
}
pub fn content_rect(&self) -> Rect {
+3
View File
@@ -52,6 +52,9 @@ fn android_main(app: AndroidApp) {
MainEvent::InputAvailable { .. } => {
redraw_pending = true;
}
MainEvent::ConfigChanged { .. } => {
info!("Config Changed: {:#?}", app.config());
}
MainEvent::LowMemory => {}
MainEvent::Destroy => quit = true,
-1
View File
@@ -13,7 +13,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
+3
View File
@@ -52,6 +52,9 @@ fn android_main(app: AndroidApp) {
MainEvent::InputAvailable { .. } => {
redraw_pending = true;
}
MainEvent::ConfigChanged { .. } => {
info!("Config Changed: {:#?}", app.config());
}
MainEvent::LowMemory => {}
MainEvent::Destroy => quit = true,