Files
RetroArch/input/drivers/android_input_ring.h
T
c6046da20f Android: read the input queue on a thread of its own (#19923)
* Android: read the input queue on a thread of its own

Android sends a window its next key only once the window has finished
the last one, and a NativeActivity gets every key twice: before the
IME, marked AKEY_EVENT_FLAG_PREDISPATCH, and after it. Read on the
main thread once a frame, with the first copy finished unhandled for
the IME, each key took two polls, about 30 keys a second at 60 Hz. A
pad mashed faster than that queued up in the system, and its presses
kept arriving seconds after it was let go.

A thread with its own looper now reads the queue. It copies each event
into an SPSC ring and finishes it at once, so Android sends the next
one straight away. The copy before the IME is the one read, unless the
system keyboard is open, when keys still go by the IME first. Volume
keys are left to the system and read once, after the IME. Until the
first input driver starts, events are finished unread. With the ring
full, the reader leaves events in the queue until the poll makes room.

The poll applies the copies in order through the same handlers. A key,
a finger or a mouse button can now go down and back up between two
polls, so the poll stops at the first event that would undo what an
earlier one did in the same poll and leaves the rest for the next
(android_poll_changes.h): a press shorter than a frame is still seen
for one. Mouse moves in one poll are summed.

The reader answers APP_CMD_INPUT_CHANGED once it has let go of the old
queue. The AMotionEvent calls looked up with dlsym are looked up once,
before the reader starts, since it reads them too.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01YTBjv1trXRxAXvNwCt89Ka

* Android input reader: records of their own size, read where they lie

Each event went through the input reader's ring as a 312-byte record,
whatever it was, built on the reader's stack, copied into the ring and
copied out again by the poll. A key needs 44 bytes of it and a stick's
move 88.

A record is now as long as its event: a key ends before the stick
axes, a stick's move before the touch fields, and a touch, mouse or
pen carries only the pointers it has. The reader builds each where the
poll will read it, and the poll applies it there
(android_input_ring.h); nothing is copied after the event leaves the
input queue. A record never crosses the ring's end: the rest of a lap
is a pad the poll steps over. The ring is 64 KiB, some 1500 keys.

A stick's moves are its state: the poll keeps only the last of a frame.
The reader publishes what it writes once the queue is empty, and a
stick's move written over its unpublished move before it, as long as
the hat stays where it was, so a fast pad no longer fills the ring and
holds back keys behind it.

A hat, the D-pad of many pads, tapped between two polls was lost: the
poll's hold-back covered keys, fingers and mouse buttons only. Its four
directions are kept back the same way now.

android_input_ring_test holds the ring to reading in place, records
whole across laps, merging only unpublished moves, and two threads;
android_poll_changes_test has a hat lane.

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: LibretroAdmin <[email protected]>
2026-10-27 04:10:15 +01:00

219 lines
6.6 KiB
C

/* RetroArch - A frontend for libretro.
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _ANDROID_INPUT_RING_H
#define _ANDROID_INPUT_RING_H
#include <stddef.h>
#include <stdint.h>
#include <boolean.h>
#include <retro_inline.h>
#include <retro_spsc.h>
/* The input reader's ring (input/drivers/android_input.c): records of
* the size each event needs, built by the reader where the poll reads
* them, and applied by the poll where they lie. Nothing is copied
* after the event is read from the input queue.
*
* A record is a multiple of 8 bytes and never crosses the ring's end:
* where the next would not fit before it, the rest of the lap is a pad
* record the poll steps over. The ring's capacity is a power of two,
* so a header is never split.
*
* The reader builds a burst of records and publishes them together. A
* record with a merge key replaces the one before it, while that one
* is unpublished and has the same key: a stick's moves, whose last is
* all the poll would keep. Records with no key (0) are never merged. */
typedef struct android_input_rec
{
uint32_t size; /* the whole record, a multiple of 8 */
uint32_t merge; /* 0, a merge key, or ANDROID_INPUT_REC_PAD */
} android_input_rec_t;
#define ANDROID_INPUT_REC_PAD 0xffffffffu
#define ANDROID_INPUT_REC_NONE ((size_t)-1)
static INLINE size_t android_input_rec_round(size_t bytes)
{
return (bytes + 7) & ~(size_t)7;
}
/* ---- the reader's side ---- */
typedef struct android_input_ring_writer
{
retro_spsc_t *q;
uint8_t *span; /* write_begin's, or NULL */
size_t room; /* its length */
size_t used; /* built in it, not yet published */
size_t last; /* the last record built, or ANDROID_INPUT_REC_NONE */
size_t slot; /* where the record being built is */
} android_input_ring_writer_t;
static INLINE void android_input_ring_writer_init(
android_input_ring_writer_t *w, retro_spsc_t *q)
{
w->q = q;
w->span = NULL;
w->room = 0;
w->used = 0;
w->last = ANDROID_INPUT_REC_NONE;
w->slot = 0;
}
/* What was built, to the poll */
static INLINE void android_input_ring_publish(android_input_ring_writer_t *w)
{
if (w->span)
retro_spsc_write_end(w->q, w->used);
w->span = NULL;
w->room = 0;
w->used = 0;
w->last = ANDROID_INPUT_REC_NONE;
}
/* Where to build a record of at most @max bytes with @merge: over the
* last one built when it has the same key, else after it. NULL when
* the ring has no room for @max; what was built is published then. */
static INLINE void *android_input_ring_slot(android_input_ring_writer_t *w,
size_t max, uint32_t merge)
{
void *ptr;
if ( merge && w->last != ANDROID_INPUT_REC_NONE
&& ((android_input_rec_t*)(w->span + w->last))->merge == merge)
{
w->slot = w->last;
return w->span + w->last;
}
if (!w->span || w->room - w->used < max)
{
android_input_ring_publish(w);
w->room = retro_spsc_write_begin(w->q, &ptr);
w->span = (uint8_t*)ptr;
if (w->room < max)
{
/* at the end of the lap, with room after it: a pad to the end */
if ( w->room
&& retro_spsc_write_avail(w->q) >= w->room + max)
{
android_input_rec_t *pad = (android_input_rec_t*)w->span;
pad->size = (uint32_t)w->room;
pad->merge = ANDROID_INPUT_REC_PAD;
retro_spsc_write_end(w->q, w->room);
w->room = retro_spsc_write_begin(w->q, &ptr);
w->span = (uint8_t*)ptr;
}
if (w->room < max)
{
if (w->span)
retro_spsc_write_end(w->q, 0);
w->span = NULL;
w->room = 0;
return NULL;
}
}
}
w->slot = w->used;
return w->span + w->used;
}
/* The record at the last slot is built, @size bytes */
static INLINE void android_input_ring_built(android_input_ring_writer_t *w,
size_t size, uint32_t merge)
{
android_input_rec_t *rec = (android_input_rec_t*)(w->span + w->slot);
rec->size = (uint32_t)android_input_rec_round(size);
rec->merge = merge;
if (w->slot == w->used)
{
w->last = w->used;
w->used += rec->size;
}
}
/* ---- the poll's side ---- */
typedef struct android_input_ring_reader
{
retro_spsc_t *q;
const uint8_t *span;
size_t left; /* read_begin's length */
size_t done; /* applied of it */
bool took; /* anything given back this poll */
} android_input_ring_reader_t;
static INLINE void android_input_ring_reader_begin(
android_input_ring_reader_t *r, retro_spsc_t *q)
{
const void *ptr;
r->q = q;
r->left = retro_spsc_read_begin(q, &ptr);
r->span = (const uint8_t*)ptr;
r->done = 0;
r->took = false;
}
/* The next record, where it lies, or NULL when there is none */
static INLINE const android_input_rec_t *android_input_ring_next(
android_input_ring_reader_t *r)
{
for (;;)
{
const android_input_rec_t *rec;
if (r->left - r->done < sizeof(android_input_rec_t))
{
const void *ptr;
if (!r->done)
return NULL;
/* the rest of the lap is done: on from the start */
retro_spsc_read_end(r->q, r->done);
r->took = true;
r->left = retro_spsc_read_begin(r->q, &ptr);
r->span = (const uint8_t*)ptr;
r->done = 0;
continue;
}
rec = (const android_input_rec_t*)(r->span + r->done);
if (rec->merge != ANDROID_INPUT_REC_PAD)
return rec;
r->done += rec->size;
}
}
/* The record android_input_ring_next() gave is applied */
static INLINE void android_input_ring_taken(android_input_ring_reader_t *r,
const android_input_rec_t *rec)
{
r->done += rec->size;
}
/* Gives back what was applied; true if anything was this poll */
static INLINE bool android_input_ring_reader_end(
android_input_ring_reader_t *r)
{
if (r->done)
{
retro_spsc_read_end(r->q, r->done);
r->took = true;
}
r->done = 0;
r->left = 0;
return r->took;
}
#endif