From 69b7dc0d08f58fe9cc4511afede1ca78a6a750d8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 11 Sep 2016 22:01:47 -0400 Subject: [PATCH 01/22] Multitudinous fixes and updates to Netplay. Had to be one commit since they're mostly related: (1) Renamed frame_count to self_frame_count to be consistent with all other names. (2) Previously, it was possible to overwrite data in the ring buffer that hadn't yet been used. Now that's not possible, but that just changes one breakage for another: It's now possible to miss the NEW data. The final resolution for this will probably be requesting stalls. This is accomplished simply by storing frame numbers in the ring buffer and checking them against the 'other' head. (3) In TCP packets, separated cmd_size from cmd. It was beyond pointless for these to be combined, and restricted cmd_size to 16 bits, which will probably fail when/if state loading is supported. (4) Readahead is now allowed. In the past, if the peer got ahead of us, we would simply ignore their data. Thus, if they got too far ahead of us, we'd stop reading their data altogether. Fabulous. Now, we're happy to read future input. (5) If the peer gets too far ahead of us (currently an unconfigurable 10 frames), fast forward to catch up. This should prevent desync due to clock drift or stutter. (6) Used frame_count in a few places where ptr was used. Doing a comparison of pointers on a ring buffer is a far more dangerous way to assure we're done with a task than simply using the count, since the ring buffer is... well, a ring. (7) Renamed tmp_{ptr,frame_count} to replay_{ptr,frame_count} for clarity. (8) Slightly changed the protocol version hash, just to assure that other clients wouldn't think they were compatible with this one. (9) There was an off-by-one error which, under some circumstances, could allow the replay engine to run a complete round through the ring buffer, replaying stale data. Fixed. --- network/netplay/netplay.c | 47 +++++++++++++++------- network/netplay/netplay_common.c | 15 +++++++ network/netplay/netplay_net.c | 66 +++++++++++++++++++++++++------ network/netplay/netplay_private.h | 17 ++++++-- 4 files changed, 114 insertions(+), 31 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index a2a574ac52..d80728b8ac 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -65,7 +65,7 @@ static void warn_hangup(void) bool check_netplay_synched(netplay_t* netplay) { retro_assert(netplay); - return netplay->frame_count < (netplay->flip_frame + 2 * UDP_FRAME_PACKETS); + return netplay->self_frame_count < (netplay->flip_frame + 2 * UDP_FRAME_PACKETS); } static bool netplay_info_cb(netplay_t* netplay, unsigned frames) { @@ -142,7 +142,9 @@ static bool get_self_input_state(netplay_t *netplay) uint32_t state[UDP_WORDS_PER_FRAME - 1] = {0}; struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; - if (!input_driver_is_libretro_input_blocked() && netplay->frame_count > 0) + if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) return false; + + if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) { unsigned i; settings_t *settings = config_get_ptr(); @@ -185,7 +187,7 @@ static bool get_self_input_state(netplay_t *netplay) */ memmove(netplay->packet_buffer, netplay->packet_buffer + UDP_WORDS_PER_FRAME, sizeof (netplay->packet_buffer) - UDP_WORDS_PER_FRAME * sizeof(uint32_t)); - netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME] = htonl(netplay->frame_count); + netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME] = htonl(netplay->self_frame_count); netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME + 1] = htonl(state[0]); netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME + 2] = htonl(state[1]); netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME + 3] = htonl(state[2]); @@ -227,15 +229,17 @@ static bool netplay_get_cmd(netplay_t *netplay) { uint32_t cmd; uint32_t flip_frame; - size_t cmd_size; + uint32_t cmd_size; if (!socket_receive_all_blocking(netplay->fd, &cmd, sizeof(cmd))) return false; cmd = ntohl(cmd); - cmd_size = cmd & 0xffff; - cmd = cmd >> 16; + if (!socket_receive_all_blocking(netplay->fd, &cmd_size, sizeof(cmd))) + return false; + + cmd_size = ntohl(cmd_size); switch (cmd) { @@ -371,7 +375,7 @@ static void parse_packet(netplay_t *netplay, uint32_t *buffer, unsigned size) for (i = 0; i < size * UDP_WORDS_PER_FRAME; i++) buffer[i] = ntohl(buffer[i]); - for (i = 0; i < size && netplay->read_frame_count <= netplay->frame_count; i++) + for (i = 0; i < size; i++) { uint32_t frame = buffer[UDP_WORDS_PER_FRAME * i + 0]; const uint32_t *state = &buffer[UDP_WORDS_PER_FRAME * i + 1]; @@ -379,6 +383,14 @@ static void parse_packet(netplay_t *netplay, uint32_t *buffer, unsigned size) if (frame != netplay->read_frame_count) continue; + if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) + { + netplay->must_fast_forward = true; + continue; + } + + /* FIXME: acknowledge when we completely drop data on the floor */ + netplay->buffer[netplay->read_ptr].is_simulated = false; memcpy(netplay->buffer[netplay->read_ptr].real_input_state, state, sizeof(netplay->buffer[netplay->read_ptr].real_input_state)); @@ -427,7 +439,7 @@ static bool netplay_poll(netplay_t *netplay) /* We skip reading the first frame so the host has a chance to grab * our host info so we don't block forever :') */ - if (netplay->frame_count == 0) + if (netplay->self_frame_count == 0) { netplay->buffer[0].used_real = true; netplay->buffer[0].is_simulated = false; @@ -464,7 +476,7 @@ static bool netplay_poll(netplay_t *netplay) } parse_packet(netplay, buffer, UDP_FRAME_PACKETS); - } while ((netplay->read_frame_count <= netplay->frame_count) && + } while ((netplay->read_frame_count <= netplay->self_frame_count) && poll_input(netplay, (netplay->other_ptr == netplay->self_ptr) && (first_read == netplay->read_frame_count)) == 1); } @@ -478,7 +490,7 @@ static bool netplay_poll(netplay_t *netplay) } } - if (netplay->read_ptr != netplay->self_ptr) + if (netplay->read_frame_count <= netplay->self_frame_count) simulate_input(netplay); else netplay->buffer[PREV_PTR(netplay->self_ptr)].used_real = true; @@ -533,13 +545,13 @@ static bool netplay_is_alive(netplay_t *netplay) static bool netplay_flip_port(netplay_t *netplay, bool port) { - size_t frame = netplay->frame_count; + size_t frame = netplay->self_frame_count; if (netplay->flip_frame == 0) return port; if (netplay->is_replay) - frame = netplay->tmp_frame_count; + frame = netplay->replay_frame_count; return port ^ netplay->flip ^ (frame < netplay->flip_frame); } @@ -549,7 +561,7 @@ static int16_t netplay_input_state(netplay_t *netplay, unsigned idx, unsigned id) { size_t ptr = netplay->is_replay ? - netplay->tmp_ptr : PREV_PTR(netplay->self_ptr); + netplay->replay_ptr : PREV_PTR(netplay->self_ptr); const uint32_t *curr_input_state = netplay->buffer[ptr].self_state; @@ -871,12 +883,17 @@ error: static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, const void *data, size_t size) { - cmd = (cmd << 16) | (size & 0xffff); + uint32_t cmd_size; + cmd = htonl(cmd); + cmd_size = htonl(size); if (!socket_send_all_blocking(netplay->fd, &cmd, sizeof(cmd), false)) return false; + if (!socket_send_all_blocking(netplay->fd, &cmd_size, sizeof(cmd_size), false)) + return false; + if (!socket_send_all_blocking(netplay->fd, data, size, false)) return false; @@ -953,7 +970,7 @@ error: **/ static void netplay_flip_users(netplay_t *netplay) { - uint32_t flip_frame = netplay->frame_count + 2 * UDP_FRAME_PACKETS; + uint32_t flip_frame = netplay->self_frame_count + 2 * UDP_FRAME_PACKETS; uint32_t flip_frame_net = htonl(flip_frame); bool command = netplay_command( netplay, NETPLAY_CMD_FLIP_PLAYERS, diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index dba2eef948..ffdbddaa80 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -199,6 +199,8 @@ uint32_t netplay_impl_magic(void) for (i = 0; i < len; i++) res ^= ver[i] << ((i & 0xf) + 16); + res ^= NETPLAY_PROTOCOL_VERSION << 24; + return res; } @@ -342,3 +344,16 @@ bool netplay_is_spectate(netplay_t* netplay) return false; return netplay->spectate.enabled; } + +bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) +{ + if (delta->frame == frame) return true; + if (netplay->other_frame_count <= delta->frame) + { + /* We haven't even replayed this frame yet, so we can't overwrite it! */ + return false; + } + memset(delta, 0, sizeof(struct delta_frame)); + delta->frame = frame; + return true; +} diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 91dfc88615..c6ee585ce8 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -30,10 +30,13 @@ static void netplay_net_pre_frame(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; - serial_info.data = netplay->buffer[netplay->self_ptr].state; - serial_info.size = netplay->state_size; + if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + { + serial_info.data = netplay->buffer[netplay->self_ptr].state; + serial_info.size = netplay->state_size; - core_serialize(&serial_info); + core_serialize(&serial_info); + } netplay->can_poll = true; @@ -49,7 +52,7 @@ static void netplay_net_pre_frame(netplay_t *netplay) **/ static void netplay_net_post_frame(netplay_t *netplay) { - netplay->frame_count++; + netplay->self_frame_count++; /* Nothing to do... */ if (netplay->other_frame_count == netplay->read_frame_count) @@ -69,24 +72,24 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->other_frame_count++; } + /* Now replay the real input if we've gotten ahead of it */ if (netplay->other_frame_count < netplay->read_frame_count) { retro_ctx_serialize_info_t serial_info; - bool first = true; /* Replay frames. */ netplay->is_replay = true; - netplay->tmp_ptr = netplay->other_ptr; - netplay->tmp_frame_count = netplay->other_frame_count; + netplay->replay_ptr = netplay->other_ptr; + netplay->replay_frame_count = netplay->other_frame_count; serial_info.data_const = netplay->buffer[netplay->other_ptr].state; serial_info.size = netplay->state_size; core_unserialize(&serial_info); - while (first || (netplay->tmp_ptr != netplay->self_ptr)) + while (netplay->replay_frame_count < netplay->self_frame_count) { - serial_info.data = netplay->buffer[netplay->tmp_ptr].state; + serial_info.data = netplay->buffer[netplay->replay_ptr].state; serial_info.size = netplay->state_size; serial_info.data_const = NULL; @@ -99,15 +102,54 @@ static void netplay_net_post_frame(netplay_t *netplay) #if defined(HAVE_THREADS) autosave_unlock(); #endif - netplay->tmp_ptr = NEXT_PTR(netplay->tmp_ptr); - netplay->tmp_frame_count++; - first = false; + netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); + netplay->replay_frame_count++; } netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = netplay->read_frame_count; netplay->is_replay = false; } + + /* And if the other side has gotten too far ahead of /us/, skip to catch up + * FIXME: Make this configurable */ + if (netplay->read_frame_count > netplay->self_frame_count + 10 || + netplay->must_fast_forward) + { + /* "replay" into the future */ + netplay->is_replay = true; + netplay->replay_ptr = netplay->self_ptr; + netplay->replay_frame_count = netplay->self_frame_count; + + /* just assume input doesn't change for the intervening frames */ + while (netplay->replay_frame_count < netplay->read_frame_count) + { + size_t cur = netplay->replay_ptr; + size_t prev = PREV_PTR(cur); + + memcpy(netplay->buffer[cur].self_state, netplay->buffer[prev].self_state, + sizeof(netplay->buffer[prev].self_state)); + +#if defined(HAVE_THREADS) + autosave_lock(); +#endif + core_run(); +#if defined(HAVE_THREADS) + autosave_unlock(); +#endif + + netplay->replay_ptr = NEXT_PTR(cur); + netplay->replay_frame_count++; + } + + /* at this point, other = read = self */ + netplay->self_ptr = netplay->replay_ptr; + netplay->self_frame_count = netplay->replay_frame_count; + netplay->other_ptr = netplay->read_ptr; + netplay->other_frame_count = netplay->read_frame_count; + netplay->is_replay = false; + } + } static bool netplay_net_init_buffers(netplay_t *netplay) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index f0e30aac17..13a7dbc904 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -35,17 +35,22 @@ #define MAX_SPECTATORS 16 #define RARCH_DEFAULT_PORT 55435 +#define NETPLAY_PROTOCOL_VERSION 1 + #define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1) #define NEXT_PTR(x) ((x + 1) % netplay->buffer_size) struct delta_frame { + uint32_t frame; + void *state; uint32_t real_input_state[UDP_WORDS_PER_FRAME - 1]; uint32_t simulated_input_state[UDP_WORDS_PER_FRAME - 1]; uint32_t self_state[UDP_WORDS_PER_FRAME - 1]; + bool have_local; bool is_simulated; bool used_real; }; @@ -81,8 +86,8 @@ struct netplay /* Pointer to where we are reading. * Generally, other_ptr <= read_ptr <= self_ptr. */ size_t read_ptr; - /* A temporary pointer used on replay. */ - size_t tmp_ptr; + /* A pointer used temporarily for replay. */ + size_t replay_ptr; size_t state_size; @@ -90,14 +95,16 @@ struct netplay bool is_replay; /* We don't want to poll several times on a frame. */ bool can_poll; + /* If we end up having to drop remote frame data because it's ahead of us, fast-forward is URGENT */ + bool must_fast_forward; /* To compat UDP packet loss we also send * old data along with the packets. */ uint32_t packet_buffer[UDP_FRAME_PACKETS * UDP_WORDS_PER_FRAME]; - uint32_t frame_count; + uint32_t self_frame_count; uint32_t read_frame_count; uint32_t other_frame_count; - uint32_t tmp_frame_count; + uint32_t replay_frame_count; struct addrinfo *addr; struct sockaddr_storage their_addr; bool has_client_addr; @@ -158,4 +165,6 @@ bool netplay_is_server(netplay_t* netplay); bool netplay_is_spectate(netplay_t* netplay); +bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame); + #endif From 9a80a1bd7e0183ecb8c0092b24fe0e0a2faece67 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Sun, 11 Sep 2016 22:17:07 -0400 Subject: [PATCH 02/22] Adding a bit of Netplay documentation. --- network/netplay/README | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 network/netplay/README diff --git a/network/netplay/README b/network/netplay/README new file mode 100644 index 0000000000..3779c0ce68 --- /dev/null +++ b/network/netplay/README @@ -0,0 +1,52 @@ +How Netplay works (as documented by somebody who didn't write it): + +Note that this documentation is all for (the poorly-named) “net” mode, which is +the normal mode, and not “spectator” mode, which has its own whole host of +problems. + +Netplay in RetroArch works by expecting input to come delayed from the network, +then rewinding and re-playing with the delayed input to get a consistent state. +So long as both sides agree on which frame is which, it should be impossible +for them to become de-synced, since each input event always happens at the +correct frame. + +Within the state buffers, there are three locations: self, other and read. Self +is where the emulator believes itself to be, which inevitably will be ahead of +what it's read from the peer. Other is where it was most recently in perfect +sync: i.e., other has been read and actioned. Read is where it's read up to, +which can be slightly ahead of other since it can't always immediately act upon +new data. In general, other ≤ read ≤ self. If read > self, logically it should +try to catch up, but that logic currently doesn't seem to exist (?) + +In terms of the implementation, Netplay is in effect an input buffer and some +pre- and post-frame behaviors. + +Pre-frame, it serializes the core's state, polls for input from the other side, +and if input has not been received for the other side up to the current frame +(which it shouldn't be), “simulates” the other side's input up to the current +frame. The simulation is simply assuming that the input state hasn't changed. +Each frame's local serialized state and simulated or real input goes into the +frame buffers. Frame buffers that are simulated are marked as such. + +Post-frame, it checks whether it's read more than it's actioned, i.e. if read > +other. If so, it rewinds to other and runs the core in replay mode with the +real data up to read, then sets other = read. + +When in Netplay mode, the callback for receiving input is replaced by +input_state_net. It is the role of input_state_net to combine the true local +input (whether live or replay) with the remote input (whether true or +simulated). + +In the previous implementation, there was seemingly nothing to prevent the self +frame from advancing past the other frame in the ring buffer, causing local +input from one time to be mixed with remote input from another. The new +implementation uses a not-at-all-better strategy of simply refusing local input +if it steps past remote input. + +Some thoughts about "frame counts": The frame counters act like indexes into a +0-indexed array; i.e., they refer to the first unactioned frame. With +self_frame_count it's slightly more complicated, since there are two relevant +actions: Reading the data and emulating with the data. The frame count is only +incremented after the latter, but the nature of the emulation assures that the +current frame's input will always be read before it's actioned (or at least, we +should certainly hope so!) From 5edfbeafb0f6bca2a613b52ca0d3329cd64b1684 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Sep 2016 07:42:35 -0400 Subject: [PATCH 03/22] Switched Netplay over to TCP. A lot of the stalling logic had to change for this, and in particular, it now sometimes stalls in a way that makes it very difficult to actually input anything (whoops :) ). Simply setting the sync frames higher avoids that. With supported cores, this is incredibly risilient, but when it fails, it mostly fails to freezing, which is less than ideal. TODO: Stall frames should be configurable. All the UDP code is still there but commented out, should be gutted. The original fast-forward code is now commented out, but really both fast-forward and stalling should be options; the only complication is that it needs to send simulated self-input for fast-forward. --- network/netplay/netplay.c | 159 +++++++++++++++++++++--------- network/netplay/netplay.h | 17 ++-- network/netplay/netplay_common.c | 3 + network/netplay/netplay_net.c | 28 ++++-- network/netplay/netplay_private.h | 18 ++-- 5 files changed, 157 insertions(+), 68 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index d80728b8ac..3425c8c712 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -65,7 +65,9 @@ static void warn_hangup(void) bool check_netplay_synched(netplay_t* netplay) { retro_assert(netplay); - return netplay->self_frame_count < (netplay->flip_frame + 2 * UDP_FRAME_PACKETS); + /*return netplay->self_frame_count < (netplay->flip_frame + 2 * UDP_FRAME_PACKETS);*/ + /* FIXME */ + return true; } static bool netplay_info_cb(netplay_t* netplay, unsigned frames) { @@ -96,6 +98,7 @@ static bool netplay_can_poll(netplay_t *netplay) return netplay->can_poll; } +#if 0 static bool send_chunk(netplay_t *netplay) { const struct sockaddr *addr = NULL; @@ -128,6 +131,7 @@ static bool send_chunk(netplay_t *netplay) } return true; } +#endif /** * get_self_input_state: @@ -139,10 +143,11 @@ static bool send_chunk(netplay_t *netplay) **/ static bool get_self_input_state(netplay_t *netplay) { - uint32_t state[UDP_WORDS_PER_FRAME - 1] = {0}; + uint32_t state[WORDS_PER_FRAME - 1] = {0}; struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; - if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) return false; + if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) + return false; if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) { @@ -181,18 +186,19 @@ static bool get_self_input_state(netplay_t *netplay) * } * * payload { - * ; To compat packet losses, send input in a sliding window - * frame redundancy_frames[UDP_FRAME_PACKETS]; + * cmd (CMD_INPUT) + * cmd_size (4 words) + * frame * } */ - memmove(netplay->packet_buffer, netplay->packet_buffer + UDP_WORDS_PER_FRAME, - sizeof (netplay->packet_buffer) - UDP_WORDS_PER_FRAME * sizeof(uint32_t)); - netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME] = htonl(netplay->self_frame_count); - netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME + 1] = htonl(state[0]); - netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME + 2] = htonl(state[1]); - netplay->packet_buffer[(UDP_FRAME_PACKETS - 1) * UDP_WORDS_PER_FRAME + 3] = htonl(state[2]); + netplay->packet_buffer[0] = htonl(NETPLAY_CMD_INPUT); + netplay->packet_buffer[1] = htonl(WORDS_PER_FRAME * sizeof(uint32_t)); + netplay->packet_buffer[2] = htonl(netplay->self_frame_count); + netplay->packet_buffer[3] = htonl(state[0]); + netplay->packet_buffer[4] = htonl(state[1]); + netplay->packet_buffer[5] = htonl(state[2]); - if (!send_chunk(netplay)) + if (!socket_send_all_blocking(netplay->fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) { warn_hangup(); netplay->has_connection = false; @@ -204,16 +210,32 @@ static bool get_self_input_state(netplay_t *netplay) return true; } +static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, + const void *data, size_t size) +{ + uint32_t cmdbuf[2]; + + cmdbuf[0] = htonl(cmd); + cmdbuf[1] = htonl(size); + + if (!socket_send_all_blocking(netplay->fd, cmdbuf, sizeof(cmdbuf), false)) + return false; + + if (size > 0) + if (!socket_send_all_blocking(netplay->fd, data, size, false)) + return false; + + return true; +} + static bool netplay_cmd_ack(netplay_t *netplay) { - uint32_t cmd = htonl(NETPLAY_CMD_ACK); - return socket_send_all_blocking(netplay->fd, &cmd, sizeof(cmd), false); + return netplay_send_raw_cmd(netplay, NETPLAY_CMD_ACK, NULL, 0); } static bool netplay_cmd_nak(netplay_t *netplay) { - uint32_t cmd = htonl(NETPLAY_CMD_NAK); - return socket_send_all_blocking(netplay->fd, &cmd, sizeof(cmd), false); + return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); } static bool netplay_get_response(netplay_t *netplay) @@ -231,6 +253,10 @@ static bool netplay_get_cmd(netplay_t *netplay) uint32_t flip_frame; uint32_t cmd_size; + /* If we're not ready for input, wait until we are. Could fill the TCP buffer, stalling the other side. */ + if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) + return false; + if (!socket_receive_all_blocking(netplay->fd, &cmd, sizeof(cmd))) return false; @@ -243,6 +269,53 @@ static bool netplay_get_cmd(netplay_t *netplay) switch (cmd) { + case NETPLAY_CMD_ACK: + case NETPLAY_CMD_NAK: + /* Why are we even bothering? */ + return true; + + case NETPLAY_CMD_INPUT: + { + uint32_t buffer[WORDS_PER_FRAME]; + unsigned i; + + if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) + { + RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) + { + RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); + return netplay_cmd_nak(netplay); + } + + for (i = 0; i < WORDS_PER_FRAME; i++) + buffer[i] = ntohl(buffer[i]); + + if (buffer[0] != netplay->read_frame_count) + { + /* FIXME: JUST drop it? */ + return netplay_cmd_nak(netplay); + } + + if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) + { + /* FIXME: If we're here, we're desyncing. */ + netplay->must_fast_forward = true; + return netplay_cmd_nak(netplay); + } + + /* The data's good! */ + netplay->buffer[netplay->read_ptr].is_simulated = false; + memcpy(netplay->buffer[netplay->read_ptr].real_input_state, buffer + 1, sizeof(buffer) - sizeof(uint32_t)); + netplay->read_ptr = NEXT_PTR(netplay->read_ptr); + netplay->read_frame_count++; + netplay->timeout_cnt = 0; + return netplay_cmd_ack(netplay); + } + case NETPLAY_CMD_FLIP_PLAYERS: if (cmd_size != sizeof(uint32_t)) { @@ -305,6 +378,7 @@ static bool netplay_get_cmd(netplay_t *netplay) static int poll_input(netplay_t *netplay, bool block) { + bool had_input = false; int max_fd = (netplay->fd > netplay->udp_fd ? netplay->fd : netplay->udp_fd) + 1; struct timeval tv = {0}; @@ -318,6 +392,7 @@ static int poll_input(netplay_t *netplay, bool block) * Technically possible for select() to modify tmp_tv, so * we go paranoia mode. */ struct timeval tmp_tv = tv; + had_input = false; netplay->timeout_cnt++; @@ -330,31 +405,40 @@ static int poll_input(netplay_t *netplay, bool block) /* Somewhat hacky, * but we aren't using the TCP connection for anything useful atm. */ - if (FD_ISSET(netplay->fd, &fds) && !netplay_get_cmd(netplay)) - return -1; + if (FD_ISSET(netplay->fd, &fds)) + { + had_input = true; + if (!netplay_get_cmd(netplay)) + return -1; + } +#if 0 if (FD_ISSET(netplay->udp_fd, &fds)) return 1; +#endif if (!block) continue; +#if 0 if (!send_chunk(netplay)) { warn_hangup(); netplay->has_connection = false; return -1; } +#endif RARCH_LOG("Network is stalling, resending packet... Count %u of %d ...\n", netplay->timeout_cnt, MAX_RETRIES); - } while ((netplay->timeout_cnt < MAX_RETRIES) && block); + } while (had_input || (block && netplay->read_frame_count < netplay->self_frame_count)); - if (block) - return -1; + /*if (block) + return -1;*/ return 0; } +#if 0 static bool receive_data(netplay_t *netplay, uint32_t *buffer, size_t size) { socklen_t addrlen = sizeof(netplay->their_addr); @@ -400,6 +484,7 @@ static void parse_packet(netplay_t *netplay, uint32_t *buffer, unsigned size) netplay->timeout_cnt = 0; } } +#endif /* TODO: Somewhat better prediction. :P */ static void simulate_input(netplay_t *netplay) @@ -454,7 +539,7 @@ static bool netplay_poll(netplay_t *netplay) /* We might have reached the end of the buffer, where we * simply have to block. */ - res = poll_input(netplay, netplay->other_ptr == netplay->self_ptr); + res = poll_input(netplay, netplay->read_frame_count < netplay->self_frame_count - 10); /* FIXME: configure stalling intervals */ if (res == -1) { netplay->has_connection = false; @@ -462,6 +547,7 @@ static bool netplay_poll(netplay_t *netplay) return false; } +#if 0 if (res == 1) { uint32_t first_read = netplay->read_frame_count; @@ -489,8 +575,9 @@ static bool netplay_poll(netplay_t *netplay) return false; } } +#endif - if (netplay->read_frame_count <= netplay->self_frame_count) + if (netplay->read_frame_count < netplay->self_frame_count) simulate_input(netplay); else netplay->buffer[PREV_PTR(netplay->self_ptr)].used_real = true; @@ -839,8 +926,8 @@ netplay_t *netplay_new(const char *server, uint16_t port, { netplay_t *netplay = NULL; - if (frames > UDP_FRAME_PACKETS) - frames = UDP_FRAME_PACKETS; + /*if (frames > UDP_FRAME_PACKETS) + frames = UDP_FRAME_PACKETS;*/ netplay = (netplay_t*)calloc(1, sizeof(*netplay)); if (!netplay) @@ -880,26 +967,6 @@ error: return NULL; } -static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, - const void *data, size_t size) -{ - uint32_t cmd_size; - - cmd = htonl(cmd); - cmd_size = htonl(size); - - if (!socket_send_all_blocking(netplay->fd, &cmd, sizeof(cmd), false)) - return false; - - if (!socket_send_all_blocking(netplay->fd, &cmd_size, sizeof(cmd_size), false)) - return false; - - if (!socket_send_all_blocking(netplay->fd, data, size, false)) - return false; - - return true; -} - /** * netplay_command: * @netplay : pointer to netplay object @@ -970,7 +1037,7 @@ error: **/ static void netplay_flip_users(netplay_t *netplay) { - uint32_t flip_frame = netplay->self_frame_count + 2 * UDP_FRAME_PACKETS; + uint32_t flip_frame = netplay->self_frame_count + 32; /* FIXME: This value is now arbitrary */ uint32_t flip_frame_net = htonl(flip_frame); bool command = netplay_command( netplay, NETPLAY_CMD_FLIP_PLAYERS, diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index b0888a2ba0..3aac5bf1c0 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -40,7 +40,7 @@ enum rarch_netplay_ctl_state enum netplay_cmd { - /* Miscellaneous commands */ + /* Basic commands */ /* Acknowlegement response */ NETPLAY_CMD_ACK = 0x0000, @@ -48,23 +48,28 @@ enum netplay_cmd /* Failed acknowlegement response */ NETPLAY_CMD_NAK = 0x0001, + /* Input data */ + NETPLAY_CMD_INPUT = 0x0002, + + /* Misc. commands */ + /* Swap inputs between player 1 and player 2 */ - NETPLAY_CMD_FLIP_PLAYERS = 0x0002, + NETPLAY_CMD_FLIP_PLAYERS = 0x0003, /* Toggle spectate/join mode */ - NETPLAY_CMD_SPECTATE = 0x0003, + NETPLAY_CMD_SPECTATE = 0x0004, /* Gracefully disconnects from host */ - NETPLAY_CMD_DISCONNECT = 0x0004, + NETPLAY_CMD_DISCONNECT = 0x0005, /* Sends multiple config requests over, * See enum netplay_cmd_cfg */ - NETPLAY_CMD_CFG = 0x0005, + NETPLAY_CMD_CFG = 0x0006, /* CMD_CFG streamlines sending multiple configurations. This acknowledges each one individually */ - NETPLAY_CMD_CFG_ACK = 0x0006, + NETPLAY_CMD_CFG_ACK = 0x0007, /* Loading and synchronization */ diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index ffdbddaa80..cbcceedf24 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -347,13 +347,16 @@ bool netplay_is_spectate(netplay_t* netplay) bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) { + void *remember_state; if (delta->frame == frame) return true; if (netplay->other_frame_count <= delta->frame) { /* We haven't even replayed this frame yet, so we can't overwrite it! */ return false; } + remember_state = delta->state; memset(delta, 0, sizeof(struct delta_frame)); delta->frame = frame; + delta->state = remember_state; return true; } diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index c6ee585ce8..35bfae9866 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -32,6 +32,7 @@ static void netplay_net_pre_frame(netplay_t *netplay) if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) { + serial_info.data_const = NULL; serial_info.data = netplay->buffer[netplay->self_ptr].state; serial_info.size = netplay->state_size; @@ -79,13 +80,17 @@ static void netplay_net_post_frame(netplay_t *netplay) /* Replay frames. */ netplay->is_replay = true; - netplay->replay_ptr = netplay->other_ptr; - netplay->replay_frame_count = netplay->other_frame_count; + netplay->replay_ptr = PREV_PTR(netplay->other_ptr); + netplay->replay_frame_count = netplay->other_frame_count - 1; - serial_info.data_const = netplay->buffer[netplay->other_ptr].state; - serial_info.size = netplay->state_size; - - core_unserialize(&serial_info); + if (netplay->replay_frame_count < netplay->self_frame_count) + { + serial_info.data = NULL; + serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; + serial_info.size = netplay->state_size; + + core_unserialize(&serial_info); + } while (netplay->replay_frame_count < netplay->self_frame_count) { @@ -106,11 +111,21 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->replay_frame_count++; } + /* For the remainder of the frames up to the read count, we can use the real data */ + while (netplay->replay_frame_count < netplay->read_frame_count) + { + netplay->buffer[netplay->replay_ptr].is_simulated = false; + netplay->buffer[netplay->replay_ptr].used_real = true; + netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); + netplay->replay_frame_count++; + } + netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = netplay->read_frame_count; netplay->is_replay = false; } +#if 0 /* And if the other side has gotten too far ahead of /us/, skip to catch up * FIXME: Make this configurable */ if (netplay->read_frame_count > netplay->self_frame_count + 10 || @@ -149,6 +164,7 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->other_frame_count = netplay->read_frame_count; netplay->is_replay = false; } +#endif } static bool netplay_net_init_buffers(netplay_t *netplay) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 13a7dbc904..2479376c0e 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -30,10 +30,9 @@ #define HAVE_IPV6 #endif -#define UDP_FRAME_PACKETS 16 -#define UDP_WORDS_PER_FRAME 4 /* Allows us to send 128 bits worth of state per frame. */ -#define MAX_SPECTATORS 16 -#define RARCH_DEFAULT_PORT 55435 +#define WORDS_PER_FRAME 4 /* Allows us to send 128 bits worth of state per frame. */ +#define MAX_SPECTATORS 16 +#define RARCH_DEFAULT_PORT 55435 #define NETPLAY_PROTOCOL_VERSION 1 @@ -46,9 +45,9 @@ struct delta_frame void *state; - uint32_t real_input_state[UDP_WORDS_PER_FRAME - 1]; - uint32_t simulated_input_state[UDP_WORDS_PER_FRAME - 1]; - uint32_t self_state[UDP_WORDS_PER_FRAME - 1]; + uint32_t real_input_state[WORDS_PER_FRAME - 1]; + uint32_t simulated_input_state[WORDS_PER_FRAME - 1]; + uint32_t self_state[WORDS_PER_FRAME - 1]; bool have_local; bool is_simulated; @@ -98,9 +97,8 @@ struct netplay /* If we end up having to drop remote frame data because it's ahead of us, fast-forward is URGENT */ bool must_fast_forward; - /* To compat UDP packet loss we also send - * old data along with the packets. */ - uint32_t packet_buffer[UDP_FRAME_PACKETS * UDP_WORDS_PER_FRAME]; + /* A buffer for outgoing input packets. */ + uint32_t packet_buffer[2 + WORDS_PER_FRAME]; uint32_t self_frame_count; uint32_t read_frame_count; uint32_t other_frame_count; From 4f16a19f5e7c380c896718fbdcfb92b3c2fdb004 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Sep 2016 09:13:26 -0400 Subject: [PATCH 04/22] Rather than stalling by blocking and becoming unresponsive, stall by replaying the same frame. TODO: Maybe mute the audio? --- network/netplay/netplay.c | 39 ++++++++++++++++++++----------- network/netplay/netplay_net.c | 14 +++++++++++ network/netplay/netplay_private.h | 14 +++++++++++ 3 files changed, 54 insertions(+), 13 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 3425c8c712..bbf680b467 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -149,6 +149,13 @@ static bool get_self_input_state(netplay_t *netplay) if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) return false; + if (ptr->have_local) + { + /* We've already read this frame! */ + netplay->self_ptr = NEXT_PTR(netplay->self_ptr); + return true; + } + if (!input_driver_is_libretro_input_blocked() && netplay->self_frame_count > 0) { unsigned i; @@ -206,6 +213,7 @@ static bool get_self_input_state(netplay_t *netplay) } memcpy(ptr->self_state, state, sizeof(state)); + ptr->have_local = true; netplay->self_ptr = NEXT_PTR(netplay->self_ptr); return true; } @@ -296,14 +304,8 @@ static bool netplay_get_cmd(netplay_t *netplay) if (buffer[0] != netplay->read_frame_count) { - /* FIXME: JUST drop it? */ - return netplay_cmd_nak(netplay); - } - - if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) - { - /* FIXME: If we're here, we're desyncing. */ - netplay->must_fast_forward = true; + /* FIXME: Except on the first (null) frame, this should be + * impossible, so maybe just disconnect? */ return netplay_cmd_nak(netplay); } @@ -313,7 +315,7 @@ static bool netplay_get_cmd(netplay_t *netplay) netplay->read_ptr = NEXT_PTR(netplay->read_ptr); netplay->read_frame_count++; netplay->timeout_cnt = 0; - return netplay_cmd_ack(netplay); + return true; } case NETPLAY_CMD_FLIP_PLAYERS: @@ -431,7 +433,7 @@ static int poll_input(netplay_t *netplay, bool block) RARCH_LOG("Network is stalling, resending packet... Count %u of %d ...\n", netplay->timeout_cnt, MAX_RETRIES); - } while (had_input || (block && netplay->read_frame_count < netplay->self_frame_count)); + } while (had_input); /*if (block) return -1;*/ @@ -537,9 +539,8 @@ static bool netplay_poll(netplay_t *netplay) return true; } - /* We might have reached the end of the buffer, where we - * simply have to block. */ - res = poll_input(netplay, netplay->read_frame_count < netplay->self_frame_count - 10); /* FIXME: configure stalling intervals */ + /* Read Netplay input */ + res = poll_input(netplay, 0); /* FIXME: configure stalling intervals */ if (res == -1) { netplay->has_connection = false; @@ -582,6 +583,18 @@ static bool netplay_poll(netplay_t *netplay) else netplay->buffer[PREV_PTR(netplay->self_ptr)].used_real = true; + /* Consider stalling */ + switch (netplay->stall) { + case RARCH_NETPLAY_STALL_RUNNING_FAST: + if (netplay->read_frame_count >= netplay->self_frame_count) + netplay->stall = RARCH_NETPLAY_STALL_NONE; + break; + + default: /* not stalling */ + if (netplay->read_frame_count < netplay->self_frame_count - 10) + netplay->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; + } + return true; } diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 35bfae9866..b234aa98a3 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -166,6 +166,20 @@ static void netplay_net_post_frame(netplay_t *netplay) } #endif + /* If we're supposed to stall, rewind */ + if (netplay->stall) + { + retro_ctx_serialize_info_t serial_info; + + netplay->self_ptr = PREV_PTR(netplay->self_ptr); + netplay->self_frame_count--; + + serial_info.data = NULL; + serial_info.data_const = netplay->buffer[netplay->self_ptr].state; + serial_info.size = netplay->state_size; + + core_unserialize(&serial_info); + } } static bool netplay_net_init_buffers(netplay_t *netplay) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 2479376c0e..381c934aaf 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -49,8 +49,13 @@ struct delta_frame uint32_t simulated_input_state[WORDS_PER_FRAME - 1]; uint32_t self_state[WORDS_PER_FRAME - 1]; + /* Have we read local input? */ bool have_local; + + /* Badly named: This is !have_real(_remote) */ bool is_simulated; + + /* Is the current state as of self_frame_count using the real data? */ bool used_real; }; @@ -60,6 +65,12 @@ struct netplay_callbacks { bool (*info_cb) (netplay_t *netplay, unsigned frames); }; +enum rarch_netplay_stall_reasons +{ + RARCH_NETPLAY_STALL_NONE = 0, + RARCH_NETPLAY_STALL_RUNNING_FAST +}; + struct netplay { char nick[32]; @@ -131,6 +142,9 @@ struct netplay bool pause; uint32_t pause_frame; + /* And stalling */ + int stall; + struct netplay_callbacks* net_cbs; }; From 07e869ccae82fcf7b4fb0b2ffff4bd0755a8cc9b Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Sep 2016 09:18:27 -0400 Subject: [PATCH 05/22] is_simulated was confusing and poorly named. Using have_remote in its place, which is simply true if we've received remote data. Could also just use read_frame_crount instead, but this keeps the info frame-local. --- network/netplay/netplay.c | 12 ++++++------ network/netplay/netplay_net.c | 3 --- network/netplay/netplay_private.h | 6 +++--- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index bbf680b467..e797b02451 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -310,7 +310,7 @@ static bool netplay_get_cmd(netplay_t *netplay) } /* The data's good! */ - netplay->buffer[netplay->read_ptr].is_simulated = false; + netplay->buffer[netplay->read_ptr].have_remote = true; memcpy(netplay->buffer[netplay->read_ptr].real_input_state, buffer + 1, sizeof(buffer) - sizeof(uint32_t)); netplay->read_ptr = NEXT_PTR(netplay->read_ptr); netplay->read_frame_count++; @@ -498,7 +498,7 @@ static void simulate_input(netplay_t *netplay) netplay->buffer[prev].real_input_state, sizeof(netplay->buffer[prev].real_input_state)); - netplay->buffer[ptr].is_simulated = true; + netplay->buffer[ptr].have_remote = false; netplay->buffer[ptr].used_real = false; } @@ -529,7 +529,7 @@ static bool netplay_poll(netplay_t *netplay) if (netplay->self_frame_count == 0) { netplay->buffer[0].used_real = true; - netplay->buffer[0].is_simulated = false; + netplay->buffer[0].have_remote = true; memset(netplay->buffer[0].real_input_state, 0, sizeof(netplay->buffer[0].real_input_state)); @@ -667,10 +667,10 @@ static int16_t netplay_input_state(netplay_t *netplay, if (netplay->port == (netplay_flip_port(netplay, port) ? 1 : 0)) { - if (netplay->buffer[ptr].is_simulated) - curr_input_state = netplay->buffer[ptr].simulated_input_state; - else + if (netplay->buffer[ptr].have_remote) curr_input_state = netplay->buffer[ptr].real_input_state; + else + curr_input_state = netplay->buffer[ptr].simulated_input_state; } switch (device) diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index b234aa98a3..f02b79ce20 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -114,7 +114,6 @@ static void netplay_net_post_frame(netplay_t *netplay) /* For the remainder of the frames up to the read count, we can use the real data */ while (netplay->replay_frame_count < netplay->read_frame_count) { - netplay->buffer[netplay->replay_ptr].is_simulated = false; netplay->buffer[netplay->replay_ptr].used_real = true; netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; @@ -205,8 +204,6 @@ static bool netplay_net_init_buffers(netplay_t *netplay) if (!netplay->buffer[i].state) return false; - - netplay->buffer[i].is_simulated = true; } return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 381c934aaf..66543e4265 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -52,10 +52,10 @@ struct delta_frame /* Have we read local input? */ bool have_local; - /* Badly named: This is !have_real(_remote) */ - bool is_simulated; + /* Have we read the real remote input? */ + bool have_remote; - /* Is the current state as of self_frame_count using the real data? */ + /* Is the current state as of self_frame_count using the real remote data? */ bool used_real; }; From c7d0bf90f6512c2750b2fa918bf00fff2d09029a Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Sep 2016 17:50:38 -0400 Subject: [PATCH 06/22] Bugfixes to bring Netplay Nouveau from "kinda working" to "stably working": (1) Fixups to the stall logic to make sure it always receives frames while stalling :) (2) Disused the used_real field. It was misconfigured and would frequently claim to be using real data when real data hadn't been used... this means more replays for now, but used_real will be readded. (TODO) (3) Stall duration is now related to sync frames, and thus configurable. (4) Delta frames were having the good ol' initialization problem, as frame==0 was indistinguishable from unused. Quickfixed by adding a "used" field, but maybe there's a better way. (5) If serialization fails, switch immediately to blocking mode (stall_frames = 0). Blocking mode barely works, but if serialization fails, no mode will work! (6) I'm not sure which bug my replaying-from-previous-frame was trying to fix, but the correct behavior is to replay from the last frame we had vital information, not the frame prior. Notionally this should just be an efficiency thing, but unsigned arithmetic at 0 made this a "just ignore all input from now on" thing. --- network/netplay/netplay.c | 49 +++++++++++++++++++------------ network/netplay/netplay_common.c | 12 +++++--- network/netplay/netplay_net.c | 21 +++++++++---- network/netplay/netplay_private.h | 2 ++ 4 files changed, 56 insertions(+), 28 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index e797b02451..f7066ddf4a 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -143,7 +143,7 @@ static bool send_chunk(netplay_t *netplay) **/ static bool get_self_input_state(netplay_t *netplay) { - uint32_t state[WORDS_PER_FRAME - 1] = {0}; + uint32_t state[WORDS_PER_FRAME - 1] = {0, 0, 0}; struct delta_frame *ptr = &netplay->buffer[netplay->self_ptr]; if (!netplay_delta_frame_ready(netplay, ptr, netplay->self_frame_count)) @@ -261,9 +261,9 @@ static bool netplay_get_cmd(netplay_t *netplay) uint32_t flip_frame; uint32_t cmd_size; - /* If we're not ready for input, wait until we are. Could fill the TCP buffer, stalling the other side. */ - if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) - return false; + /* FIXME: This depends on delta_frame_ready */ + + netplay->timeout_cnt = 0; if (!socket_receive_all_blocking(netplay->fd, &cmd, sizeof(cmd))) return false; @@ -314,7 +314,6 @@ static bool netplay_get_cmd(netplay_t *netplay) memcpy(netplay->buffer[netplay->read_ptr].real_input_state, buffer + 1, sizeof(buffer) - sizeof(uint32_t)); netplay->read_ptr = NEXT_PTR(netplay->read_ptr); netplay->read_frame_count++; - netplay->timeout_cnt = 0; return true; } @@ -409,9 +408,13 @@ static int poll_input(netplay_t *netplay, bool block) * but we aren't using the TCP connection for anything useful atm. */ if (FD_ISSET(netplay->fd, &fds)) { - had_input = true; - if (!netplay_get_cmd(netplay)) - return -1; + /* If we're not ready for input, wait until we are. Could fill the TCP buffer, stalling the other side. */ + if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) + { + had_input = true; + if (!netplay_get_cmd(netplay)) + return -1; + } } #if 0 @@ -431,9 +434,9 @@ static int poll_input(netplay_t *netplay, bool block) } #endif - RARCH_LOG("Network is stalling, resending packet... Count %u of %d ...\n", - netplay->timeout_cnt, MAX_RETRIES); - } while (had_input); + RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n", + netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES); + } while (had_input || (block && (netplay->read_frame_count <= netplay->self_frame_count))); /*if (block) return -1;*/ @@ -521,9 +524,13 @@ static bool netplay_poll(netplay_t *netplay) netplay->can_poll = false; +#if 0 if (!get_self_input_state(netplay)) return false; +#endif + get_self_input_state(netplay); +#if 0 /* We skip reading the first frame so the host has a chance to grab * our host info so we don't block forever :') */ if (netplay->self_frame_count == 0) @@ -538,9 +545,11 @@ static bool netplay_poll(netplay_t *netplay) netplay->read_frame_count++; return true; } +#endif - /* Read Netplay input */ - res = poll_input(netplay, 0); /* FIXME: configure stalling intervals */ + /* Read Netplay input, block if we're configured to stall for input every + * frame */ + res = poll_input(netplay, netplay->stall_frames == 0); if (res == -1) { netplay->has_connection = false; @@ -580,8 +589,8 @@ static bool netplay_poll(netplay_t *netplay) if (netplay->read_frame_count < netplay->self_frame_count) simulate_input(netplay); - else - netplay->buffer[PREV_PTR(netplay->self_ptr)].used_real = true; + /*else + netplay->buffer[PREV_PTR(netplay->self_ptr)].used_real = true;*/ /* Consider stalling */ switch (netplay->stall) { @@ -591,7 +600,7 @@ static bool netplay_poll(netplay_t *netplay) break; default: /* not stalling */ - if (netplay->read_frame_count < netplay->self_frame_count - 10) + if (netplay->read_frame_count + netplay->stall_frames <= netplay->self_frame_count) netplay->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; } @@ -774,6 +783,9 @@ static int init_tcp_connection(const struct addrinfo *res, { bool ret = true; int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + int flag = 1; + + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)); if (fd < 0) { @@ -937,11 +949,9 @@ netplay_t *netplay_new(const char *server, uint16_t port, bool spectate, const char *nick) { + uint32_t buffer_frames; netplay_t *netplay = NULL; - /*if (frames > UDP_FRAME_PACKETS) - frames = UDP_FRAME_PACKETS;*/ - netplay = (netplay_t*)calloc(1, sizeof(*netplay)); if (!netplay) return NULL; @@ -953,6 +963,7 @@ netplay_t *netplay_new(const char *server, uint16_t port, netplay->spectate.enabled = spectate; netplay->is_server = server == NULL; strlcpy(netplay->nick, nick, sizeof(netplay->nick)); + netplay->stall_frames = frames; if(spectate) netplay->net_cbs = netplay_get_cbs_spectate(); diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index cbcceedf24..2468c0102c 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -348,14 +348,18 @@ bool netplay_is_spectate(netplay_t* netplay) bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) { void *remember_state; - if (delta->frame == frame) return true; - if (netplay->other_frame_count <= delta->frame) + if (delta->used) { - /* We haven't even replayed this frame yet, so we can't overwrite it! */ - return false; + if (delta->frame == frame) return true; + if (netplay->other_frame_count <= delta->frame) + { + /* We haven't even replayed this frame yet, so we can't overwrite it! */ + return false; + } } remember_state = delta->state; memset(delta, 0, sizeof(struct delta_frame)); + delta->used = true; delta->frame = frame; delta->state = remember_state; return true; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index f02b79ce20..33ef8d521f 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -36,7 +36,12 @@ static void netplay_net_pre_frame(netplay_t *netplay) serial_info.data = netplay->buffer[netplay->self_ptr].state; serial_info.size = netplay->state_size; - core_serialize(&serial_info); + if (!core_serialize(&serial_info)) + { + /* If the core can't serialize properly, we must stall for the + * remote input on EVERY frame, because we can't recover */ + netplay->stall_frames = 0; + } } netplay->can_poll = true; @@ -55,9 +60,11 @@ static void netplay_net_post_frame(netplay_t *netplay) { netplay->self_frame_count++; +#if 0 /* Nothing to do... */ if (netplay->other_frame_count == netplay->read_frame_count) return; +#endif /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ @@ -80,8 +87,8 @@ static void netplay_net_post_frame(netplay_t *netplay) /* Replay frames. */ netplay->is_replay = true; - netplay->replay_ptr = PREV_PTR(netplay->other_ptr); - netplay->replay_frame_count = netplay->other_frame_count - 1; + netplay->replay_ptr = netplay->other_ptr; + netplay->replay_frame_count = netplay->other_frame_count; if (netplay->replay_frame_count < netplay->self_frame_count) { @@ -114,7 +121,7 @@ static void netplay_net_post_frame(netplay_t *netplay) /* For the remainder of the frames up to the read count, we can use the real data */ while (netplay->replay_frame_count < netplay->read_frame_count) { - netplay->buffer[netplay->replay_ptr].used_real = true; + /*netplay->buffer[netplay->replay_ptr].used_real = true;*/ netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; } @@ -222,7 +229,11 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) return false; } - netplay->buffer_size = frames + 1; + /* * 2 + 1 because: + * Self sits in the middle, + * Other is allowed to drift as much as 'frames' frames behind + * Read is allowed to drift as much as 'frames' frames ahead */ + netplay->buffer_size = frames * 2 + 1; if (!netplay_net_init_buffers(netplay)) return false; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 66543e4265..9e6bd71749 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -41,6 +41,7 @@ struct delta_frame { + bool used; /* a bit derpy, but this is how we know if the delta's been used at all */ uint32_t frame; void *state; @@ -143,6 +144,7 @@ struct netplay uint32_t pause_frame; /* And stalling */ + uint32_t stall_frames; int stall; struct netplay_callbacks* net_cbs; From 147d739197ec96fb3c6d4b7e540915415c6c3c15 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Mon, 12 Sep 2016 21:18:00 -0400 Subject: [PATCH 07/22] Fixed stall_frames=0 mode to only block if frames are actually needed. Rather than counting on the complexicon of used_real calculations, set used_real when... real is used. (Problem: If the core doesn't read input at all, used_real won't be set; todo: test with handhelds.) Minor resilience fixes. --- network/netplay/netplay.c | 9 +++++---- network/netplay/netplay_net.c | 7 +++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index f7066ddf4a..fa2b4ec095 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -549,7 +549,7 @@ static bool netplay_poll(netplay_t *netplay) /* Read Netplay input, block if we're configured to stall for input every * frame */ - res = poll_input(netplay, netplay->stall_frames == 0); + res = poll_input(netplay, (netplay->stall_frames == 0) && (netplay->read_frame_count <= netplay->self_frame_count)); if (res == -1) { netplay->has_connection = false; @@ -587,10 +587,8 @@ static bool netplay_poll(netplay_t *netplay) } #endif - if (netplay->read_frame_count < netplay->self_frame_count) + if (netplay->read_frame_count <= netplay->self_frame_count) simulate_input(netplay); - /*else - netplay->buffer[PREV_PTR(netplay->self_ptr)].used_real = true;*/ /* Consider stalling */ switch (netplay->stall) { @@ -677,7 +675,10 @@ static int16_t netplay_input_state(netplay_t *netplay, if (netplay->port == (netplay_flip_port(netplay, port) ? 1 : 0)) { if (netplay->buffer[ptr].have_remote) + { + netplay->buffer[ptr].used_real = true; curr_input_state = netplay->buffer[ptr].real_input_state; + } else curr_input_state = netplay->buffer[ptr].simulated_input_state; } diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 33ef8d521f..a5dc367eff 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -15,9 +15,12 @@ */ #include +#include #include "netplay_private.h" +#include "retro_assert.h" + #include "../../autosave.h" /** @@ -95,7 +98,7 @@ static void netplay_net_post_frame(netplay_t *netplay) serial_info.data = NULL; serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; serial_info.size = netplay->state_size; - + core_unserialize(&serial_info); } @@ -121,7 +124,7 @@ static void netplay_net_post_frame(netplay_t *netplay) /* For the remainder of the frames up to the read count, we can use the real data */ while (netplay->replay_frame_count < netplay->read_frame_count) { - /*netplay->buffer[netplay->replay_ptr].used_real = true;*/ + retro_assert(netplay->buffer[netplay->replay_ptr].have_remote); netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); netplay->replay_frame_count++; } From c5fe0ec6be5723413cb5e26710f275c5654b17cc Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 16:57:32 -0400 Subject: [PATCH 08/22] Updating the Netplay README to be true of the current implementation. --- network/netplay/README | 89 ++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 30 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 3779c0ce68..f0eca64749 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -1,4 +1,15 @@ -How Netplay works (as documented by somebody who didn't write it): +This is RetroArch's Netplay code. RetroArch Netplay allows a second player to +be connected via the Internet, rather than local at the same computer. Netplay +in RetroArch is guaranteed* to work with perfect synchronization given a few +minor constraints: + +(1) The core is deterministic, +(2) The only input devices the core interacts with are the joypad and analog sticks, and +(3) Both the core and the loaded content are identical on host and client. + +Furthermore, if the core supports serialization (save states), Netplay allows +for latency and clock drift, providing both host and client with a smooth +experience. Note that this documentation is all for (the poorly-named) “net” mode, which is the normal mode, and not “spectator” mode, which has its own whole host of @@ -10,43 +21,61 @@ So long as both sides agree on which frame is which, it should be impossible for them to become de-synced, since each input event always happens at the correct frame. -Within the state buffers, there are three locations: self, other and read. Self -is where the emulator believes itself to be, which inevitably will be ahead of -what it's read from the peer. Other is where it was most recently in perfect -sync: i.e., other has been read and actioned. Read is where it's read up to, -which can be slightly ahead of other since it can't always immediately act upon -new data. In general, other ≤ read ≤ self. If read > self, logically it should -try to catch up, but that logic currently doesn't seem to exist (?) +In terms of the implementation, Netplay is in effect a state buffer +(implemented as a ring of buffers) and some pre- and post-frame behaviors. -In terms of the implementation, Netplay is in effect an input buffer and some -pre- and post-frame behaviors. +Within the state buffers, there are three locations: self, other and read. Each +refers to a frame, and a state buffer corresponding to that frame. The state +buffer contains the savestate for the frame, and the input from both the local +and remote players. -Pre-frame, it serializes the core's state, polls for input from the other side, -and if input has not been received for the other side up to the current frame -(which it shouldn't be), “simulates” the other side's input up to the current -frame. The simulation is simply assuming that the input state hasn't changed. -Each frame's local serialized state and simulated or real input goes into the -frame buffers. Frame buffers that are simulated are marked as such. +Self is where the emulator believes itself to be, which may be ahead or behind +of what it's read from the peer. Generally speaking, self progresses at 1 frame +per frame, except when the network stalls, described later. + +Other is where it was most recently in perfect sync: i.e., other-1 is the last +frame from which both local and remote input have been actioned. As such, other +is always less than or equal to both self and read. Since the state buffer is a +ring, other is the first frame that it's unsafe to overwrite. + +Read is where it's read up to, which can be slightly ahead of other since it +can't always immediately act upon new data. + +In general, other ≤ read and other ≤ self. In all likelyhood, read ≤ self, but +it is both possible and supported for the remote host to get ahead of the local +host. + +Pre-frame, Netplay serializes the core's state, polls for local input, and +polls for input from the other side. If the input from the other side is too +far behind, it stalls to allow the other side to catch up. To assure that this +stalling does not block the UI thread, it is implemented by rewinding the +thread every frame until data is ready. + +If input has not been received for the other side up to the current frame (the +usual case), the remote input is simulated in a simplistic manner. Each +frame's local serialized state and simulated or real input goes into the frame +buffers. + +During the frame of execution, when the core requests input, it receives the +input from the thread buffer, both local and real or simulated remote. Post-frame, it checks whether it's read more than it's actioned, i.e. if read > -other. If so, it rewinds to other and runs the core in replay mode with the -real data up to read, then sets other = read. +other. If so, it rewinds to other (by loading the serialized state there) and +runs the core in replay mode with the real data up to read, then sets other = +read. When in Netplay mode, the callback for receiving input is replaced by input_state_net. It is the role of input_state_net to combine the true local input (whether live or replay) with the remote input (whether true or simulated). -In the previous implementation, there was seemingly nothing to prevent the self -frame from advancing past the other frame in the ring buffer, causing local -input from one time to be mixed with remote input from another. The new -implementation uses a not-at-all-better strategy of simply refusing local input -if it steps past remote input. - Some thoughts about "frame counts": The frame counters act like indexes into a -0-indexed array; i.e., they refer to the first unactioned frame. With -self_frame_count it's slightly more complicated, since there are two relevant -actions: Reading the data and emulating with the data. The frame count is only -incremented after the latter, but the nature of the emulation assures that the -current frame's input will always be read before it's actioned (or at least, we -should certainly hope so!) +0-indexed array; i.e., they refer to the first unactioned frame. So, when +read_frame_count is 23, we've read 23 frames, but the last frame we read is +frame 22. With self_frame_count it's slightly more complicated, since there are +two relevant actions: Reading the data and emulating with the data. The frame +count is only incremented after the latter, so there is a period of time during +which we've actually read self_frame_count+1 frames of local input. + + +* Guarantee not actually a guarantee. From f9f4e15d33c2f99f9700d553a6a7d24ff41abe1c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 17:01:31 -0400 Subject: [PATCH 09/22] Removing commented-out code (mostly old UDP stuff) --- network/netplay/netplay.c | 192 +----------------------------- network/netplay/netplay_net.c | 47 -------- network/netplay/netplay_private.h | 2 - 3 files changed, 1 insertion(+), 240 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index fa2b4ec095..646b06a724 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -98,41 +98,6 @@ static bool netplay_can_poll(netplay_t *netplay) return netplay->can_poll; } -#if 0 -static bool send_chunk(netplay_t *netplay) -{ - const struct sockaddr *addr = NULL; - - if (netplay->addr) - addr = netplay->addr->ai_addr; - else if (netplay->has_client_addr) - addr = (const struct sockaddr*)&netplay->their_addr; - - if (addr) - { - ssize_t bytes_sent; - -#ifdef HAVE_IPV6 - bytes_sent = (sendto(netplay->udp_fd, (const char*)netplay->packet_buffer, - sizeof(netplay->packet_buffer), 0, addr, - sizeof(struct sockaddr_in6))); -#else - bytes_sent = (sendto(netplay->udp_fd, (const char*)netplay->packet_buffer, - sizeof(netplay->packet_buffer), 0, addr, - sizeof(struct sockaddr_in))); -#endif - - if (bytes_sent != sizeof(netplay->packet_buffer)) - { - warn_hangup(); - netplay->has_connection = false; - return false; - } - } - return true; -} -#endif - /** * get_self_input_state: * @netplay : pointer to netplay object @@ -380,8 +345,7 @@ static bool netplay_get_cmd(netplay_t *netplay) static int poll_input(netplay_t *netplay, bool block) { bool had_input = false; - int max_fd = (netplay->fd > netplay->udp_fd ? - netplay->fd : netplay->udp_fd) + 1; + int max_fd = netplay->fd + 1; struct timeval tv = {0}; tv.tv_sec = 0; tv.tv_usec = block ? (RETRY_MS * 1000) : 0; @@ -398,7 +362,6 @@ static int poll_input(netplay_t *netplay, bool block) netplay->timeout_cnt++; FD_ZERO(&fds); - FD_SET(netplay->udp_fd, &fds); FD_SET(netplay->fd, &fds); if (socket_select(max_fd, &fds, NULL, NULL, &tmp_tv) < 0) @@ -417,80 +380,16 @@ static int poll_input(netplay_t *netplay, bool block) } } -#if 0 - if (FD_ISSET(netplay->udp_fd, &fds)) - return 1; -#endif - if (!block) continue; -#if 0 - if (!send_chunk(netplay)) - { - warn_hangup(); - netplay->has_connection = false; - return -1; - } -#endif - RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n", netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES); } while (had_input || (block && (netplay->read_frame_count <= netplay->self_frame_count))); - /*if (block) - return -1;*/ return 0; } -#if 0 -static bool receive_data(netplay_t *netplay, uint32_t *buffer, size_t size) -{ - socklen_t addrlen = sizeof(netplay->their_addr); - - if (recvfrom(netplay->udp_fd, (char*)buffer, size, 0, - (struct sockaddr*)&netplay->their_addr, &addrlen) != (ssize_t)size) - return false; - - netplay->has_client_addr = true; - - return true; -} - -static void parse_packet(netplay_t *netplay, uint32_t *buffer, unsigned size) -{ - unsigned i; - - for (i = 0; i < size * UDP_WORDS_PER_FRAME; i++) - buffer[i] = ntohl(buffer[i]); - - for (i = 0; i < size; i++) - { - uint32_t frame = buffer[UDP_WORDS_PER_FRAME * i + 0]; - const uint32_t *state = &buffer[UDP_WORDS_PER_FRAME * i + 1]; - - if (frame != netplay->read_frame_count) - continue; - - if (!netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) - { - netplay->must_fast_forward = true; - continue; - } - - /* FIXME: acknowledge when we completely drop data on the floor */ - - netplay->buffer[netplay->read_ptr].is_simulated = false; - memcpy(netplay->buffer[netplay->read_ptr].real_input_state, state, - sizeof(netplay->buffer[netplay->read_ptr].real_input_state)); - - netplay->read_ptr = NEXT_PTR(netplay->read_ptr); - netplay->read_frame_count++; - netplay->timeout_cnt = 0; - } -} -#endif - /* TODO: Somewhat better prediction. :P */ static void simulate_input(netplay_t *netplay) { @@ -524,29 +423,8 @@ static bool netplay_poll(netplay_t *netplay) netplay->can_poll = false; -#if 0 - if (!get_self_input_state(netplay)) - return false; -#endif get_self_input_state(netplay); -#if 0 - /* We skip reading the first frame so the host has a chance to grab - * our host info so we don't block forever :') */ - if (netplay->self_frame_count == 0) - { - netplay->buffer[0].used_real = true; - netplay->buffer[0].have_remote = true; - - memset(netplay->buffer[0].real_input_state, - 0, sizeof(netplay->buffer[0].real_input_state)); - - netplay->read_ptr = NEXT_PTR(netplay->read_ptr); - netplay->read_frame_count++; - return true; - } -#endif - /* Read Netplay input, block if we're configured to stall for input every * frame */ res = poll_input(netplay, (netplay->stall_frames == 0) && (netplay->read_frame_count <= netplay->self_frame_count)); @@ -557,36 +435,6 @@ static bool netplay_poll(netplay_t *netplay) return false; } -#if 0 - if (res == 1) - { - uint32_t first_read = netplay->read_frame_count; - do - { - uint32_t buffer[UDP_FRAME_PACKETS * UDP_WORDS_PER_FRAME]; - if (!receive_data(netplay, buffer, sizeof(buffer))) - { - warn_hangup(); - netplay->has_connection = false; - return false; - } - parse_packet(netplay, buffer, UDP_FRAME_PACKETS); - - } while ((netplay->read_frame_count <= netplay->self_frame_count) && - poll_input(netplay, (netplay->other_ptr == netplay->self_ptr) && - (first_read == netplay->read_frame_count)) == 1); - } - else - { - /* Cannot allow this. Should not happen though. */ - if (netplay->self_ptr == netplay->other_ptr) - { - warn_hangup(); - return false; - } - } -#endif - if (netplay->read_frame_count <= netplay->self_frame_count) simulate_input(netplay); @@ -887,37 +735,6 @@ static bool init_tcp_socket(netplay_t *netplay, const char *server, return ret; } -static bool init_udp_socket(netplay_t *netplay, const char *server, - uint16_t port) -{ - int fd = socket_init((void**)&netplay->addr, port, server, SOCKET_TYPE_DATAGRAM); - - if (fd < 0) - goto error; - - netplay->udp_fd = fd; - - if (!server) - { - /* Not sure if we have to do this for UDP, but hey :) */ - if (!socket_bind(netplay->udp_fd, (void*)netplay->addr)) - { - RARCH_ERR("Failed to bind socket.\n"); - socket_close(netplay->udp_fd); - netplay->udp_fd = -1; - } - - freeaddrinfo_retro(netplay->addr); - netplay->addr = NULL; - } - - return true; - -error: - RARCH_ERR("Failed to initialize socket.\n"); - return false; -} - static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) { if (!network_init()) @@ -925,8 +742,6 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) if (!init_tcp_socket(netplay, server, port, netplay->spectate.enabled)) return false; - if (!netplay->spectate.enabled && !init_udp_socket(netplay, server, port)) - return false; return true; } @@ -958,7 +773,6 @@ netplay_t *netplay_new(const char *server, uint16_t port, return NULL; netplay->fd = -1; - netplay->udp_fd = -1; netplay->cbs = *cb; netplay->port = server ? 0 : 1; netplay->spectate.enabled = spectate; @@ -985,8 +799,6 @@ netplay_t *netplay_new(const char *server, uint16_t port, error: if (netplay->fd >= 0) socket_close(netplay->fd); - if (netplay->udp_fd >= 0) - socket_close(netplay->udp_fd); free(netplay); return NULL; @@ -1099,8 +911,6 @@ void netplay_free(netplay_t *netplay) } else { - socket_close(netplay->udp_fd); - for (i = 0; i < netplay->buffer_size; i++) free(netplay->buffer[i].state); diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index a5dc367eff..3ce115e99c 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -63,12 +63,6 @@ static void netplay_net_post_frame(netplay_t *netplay) { netplay->self_frame_count++; -#if 0 - /* Nothing to do... */ - if (netplay->other_frame_count == netplay->read_frame_count) - return; -#endif - /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ while (netplay->other_frame_count < netplay->read_frame_count) @@ -134,47 +128,6 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->is_replay = false; } -#if 0 - /* And if the other side has gotten too far ahead of /us/, skip to catch up - * FIXME: Make this configurable */ - if (netplay->read_frame_count > netplay->self_frame_count + 10 || - netplay->must_fast_forward) - { - /* "replay" into the future */ - netplay->is_replay = true; - netplay->replay_ptr = netplay->self_ptr; - netplay->replay_frame_count = netplay->self_frame_count; - - /* just assume input doesn't change for the intervening frames */ - while (netplay->replay_frame_count < netplay->read_frame_count) - { - size_t cur = netplay->replay_ptr; - size_t prev = PREV_PTR(cur); - - memcpy(netplay->buffer[cur].self_state, netplay->buffer[prev].self_state, - sizeof(netplay->buffer[prev].self_state)); - -#if defined(HAVE_THREADS) - autosave_lock(); -#endif - core_run(); -#if defined(HAVE_THREADS) - autosave_unlock(); -#endif - - netplay->replay_ptr = NEXT_PTR(cur); - netplay->replay_frame_count++; - } - - /* at this point, other = read = self */ - netplay->self_ptr = netplay->replay_ptr; - netplay->self_frame_count = netplay->replay_frame_count; - netplay->other_ptr = netplay->read_ptr; - netplay->other_frame_count = netplay->read_frame_count; - netplay->is_replay = false; - } -#endif - /* If we're supposed to stall, rewind */ if (netplay->stall) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 9e6bd71749..7785195cb9 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -81,8 +81,6 @@ struct netplay struct retro_callbacks cbs; /* TCP connection for state sending, etc. Also used for commands */ int fd; - /* UDP connection for game state updates. */ - int udp_fd; /* Which port is governed by netplay (other user)? */ unsigned port; bool has_connection; From ae8e69564452bdf48900887d3ef8aa8d4085e1d9 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 17:05:28 -0400 Subject: [PATCH 10/22] Fixing indentation to align with the rest of RetroArch. --- network/netplay/netplay.c | 96 ++++++++++++++++---------------- network/netplay/netplay_common.c | 40 ++++++------- network/netplay/netplay_net.c | 26 ++++----- 3 files changed, 81 insertions(+), 81 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 646b06a724..2ee8be9bd4 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -196,7 +196,7 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, if (size > 0) if (!socket_send_all_blocking(netplay->fd, data, size, false)) - return false; + return false; return true; } @@ -236,7 +236,7 @@ static bool netplay_get_cmd(netplay_t *netplay) cmd = ntohl(cmd); if (!socket_receive_all_blocking(netplay->fd, &cmd_size, sizeof(cmd))) - return false; + return false; cmd_size = ntohl(cmd_size); @@ -248,40 +248,40 @@ static bool netplay_get_cmd(netplay_t *netplay) return true; case NETPLAY_CMD_INPUT: - { - uint32_t buffer[WORDS_PER_FRAME]; - unsigned i; - - if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) { - RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); - return netplay_cmd_nak(netplay); + uint32_t buffer[WORDS_PER_FRAME]; + unsigned i; + + if (cmd_size != WORDS_PER_FRAME * sizeof(uint32_t)) + { + RARCH_ERR("NETPLAY_CMD_INPUT received an unexpected payload size.\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) + { + RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); + return netplay_cmd_nak(netplay); + } + + for (i = 0; i < WORDS_PER_FRAME; i++) + buffer[i] = ntohl(buffer[i]); + + if (buffer[0] != netplay->read_frame_count) + { + /* FIXME: Except on the first (null) frame, this should be + * impossible, so maybe just disconnect? */ + return netplay_cmd_nak(netplay); + } + + /* The data's good! */ + netplay->buffer[netplay->read_ptr].have_remote = true; + memcpy(netplay->buffer[netplay->read_ptr].real_input_state, buffer + 1, sizeof(buffer) - sizeof(uint32_t)); + netplay->read_ptr = NEXT_PTR(netplay->read_ptr); + netplay->read_frame_count++; + return true; } - if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) - { - RARCH_ERR("Failed to receive NETPLAY_CMD_INPUT input.\n"); - return netplay_cmd_nak(netplay); - } - - for (i = 0; i < WORDS_PER_FRAME; i++) - buffer[i] = ntohl(buffer[i]); - - if (buffer[0] != netplay->read_frame_count) - { - /* FIXME: Except on the first (null) frame, this should be - * impossible, so maybe just disconnect? */ - return netplay_cmd_nak(netplay); - } - - /* The data's good! */ - netplay->buffer[netplay->read_ptr].have_remote = true; - memcpy(netplay->buffer[netplay->read_ptr].real_input_state, buffer + 1, sizeof(buffer) - sizeof(uint32_t)); - netplay->read_ptr = NEXT_PTR(netplay->read_ptr); - netplay->read_frame_count++; - return true; - } - case NETPLAY_CMD_FLIP_PLAYERS: if (cmd_size != sizeof(uint32_t)) { @@ -371,13 +371,13 @@ static int poll_input(netplay_t *netplay, bool block) * but we aren't using the TCP connection for anything useful atm. */ if (FD_ISSET(netplay->fd, &fds)) { - /* If we're not ready for input, wait until we are. Could fill the TCP buffer, stalling the other side. */ - if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) - { - had_input = true; - if (!netplay_get_cmd(netplay)) - return -1; - } + /* If we're not ready for input, wait until we are. Could fill the TCP buffer, stalling the other side. */ + if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->read_ptr], netplay->read_frame_count)) + { + had_input = true; + if (!netplay_get_cmd(netplay)) + return -1; + } } if (!block) @@ -440,14 +440,14 @@ static bool netplay_poll(netplay_t *netplay) /* Consider stalling */ switch (netplay->stall) { - case RARCH_NETPLAY_STALL_RUNNING_FAST: - if (netplay->read_frame_count >= netplay->self_frame_count) - netplay->stall = RARCH_NETPLAY_STALL_NONE; - break; + case RARCH_NETPLAY_STALL_RUNNING_FAST: + if (netplay->read_frame_count >= netplay->self_frame_count) + netplay->stall = RARCH_NETPLAY_STALL_NONE; + break; - default: /* not stalling */ - if (netplay->read_frame_count + netplay->stall_frames <= netplay->self_frame_count) - netplay->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; + default: /* not stalling */ + if (netplay->read_frame_count + netplay->stall_frames <= netplay->self_frame_count) + netplay->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; } return true; @@ -881,7 +881,7 @@ static void netplay_flip_users(netplay_t *netplay) &flip_frame_net, sizeof flip_frame_net, CMD_OPT_HOST_ONLY | CMD_OPT_REQUIRE_SYNC, "flip users", "Successfully flipped users.\n"); - + if(command) { netplay->flip ^= true; diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index 2468c0102c..b1053c08e7 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -79,7 +79,7 @@ uint32_t *netplay_bsv_header_generate(size_t *size, uint32_t magic) uint32_t *header, bsv_header[4] = {0}; core_serialize_size(&info); - + serialize_size = info.size; header_size = sizeof(bsv_header) + serialize_size; *size = header_size; @@ -177,9 +177,9 @@ uint32_t netplay_impl_magic(void) core_api_version(&api_info); api = api_info.version; - + runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &info); - + if (info) lib = info->info.library_name; @@ -217,7 +217,7 @@ bool netplay_send_info(netplay_t *netplay) core_get_memory(&mem_info); content_get_crc(&content_crc_ptr); - + header[0] = htonl(*content_crc_ptr); header[1] = htonl(netplay_impl_magic()); header[2] = htonl(mem_info.size); @@ -347,20 +347,20 @@ bool netplay_is_spectate(netplay_t* netplay) bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame) { - void *remember_state; - if (delta->used) - { - if (delta->frame == frame) return true; - if (netplay->other_frame_count <= delta->frame) - { - /* We haven't even replayed this frame yet, so we can't overwrite it! */ - return false; - } - } - remember_state = delta->state; - memset(delta, 0, sizeof(struct delta_frame)); - delta->used = true; - delta->frame = frame; - delta->state = remember_state; - return true; + void *remember_state; + if (delta->used) + { + if (delta->frame == frame) return true; + if (netplay->other_frame_count <= delta->frame) + { + /* We haven't even replayed this frame yet, so we can't overwrite it! */ + return false; + } + } + remember_state = delta->state; + memset(delta, 0, sizeof(struct delta_frame)); + delta->used = true; + delta->frame = frame; + delta->state = remember_state; + return true; } diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 3ce115e99c..3ef0d57913 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -35,16 +35,16 @@ static void netplay_net_pre_frame(netplay_t *netplay) if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) { - serial_info.data_const = NULL; - serial_info.data = netplay->buffer[netplay->self_ptr].state; - serial_info.size = netplay->state_size; + serial_info.data_const = NULL; + serial_info.data = netplay->buffer[netplay->self_ptr].state; + serial_info.size = netplay->state_size; - if (!core_serialize(&serial_info)) - { - /* If the core can't serialize properly, we must stall for the - * remote input on EVERY frame, because we can't recover */ - netplay->stall_frames = 0; - } + if (!core_serialize(&serial_info)) + { + /* If the core can't serialize properly, we must stall for the + * remote input on EVERY frame, because we can't recover */ + netplay->stall_frames = 0; + } } netplay->can_poll = true; @@ -118,9 +118,9 @@ static void netplay_net_post_frame(netplay_t *netplay) /* For the remainder of the frames up to the read count, we can use the real data */ while (netplay->replay_frame_count < netplay->read_frame_count) { - retro_assert(netplay->buffer[netplay->replay_ptr].have_remote); - netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); - netplay->replay_frame_count++; + retro_assert(netplay->buffer[netplay->replay_ptr].have_remote); + netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); + netplay->replay_frame_count++; } netplay->other_ptr = netplay->read_ptr; @@ -153,7 +153,7 @@ static bool netplay_net_init_buffers(netplay_t *netplay) netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, sizeof(*netplay->buffer)); - + if (!netplay->buffer) return false; From 0ccc39769d98840d58f7ca630af8e26c7ecc3078 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 17:06:23 -0400 Subject: [PATCH 11/22] Adding my copyright lines to files I've touched. --- network/netplay/netplay.c | 1 + network/netplay/netplay_common.c | 1 + network/netplay/netplay_net.c | 1 + 3 files changed, 3 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 2ee8be9bd4..5849136258 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1,6 +1,7 @@ /* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2016 - Daniel De Matteis + * Copyright (C) 2016 - Gregor Richards * * 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- diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index b1053c08e7..a1b26e1154 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -1,6 +1,7 @@ /* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2016 - Daniel De Matteis + * Copyright (C) 2016 - Gregor Richards * * 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- diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 3ef0d57913..ea15cac265 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -1,6 +1,7 @@ /* RetroArch - A frontend for libretro. * Copyright (C) 2010-2014 - Hans-Kristian Arntzen * Copyright (C) 2011-2016 - Daniel De Matteis + * Copyright (C) 2016 - Gregor Richards * * 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- From 8aa48cd3f955e8e4360720aab6736a0b79a4eeae Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 17:08:59 -0400 Subject: [PATCH 12/22] Reinstituted "standard" timeout-based disconnection, which only works for stall_frames==0 due to the fixes to not stall the UI. --- network/netplay/netplay.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 5849136258..0cf6653012 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -386,6 +386,9 @@ static int poll_input(netplay_t *netplay, bool block) RARCH_LOG("Network is stalling at frame %u, count %u of %d ...\n", netplay->self_frame_count, netplay->timeout_cnt, MAX_RETRIES); + + if (netplay->timeout_cnt >= MAX_RETRIES) + return -1; } while (had_input || (block && (netplay->read_frame_count <= netplay->self_frame_count))); return 0; From 6829b80c6bb1ff530658d40d187ac533421a11de Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 17:33:26 -0400 Subject: [PATCH 13/22] Reimplemented disconnection based on stalls. If we stall for 10 seconds, disconnect. --- network/netplay/netplay.c | 19 +++++++++++++++++++ network/netplay/netplay_net.c | 4 ++++ network/netplay/netplay_private.h | 2 ++ 3 files changed, 25 insertions(+) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 0cf6653012..ef72732002 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "netplay_private.h" @@ -408,6 +409,8 @@ static void simulate_input(netplay_t *netplay) netplay->buffer[ptr].used_real = false; } +#define MAX_STALL_TIME_USEC 10000000 + /** * netplay_poll: * @netplay : pointer to netplay object @@ -451,7 +454,23 @@ static bool netplay_poll(netplay_t *netplay) default: /* not stalling */ if (netplay->read_frame_count + netplay->stall_frames <= netplay->self_frame_count) + { netplay->stall = RARCH_NETPLAY_STALL_RUNNING_FAST; + netplay->stall_time = cpu_features_get_time_usec(); + } + } + + /* If we're stalling, consider disconnection */ + if (netplay->stall) + { + retro_time_t now = cpu_features_get_time_usec(); + if (now - netplay->stall_time >= MAX_STALL_TIME_USEC) + { + /* Stalled out! */ + netplay->has_connection = false; + warn_hangup(); + return false; + } } return true; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index ea15cac265..6458f1e412 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -64,6 +64,10 @@ static void netplay_net_post_frame(netplay_t *netplay) { netplay->self_frame_count++; + /* Only relevant if we're connected */ + if (!netplay->has_connection) + return; + /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ while (netplay->other_frame_count < netplay->read_frame_count) diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 7785195cb9..e8bf820e40 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -20,6 +20,7 @@ #include "netplay.h" #include +#include #include #include "../../core.h" @@ -144,6 +145,7 @@ struct netplay /* And stalling */ uint32_t stall_frames; int stall; + retro_time_t stall_time; struct netplay_callbacks* net_cbs; }; From b140b16b5df72652150d75554d20fca3ac2ae335 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 18:03:36 -0400 Subject: [PATCH 14/22] Removed Netplay positive acknowledgement messages: They didn't document their corresponding sent message, and so couldn't be used for acknowledgement anyway. Negative acknowledgement is sufficient. --- network/netplay/netplay.c | 53 ++++++--------------------------------- 1 file changed, 7 insertions(+), 46 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index ef72732002..b3b818266a 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -58,20 +58,6 @@ static void warn_hangup(void) runloop_msg_queue_push("Netplay has disconnected. Will continue without connection.", 0, 480, false); } -/** - * check_netplay_synched: - * @netplay: pointer to the netplay object. - * Checks to see if the host and client have synchronized states. Returns true - * on success and false on failure. - */ -bool check_netplay_synched(netplay_t* netplay) -{ - retro_assert(netplay); - /*return netplay->self_frame_count < (netplay->flip_frame + 2 * UDP_FRAME_PACKETS);*/ - /* FIXME */ - return true; -} - static bool netplay_info_cb(netplay_t* netplay, unsigned frames) { return netplay->net_cbs->info_cb(netplay, frames); } @@ -203,25 +189,11 @@ static bool netplay_send_raw_cmd(netplay_t *netplay, uint32_t cmd, return true; } -static bool netplay_cmd_ack(netplay_t *netplay) -{ - return netplay_send_raw_cmd(netplay, NETPLAY_CMD_ACK, NULL, 0); -} - static bool netplay_cmd_nak(netplay_t *netplay) { return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); } -static bool netplay_get_response(netplay_t *netplay) -{ - uint32_t response; - if (!socket_receive_all_blocking(netplay->fd, &response, sizeof(response))) - return false; - - return ntohl(response) == NETPLAY_CMD_ACK; -} - static bool netplay_get_cmd(netplay_t *netplay) { uint32_t cmd; @@ -312,7 +284,7 @@ static bool netplay_get_cmd(netplay_t *netplay) RARCH_LOG("Netplay users are flipped.\n"); runloop_msg_queue_push("Netplay users are flipped.", 1, 180, false); - return netplay_cmd_ack(netplay); + return true; case NETPLAY_CMD_SPECTATE: RARCH_ERR("NETPLAY_CMD_SPECTATE unimplemented.\n"); @@ -320,7 +292,7 @@ static bool netplay_get_cmd(netplay_t *netplay) case NETPLAY_CMD_DISCONNECT: warn_hangup(); - return netplay_cmd_ack(netplay); + return true; case NETPLAY_CMD_LOAD_SAVESTATE: RARCH_ERR("NETPLAY_CMD_LOAD_SAVESTATE unimplemented.\n"); @@ -328,11 +300,11 @@ static bool netplay_get_cmd(netplay_t *netplay) case NETPLAY_CMD_PAUSE: command_event(CMD_EVENT_PAUSE, NULL); - return netplay_cmd_ack(netplay); + return true; case NETPLAY_CMD_RESUME: command_event(CMD_EVENT_UNPAUSE, NULL); - return netplay_cmd_ack(netplay); + return true; default: break; } @@ -849,7 +821,6 @@ bool netplay_command(netplay_t* netplay, enum netplay_cmd cmd, const char* msg = NULL; bool allowed_spectate = !!(flags & CMD_OPT_ALLOWED_IN_SPECTATE_MODE); bool host_only = !!(flags & CMD_OPT_HOST_ONLY); - bool require_sync = !!(flags & CMD_OPT_REQUIRE_SYNC); retro_assert(netplay); @@ -865,21 +836,11 @@ bool netplay_command(netplay_t* netplay, enum netplay_cmd cmd, goto error; } - if(require_sync && check_netplay_synched(netplay)) - { - msg = "Cannot %s while host and client are not in sync."; + if (!netplay_send_raw_cmd(netplay, cmd, data, sz)) goto error; - } - if(netplay_send_raw_cmd(netplay, cmd, data, sz)) { - if(netplay_get_response(netplay)) - runloop_msg_queue_push(success_msg, 1, 180, false); - else - { - msg = "Failed to send command \"%s\""; - goto error; - } - } + runloop_msg_queue_push(success_msg, 1, 180, false); + return true; error: From a0cfdb8a9cc31762f0dff3b4076e13fe968d39b3 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 20:34:10 -0400 Subject: [PATCH 15/22] naks now cause disconnection. --- network/netplay/netplay.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index b3b818266a..79e0f8f212 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -217,10 +217,13 @@ static bool netplay_get_cmd(netplay_t *netplay) switch (cmd) { case NETPLAY_CMD_ACK: - case NETPLAY_CMD_NAK: /* Why are we even bothering? */ return true; + case NETPLAY_CMD_NAK: + /* Disconnect now! */ + return false; + case NETPLAY_CMD_INPUT: { uint32_t buffer[WORDS_PER_FRAME]; @@ -243,8 +246,7 @@ static bool netplay_get_cmd(netplay_t *netplay) if (buffer[0] != netplay->read_frame_count) { - /* FIXME: Except on the first (null) frame, this should be - * impossible, so maybe just disconnect? */ + /* Out of order = out of luck */ return netplay_cmd_nak(netplay); } From 1267e5e867cbfcb557637a944effefbeacdaa1a9 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 20:34:40 -0400 Subject: [PATCH 16/22] A few clarifications regarding the buffer's have_remote and used_real data. simulation shouldn't touch 'em. --- network/netplay/netplay.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 79e0f8f212..16fcd9fb23 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -378,9 +378,6 @@ static void simulate_input(netplay_t *netplay) memcpy(netplay->buffer[ptr].simulated_input_state, netplay->buffer[prev].real_input_state, sizeof(netplay->buffer[prev].real_input_state)); - - netplay->buffer[ptr].have_remote = false; - netplay->buffer[ptr].used_real = false; } #define MAX_STALL_TIME_USEC 10000000 @@ -416,7 +413,8 @@ static bool netplay_poll(netplay_t *netplay) return false; } - if (netplay->read_frame_count <= netplay->self_frame_count) + /* Simulate the input if we don't have real input */ + if (!netplay->buffer[PREV_PTR(netplay->self_ptr)].have_remote) simulate_input(netplay); /* Consider stalling */ From 99b5ed92ed335c5211ed17381499d6589c321559 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 20:35:10 -0400 Subject: [PATCH 17/22] other should always be <= both real AND self. Before this fix, it was possible (albeit unlikely) for the remote to get so far ahead of us that they actually overwrote our own current data :) --- network/netplay/netplay_net.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 6458f1e412..10f00e2054 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -70,7 +70,8 @@ static void netplay_net_post_frame(netplay_t *netplay) /* Skip ahead if we predicted correctly. * Skip until our simulation failed. */ - while (netplay->other_frame_count < netplay->read_frame_count) + while (netplay->other_frame_count < netplay->read_frame_count && + netplay->other_frame_count < netplay->self_frame_count) { const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; @@ -83,7 +84,8 @@ static void netplay_net_post_frame(netplay_t *netplay) } /* Now replay the real input if we've gotten ahead of it */ - if (netplay->other_frame_count < netplay->read_frame_count) + if (netplay->other_frame_count < netplay->read_frame_count && + netplay->other_frame_count < netplay->self_frame_count) { retro_ctx_serialize_info_t serial_info; @@ -120,16 +122,14 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->replay_frame_count++; } - /* For the remainder of the frames up to the read count, we can use the real data */ - while (netplay->replay_frame_count < netplay->read_frame_count) + if (netplay->read_frame_count < netplay->self_frame_count) { - retro_assert(netplay->buffer[netplay->replay_ptr].have_remote); - netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); - netplay->replay_frame_count++; + netplay->other_ptr = netplay->read_ptr; + netplay->other_frame_count = netplay->read_frame_count; + } else { + netplay->other_ptr = netplay->self_ptr; + netplay->other_frame_count = netplay->self_frame_count; } - - netplay->other_ptr = netplay->read_ptr; - netplay->other_frame_count = netplay->read_frame_count; netplay->is_replay = false; } From d4e074dbedeb2b9275c592a1b01913f7e59d534f Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 20:37:53 -0400 Subject: [PATCH 18/22] Moved the advance of self_ptr to the same place as the advance of self_frame_count, which is much clearer. --- network/netplay/netplay.c | 8 +++----- network/netplay/netplay_net.c | 1 + 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 16fcd9fb23..7de915619f 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -105,7 +105,6 @@ static bool get_self_input_state(netplay_t *netplay) if (ptr->have_local) { /* We've already read this frame! */ - netplay->self_ptr = NEXT_PTR(netplay->self_ptr); return true; } @@ -167,7 +166,6 @@ static bool get_self_input_state(netplay_t *netplay) memcpy(ptr->self_state, state, sizeof(state)); ptr->have_local = true; - netplay->self_ptr = NEXT_PTR(netplay->self_ptr); return true; } @@ -372,7 +370,7 @@ static int poll_input(netplay_t *netplay, bool block) /* TODO: Somewhat better prediction. :P */ static void simulate_input(netplay_t *netplay) { - size_t ptr = PREV_PTR(netplay->self_ptr); + size_t ptr = netplay->self_ptr; size_t prev = PREV_PTR(netplay->read_ptr); memcpy(netplay->buffer[ptr].simulated_input_state, @@ -414,7 +412,7 @@ static bool netplay_poll(netplay_t *netplay) } /* Simulate the input if we don't have real input */ - if (!netplay->buffer[PREV_PTR(netplay->self_ptr)].have_remote) + if (!netplay->buffer[netplay->self_ptr].have_remote) simulate_input(netplay); /* Consider stalling */ @@ -511,7 +509,7 @@ static int16_t netplay_input_state(netplay_t *netplay, unsigned idx, unsigned id) { size_t ptr = netplay->is_replay ? - netplay->replay_ptr : PREV_PTR(netplay->self_ptr); + netplay->replay_ptr : netplay->self_ptr; const uint32_t *curr_input_state = netplay->buffer[ptr].self_state; diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 10f00e2054..a6d872734b 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -62,6 +62,7 @@ static void netplay_net_pre_frame(netplay_t *netplay) **/ static void netplay_net_post_frame(netplay_t *netplay) { + netplay->self_ptr = NEXT_PTR(netplay->self_ptr); netplay->self_frame_count++; /* Only relevant if we're connected */ From ea0bb6f812866571e3fc568da006951c95c9ff95 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 21:39:10 -0400 Subject: [PATCH 19/22] Just in case some systems don't have TCP_NODElAY, put that in an ifdef. --- network/netplay/netplay.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 7de915619f..48a50c4f3d 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -625,9 +625,13 @@ static int init_tcp_connection(const struct addrinfo *res, { bool ret = true; int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); - int flag = 1; - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)); +#if defined(IPPROTO_TCP) && defined(TCP_NODELAY) + { + int flag = 1; + setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(int)); + } +#endif if (fd < 0) { From 12bf3e4824e190e099ee53b13b828ce19b435d20 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Tue, 13 Sep 2016 21:45:06 -0400 Subject: [PATCH 20/22] Rather than repeating the same sample for every frame while stalled, just pause the sound while stalled. Both options are annoying, but this option is less annoying. --- network/netplay/netplay.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 48a50c4f3d..f56da9fd34 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -464,14 +464,14 @@ void video_frame_net(const void *data, unsigned width, void audio_sample_net(int16_t left, int16_t right) { netplay_t *netplay = (netplay_t*)netplay_data; - if (!netplay_should_skip(netplay)) + if (!netplay_should_skip(netplay) && !netplay->stall) netplay->cbs.sample_cb(left, right); } size_t audio_sample_batch_net(const int16_t *data, size_t frames) { netplay_t *netplay = (netplay_t*)netplay_data; - if (!netplay_should_skip(netplay)) + if (!netplay_should_skip(netplay) && !netplay->stall) return netplay->cbs.sample_batch_cb(data, frames); return frames; } From b1a2e096e5ae3c88776ad73fe5fb345abe1f0672 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 10:03:26 -0400 Subject: [PATCH 21/22] Use consistent quotes in Netplay README. --- network/netplay/README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index f0eca64749..5977cf63ab 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -11,8 +11,8 @@ Furthermore, if the core supports serialization (save states), Netplay allows for latency and clock drift, providing both host and client with a smooth experience. -Note that this documentation is all for (the poorly-named) “net” mode, which is -the normal mode, and not “spectator” mode, which has its own whole host of +Note that this documentation is all for (the poorly-named) "net" mode, which is +the normal mode, and not "spectator" mode, which has its own whole host of problems. Netplay in RetroArch works by expecting input to come delayed from the network, From 9bc78d25a0ce248f44363a12699e80e814290a4c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 10:06:57 -0400 Subject: [PATCH 22/22] Minor stylistic fixes for clarity and consistency with the rest of RetroArch. --- network/netplay/netplay.c | 4 +++- network/netplay/netplay_net.c | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index f56da9fd34..6fc49d4c72 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -378,7 +378,7 @@ static void simulate_input(netplay_t *netplay) sizeof(netplay->buffer[prev].real_input_state)); } -#define MAX_STALL_TIME_USEC 10000000 +#define MAX_STALL_TIME_USEC (10*1000*1000) /** * netplay_poll: @@ -521,7 +521,9 @@ static int16_t netplay_input_state(netplay_t *netplay, curr_input_state = netplay->buffer[ptr].real_input_state; } else + { curr_input_state = netplay->buffer[ptr].simulated_input_state; + } } switch (device) diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index a6d872734b..ee9a13c5df 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -127,7 +127,9 @@ static void netplay_net_post_frame(netplay_t *netplay) { netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = netplay->read_frame_count; - } else { + } + else + { netplay->other_ptr = netplay->self_ptr; netplay->other_frame_count = netplay->self_frame_count; }