mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
input: keyboard events from another thread wait for the poll
input_keyboard_event() acts on a key at once: the menu, the on-screen keyboard, the frontend's own key handling, and the core's keyboard callback. All of that belongs to the thread the frontend and the core run on. Most drivers report their keys from the poll, on that thread. The Windows window procedure and X11's event loop report them from where the window's messages are handled, and with threaded video that is the video thread. So with threaded video and any Windows input driver but raw input (which reads at the poll), every key pressed ran the menu's and the frontend's key handling on the video thread, and called the core's keyboard callback there, while the core ran its frame on the other. Run for real - retroarch.exe under Wine, DirectInput, threaded video, a core that reports the thread its callback is called on - five key events of five came on a thread that was not the core's. With threaded video off, or raw input, none. An event reported on another thread now goes into a queue, and the next input_driver_poll(), on the frontend's thread, takes the queue in order and does with each what used to be done at once. Reported on the frontend's own thread, an event is acted on at once as before, so every driver that reports from the poll is unchanged. The window's thread hands its keys over once a frame anyway, so the poll that takes them is the first that could have seen them. The poll is the one reader and takes no lock. Writers take turns through a flag; there is one in practice. The queue holds sixty-four events; with no poll for that long the newest are dropped, counted, and a warning is logged once. This is the first of the plan's event lanes (WP-04). samples/runloop/frontend_overhead, a new lane: keys reported from a second thread while frames run reach the core's callback all of them, in order, on the core's thread; reported on the frontend's thread they are acted on at once; a hundred with no poll between leaves the core sixty-four, in order, and thirty-six counted as dropped. samples/input/windows_wine_smoke: the test core says so in the log if a key event reaches it on a thread other than the one retro_run() is on, and every scenario wants none. All seven pass with retroarch.exe built from this; the DirectInput one did not before.
This commit is contained in:
1 parent
31f2d6b4e2
commit
9b9b659eda
5 files changed
+289
-4
No files matched your search
@@ -8612,6 +8612,10 @@ void input_driver_read_sensor_snapshot(float *gyro3,
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
static void input_key_lane_take(void);
|
||||
#endif
|
||||
|
||||
void input_driver_poll(void)
|
||||
{
|
||||
size_t i, j;
|
||||
@@ -8644,6 +8648,11 @@ void input_driver_poll(void)
|
||||
if (input && input->poll)
|
||||
input->poll(input_st->current_data);
|
||||
|
||||
#ifdef HAVE_THREADS
|
||||
/* the keys another thread has reported since the last poll */
|
||||
input_key_lane_take();
|
||||
#endif
|
||||
|
||||
/* When the devices were read. The statistics show how old the input
|
||||
* a frame was made from is by the time that frame is on screen, and
|
||||
* measure from here - wherever the poll mode puts this call: before
|
||||
@@ -10023,8 +10032,134 @@ static const char *accessibility_lut_name(char key)
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Keyboard events from a thread that is not the frontend's.
|
||||
*
|
||||
* input_keyboard_event() reaches into the menu, the on-screen
|
||||
* keyboard, the frontend's own key handling, and calls the core's
|
||||
* keyboard callback. All of that belongs to the thread the frontend
|
||||
* and the core run on. Most drivers report their keys from the poll,
|
||||
* on that thread. Some report them from where the window's messages
|
||||
* are handled, and with threaded video that is the video thread: the
|
||||
* Windows window procedure (every Windows input driver but raw input,
|
||||
* which reads at the poll) and X11's event loop. A key pressed there
|
||||
* used to run all of the above on the video thread, the core's
|
||||
* callback included, while the core ran its frame on the other.
|
||||
*
|
||||
* An event that comes on another thread is put in a queue instead, and
|
||||
* the next input_driver_poll() - on the frontend's thread - takes the
|
||||
* queue in order and does with each what would have been done at once.
|
||||
* The window's thread hands its keys over once a frame as it is, so
|
||||
* the poll that takes them is the first that could have seen them.
|
||||
*
|
||||
* One reader, the poll, which takes no lock. Writers take turns
|
||||
* through a flag; in practice there is one, the video thread. Sixty-
|
||||
* four events between two polls is more than a keyboard sends; past
|
||||
* that the newest are dropped, counted and said. */
|
||||
#ifdef HAVE_THREADS
|
||||
#define INPUT_KEY_LANE_SIZE 64 /* a power of two */
|
||||
|
||||
static struct
|
||||
{
|
||||
struct
|
||||
{
|
||||
uint32_t character;
|
||||
unsigned code;
|
||||
unsigned device;
|
||||
uint16_t mod;
|
||||
bool down;
|
||||
} slot[INPUT_KEY_LANE_SIZE];
|
||||
retro_atomic_int_t head; /* next to take: the poll's */
|
||||
retro_atomic_int_t tail; /* next to fill: the writers' */
|
||||
retro_atomic_int_t writing; /* a writer is in */
|
||||
retro_atomic_int_t dropped;
|
||||
bool warned;
|
||||
} input_key_lane;
|
||||
|
||||
static void input_key_lane_push(bool down, unsigned code,
|
||||
uint32_t character, uint16_t mod, unsigned device)
|
||||
{
|
||||
int head, tail;
|
||||
|
||||
while (retro_atomic_exchange_int(&input_key_lane.writing, 1))
|
||||
; /* another writer: there is not one in practice */
|
||||
|
||||
head = retro_atomic_load_acquire_int(&input_key_lane.head);
|
||||
tail = retro_atomic_load_relaxed_int(&input_key_lane.tail);
|
||||
if ((unsigned)(tail - head) >= INPUT_KEY_LANE_SIZE)
|
||||
retro_atomic_fetch_add_int(&input_key_lane.dropped, 1);
|
||||
else
|
||||
{
|
||||
unsigned i = (unsigned)tail
|
||||
& (INPUT_KEY_LANE_SIZE - 1);
|
||||
input_key_lane.slot[i].down = down;
|
||||
input_key_lane.slot[i].code = code;
|
||||
input_key_lane.slot[i].character = character;
|
||||
input_key_lane.slot[i].mod = mod;
|
||||
input_key_lane.slot[i].device = device;
|
||||
retro_atomic_store_release_int(&input_key_lane.tail, tail + 1);
|
||||
}
|
||||
|
||||
retro_atomic_store_release_int(&input_key_lane.writing, 0);
|
||||
}
|
||||
|
||||
static void input_keyboard_event_now(bool down, unsigned code,
|
||||
uint32_t character, uint16_t mod, unsigned device);
|
||||
|
||||
/* The poll's: every event that has come since the last one. */
|
||||
static void input_key_lane_take(void)
|
||||
{
|
||||
int head = retro_atomic_load_relaxed_int(&input_key_lane.head);
|
||||
int tail = retro_atomic_load_acquire_int(&input_key_lane.tail);
|
||||
|
||||
while (head != tail)
|
||||
{
|
||||
unsigned i = (unsigned)head & (INPUT_KEY_LANE_SIZE - 1);
|
||||
bool down = input_key_lane.slot[i].down;
|
||||
unsigned code = input_key_lane.slot[i].code;
|
||||
uint32_t character = input_key_lane.slot[i].character;
|
||||
uint16_t mod = input_key_lane.slot[i].mod;
|
||||
unsigned device = input_key_lane.slot[i].device;
|
||||
|
||||
/* the slot is free before the event is acted on */
|
||||
retro_atomic_store_release_int(&input_key_lane.head, ++head);
|
||||
input_keyboard_event_now(down, code, character, mod, device);
|
||||
}
|
||||
|
||||
if ( !input_key_lane.warned
|
||||
&& retro_atomic_load_relaxed_int(&input_key_lane.dropped))
|
||||
{
|
||||
input_key_lane.warned = true;
|
||||
RARCH_WARN("[Input] More than %d keyboard events came between two"
|
||||
" polls: the newest were dropped.\n", INPUT_KEY_LANE_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned input_driver_key_events_dropped(void)
|
||||
{
|
||||
return (unsigned)retro_atomic_load_relaxed_int(&input_key_lane.dropped);
|
||||
}
|
||||
#endif
|
||||
|
||||
void input_keyboard_event(bool down, unsigned code,
|
||||
uint32_t character, uint16_t mod, unsigned device)
|
||||
{
|
||||
#ifdef HAVE_THREADS
|
||||
if (!task_is_on_main_thread())
|
||||
{
|
||||
input_key_lane_push(down, code, character, mod, device);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
input_keyboard_event_now(down, code, character, mod, device);
|
||||
}
|
||||
|
||||
#ifndef HAVE_THREADS
|
||||
static void input_keyboard_event_now(bool down, unsigned code,
|
||||
uint32_t character, uint16_t mod, unsigned device);
|
||||
#endif
|
||||
|
||||
static void input_keyboard_event_now(bool down, unsigned code,
|
||||
uint32_t character, uint16_t mod, unsigned device)
|
||||
{
|
||||
runloop_state_t *runloop_st = runloop_state_get_ptr();
|
||||
retro_keyboard_event_t
|
||||
|
||||
@@ -975,6 +975,11 @@ void input_pad_connect(unsigned port, input_device_driver_t *driver);
|
||||
void input_keyboard_event(bool down, unsigned code, uint32_t character,
|
||||
uint16_t mod, unsigned device);
|
||||
|
||||
/* Keyboard events reported from another thread wait for the poll in a
|
||||
* queue (see input_keyboard_event() in input_driver.c); this is how
|
||||
* many were dropped because it was full. */
|
||||
unsigned input_driver_key_events_dropped(void);
|
||||
|
||||
input_driver_state_t *input_state_get_ptr(void);
|
||||
|
||||
/*************************************/
|
||||
|
||||
@@ -19,8 +19,11 @@
|
||||
# quit with Escape. What is checked is what the log says happened. In
|
||||
# every scenario: RetroArch quit on the Escape pressed after the
|
||||
# content was closed; the core saw B pressed and released before the
|
||||
# restart and after it; and it was given J's key-down and key-up, in
|
||||
# that order, both times. And by scenario:
|
||||
# restart and after it; it was given J's key-down and key-up, in that
|
||||
# order, both times; and every key event it was given came on the
|
||||
# thread it runs on - with threaded video the window's messages are
|
||||
# handled on the video thread, and DirectInput's keys used to reach
|
||||
# the core's keyboard callback from there. And by scenario:
|
||||
#
|
||||
# read by the poll, kept the default. The input driver starts
|
||||
# once and is restarted neither by the
|
||||
@@ -173,7 +176,7 @@ failures=0
|
||||
# passes through
|
||||
scenario() {
|
||||
local name=$1 threaded=$2 want_bulk=$3 want_joy=$4 nomsg=$5
|
||||
local log="$work/$name.log" try rc video bulk joy msg ok b_down b_up j_seq stale win
|
||||
local log="$work/$name.log" try rc video bulk joy msg ok b_down b_up j_seq stale win off
|
||||
write_cfg "$threaded" "${6:-raw}" "${7:-winraw_joypad}"
|
||||
# key delivery on a virtual display with no window manager is not
|
||||
# exact: a scenario gets a second go before it counts as failed
|
||||
@@ -198,6 +201,8 @@ scenario() {
|
||||
[ "$joy" = "$want_joy" ] || ok=no
|
||||
[ "$b_down" = 2 ] && [ "$b_up" = 2 ] || ok=no
|
||||
[ "$j_seq" = "down up down up " ] || ok=no
|
||||
off=$(count "$log" "not the core's")
|
||||
[ "$off" = 0 ] || ok=no
|
||||
[ "$nomsg" != nomsg ] || [ "$msg" = 0 ] || ok=no
|
||||
# the old path measures the wait the new one removes; the new
|
||||
# one has nothing to measure
|
||||
@@ -222,7 +227,8 @@ scenario() {
|
||||
"joypad driver started $joy (want $want_joy);" \
|
||||
"lines on how old the state was at the poll: $stale;" \
|
||||
"window left up, taken, not taken: $win (want ${WANT_WINDOW:-2 2 0});" \
|
||||
"the core saw B pressed $b_down and released $b_up (want 2 and 2), J: ${j_seq:-nothing}(want down up down up)" \
|
||||
"the core saw B pressed $b_down and released $b_up (want 2 and 2), J: ${j_seq:-nothing}(want down up down up)," \
|
||||
"key events on a thread that is not the core's: $off (want 0)" \
|
||||
"$([ "$nomsg" = nomsg ] && echo ", driver instances with reports taken as messages: $msg (want 0)")"
|
||||
sed 's/\x1b\[[0-9;]*m//g' "$log" | tr -d '\r' | grep -av "ALSA lib\|Playlist\]" | tail -25
|
||||
failures=$((failures + 1))
|
||||
|
||||
@@ -14,6 +14,13 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
/* the thread retro_run() is on: a key event that comes on another has
|
||||
* been handed to the core behind its back */
|
||||
static DWORD run_thread;
|
||||
#endif
|
||||
|
||||
#include <libretro.h>
|
||||
|
||||
static retro_environment_t environ_cb;
|
||||
@@ -43,6 +50,11 @@ static void RETRO_CALLCONV on_key(bool down, unsigned keycode,
|
||||
(void)character;
|
||||
say("key event: %s keycode %u mods %u", down ? "down" : "up",
|
||||
keycode, (unsigned)mods);
|
||||
#ifdef _WIN32
|
||||
if (run_thread && GetCurrentThreadId() != run_thread)
|
||||
say("key event on thread %lu, not the core's (%lu)",
|
||||
(unsigned long)GetCurrentThreadId(), (unsigned long)run_thread);
|
||||
#endif
|
||||
}
|
||||
|
||||
void retro_set_environment(retro_environment_t cb)
|
||||
@@ -93,6 +105,9 @@ void retro_run(void)
|
||||
{
|
||||
int b, a;
|
||||
|
||||
#ifdef _WIN32
|
||||
run_thread = GetCurrentThreadId();
|
||||
#endif
|
||||
poll_cb();
|
||||
b = state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_B) ? 1 : 0;
|
||||
a = state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_A) ? 1 : 0;
|
||||
|
||||
@@ -88,6 +88,8 @@
|
||||
#include <boolean.h>
|
||||
#include <features/features_cpu.h>
|
||||
#include <time/rtime.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <retro_timers.h>
|
||||
#include "../../../runloop.h"
|
||||
#include "../../../runahead.h"
|
||||
#ifdef HAVE_NETWORKING
|
||||
@@ -1729,6 +1731,127 @@ static void lane_input_kept(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---- keyboard events reported from another thread ------------------ */
|
||||
|
||||
/* The Windows window procedure and X11's event loop report keys from
|
||||
* where the window's messages are handled, which with threaded video
|
||||
* is the video thread. input_keyboard_event() used to act on them
|
||||
* there - menu, frontend, and the core's keyboard callback, on a
|
||||
* thread the core does not run on. They wait for the poll now. This
|
||||
* reports keys from a second thread and looks at where and in what
|
||||
* order the core's callback gets them. */
|
||||
#define KEYLANE_EVENTS 40
|
||||
|
||||
static uintptr_t keylane_main_thread;
|
||||
static unsigned keylane_got, keylane_off_thread, keylane_out_of_order;
|
||||
|
||||
static void RETRO_CALLCONV keylane_core_cb(bool down, unsigned keycode,
|
||||
uint32_t character, uint16_t mods)
|
||||
{
|
||||
(void)mods;
|
||||
if (keycode != RETROK_F14)
|
||||
return;
|
||||
if (sthread_get_current_thread_id() != keylane_main_thread)
|
||||
keylane_off_thread++;
|
||||
/* the sender numbers them, and sends down, up, down, ... */
|
||||
if ( character != keylane_got
|
||||
|| down != ((keylane_got & 1) == 0))
|
||||
keylane_out_of_order++;
|
||||
keylane_got++;
|
||||
}
|
||||
|
||||
static void keylane_sender(void *data)
|
||||
{
|
||||
unsigned i, n = *(unsigned*)data;
|
||||
bool pause = (n == KEYLANE_EVENTS);
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
input_keyboard_event((i & 1) == 0, RETROK_F14, i, 0,
|
||||
RETRO_DEVICE_KEYBOARD);
|
||||
if (pause)
|
||||
retro_sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void lane_key_events(void)
|
||||
{
|
||||
runloop_state_t *runloop_st = runloop_state_get_ptr();
|
||||
retro_keyboard_event_t saved = runloop_st->key_event;
|
||||
unsigned had = failures;
|
||||
unsigned dropped0, n, i;
|
||||
sthread_t *thr;
|
||||
|
||||
keylane_main_thread = sthread_get_current_thread_id();
|
||||
runloop_st->key_event = keylane_core_cb;
|
||||
fast_forward(true);
|
||||
run_frames(5);
|
||||
|
||||
/* 1. Reported on this thread: acted on at once, as ever. */
|
||||
keylane_got = keylane_off_thread = keylane_out_of_order = 0;
|
||||
input_keyboard_event(true, RETROK_F14, 0, 0, RETRO_DEVICE_KEYBOARD);
|
||||
input_keyboard_event(false, RETROK_F14, 1, 0, RETRO_DEVICE_KEYBOARD);
|
||||
CHECK(keylane_got == 2 && !keylane_out_of_order,
|
||||
"key events: one reported on the frontend's thread was not acted on at once");
|
||||
|
||||
/* 2. Reported on another thread while frames run: the core gets
|
||||
* every one, in order, and on its own thread. */
|
||||
keylane_got = keylane_off_thread = keylane_out_of_order = 0;
|
||||
dropped0 = input_driver_key_events_dropped();
|
||||
n = KEYLANE_EVENTS;
|
||||
thr = sthread_create(keylane_sender, &n);
|
||||
CHECK(thr != NULL, "key events: no second thread to report from");
|
||||
if (thr)
|
||||
{
|
||||
for (i = 0; i < 400 && keylane_got < KEYLANE_EVENTS; i++)
|
||||
{
|
||||
run_frames(1);
|
||||
retro_sleep(1);
|
||||
}
|
||||
sthread_join(thr);
|
||||
run_frames(2);
|
||||
}
|
||||
if (keylane_got != KEYLANE_EVENTS || keylane_off_thread || keylane_out_of_order)
|
||||
fprintf(stderr, " %u of %u reached the core, %u on another thread, %u out of order\n",
|
||||
keylane_got, KEYLANE_EVENTS, keylane_off_thread, keylane_out_of_order);
|
||||
CHECK(keylane_got == KEYLANE_EVENTS,
|
||||
"key events: reported on another thread, not all reached the core");
|
||||
CHECK(keylane_off_thread == 0,
|
||||
"key events: the core's keyboard callback was called on a thread that is not the core's");
|
||||
CHECK(keylane_out_of_order == 0,
|
||||
"key events: reported on another thread, they reached the core out of order");
|
||||
CHECK(input_driver_key_events_dropped() == dropped0,
|
||||
"key events: some were dropped with a poll every frame");
|
||||
|
||||
/* 3. A hundred with no poll in between: the queue holds
|
||||
* sixty-four, the rest are dropped and counted, and nothing is
|
||||
* acted on until the poll. */
|
||||
keylane_got = keylane_off_thread = keylane_out_of_order = 0;
|
||||
n = 100;
|
||||
thr = sthread_create(keylane_sender, &n);
|
||||
if (thr)
|
||||
{
|
||||
sthread_join(thr);
|
||||
CHECK(keylane_got == 0,
|
||||
"key events: reported on another thread, acted on before the poll");
|
||||
run_frames(2);
|
||||
if (keylane_got != 64 || input_driver_key_events_dropped() - dropped0 != 36)
|
||||
fprintf(stderr, " %u reached the core, %u dropped\n", keylane_got,
|
||||
input_driver_key_events_dropped() - dropped0);
|
||||
CHECK(keylane_got == 64 && !keylane_off_thread && !keylane_out_of_order,
|
||||
"key events: a full queue did not give the core its sixty-four, in order");
|
||||
CHECK(input_driver_key_events_dropped() - dropped0 == 36,
|
||||
"key events: those that did not fit were not counted");
|
||||
}
|
||||
|
||||
runloop_st->key_event = saved;
|
||||
fast_forward(false);
|
||||
run_frames(2);
|
||||
if (failures == had)
|
||||
printf("[pass] key events: from the frontend's thread acted on at once;"
|
||||
" from another, the core gets all of them in order on its own thread"
|
||||
" at the next poll; a full queue drops the newest and counts them\n");
|
||||
}
|
||||
|
||||
static void lane_frame_cost(void)
|
||||
{
|
||||
unsigned had = failures;
|
||||
@@ -1931,6 +2054,7 @@ int main(int argc, char *argv[])
|
||||
lane_input_poll_sites(load_poll_name[load_poll]);
|
||||
lane_output_store();
|
||||
lane_core_view();
|
||||
lane_key_events();
|
||||
/* last: it restarts the drivers */
|
||||
lane_input_kept();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user