Supports InputAvailable events with GameActivity

This makes a small change to the C glue code for GameActivity to send
looper wake ups when new input is received (only sending a single wake
up, until the application next handles input).

When a wake up is received and we recognise that new input is available
then an `InputAvailable` event is sent to the application - consistent
with how NativeActivity can deliver `InputAvailable` events.

This addresses a significant feature disparity between GameActivity and
NativeActivity that meant GameActivity was not practically usable for
GUI applications that wouldn't want to render continuously like a game.

Addresses #4
This commit is contained in:
Robert Bragg
2022-08-13 05:42:37 +01:00
parent 00b116bcfe
commit 3d1b1c5cb9
9 changed files with 182 additions and 5 deletions
@@ -449,6 +449,31 @@ void android_app_set_motion_event_filter(struct android_app* app,
pthread_mutex_unlock(&app->mutex);
}
bool android_app_input_available_wake_up(struct android_app* app) {
pthread_mutex_lock(&app->mutex);
// TODO: use atomic ops for this
bool available = app->inputAvailableWakeUp;
app->inputAvailableWakeUp = false;
pthread_mutex_unlock(&app->mutex);
return available;
}
// NB: should be called with the android_app->mutex held already
static void notifyInput(struct android_app* android_app) {
// Don't spam the mainloop with wake ups if we've already sent one
if (android_app->inputSwapPending) {
return;
}
if (android_app->looper != NULL) {
LOGV("Input Notify: %p", android_app);
// for the app thread to know why it received the wake() up
android_app->inputAvailableWakeUp = true;
android_app->inputSwapPending = true;
ALooper_wake(android_app->looper);
}
}
static bool onTouchEvent(GameActivity* activity,
const GameActivityMotionEvent* event,
const GameActivityHistoricalPointerAxes* historical,
@@ -486,12 +511,15 @@ static bool onTouchEvent(GameActivity* activity,
} else {
inputBuffer->motionEvents[new_ix].historicalCount = 0;
}
notifyInput(android_app);
} else {
LOGW_ONCE("Motion event will be dropped because the number of unconsumed motion"
" events exceeded NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS (%d). Consider setting"
" NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS_OVERRIDE to a larger value",
NATIVE_APP_GLUE_MAX_NUM_MOTION_EVENTS);
}
pthread_mutex_unlock(&android_app->mutex);
return true;
}
@@ -512,6 +540,9 @@ struct android_input_buffer* android_app_swap_input_buffers(
NATIVE_APP_GLUE_MAX_INPUT_BUFFERS;
}
android_app->inputSwapPending = false;
android_app->inputAvailableWakeUp = false;
pthread_mutex_unlock(&android_app->mutex);
return inputBuffer;
@@ -547,6 +578,8 @@ static bool onKey(GameActivity* activity, const GameActivityKeyEvent* event) {
memcpy(&inputBuffer->keyEvents[new_ix], event,
sizeof(GameActivityKeyEvent));
++inputBuffer->keyEventsCount;
notifyInput(android_app);
} else {
LOGW_ONCE("Key event will be dropped because the number of unconsumed key events exceeded"
" NATIVE_APP_GLUE_MAX_NUM_KEY_EVENTS (%d). Consider setting"
@@ -295,6 +295,26 @@ struct android_app {
android_key_event_filter keyEventFilter;
android_motion_event_filter motionEventFilter;
// When new input is received we set both of these flags and use the looper to
// wake up the application mainloop.
//
// To avoid spamming the mainloop with wake ups from lots of input though we
// don't sent a wake up if the inputSwapPending flag is already set. (i.e.
// we already expect input to be processed in a finite amount of time due to
// our previous wake up)
//
// When a wake up is received then we will check this flag (clearing it
// at the same time). If it was set then an InputAvailable event is sent to
// the application - which should lead to all input being processed within
// a finite amount of time.
//
// The next time android_app_swap_input_buffers is called, both flags will be
// cleared.
//
// NB: both of these should only be read with the app mutex held
bool inputAvailableWakeUp;
bool inputSwapPending;
/** @endcond */
};
@@ -507,6 +527,10 @@ void android_app_set_key_event_filter(struct android_app* app,
void android_app_set_motion_event_filter(struct android_app* app,
android_motion_event_filter filter);
/**
* Determines if a looper wake up was due to new input becoming available
*/
bool android_app_input_available_wake_up(struct android_app* app);
void GameActivity_onCreate_C(GameActivity* activity, void* savedState,
size_t savedStateSize);
@@ -8437,12 +8437,14 @@ pub struct android_app {
pub pendingContentRect: ARect,
pub keyEventFilter: android_key_event_filter,
pub motionEventFilter: android_motion_event_filter,
pub inputAvailableWakeUp: bool,
pub inputSwapPending: bool,
}
#[test]
fn bindgen_test_layout_android_app() {
assert_eq!(
::std::mem::size_of::<android_app>(),
80904usize,
80912usize,
concat!("Size of: ", stringify!(android_app))
);
assert_eq!(
@@ -8730,6 +8732,28 @@ fn bindgen_test_layout_android_app() {
stringify!(motionEventFilter)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<android_app>())).inputAvailableWakeUp as *const _ as usize
},
80904usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputAvailableWakeUp)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<android_app>())).inputSwapPending as *const _ as usize },
80905usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputSwapPending)
)
);
}
#[doc = " Looper data ID of commands coming from the app's main thread, which"]
#[doc = " is returned as an identifier from ALooper_pollOnce(). The data for this"]
@@ -8869,4 +8893,8 @@ extern "C" {
filter: android_motion_event_filter,
);
}
extern "C" {
#[doc = " Determines if a looper wake up was due to new input becoming available"]
pub fn android_app_input_available_wake_up(app: *mut android_app) -> bool;
}
pub type __uint128_t = u128;
@@ -8906,6 +8906,8 @@ pub struct android_app {
pub pendingContentRect: ARect,
pub keyEventFilter: android_key_event_filter,
pub motionEventFilter: android_motion_event_filter,
pub inputAvailableWakeUp: bool,
pub inputSwapPending: bool,
}
#[test]
fn bindgen_test_layout_android_app() {
@@ -9199,6 +9201,28 @@ fn bindgen_test_layout_android_app() {
stringify!(motionEventFilter)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<android_app>())).inputAvailableWakeUp as *const _ as usize
},
80764usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputAvailableWakeUp)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<android_app>())).inputSwapPending as *const _ as usize },
80765usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputSwapPending)
)
);
}
#[doc = " Looper data ID of commands coming from the app's main thread, which"]
#[doc = " is returned as an identifier from ALooper_pollOnce(). The data for this"]
@@ -9338,3 +9362,7 @@ extern "C" {
filter: android_motion_event_filter,
);
}
extern "C" {
#[doc = " Determines if a looper wake up was due to new input becoming available"]
pub fn android_app_input_available_wake_up(app: *mut android_app) -> bool;
}
+29 -1
View File
@@ -10651,12 +10651,14 @@ pub struct android_app {
pub pendingContentRect: ARect,
pub keyEventFilter: android_key_event_filter,
pub motionEventFilter: android_motion_event_filter,
pub inputAvailableWakeUp: bool,
pub inputSwapPending: bool,
}
#[test]
fn bindgen_test_layout_android_app() {
assert_eq!(
::std::mem::size_of::<android_app>(),
80604usize,
80608usize,
concat!("Size of: ", stringify!(android_app))
);
assert_eq!(
@@ -10944,6 +10946,28 @@ fn bindgen_test_layout_android_app() {
stringify!(motionEventFilter)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<android_app>())).inputAvailableWakeUp as *const _ as usize
},
80604usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputAvailableWakeUp)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<android_app>())).inputSwapPending as *const _ as usize },
80605usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputSwapPending)
)
);
}
#[doc = " Looper data ID of commands coming from the app's main thread, which"]
#[doc = " is returned as an identifier from ALooper_pollOnce(). The data for this"]
@@ -11083,4 +11107,8 @@ extern "C" {
filter: android_motion_event_filter,
);
}
extern "C" {
#[doc = " Determines if a looper wake up was due to new input becoming available"]
pub fn android_app_input_available_wake_up(app: *mut android_app) -> bool;
}
pub type __builtin_va_list = *mut ::std::os::raw::c_char;
@@ -10680,12 +10680,14 @@ pub struct android_app {
pub pendingContentRect: ARect,
pub keyEventFilter: android_key_event_filter,
pub motionEventFilter: android_motion_event_filter,
pub inputAvailableWakeUp: bool,
pub inputSwapPending: bool,
}
#[test]
fn bindgen_test_layout_android_app() {
assert_eq!(
::std::mem::size_of::<android_app>(),
80904usize,
80912usize,
concat!("Size of: ", stringify!(android_app))
);
assert_eq!(
@@ -10973,6 +10975,28 @@ fn bindgen_test_layout_android_app() {
stringify!(motionEventFilter)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<android_app>())).inputAvailableWakeUp as *const _ as usize
},
80904usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputAvailableWakeUp)
)
);
assert_eq!(
unsafe { &(*(::std::ptr::null::<android_app>())).inputSwapPending as *const _ as usize },
80905usize,
concat!(
"Offset of field: ",
stringify!(android_app),
"::",
stringify!(inputSwapPending)
)
);
}
#[doc = " Looper data ID of commands coming from the app's main thread, which"]
#[doc = " is returned as an identifier from ALooper_pollOnce(). The data for this"]
@@ -11112,6 +11136,10 @@ extern "C" {
filter: android_motion_event_filter,
);
}
extern "C" {
#[doc = " Determines if a looper wake up was due to new input becoming available"]
pub fn android_app_input_available_wake_up(app: *mut android_app) -> bool;
}
pub type __builtin_va_list = [__va_list_tag; 1usize];
#[repr(C)]
#[derive(Debug, Copy, Clone)]
@@ -180,6 +180,12 @@ impl AndroidAppInner {
match id {
ffi::ALOOPER_POLL_WAKE => {
trace!("ALooper_pollAll returned POLL_WAKE");
if ffi::android_app_input_available_wake_up(app_ptr.as_ptr()) {
log::debug!("Notifying Input Available");
callback(PollEvent::Main(MainEvent::InputAvailable));
}
callback(PollEvent::Wake);
}
ffi::ALOOPER_POLL_CALLBACK => {
-1
View File
@@ -13,7 +13,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
+4 -1
View File
@@ -11,7 +11,7 @@ fn android_main(app: AndroidApp) {
while !quit {
app.poll_events(
Some(std::time::Duration::from_millis(500)), /* timeout */
Some(std::time::Duration::from_secs(1)), /* timeout */
|event| {
match event {
PollEvent::Wake => {
@@ -49,6 +49,9 @@ fn android_main(app: AndroidApp) {
MainEvent::RedrawNeeded { .. } => {
redraw_pending = true;
}
MainEvent::InputAvailable { .. } => {
redraw_pending = true;
}
MainEvent::LowMemory => {}
MainEvent::Destroy => quit = true,