From c01a199493b048d6436a6305148903c861ef73e1 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 13 Sep 2017 11:39:41 -0400 Subject: [PATCH] Netplay input device abstraction, support for mice This abstracts away the details of particular input devices for netplay, and adds support for mice and (similar) lightguns. Unfortunately, in practice, no core handles mice or lightguns in a savestate-safe way, so they need to be used in stateless mode to be reliable, but they do work. --- network/netplay/README | 37 +++++++++++ network/netplay/netplay_delta.c | 20 ++++-- network/netplay/netplay_frontend.c | 94 ++++++++++++++++++++++------ network/netplay/netplay_handshake.c | 4 +- network/netplay/netplay_init.c | 16 ++++- network/netplay/netplay_io.c | 19 +++--- network/netplay/netplay_private.h | 6 +- network/netplay/netplay_sync.c | 97 ++++++++++++++++++++--------- 8 files changed, 227 insertions(+), 66 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 00291d1850..dda433a9b5 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -357,3 +357,40 @@ Unused Command: CFG_ACK Unused + + +Input types + +Each input device uses a number of words fixed by the type of device. When +buttons are listed, they are listed from lowest bit to highest bit, i.e., +reverse order. When insufficient buttons are present to represent to represent +a full 32-bit word, the remainder are reserved and unused. + +JOYPAD + { + B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R, L2, R2, L3, R3 + } + +MOUSE + { + unused, unused, Left, Right, Wheel up, Wheel down, Middle, Horizontal + wheel up, Horizontal wheel down + Y: int16 + X: int16 + } + +LIGHTGUN + { + unused, unused, Trigger, Cursor, Turbo, Pause, Start + Y: int16 + X: int16 + } + +ANALOG + { + buttons: uint32, as JOYPAD + Left analog Y: int16 + Left analog X: int16 + Right analog Y: int16 + Right analog X: int16 + } diff --git a/network/netplay/netplay_delta.c b/network/netplay/netplay_delta.c index 7828cb9f9a..de4737b7c1 100644 --- a/network/netplay/netplay_delta.c +++ b/network/netplay/netplay_delta.c @@ -167,14 +167,22 @@ netplay_input_state_t netplay_input_state_for(netplay_input_state_t *list, * * Size in words for a given set of devices. */ -uint32_t netplay_expected_input_size(uint32_t devices) +uint32_t netplay_expected_input_size(netplay_t *netplay, uint32_t devices) { - /* FIXME: For now, we assume all devices are three words, because in the implementation, they are. */ - uint32_t ret = 0; - while (devices) + uint32_t ret = 0, device; + for (device = 0; device < MAX_INPUT_DEVICES; device++) { - if (devices & 1) ret += 3; - devices >>= 1; + if (!(devices & (1<config_devices[device]&RETRO_DEVICE_MASK) + { + /* These are all essentially magic numbers, but each device has a + * fixed size, documented in network/netplay/README */ + case RETRO_DEVICE_JOYPAD: ret += 1; break; + case RETRO_DEVICE_MOUSE: ret += 2; break; + case RETRO_DEVICE_LIGHTGUN: ret += 2; break; + case RETRO_DEVICE_ANALOG: ret += 3; break; + default: break; /* Unsupported */ + } } return ret; } diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index e2124fc7d9..89b914f646 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -137,41 +137,70 @@ static bool get_self_input_state(netplay_t *netplay) continue; /* Find an appropriate local device */ - dev_type = input_config_get_device(devi)&RETRO_DEVICE_MASK; + dev_type = netplay->config_devices[devi]&RETRO_DEVICE_MASK; for (local_device = 0; local_device < MAX_INPUT_DEVICES; local_device++) { if (used_devices & (1<config_devices[local_device]&RETRO_DEVICE_MASK) == dev_type) break; } if (local_device == MAX_INPUT_DEVICES) local_device = 0; used_devices |= (1<real_input[devi], - netplay->self_client_num, 3 /* FIXME */, true, false); + netplay->self_client_num, netplay_expected_input_size(netplay, 1 << devi), + true, false); if (!istate) continue; /* FIXME: More severe? */ + /* First frame we always give zero input since relying on + * input from first frame screws up when we use -F 0. */ if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) { - /* First frame we always give zero input since relying on - * input from first frame screws up when we use -F 0. */ uint32_t *state = istate->data; retro_input_state_t cb = netplay->cbs.state_cb; - for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) - { - int16_t tmp = cb(local_device, - RETRO_DEVICE_JOYPAD, 0, (unsigned)i); - state[0] |= tmp ? 1 << i : 0; - } + unsigned dtype = netplay->config_devices[devi]&RETRO_DEVICE_MASK; - for (i = 0; i < 2; i++) + switch (dtype) { - int16_t tmp_x = cb(local_device, - RETRO_DEVICE_ANALOG, (unsigned)i, 0); - int16_t tmp_y = cb(local_device, - RETRO_DEVICE_ANALOG, (unsigned)i, 1); - state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); + case RETRO_DEVICE_ANALOG: + for (i = 0; i < 2; i++) + { + int16_t tmp_x = cb(local_device, + RETRO_DEVICE_ANALOG, (unsigned)i, 0); + int16_t tmp_y = cb(local_device, + RETRO_DEVICE_ANALOG, (unsigned)i, 1); + state[1 + i] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); + } + /* no break */ + + case RETRO_DEVICE_JOYPAD: + for (i = 0; i <= RETRO_DEVICE_ID_JOYPAD_R3; i++) + { + int16_t tmp = cb(local_device, + RETRO_DEVICE_JOYPAD, 0, (unsigned)i); + state[0] |= tmp ? 1 << i : 0; + } + break; + + case RETRO_DEVICE_MOUSE: + case RETRO_DEVICE_LIGHTGUN: + { + int16_t tmp_x = cb(local_device, dtype, 0, 0); + int16_t tmp_y = cb(local_device, dtype, 0, 1); + state[1] = (uint16_t)tmp_x | (((uint16_t)tmp_y) << 16); + for (i = 2; + i <= ((dtype == RETRO_DEVICE_MOUSE) ? + RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN : + RETRO_DEVICE_ID_LIGHTGUN_START); + i++) + { + int16_t tmp = cb(local_device, dtype, 0, + (unsigned) i); + state[0] |= tmp ? 1 << i : 0; + } + break; + } } } } @@ -504,8 +533,20 @@ static int16_t netplay_input_state(netplay_t *netplay, const uint32_t *curr_input_state = NULL; if (port >= MAX_INPUT_DEVICES) - { return 0; + + /* If the port doesn't seem to correspond to the device, "correct" it. This + * is common with e.g. zappers. */ + if (device != RETRO_DEVICE_JOYPAD && + (netplay->config_devices[port]&RETRO_DEVICE_MASK) != device) + { + for (port = 0; port < MAX_INPUT_DEVICES; port++) + { + if ((netplay->config_devices[port]&RETRO_DEVICE_MASK) == device) + break; + } + if (port == MAX_INPUT_DEVICES) + return 0; } delta = &netplay->buffer[ptr]; @@ -513,6 +554,8 @@ static int16_t netplay_input_state(netplay_t *netplay, if (!istate || !istate->used) return 0; + if (istate->size == 0) + return 0; curr_input_state = istate->data; switch (device) @@ -522,10 +565,23 @@ static int16_t netplay_input_state(netplay_t *netplay, case RETRO_DEVICE_ANALOG: { - uint32_t state = curr_input_state[1 + idx]; + uint32_t state; + if (istate->size != 3) + return 0; + state = curr_input_state[1 + idx]; return (int16_t)(uint16_t)(state >> (id * 16)); } + case RETRO_DEVICE_MOUSE: + case RETRO_DEVICE_LIGHTGUN: + { + if (istate->size != 2) + return 0; + if (id <= RETRO_DEVICE_ID_MOUSE_Y) + return (int16_t)(uint16_t)(curr_input_state[1] >> (id * 16)); + return ((1 << id) & curr_input_state[0]) ? 1 : 0; + } + default: return 0; } diff --git a/network/netplay/netplay_handshake.c b/network/netplay/netplay_handshake.c index a369bca35e..2f4dd40dc1 100644 --- a/network/netplay/netplay_handshake.c +++ b/network/netplay/netplay_handshake.c @@ -35,7 +35,6 @@ #include "../../content.h" #include "../../retroarch.h" #include "../../version.h" -#include "../../input/input_driver.h" #ifdef HAVE_MENU #include "../../menu/widgets/menu_input_dialog.h" @@ -594,7 +593,7 @@ bool netplay_handshake_sync(netplay_t *netplay, /* Now send the device info */ for (i = 0; i < MAX_INPUT_DEVICES; i++) { - device = htonl(input_config_get_device((unsigned)i)); + device = htonl(netplay->config_devices[i]); if (!netplay_send(&connection->send_packet_buffer, connection->fd, &device, sizeof(device))) return false; @@ -1022,6 +1021,7 @@ bool netplay_handshake_pre_sync(netplay_t *netplay, pad.port = (unsigned)i; pad.device = ntohl(device); + netplay->config_devices[i] = pad.device; core_set_controller_port_device(&pad); } diff --git a/network/netplay/netplay_init.c b/network/netplay/netplay_init.c index 18e6af47ee..893e3d89e9 100644 --- a/network/netplay/netplay_init.c +++ b/network/netplay/netplay_init.c @@ -32,6 +32,7 @@ #include "../../autosave.h" #include "../../retroarch.h" +#include "../../input/input_driver.h" #if defined(AF_INET6) && !defined(HAVE_SOCKET_LEGACY) #define HAVE_INET6 1 @@ -466,8 +467,21 @@ netplay_t *netplay_new(void *direct_host, const char *server, uint16_t port, return NULL; } - if (!netplay->is_server) + if (netplay->is_server) { + /* Clients get device info from the server */ + unsigned i; + for (i = 0; i < MAX_INPUT_DEVICES; i++) + { + uint32_t dtype = input_config_get_device(i); + netplay->config_devices[i] = dtype; + if (dtype != RETRO_DEVICE_NONE && !netplay_expected_input_size(netplay, 1<connections[0]); netplay->connections[0].mode = NETPLAY_CONNECTION_INIT; diff --git a/network/netplay/netplay_io.c b/network/netplay/netplay_io.c index 9498efb8e1..c611397ef9 100644 --- a/network/netplay/netplay_io.c +++ b/network/netplay/netplay_io.c @@ -232,7 +232,7 @@ static bool send_input_frame(netplay_t *netplay, struct delta_frame *dframe, if (!(devices & (1<real_input[device]; - while (istate && istate->client_num != client_num) + while (istate && (!istate->used || istate->client_num != client_num)) istate = istate->next; if (!istate) continue; @@ -810,7 +810,7 @@ static void handle_play_spectate(netplay_t *netplay, uint32_t client_num, /* Find an available device */ for (device = 0; device < MAX_INPUT_DEVICES; device++) { - if (input_config_get_device(device) == RETRO_DEVICE_NONE) + if (netplay->config_devices[device] == RETRO_DEVICE_NONE) { device = MAX_INPUT_DEVICES; break; @@ -985,7 +985,7 @@ static bool netplay_get_cmd(netplay_t *netplay, /* Figure out how much input is expected */ devices = netplay->client_devices[client_num]; - input_size = netplay_expected_input_size(devices); + input_size = netplay_expected_input_size(netplay, devices); if (cmd_size != (2+input_size) * sizeof(uint32_t)) { @@ -1037,7 +1037,7 @@ static bool netplay_get_cmd(netplay_t *netplay, if (!(devices & (1<real_input[device], client_num, dsize, true, false); if (!istate) @@ -1297,12 +1297,15 @@ static bool netplay_get_cmd(netplay_t *netplay, { for (device = 0; device < MAX_INPUT_DEVICES; device++) { + uint32_t dsize; + netplay_input_state_t istate; if (!(devices & (1<real_input[device], client_num, - 3 /* FIXME */, false, false); + dsize = netplay_expected_input_size(netplay, 1 << device); + istate = netplay_input_state_for( + &dframe->real_input[device], client_num, dsize, + false, false); if (!istate) continue; - memset(istate->data, 0, istate->size*sizeof(uint32_t)); + memset(istate->data, 0, dsize*sizeof(uint32_t)); } dframe->have_local = true; dframe->have_real[client_num] = true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index eada25d118..cb87db74c1 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -436,6 +436,10 @@ struct netplay * attempt to stay in sync. */ uint32_t desync; + /* The device types for every connected device. We store them and ignore any + * menu changes, as netplay needs fixed devices. */ + uint32_t config_devices[MAX_INPUT_DEVICES]; + struct retro_callbacks cbs; /* TCP port (only set if serving) */ @@ -680,7 +684,7 @@ netplay_input_state_t netplay_input_state_for(netplay_input_state_t *list, * * Size in words for a given set of devices. */ -uint32_t netplay_expected_input_size(uint32_t devices); +uint32_t netplay_expected_input_size(netplay_t *netplay, uint32_t devices); /*************************************************************** diff --git a/network/netplay/netplay_sync.c b/network/netplay/netplay_sync.c index 9d349c72ed..6dab8938e5 100644 --- a/network/netplay/netplay_sync.c +++ b/network/netplay/netplay_sync.c @@ -96,15 +96,17 @@ struct vote_count { netplay_input_state_t netplay_device_client_state(netplay_t *netplay, struct delta_frame *simframe, uint32_t device, uint32_t client) { + uint32_t dsize = netplay_expected_input_size(netplay, 1 << device); netplay_input_state_t simstate = netplay_input_state_for( - &simframe->real_input[device], client, 3 /* FIXME */, false, true); + &simframe->real_input[device], client, + dsize, false, true); if (!simstate) { if (netplay->read_frame_count[client] > simframe->frame) return NULL; simstate = netplay_input_state_for(&simframe->simlated_input[device], - client, 3 /* FIXME */, false, true); + client, dsize, false, true); } return simstate; } @@ -129,7 +131,7 @@ static void netplay_merge_digital(netplay_t *netplay, /* Make sure all real clients are accounted for */ for (simstate = simframe->real_input[device]; simstate; simstate = simstate->next) { - if (!simstate->used || simstate->size != 3 /* FIXME */) continue; + if (!simstate->used || simstate->size != resstate->size) continue; clients |= 1<client_num; } @@ -137,6 +139,8 @@ static void netplay_merge_digital(netplay_t *netplay, { /* Vote mode requires counting all the bits */ uint32_t client_count = 0; + + /* This just assumes we have no more than three words, will need to be adjusted for new devices */ struct vote_count votes[3] = {0}; for (client = 0; client < MAX_CLIENTS; client++) { @@ -145,7 +149,7 @@ static void netplay_merge_digital(netplay_t *netplay, if (!simstate) continue; client_count++; - for (word = 0; word < 3 /* FIXME */; word++) + for (word = 0; word < resstate->size; word++) { if (!digital[word]) continue; for (bit = 0; bit < 32; bit++) @@ -159,7 +163,7 @@ static void netplay_merge_digital(netplay_t *netplay, /* Now count all the bits */ client_count /= 2; - for (word = 0; word < 3 /* FIXME */; word++) + for (word = 0; word < resstate->size; word++) { for (bit = 0; bit < 32; bit++) { @@ -176,7 +180,7 @@ static void netplay_merge_digital(netplay_t *netplay, if (!(clients & (1<size; word++) { uint32_t part; if (!digital[word]) continue; @@ -231,7 +235,7 @@ static void merge_analog_part(netplay_t *netplay, /* Make sure all real clients are accounted for */ for (simstate = simframe->real_input[device]; simstate; simstate = simstate->next) { - if (!simstate->used || simstate->size != 3 /* FIXME */) continue; + if (!simstate->used || simstate->size != resstate->size) continue; clients |= 1<client_num; } @@ -267,16 +271,23 @@ static void merge_analog_part(netplay_t *netplay, * @simframe : frame in which merging is being performed * @device : device being merged * @clients : bitmap of clients being merged + * @dtype : device type */ static void netplay_merge_analog(netplay_t *netplay, netplay_input_state_t resstate, struct delta_frame *simframe, - uint32_t device, uint32_t clients) + uint32_t device, uint32_t clients, unsigned dtype) { - /* FIXME: Assumes 3-byte gamepad */ + if (dtype == RETRO_DEVICE_JOYPAD) + return; + /* All other devices have analog */ + merge_analog_part(netplay, resstate, simframe, device, clients, 1, 0); merge_analog_part(netplay, resstate, simframe, device, clients, 1, 16); - merge_analog_part(netplay, resstate, simframe, device, clients, 2, 0); - merge_analog_part(netplay, resstate, simframe, device, clients, 2, 16); + if (dtype == RETRO_DEVICE_ANALOG) + { + merge_analog_part(netplay, resstate, simframe, device, clients, 2, 0); + merge_analog_part(netplay, resstate, simframe, device, clients, 2, 16); + } } /** @@ -303,13 +314,15 @@ bool netplay_resolve_input(netplay_t *netplay, size_t sim_ptr, bool resim) for (device = 0; device < MAX_INPUT_DEVICES; device++) { + unsigned dtype = netplay->config_devices[device]&RETRO_DEVICE_MASK; + uint32_t dsize = netplay_expected_input_size(netplay, 1 << device); clients = netplay->device_clients[device]; client_count = 0; /* Make sure all real clients are accounted for */ for (simstate = simframe->real_input[device]; simstate; simstate = simstate->next) { - if (!simstate->used || simstate->size != 3 /* FIXME */) continue; + if (!simstate->used || simstate->size != dsize) continue; clients |= 1<client_num; } @@ -318,19 +331,19 @@ bool netplay_resolve_input(netplay_t *netplay, size_t sim_ptr, bool resim) if (!(clients & (1<real_input[device], client, 3 /* FIXME */, false, true); + simstate = netplay_input_state_for(&simframe->real_input[device], client, dsize, false, true); if (!simstate) { /* Don't already have this input, so must simulate if we're supposed to have it at all */ if (netplay->read_frame_count[client] > simframe->frame) continue; - simstate = netplay_input_state_for(&simframe->simlated_input[device], client, 3 /* FIXME */, false, false); + simstate = netplay_input_state_for(&simframe->simlated_input[device], client, dsize, false, false); if (!simstate) continue; prev = PREV_PTR(netplay->read_ptr[client]); pframe = &netplay->buffer[prev]; - pstate = netplay_input_state_for(&pframe->real_input[device], client, 3 /* FIXME */, false, true); + pstate = netplay_input_state_for(&pframe->real_input[device], client, dsize, false, true); if (!pstate) continue; @@ -351,17 +364,28 @@ bool netplay_resolve_input(netplay_t *netplay, size_t sim_ptr, bool resim) * which will seem to jerkily be pressed numerous times with those * wavefronts. */ - const uint32_t keep = (1U<data[0] &= keep; simstate->data[0] |= pstate->data[0] & ~keep; } else { memcpy(simstate->data, pstate->data, - simstate->size * sizeof(uint32_t)); + dsize * sizeof(uint32_t)); } } @@ -369,23 +393,35 @@ bool netplay_resolve_input(netplay_t *netplay, size_t sim_ptr, bool resim) client_count++; } + /* The frontend always uses the first resolved input, so make sure it's right */ + while (simframe->resolved_input[device] + && (simframe->resolved_input[device]->size != dsize + || simframe->resolved_input[device]->client_num != 0)) + { + /* The default resolved input is of the wrong size! */ + netplay_input_state_t nextistate = simframe->resolved_input[device]->next; + free(simframe->resolved_input[device]); + simframe->resolved_input[device] = nextistate; + } + /* Now we copy the state, whether real or simulated, out into the resolved state */ - resstate = netplay_input_state_for(&simframe->resolved_input[device], 0, 3 /* FIXME */, false, false); + resstate = netplay_input_state_for(&simframe->resolved_input[device], 0, + dsize, false, false); if (!resstate) continue; if (client_count == 1) { /* Trivial in the common 1-client case */ - if (memcmp(resstate->data, client_state->data, resstate->size * sizeof(uint32_t))) + if (memcmp(resstate->data, client_state->data, dsize * sizeof(uint32_t))) ret = true; - memcpy(resstate->data, client_state->data, resstate->size * sizeof(uint32_t)); + memcpy(resstate->data, client_state->data, dsize * sizeof(uint32_t)); } else if (client_count == 0) { uint32_t word; - for (word = 0; word < resstate->size; word++) + for (word = 0; word < dsize; word++) { if (resstate->data[word]) ret = true; @@ -396,17 +432,20 @@ bool netplay_resolve_input(netplay_t *netplay, size_t sim_ptr, bool resim) else { /* Merge them */ + /* NOTE: As it happens, all of our devices keep the digital data in + * the first word, so this is fine, but it'll have to change when + * other devices are supported. */ static const uint32_t digital[3] = {-1, 0, 0}; - oldresstate = netplay_input_state_for(&simframe->resolved_input[device], 1, 3 /* FIXME */, false, false); + oldresstate = netplay_input_state_for(&simframe->resolved_input[device], 1, dsize, false, false); if (!oldresstate) continue; - memcpy(oldresstate->data, resstate->data, oldresstate->size * sizeof(uint32_t)); - memset(resstate->data, 0, resstate->size * sizeof(uint32_t)); + memcpy(oldresstate->data, resstate->data, dsize * sizeof(uint32_t)); + memset(resstate->data, 0, dsize * sizeof(uint32_t)); netplay_merge_digital(netplay, resstate, simframe, device, clients, digital); - netplay_merge_analog(netplay, resstate, simframe, device, clients); + netplay_merge_analog(netplay, resstate, simframe, device, clients, dtype); - if (memcmp(resstate->data, oldresstate->data, resstate->size * sizeof(uint32_t))) + if (memcmp(resstate->data, oldresstate->data, dsize * sizeof(uint32_t))) ret = true; }