mirror of
https://github.com/rust-mobile/android-activity.git
synced 2026-07-10 08:44:11 +00:00
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:
+33
@@ -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"
|
||||
|
||||
+24
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
Generated
-1
@@ -13,7 +13,6 @@
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user