From 96186438ec488f65bfcdbd46af3cbc7bb4fdbe58 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 14:25:54 -0400 Subject: [PATCH 01/13] Remote pausing Support for remote pausing, and with it, support for Netplay pausing the frontend correctly. With this patch alone this doesn't work, since there's no clean way for the frontend to tell Netplay that it's paused. --- network/netplay/netplay.c | 52 ++++++++++++++++++++++++++----- network/netplay/netplay.h | 18 +++++++++-- network/netplay/netplay_net.c | 3 +- network/netplay/netplay_private.h | 4 +-- runloop.c | 8 ++++- 5 files changed, 72 insertions(+), 13 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 1d09b6b18d..51a67e74cc 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -299,11 +299,11 @@ static bool netplay_get_cmd(netplay_t *netplay) return netplay_cmd_nak(netplay); case NETPLAY_CMD_PAUSE: - command_event(CMD_EVENT_PAUSE, NULL); + netplay->remote_paused = true; return true; case NETPLAY_CMD_RESUME: - command_event(CMD_EVENT_UNPAUSE, NULL); + netplay->remote_paused = false; return true; default: break; @@ -360,7 +360,7 @@ 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) + if (netplay->timeout_cnt >= MAX_RETRIES && !netplay->remote_paused) return -1; } while (had_input || (block && (netplay->read_frame_count <= netplay->self_frame_count))); @@ -434,7 +434,12 @@ static bool netplay_poll(netplay_t *netplay) if (netplay->stall) { retro_time_t now = cpu_features_get_time_usec(); - if (now - netplay->stall_time >= MAX_STALL_TIME_USEC) + if (netplay->remote_paused) + { + /* Don't stall out while they're paused */ + netplay->stall_time = now; + } + else if (now - netplay->stall_time >= MAX_STALL_TIME_USEC) { /* Stalled out! */ netplay->has_connection = false; @@ -964,11 +969,20 @@ int16_t input_state_spectate_client(unsigned port, unsigned device, * * Pre-frame for Netplay. * Call this before running retro_run(). + * + * Returns: true (1) if the frontend is cleared to emulate the frame, false (0) + * if we're stalled or paused **/ -void netplay_pre_frame(netplay_t *netplay) +bool netplay_pre_frame(netplay_t *netplay) { retro_assert(netplay && netplay->net_cbs->pre_frame); + if (netplay->local_paused) + { + /* FIXME: This is an ugly way to learn we're not paused anymore */ + netplay_frontend_paused(netplay, false); + } netplay->net_cbs->pre_frame(netplay); + return (!netplay->stall && !netplay->remote_paused); } /** @@ -985,6 +999,25 @@ void netplay_post_frame(netplay_t *netplay) netplay->net_cbs->post_frame(netplay); } +/** + * netplay_frontend_paused + * @netplay : pointer to netplay object + * @paused : true if frontend is paused + * + * Inform Netplay of the frontend's pause state (paused or otherwise) + **/ +void netplay_frontend_paused(netplay_t *netplay, bool paused) +{ + /* Nothing to do if we already knew this */ + if (netplay->local_paused == paused) + return; + + fprintf(stderr, "Paused? %d\n", paused); + netplay->local_paused = paused; + if (netplay->has_connection) + netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); +} + void deinit_netplay(void) { netplay_t *netplay = (netplay_t*)netplay_data; @@ -1060,8 +1093,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) netplay_post_frame((netplay_t*)netplay_data); break; case RARCH_NETPLAY_CTL_PRE_FRAME: - netplay_pre_frame((netplay_t*)netplay_data); - break; + return netplay_pre_frame((netplay_t*)netplay_data); case RARCH_NETPLAY_CTL_FLIP_PLAYERS: { bool *state = (bool*)data; @@ -1076,6 +1108,12 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) command_event(CMD_EVENT_FULLSCREEN_TOGGLE, NULL); } break; + case RARCH_NETPLAY_CTL_PAUSE: + netplay_frontend_paused((netplay_t*)netplay_data, true); + break; + case RARCH_NETPLAY_CTL_UNPAUSE: + netplay_frontend_paused((netplay_t*)netplay_data, false); + break; default: case RARCH_NETPLAY_CTL_NONE: break; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 3aac5bf1c0..54a32fb789 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -35,7 +35,9 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_FULLSCREEN_TOGGLE, RARCH_NETPLAY_CTL_POST_FRAME, RARCH_NETPLAY_CTL_PRE_FRAME, - RARCH_NETPLAY_CTL_IS_DATA_INITED + RARCH_NETPLAY_CTL_IS_DATA_INITED, + RARCH_NETPLAY_CTL_PAUSE, + RARCH_NETPLAY_CTL_UNPAUSE }; enum netplay_cmd @@ -154,8 +156,11 @@ void netplay_free(netplay_t *handle); * * Pre-frame for Netplay. * Call this before running retro_run(). + * + * Returns: true (1) if the frontend is clear to emulate the frame, false (0) + * if we're stalled or paused **/ -void netplay_pre_frame(netplay_t *handle); +bool netplay_pre_frame(netplay_t *handle); /** * netplay_post_frame: @@ -167,6 +172,15 @@ void netplay_pre_frame(netplay_t *handle); **/ void netplay_post_frame(netplay_t *handle); +/** + * netplay_frontend_paused + * @netplay : pointer to netplay object + * @paused : true if frontend is paused + * + * Inform Netplay of the frontend's pause state (paused or otherwise) + **/ +void netplay_frontend_paused(netplay_t *netplay, bool paused); + /** * init_netplay: * diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index ee9a13c5df..05c106f517 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -136,7 +136,8 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->is_replay = false; } - /* If we're supposed to stall, rewind */ + /* If we're supposed to stall, rewind (we shouldn't get this far if we're + * stalled, so this is a last resort) */ if (netplay->stall) { retro_ctx_serialize_info_t serial_info; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index e8bf820e40..56cd84c29a 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -139,8 +139,8 @@ struct netplay /* Netplay pausing */ - bool pause; - uint32_t pause_frame; + bool local_paused; + bool remote_paused; /* And stalling */ uint32_t stall_frames; diff --git a/runloop.c b/runloop.c index 9a39afad95..86d5082042 100644 --- a/runloop.c +++ b/runloop.c @@ -1576,7 +1576,13 @@ int runloop_iterate(unsigned *sleep_ms) #endif #ifdef HAVE_NETPLAY - netplay_driver_ctl(RARCH_NETPLAY_CTL_PRE_FRAME, NULL); + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_PRE_FRAME, NULL)) + { + /* Paused due to Netplay */ + core_poll(); + *sleep_ms = 10; + return 1; + } #endif if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) From c3186c57ab01a66088b8984558b51297484d17f0 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 14:37:59 -0400 Subject: [PATCH 02/13] Don't stall if connection lost with remote paused --- network/netplay/netplay.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 51a67e74cc..550d16493b 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -982,7 +982,7 @@ bool netplay_pre_frame(netplay_t *netplay) netplay_frontend_paused(netplay, false); } netplay->net_cbs->pre_frame(netplay); - return (!netplay->stall && !netplay->remote_paused); + return (!netplay->has_connection || (!netplay->stall && !netplay->remote_paused)); } /** From 4d11b1d6747943e64d012ed6547bef1435e726b2 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 18:01:24 -0400 Subject: [PATCH 03/13] Don't misinterpret "Netplay isn't loaded" as "must pause" :) --- network/netplay/netplay.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 550d16493b..bf6fd4add0 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -1083,7 +1083,12 @@ bool init_netplay(void) bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) { if (!netplay_data) - return false; + { + if (state == RARCH_NETPLAY_CTL_IS_DATA_INITED) + return false; + else + return true; + } switch (state) { From 27188e102d4aebabb8ac2caa8ed04070fd2139e8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 18:03:40 -0400 Subject: [PATCH 04/13] Support for savestate loading over netplay Assuming the core supports saving/loading states, and (crucially) assuming the states are portable across the architectures on both sides of the connection, Netplay now supports the transmission of savestates. Right now the frontend doesn't actually send any such requests, as it's not clear exactly where the code for that should be. This works in either direction, although I'll admit I have no idea what happens if they both load at the same time. --- command.c | 5 -- network/netplay/netplay.c | 119 +++++++++++++++++++++++++++++- network/netplay/netplay.h | 13 +++- network/netplay/netplay_net.c | 31 ++++---- network/netplay/netplay_private.h | 7 +- 5 files changed, 151 insertions(+), 24 deletions(-) diff --git a/command.c b/command.c index e265254cdf..321b14e883 100644 --- a/command.c +++ b/command.c @@ -1919,11 +1919,6 @@ bool command_event(enum event_command cmd, void *data) if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL)) return false; -#ifdef HAVE_NETPLAY - if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL)) - return false; -#endif - #ifdef HAVE_CHEEVOS if (settings->cheevos.hardcore_mode_enable) return false; diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index bf6fd4add0..64a31c817f 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -242,7 +242,12 @@ static bool netplay_get_cmd(netplay_t *netplay) for (i = 0; i < WORDS_PER_FRAME; i++) buffer[i] = ntohl(buffer[i]); - if (buffer[0] != netplay->read_frame_count) + if (buffer[0] < netplay->read_frame_count) + { + /* We already had this, so ignore the new transmission */ + return true; + } + else if (buffer[0] > netplay->read_frame_count) { /* Out of order = out of luck */ return netplay_cmd_nak(netplay); @@ -295,8 +300,58 @@ static bool netplay_get_cmd(netplay_t *netplay) return true; case NETPLAY_CMD_LOAD_SAVESTATE: - RARCH_ERR("NETPLAY_CMD_LOAD_SAVESTATE unimplemented.\n"); - return netplay_cmd_nak(netplay); + { + uint32_t frame; + + /* There is a subtlty in whether the load comes before or after the + * current frame: + * + * If it comes before the current frame, then we need to force a + * rewind to that point. + * + * If it comes after the current frame, we need to jump ahead, then + * (strangely) force a rewind to the frame we're already on, so it + * gets loaded. This is just to avoid having reloading implemented in + * too many places. */ + if (cmd_size > netplay->state_size + sizeof(uint32_t)) + { + RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, &frame, sizeof(frame))) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate frame.\n"); + return netplay_cmd_nak(netplay); + } + frame = ntohl(frame); + + if (frame != netplay->read_frame_count) + { + RARCH_ERR("CMD_LOAD_SAVESTATE loading a state out of order!\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, + netplay->buffer[netplay->read_ptr].state, cmd_size - sizeof(uint32_t))) + { + RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n"); + return netplay_cmd_nak(netplay); + } + + /* Skip ahead if it's past where we are */ + if (frame > netplay->self_frame_count) + { + netplay->self_ptr = netplay->read_ptr; + netplay->self_frame_count = frame; + } + + /* And force rewind to it */ + netplay->force_rewind = true; + netplay->other_ptr = netplay->read_ptr; + netplay->other_frame_count = frame; + return true; + } case NETPLAY_CMD_PAUSE: netplay->remote_paused = true; @@ -1018,6 +1073,61 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); } +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded + * + * Inform Netplay of a savestate load and send it to the other side + **/ +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info) +{ + uint32_t header[3]; + + if (!netplay->has_connection) + return; + + /* Record it in our own buffer */ + if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + { + if (serial_info->size <= netplay->state_size) + { + memcpy(netplay->buffer[netplay->self_ptr].state, + serial_info->data_const, serial_info->size); + } + } + + /* We need to ignore any intervening data from the other side, and never rewind past this */ + if (netplay->read_frame_count < netplay->self_frame_count) + { + netplay->read_ptr = netplay->self_ptr; + netplay->read_frame_count = netplay->self_frame_count; + } + if (netplay->other_frame_count < netplay->self_frame_count) + { + netplay->other_ptr = netplay->self_ptr; + netplay->other_frame_count = netplay->self_frame_count; + } + + /* And send it to the peer (FIXME: this is an ugly way to do this) */ + header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); + header[1] = htonl(serial_info->size + sizeof(uint32_t)); + header[2] = htonl(netplay->self_frame_count); + if (!socket_send_all_blocking(netplay->fd, header, sizeof(header), false)) + { + warn_hangup(); + netplay->has_connection = false; + return; + } + + if (!socket_send_all_blocking(netplay->fd, serial_info->data_const, serial_info->size, false)) + { + warn_hangup(); + netplay->has_connection = false; + return; + } +} + void deinit_netplay(void) { netplay_t *netplay = (netplay_t*)netplay_data; @@ -1119,6 +1229,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) case RARCH_NETPLAY_CTL_UNPAUSE: netplay_frontend_paused((netplay_t*)netplay_data, false); break; + case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: + netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data); + break; default: case RARCH_NETPLAY_CTL_NONE: break; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 54a32fb789..94524ec5c6 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -37,7 +37,8 @@ enum rarch_netplay_ctl_state RARCH_NETPLAY_CTL_PRE_FRAME, RARCH_NETPLAY_CTL_IS_DATA_INITED, RARCH_NETPLAY_CTL_PAUSE, - RARCH_NETPLAY_CTL_UNPAUSE + RARCH_NETPLAY_CTL_UNPAUSE, + RARCH_NETPLAY_CTL_LOAD_SAVESTATE }; enum netplay_cmd @@ -77,6 +78,7 @@ enum netplay_cmd /* Send a savestate for the client to load */ NETPLAY_CMD_LOAD_SAVESTATE = 0x0012, + /* Sends over cheats enabled on client */ NETPLAY_CMD_CHEATS = 0x0013, @@ -181,6 +183,15 @@ void netplay_post_frame(netplay_t *handle); **/ void netplay_frontend_paused(netplay_t *netplay, bool paused); +/** + * netplay_load_savestate + * @netplay : pointer to netplay object + * @serial_info : the savestate being loaded + * + * Inform Netplay of a savestate load and send it to the other side + **/ +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info); + /** * init_netplay: * diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 05c106f517..ec91b3f261 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -69,24 +69,28 @@ static void netplay_net_post_frame(netplay_t *netplay) 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 && - netplay->other_frame_count < netplay->self_frame_count) + if (!netplay->force_rewind) { - const struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; + /* Skip ahead if we predicted correctly. + * Skip until our simulation failed. */ + 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]; - if (memcmp(ptr->simulated_input_state, ptr->real_input_state, - sizeof(ptr->real_input_state)) != 0 - && !ptr->used_real) - break; - netplay->other_ptr = NEXT_PTR(netplay->other_ptr); - netplay->other_frame_count++; + if (memcmp(ptr->simulated_input_state, ptr->real_input_state, + sizeof(ptr->real_input_state)) != 0 + && !ptr->used_real) + break; + netplay->other_ptr = NEXT_PTR(netplay->other_ptr); + 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 && - netplay->other_frame_count < netplay->self_frame_count) + if (netplay->force_rewind || + (netplay->other_frame_count < netplay->read_frame_count && + netplay->other_frame_count < netplay->self_frame_count)) { retro_ctx_serialize_info_t serial_info; @@ -134,6 +138,7 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->other_frame_count = netplay->self_frame_count; } netplay->is_replay = false; + netplay->force_rewind = false; } /* If we're supposed to stall, rewind (we shouldn't get this far if we're diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 56cd84c29a..f90be04353 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -103,10 +103,13 @@ struct netplay /* Are we replaying old frames? */ 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; + + /* Force a rewind to other_frame_count/other_ptr. This is for synchronized + * events, such as player flipping or savestate loading. */ + bool force_rewind; /* A buffer for outgoing input packets. */ uint32_t packet_buffer[2 + WORDS_PER_FRAME]; From 5f90f072ba4640d13dae4a4e6be6724b8fb36a3c Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 18:35:28 -0400 Subject: [PATCH 05/13] Documentation fixup Fixing up some of the function documentation, and documenting the packet formats. --- network/netplay/README | 55 +++++++++++++++++++++++++++++++++++ network/netplay/netplay_net.c | 4 +-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 5977cf63ab..6f52d5f99d 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -79,3 +79,58 @@ which we've actually read self_frame_count+1 frames of local input. * Guarantee not actually a guarantee. + + +Netplay's command format + +Netplay commands consist of a 32-bit command identifier, followed by a 32-bit +payload size, both in network byte order, followed by a payload. The command +identifiers are listed in netplay.h. The commands are described below. Unless +specified otherwise, all payload values are in network byte order. + +Command: ACK +Payload: None +Description: + Acknowledgement. Not used. + +Command: NAK +Payload: None +Description: + Negative Acknowledgement. If received, the connection is terminated. Sent + whenever a command is malformed or otherwise not understood. + +Command: INPUT +Payload: + { + frame number: uint32 + joypad input: uint32 + analog 1 input: uint32 + analog 2 input: uint32 + OPTIONAL state CRC: uint32 + } +Description: + Input state for each frame. Netplay must send an INPUT command for every + frame in order to function at all. + +Command: FLIP_PLAYERS +Payload: + { + frame number: uint32 + } +Description: + Flip players at the requested frame. + +Command: DISCONNECT +Payload: None +Description: + Gracefully disconnect. Not used. + +Command: LOAD_SAVESTATE +Payload: + { + frame number: uint32 + serialized save state: blob (variable size) + } +Description: + Cause the other side to load a savestate, notionally one which the sending + side has also loaded. diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index ec91b3f261..d2b4632378 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -25,7 +25,7 @@ #include "../../autosave.h" /** - * pre_frame: + * netplay_net_pre_frame: * @netplay : pointer to netplay object * * Pre-frame for Netplay (normal version). @@ -54,7 +54,7 @@ static void netplay_net_pre_frame(netplay_t *netplay) } /** - * post_frame: + * netplay_net_post_frame: * @netplay : pointer to netplay object * * Post-frame for Netplay (normal version). From 79eba578ff2057942561a92588a72ab2e3ae9dd0 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 20:27:22 -0400 Subject: [PATCH 06/13] Remote state load bugfix: Don't skip an input frame --- network/netplay/netplay.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 64a31c817f..c67023a334 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -342,8 +342,13 @@ static bool netplay_get_cmd(netplay_t *netplay) /* Skip ahead if it's past where we are */ if (frame > netplay->self_frame_count) { - netplay->self_ptr = netplay->read_ptr; - netplay->self_frame_count = frame; + /* This is squirrely: We need to assure that when we advance the + * frame in post_frame, THEN we're referring to the frame to + * load into. If we refer directly to read_ptr, then we'll end + * up never reading the input for read_frame_count itself, which + * will make the other side unhappy. */ + netplay->self_ptr = PREV_PTR(netplay->read_ptr); + netplay->self_frame_count = frame - 1; } /* And force rewind to it */ From 7271d1c3fa9ba2b5155015b729a3a5ea43fa692e Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 23:19:47 -0400 Subject: [PATCH 07/13] Support for frame CRCing Every frame (soon to be configurable), the server does a CRC-32 hash and sends it to the client. If the client finds that its own hash is different from the server's, it requests a fresh savestate. This is a last-ditch effort to sync if all else fails, and it's a best-effort situation. The size of the buffer should assure that we always still have the frame around to CRC, but I imagine there are edge cases where we don't. If you're in an edge case, the CRC is ignored. --- network/netplay/netplay.c | 91 +++++++++++++++++++++++++++++-- network/netplay/netplay.h | 9 ++- network/netplay/netplay_common.c | 9 +++ network/netplay/netplay_net.c | 52 ++++++++++++++---- network/netplay/netplay_private.h | 17 ++++++ 5 files changed, 162 insertions(+), 16 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index c67023a334..0fc9e9ca10 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -192,6 +192,22 @@ static bool netplay_cmd_nak(netplay_t *netplay) return netplay_send_raw_cmd(netplay, NETPLAY_CMD_NAK, NULL, 0); } +bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta) +{ + uint32_t payload[2]; + payload[0] = htonl(delta->frame); + payload[1] = htonl(delta->crc); + return netplay_send_raw_cmd(netplay, NETPLAY_CMD_CRC, payload, sizeof(payload)); +} + +bool netplay_cmd_request_savestate(netplay_t *netplay) +{ + if (netplay->savestate_request_outstanding) + return true; + netplay->savestate_request_outstanding = true; + return netplay_send_raw_cmd(netplay, NETPLAY_CMD_REQUEST_SAVESTATE, NULL, 0); +} + static bool netplay_get_cmd(netplay_t *netplay) { uint32_t cmd; @@ -299,6 +315,72 @@ static bool netplay_get_cmd(netplay_t *netplay) warn_hangup(); return true; + case NETPLAY_CMD_CRC: + { + uint32_t buffer[2]; + size_t tmp_ptr = netplay->self_ptr; + bool found = false; + + if (cmd_size != sizeof(buffer)) + { + RARCH_ERR("NETPLAY_CMD_CRC received unexpected payload size.\n"); + return netplay_cmd_nak(netplay); + } + + if (!socket_receive_all_blocking(netplay->fd, buffer, sizeof(buffer))) + { + RARCH_ERR("NETPLAY_CMD_CRC failed to receive payload.\n"); + return netplay_cmd_nak(netplay); + } + + buffer[0] = ntohl(buffer[0]); + buffer[1] = ntohl(buffer[1]); + + /* Received a CRC for some frame. If we still have it, check if it + * matched. This approach could be improved with some quick modular + * arithmetic. */ + do { + if (netplay->buffer[tmp_ptr].frame == buffer[0]) + { + found = true; + break; + } + + tmp_ptr = PREV_PTR(tmp_ptr); + } while (tmp_ptr != netplay->self_ptr); + + if (!found) + { + /* Oh well, we got rid of it! */ + return true; + } + + if (buffer[0] <= netplay->other_frame_count) + { + /* We've already replayed up to this frame, so we can check it + * directly */ + uint32_t local_crc = netplay_delta_frame_crc(netplay, &netplay->buffer[tmp_ptr]); + if (buffer[1] != local_crc) + { + /* Problem! */ + netplay_cmd_request_savestate(netplay); + } + } + else + { + /* We'll have to check it when we catch up */ + netplay->buffer[tmp_ptr].crc = buffer[1]; + } + + return true; + } + + case NETPLAY_CMD_REQUEST_SAVESTATE: + /* Delay until next frame so we don't send the savestate after the + * input */ + netplay->force_send_savestate = true; + return true; + case NETPLAY_CMD_LOAD_SAVESTATE: { uint32_t frame; @@ -353,6 +435,7 @@ static bool netplay_get_cmd(netplay_t *netplay) /* And force rewind to it */ netplay->force_rewind = true; + netplay->savestate_request_outstanding = false; netplay->other_ptr = netplay->read_ptr; netplay->other_frame_count = frame; return true; @@ -1072,7 +1155,6 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) if (netplay->local_paused == paused) return; - fprintf(stderr, "Paused? %d\n", paused); netplay->local_paused = paused; if (netplay->has_connection) netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); @@ -1082,10 +1164,11 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) * netplay_load_savestate * @netplay : pointer to netplay object * @serial_info : the savestate being loaded + * @save : whether to save the provided serial_info into the frame buffer * * Inform Netplay of a savestate load and send it to the other side **/ -void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info) +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save) { uint32_t header[3]; @@ -1093,7 +1176,7 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *seri return; /* Record it in our own buffer */ - if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + if (save && netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) { if (serial_info->size <= netplay->state_size) { @@ -1235,7 +1318,7 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data) netplay_frontend_paused((netplay_t*)netplay_data, false); break; case RARCH_NETPLAY_CTL_LOAD_SAVESTATE: - netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data); + netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data, true); break; default: case RARCH_NETPLAY_CTL_NONE: diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 94524ec5c6..eeb3783840 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -76,6 +76,12 @@ enum netplay_cmd /* Loading and synchronization */ + /* Send the CRC hash of a frame's state */ + NETPLAY_CMD_CRC = 0x0010, + + /* Request a savestate */ + NETPLAY_CMD_REQUEST_SAVESTATE = 0x0011, + /* Send a savestate for the client to load */ NETPLAY_CMD_LOAD_SAVESTATE = 0x0012, @@ -187,10 +193,11 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused); * netplay_load_savestate * @netplay : pointer to netplay object * @serial_info : the savestate being loaded + * @save : whether to save the provided serial_info into the frame buffer * * Inform Netplay of a savestate load and send it to the other side **/ -void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info); +void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *serial_info, bool save); /** * init_netplay: diff --git a/network/netplay/netplay_common.c b/network/netplay/netplay_common.c index a1b26e1154..1f4b89fca2 100644 --- a/network/netplay/netplay_common.c +++ b/network/netplay/netplay_common.c @@ -20,6 +20,8 @@ #include "netplay_private.h" #include +#include "compat/zlib.h" + #include "../../movie.h" #include "../../msg_hash.h" #include "../../content.h" @@ -365,3 +367,10 @@ bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, ui delta->state = remember_state; return true; } + +uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta) +{ + if (!netplay->state_size) + return 0; + return crc32(0L, delta->state, netplay->state_size); +} diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index d2b4632378..bb7851a08c 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -24,6 +24,25 @@ #include "../../autosave.h" +static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *delta) +{ + if (netplay_is_server(netplay)) + { + delta->crc = netplay_delta_frame_crc(netplay, delta); + netplay_cmd_crc(netplay, delta); + } + else if (delta->crc) + { + /* We have a remote CRC, so check it */ + uint32_t local_crc = netplay_delta_frame_crc(netplay, delta); + if (local_crc != delta->crc) + { + /* Fix this! */ + netplay_cmd_request_savestate(netplay); + } + } +} + /** * netplay_net_pre_frame: * @netplay : pointer to netplay object @@ -40,7 +59,17 @@ static void netplay_net_pre_frame(netplay_t *netplay) serial_info.data = netplay->buffer[netplay->self_ptr].state; serial_info.size = netplay->state_size; - if (!core_serialize(&serial_info)) + if (core_serialize(&serial_info)) + { + if (netplay->force_send_savestate) + { + /* Send this along to the other side */ + serial_info.data_const = netplay->buffer[netplay->self_ptr].state; + netplay_load_savestate(netplay, &serial_info, false); + netplay->force_send_savestate = false; + } + } + else { /* If the core can't serialize properly, we must stall for the * remote input on EVERY frame, because we can't recover */ @@ -76,12 +105,13 @@ static void netplay_net_post_frame(netplay_t *netplay) 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]; + struct delta_frame *ptr = &netplay->buffer[netplay->other_ptr]; if (memcmp(ptr->simulated_input_state, ptr->real_input_state, sizeof(ptr->real_input_state)) != 0 && !ptr->used_real) break; + netplay_handle_frame_hash(netplay, ptr); netplay->other_ptr = NEXT_PTR(netplay->other_ptr); netplay->other_frame_count++; } @@ -99,23 +129,23 @@ static void netplay_net_post_frame(netplay_t *netplay) netplay->replay_ptr = netplay->other_ptr; netplay->replay_frame_count = netplay->other_frame_count; - 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; + serial_info.data = NULL; + serial_info.data_const = netplay->buffer[netplay->replay_ptr].state; + serial_info.size = netplay->state_size; - core_unserialize(&serial_info); - } + core_unserialize(&serial_info); while (netplay->replay_frame_count < netplay->self_frame_count) { - serial_info.data = netplay->buffer[netplay->replay_ptr].state; + struct delta_frame *ptr = &netplay->buffer[netplay->replay_ptr]; + serial_info.data = ptr->state; serial_info.size = netplay->state_size; serial_info.data_const = NULL; core_serialize(&serial_info); + netplay_handle_frame_hash(netplay, ptr); + #if defined(HAVE_THREADS) autosave_lock(); #endif @@ -177,7 +207,7 @@ static bool netplay_net_init_buffers(netplay_t *netplay) for (i = 0; i < netplay->buffer_size; i++) { - netplay->buffer[i].state = malloc(netplay->state_size); + netplay->buffer[i].state = calloc(netplay->state_size, 1); if (!netplay->buffer[i].state) return false; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index f90be04353..3f9a335ef5 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -45,8 +45,12 @@ 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; + /* The serialized state of the core at this frame, before input */ void *state; + /* The CRC-32 of the serialized state if we've calculated it, else 0 */ + uint32_t crc; + 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]; @@ -111,6 +115,13 @@ struct netplay * events, such as player flipping or savestate loading. */ bool force_rewind; + /* Force our state to be sent to the other side. Used when they request a + * savestate, to send at the next pre-frame. */ + bool force_send_savestate; + + /* Have we requested a savestate as a sync point? */ + bool savestate_request_outstanding; + /* A buffer for outgoing input packets. */ uint32_t packet_buffer[2 + WORDS_PER_FRAME]; uint32_t self_frame_count; @@ -184,4 +195,10 @@ bool netplay_is_spectate(netplay_t* netplay); bool netplay_delta_frame_ready(netplay_t *netplay, struct delta_frame *delta, uint32_t frame); +uint32_t netplay_delta_frame_crc(netplay_t *netplay, struct delta_frame *delta); + +bool netplay_cmd_crc(netplay_t *netplay, struct delta_frame *delta); + +bool netplay_cmd_request_savestate(netplay_t *netplay); + #endif From 9a91570db8ad90895b7738c731c9fb7adce6a399 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 23:23:36 -0400 Subject: [PATCH 08/13] Adding new network commands to the Netplay README. --- network/netplay/README | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/network/netplay/README b/network/netplay/README index 6f52d5f99d..4629f4c8bc 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -125,6 +125,22 @@ Payload: None Description: Gracefully disconnect. Not used. +Command: CRC +Payload: + { + frame number: uint32 + hash: uint32 + } +Description: + Informs the peer of the correct CRC hash for the specified frame. If the + receiver's hash doesn't match, they should send a REQUEST_SAVESTATE + command. + +Command: REQUEST_SAVESTATE +Payload: None +Description: + Requests that the peer send a savestate. + Command: LOAD_SAVESTATE Payload: { From 2a0202ccf5cdfc38fc9067221d8a69251af987ef Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 23:54:18 -0400 Subject: [PATCH 09/13] Making Netplay check frequency configurable. --- configuration.c | 5 ++++- intl/msg_hash_us.c | 20 ++++++++++++++++++++ menu/menu_displaylist.c | 4 ++++ menu/menu_setting.c | 26 ++++++++++++++++++++++++++ msg_hash.h | 2 ++ network/netplay/netplay.c | 11 ++++++----- network/netplay/netplay.h | 3 ++- network/netplay/netplay_net.c | 7 +++++-- network/netplay/netplay_private.h | 3 +++ retroarch.c | 18 ++++++++++++++++++ retroarch.h | 1 + runloop.h | 1 + 12 files changed, 92 insertions(+), 9 deletions(-) diff --git a/configuration.c b/configuration.c index 99c3504982..b8d976551a 100644 --- a/configuration.c +++ b/configuration.c @@ -911,7 +911,8 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting SETTING_INT("state_slot", (unsigned*)&settings->state_slot, false, 0 /* TODO */, false); #ifdef HAVE_NETPLAY SETTING_INT("netplay_ip_port", &global->netplay.port, false, 0 /* TODO */, false); - SETTING_INT("netplay_delay_frames", &global->netplay.sync_frames, false, 0 /* TODO */, false); + SETTING_INT("netplay_delay_frames", &global->netplay.sync_frames, false, 16, false); + SETTING_INT("netplay_check_frames", &global->netplay.check_frames, false, 60, false); #endif #ifdef HAVE_LANGEXTRA SETTING_INT("user_language", &settings->user_language, true, RETRO_LANGUAGE_ENGLISH, false); @@ -1788,6 +1789,8 @@ static bool config_load_file(const char *path, bool set_defaults, #ifdef HAVE_NETPLAY if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES)) CONFIG_GET_INT_BASE(conf, global, netplay.sync_frames, "netplay_delay_frames"); + if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES)) + CONFIG_GET_INT_BASE(conf, global, netplay.sync_frames, "netplay_check_frames"); if (!retroarch_override_setting_is_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT)) CONFIG_GET_INT_BASE(conf, global, netplay.port, "netplay_ip_port"); #endif diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 36f0f7d8d4..dfefcb4f0b 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -1558,6 +1558,22 @@ int menu_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) "Increasing this value will increase \n" "performance, but introduce more latency."); break; + case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: + snprintf(s, len, + "The frequency in frames with which netplay \n" + "will verify that the host and client are in \n" + "sync. \n" + " \n" + "With most cores, this value will have no \n" + "visible effect and can be ignored. With \n" + "nondeterminstic cores, this value determines \n" + "how often the netplay peers will be brought \n" + "into sync. With buggy cores, setting this \n" + "to any non-zero value will cause severe \n" + "performance issues. Set to zero to perform \n" + "no checks. This value is only used on the \n" + "netplay host. \n"); + break; case MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES: snprintf(s, len, "Maximum amount of swapchain images. This \n" @@ -2424,6 +2440,8 @@ static const char *menu_hash_to_str_us_label_enum(enum msg_hash_enums msg) return "bluetooth_enable"; case MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES: return "netplay_delay_frames"; + case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: + return "netplay_check_frames"; case MENU_ENUM_LABEL_NETPLAY_MODE: return "netplay_mode"; case MENU_ENUM_LABEL_RGUI_SHOW_START_SCREEN: @@ -3747,6 +3765,8 @@ const char *msg_hash_to_str_us(enum msg_hash_enums msg) return "Bluetooth Enable"; case MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES: return "Netplay Delay Frames"; + case MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES: + return "Netplay Check Frames"; case MENU_ENUM_LABEL_VALUE_NETPLAY_MODE: return "Netplay Client Enable"; case MENU_ENUM_LABEL_VALUE_RGUI_SHOW_START_SCREEN: diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 3c880a3830..e01b84d658 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -4684,6 +4684,10 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type, void *data) MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, PARSE_ONLY_UINT, false) != -1) count++; + if (menu_displaylist_parse_settings_enum(menu, info, + MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, + PARSE_ONLY_UINT, false) != -1) + count++; if (menu_displaylist_parse_settings_enum(menu, info, MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, PARSE_ONLY_UINT, false) != -1) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 930e1ec756..4bbc769230 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -1675,6 +1675,17 @@ void general_write_handler(void *data) } #endif break; + case MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES: +#ifdef HAVE_NETPLAY + { + bool val = (global->netplay.check_frames > 0); + + if (val) + retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES); + else + retroarch_override_setting_unset(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES); + } +#endif default: break; } @@ -5814,6 +5825,21 @@ static bool setting_append_list( settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); menu_settings_list_current_add_enum_idx(list, list_info, MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES); + CONFIG_UINT( + list, list_info, + &global->netplay.check_frames, + msg_hash_to_str(MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES), + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES), + 0, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + menu_settings_list_current_add_range(list, list_info, 0, 10, 1, true, false); + settings_data_list_current_add_flags(list, list_info, SD_FLAG_ADVANCED); + menu_settings_list_current_add_enum_idx(list, list_info, MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES); + CONFIG_UINT( list, list_info, &global->netplay.port, diff --git a/msg_hash.h b/msg_hash.h index 2fcdbc3425..83b8eea021 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1111,6 +1111,8 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_NETPLAY_CLIENT_SWAP_INPUT, MENU_ENUM_LABEL_NETPLAY_DELAY_FRAMES, MENU_ENUM_LABEL_VALUE_NETPLAY_DELAY_FRAMES, + MENU_ENUM_LABEL_NETPLAY_CHECK_FRAMES, + MENU_ENUM_LABEL_VALUE_NETPLAY_CHECK_FRAMES, MENU_ENUM_LABEL_NETPLAY_SPECTATOR_MODE_ENABLE, MENU_ENUM_LABEL_VALUE_NETPLAY_SPECTATOR_MODE_ENABLE, MENU_ENUM_LABEL_NETPLAY_TCP_UDP_PORT, diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 0fc9e9ca10..77ca32ae28 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -898,6 +898,7 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) * @server : IP address of server. * @port : Port of server. * @frames : Amount of lag frames. + * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. * @spectate : If true, enable spectator mode. * @nick : Nickname of user. @@ -908,9 +909,8 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) * Returns: new netplay handle. **/ netplay_t *netplay_new(const char *server, uint16_t port, - unsigned frames, const struct retro_callbacks *cb, - bool spectate, - const char *nick) + unsigned frames, unsigned check_frames, const struct retro_callbacks *cb, + bool spectate, const char *nick) { netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay)); if (!netplay) @@ -923,6 +923,7 @@ netplay_t *netplay_new(const char *server, uint16_t port, netplay->is_server = server == NULL; strlcpy(netplay->nick, nick, sizeof(netplay->nick)); netplay->stall_frames = frames; + netplay->check_frames = check_frames; if(spectate) netplay->net_cbs = netplay_get_cbs_spectate(); @@ -1263,8 +1264,8 @@ bool init_netplay(void) netplay_data = (netplay_t*)netplay_new( global->netplay.is_client ? global->netplay.server : NULL, global->netplay.port ? global->netplay.port : RARCH_DEFAULT_PORT, - global->netplay.sync_frames, &cbs, global->netplay.is_spectate, - settings->username); + global->netplay.sync_frames, global->netplay.check_frames, &cbs, + global->netplay.is_spectate, settings->username); if (netplay_data) return true; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index eeb3783840..7ed2400841 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -136,6 +136,7 @@ int16_t input_state_spectate_client(unsigned port, unsigned device, * @server : IP address of server. * @port : Port of server. * @frames : Amount of lag frames. + * @check_frames : Frequency with which to check CRCs. * @cb : Libretro callbacks. * @spectate : If true, enable spectator mode. * @nick : Nickname of user. @@ -146,7 +147,7 @@ int16_t input_state_spectate_client(unsigned port, unsigned device, * Returns: new netplay handle. **/ netplay_t *netplay_new(const char *server, - uint16_t port, unsigned frames, + uint16_t port, unsigned frames, unsigned check_frames, const struct retro_callbacks *cb, bool spectate, const char *nick); diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index bb7851a08c..225135416a 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -28,8 +28,11 @@ static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *de { if (netplay_is_server(netplay)) { - delta->crc = netplay_delta_frame_crc(netplay, delta); - netplay_cmd_crc(netplay, delta); + if (netplay->check_frames && delta->frame % netplay->check_frames == 0) + { + delta->crc = netplay_delta_frame_crc(netplay, delta); + netplay_cmd_crc(netplay, delta); + } } else if (delta->crc) { diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 3f9a335ef5..5b4b5e28e7 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -161,6 +161,9 @@ struct netplay int stall; retro_time_t stall_time; + /* Frequency with which to check CRCs */ + uint32_t check_frames; + struct netplay_callbacks* net_cbs; }; diff --git a/retroarch.c b/retroarch.c index e482ab0b22..ca5559f93e 100644 --- a/retroarch.c +++ b/retroarch.c @@ -90,6 +90,7 @@ enum { RA_OPT_MENU = 256, /* must be outside the range of a char */ + RA_OPT_CHECK_FRAMES, RA_OPT_PORT, RA_OPT_SPECTATE, RA_OPT_NICK, @@ -306,6 +307,8 @@ static void retroarch_print_help(const char *arg0) puts(" -C, --connect=HOST Connect to netplay server as user 2."); puts(" --port=PORT Port used to netplay. Default is 55435."); puts(" -F, --frames=NUMBER Sync frames when using netplay."); + puts(" --check-frames=NUMBER\n" + " Check frames when using netplay."); puts(" --spectate Connect to netplay server as spectator."); #endif puts(" --nick=NICK Picks a username (for use with netplay). " @@ -677,6 +680,7 @@ static void retroarch_parse_input(int argc, char *argv[]) { "host", 0, NULL, 'H' }, { "connect", 1, NULL, 'C' }, { "frames", 1, NULL, 'F' }, + { "check-frames", 1, NULL, RA_OPT_CHECK_FRAMES }, { "port", 1, NULL, RA_OPT_PORT }, { "spectate", 0, NULL, RA_OPT_SPECTATE }, #endif @@ -978,6 +982,11 @@ static void retroarch_parse_input(int argc, char *argv[]) break; #ifdef HAVE_NETPLAY + case RA_OPT_CHECK_FRAMES: + retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES); + global->netplay.check_frames = strtoul(optarg, NULL, 0); + break; + case RA_OPT_PORT: retroarch_override_setting_set(RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT); global->netplay.port = strtoul(optarg, NULL, 0); @@ -1645,6 +1654,7 @@ static bool has_set_netplay_mode = false; static bool has_set_netplay_ip_address = false; static bool has_set_netplay_ip_port = false; static bool has_set_netplay_delay_frames= false; +static bool has_set_netplay_check_frames= false; static bool has_set_ups_pref = false; static bool has_set_bps_pref = false; static bool has_set_ips_pref = false; @@ -1671,6 +1681,8 @@ bool retroarch_override_setting_is_set(enum rarch_override_setting enum_idx) return has_set_netplay_ip_port; case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: return has_set_netplay_delay_frames; + case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: + return has_set_netplay_check_frames; case RARCH_OVERRIDE_SETTING_UPS_PREF: return has_set_ups_pref; case RARCH_OVERRIDE_SETTING_BPS_PREF: @@ -1717,6 +1729,9 @@ void retroarch_override_setting_set(enum rarch_override_setting enum_idx) case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: has_set_netplay_delay_frames = true; break; + case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: + has_set_netplay_check_frames = true; + break; case RARCH_OVERRIDE_SETTING_UPS_PREF: has_set_ups_pref = true; break; @@ -1763,6 +1778,9 @@ void retroarch_override_setting_unset(enum rarch_override_setting enum_idx) case RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES: has_set_netplay_delay_frames = false; break; + case RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES: + has_set_netplay_check_frames = false; + break; case RARCH_OVERRIDE_SETTING_UPS_PREF: has_set_ups_pref = false; break; diff --git a/retroarch.h b/retroarch.h index 878d24d756..eab78907c1 100644 --- a/retroarch.h +++ b/retroarch.h @@ -110,6 +110,7 @@ enum rarch_override_setting RARCH_OVERRIDE_SETTING_NETPLAY_IP_ADDRESS, RARCH_OVERRIDE_SETTING_NETPLAY_IP_PORT, RARCH_OVERRIDE_SETTING_NETPLAY_DELAY_FRAMES, + RARCH_OVERRIDE_SETTING_NETPLAY_CHECK_FRAMES, RARCH_OVERRIDE_SETTING_UPS_PREF, RARCH_OVERRIDE_SETTING_BPS_PREF, RARCH_OVERRIDE_SETTING_IPS_PREF, diff --git a/runloop.h b/runloop.h index 6bee74e3a0..b3920a9d85 100644 --- a/runloop.h +++ b/runloop.h @@ -228,6 +228,7 @@ typedef struct global bool is_client; bool is_spectate; unsigned sync_frames; + unsigned check_frames; unsigned port; } netplay; #endif From 827009d2d3be918f6a4905d18fb4e782d0ad6c28 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Sep 2016 07:40:13 -0400 Subject: [PATCH 10/13] A few corrections/clarifications in the Netplay README --- network/netplay/README | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/network/netplay/README b/network/netplay/README index 4629f4c8bc..8f0ca71057 100644 --- a/network/netplay/README +++ b/network/netplay/README @@ -41,15 +41,15 @@ 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 +In general, other ≤ read and other ≤ self. In all likelihood, 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. +stalling does not block the UI thread, it is implemented similarly to pausing, +rather than by blocking on the socket. 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 @@ -57,12 +57,13 @@ 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. +input from the state 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 (by loading the serialized state there) and -runs the core in replay mode with the real data up to read, then sets other = -read. +other self > other. If so, it first checks whether its simulated remote data +was correct. If it was, it simply moves other up. If not, it rewinds to other +(by loading the serialized state there) and runs the core in replay mode with +the real data up to the least of self and read, then sets other to that. 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 From 4076b145681dc1a611ac35a7c165021eedea6ee9 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Sep 2016 18:51:56 -0400 Subject: [PATCH 11/13] Make Netplay flipping deterministic The receiving side of a player-flip request now does a forced rewind to assure that any already-computed frames are computed with the players on the right side. --- network/netplay/netplay.c | 11 +++++++++-- network/netplay/netplay_private.h | 7 +++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 77ca32ae28..25f7faa373 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -293,7 +293,7 @@ static bool netplay_get_cmd(netplay_t *netplay) flip_frame = ntohl(flip_frame); - if (flip_frame < netplay->flip_frame) + if (flip_frame < netplay->read_frame_count) { RARCH_ERR("Host asked us to flip users in the past. Not possible ...\n"); return netplay_cmd_nak(netplay); @@ -302,6 +302,12 @@ static bool netplay_get_cmd(netplay_t *netplay) netplay->flip ^= true; netplay->flip_frame = flip_frame; + /* Force a rewind to assure the flip happens: This just prevents us + * from skipping other past the flip because our prediction was + * correct */ + if (flip_frame < netplay->self_frame_count) + netplay->force_rewind = true; + RARCH_LOG("Netplay users are flipped.\n"); runloop_msg_queue_push("Netplay users are flipped.", 1, 180, false); @@ -1008,7 +1014,8 @@ error: **/ static void netplay_flip_users(netplay_t *netplay) { - uint32_t flip_frame = netplay->self_frame_count + 32; /* FIXME: This value is now arbitrary */ + /* Must be in the future because we may have already sent this frame's data */ + uint32_t flip_frame = netplay->self_frame_count + 1; uint32_t flip_frame_net = htonl(flip_frame); bool command = netplay_command( netplay, NETPLAY_CMD_FLIP_PLAYERS, diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 5b4b5e28e7..0730ae4af3 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -143,11 +143,10 @@ struct netplay size_t input_sz; } spectate; bool is_server; + /* User flipping - * Flipping state. If ptr >= flip_frame, we apply the flip. - * If not, we apply the opposite, effectively creating a trigger point. - * To avoid collition we need to make sure our client/host is synced up - * well after flip_frame before allowing another flip. */ + * Flipping state. If frame >= flip_frame, we apply the flip. + * If not, we apply the opposite, effectively creating a trigger point. */ bool flip; uint32_t flip_frame; From a42be48638291ab85160d7ce9378ceb217765162 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Sep 2016 18:56:32 -0400 Subject: [PATCH 12/13] Don't do Netplay CRCing by default There are too many buggy cores for Netplay to CRC in the default configuration, so check_frames=0 by default. check_frames is a "last resort" option anyway, so shouldn't need to be on by default. --- configuration.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration.c b/configuration.c index b8d976551a..d120cc0935 100644 --- a/configuration.c +++ b/configuration.c @@ -912,7 +912,7 @@ static int populate_settings_int(settings_t *settings, struct config_int_setting #ifdef HAVE_NETPLAY SETTING_INT("netplay_ip_port", &global->netplay.port, false, 0 /* TODO */, false); SETTING_INT("netplay_delay_frames", &global->netplay.sync_frames, false, 16, false); - SETTING_INT("netplay_check_frames", &global->netplay.check_frames, false, 60, false); + SETTING_INT("netplay_check_frames", &global->netplay.check_frames, false, 0, false); #endif #ifdef HAVE_LANGEXTRA SETTING_INT("user_language", &settings->user_language, true, RETRO_LANGUAGE_ENGLISH, false); From ad336df70908445ad90f71e350b8c6d199cc3160 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Thu, 15 Sep 2016 23:04:48 -0400 Subject: [PATCH 13/13] Reimplemented Netplay spectate mode Spectate mode is now far more similar to net (normal) mode, and, more importantly, it works. In addition, spectate mode will not fast-forward to catch up with the server if it lags too far behind. --- core_impl.c | 18 +- network/netplay/netplay.c | 111 +++++---- network/netplay/netplay.h | 6 - network/netplay/netplay_net.c | 41 +--- network/netplay/netplay_private.h | 3 +- network/netplay/netplay_spectate.c | 349 +++++++++++++++++++---------- 6 files changed, 292 insertions(+), 236 deletions(-) diff --git a/core_impl.c b/core_impl.c index 1a5a9a667a..2175ca0dab 100644 --- a/core_impl.c +++ b/core_impl.c @@ -109,20 +109,10 @@ static bool core_init_libretro_cbs(void *data) /* Force normal poll type for netplay. */ core_poll_type = POLL_TYPE_NORMAL; - if (global->netplay.is_spectate) - { - core.retro_set_input_state( - (global->netplay.is_client ? - input_state_spectate_client : input_state_spectate) - ); - } - else - { - core.retro_set_video_refresh(video_frame_net); - core.retro_set_audio_sample(audio_sample_net); - core.retro_set_audio_sample_batch(audio_sample_batch_net); - core.retro_set_input_state(input_state_net); - } + core.retro_set_video_refresh(video_frame_net); + core.retro_set_audio_sample(audio_sample_net); + core.retro_set_audio_sample_batch(audio_sample_batch_net); + core.retro_set_input_state(input_state_net); #endif return true; diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index 25f7faa373..8ae9e7a11d 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -157,11 +157,14 @@ static bool get_self_input_state(netplay_t *netplay) netplay->packet_buffer[4] = htonl(state[1]); netplay->packet_buffer[5] = htonl(state[2]); - if (!socket_send_all_blocking(netplay->fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) + if (!netplay->spectate.enabled) /* Spectate sends in its own way */ { - warn_hangup(); - netplay->has_connection = false; - return false; + if (!socket_send_all_blocking(netplay->fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) + { + warn_hangup(); + netplay->has_connection = false; + return false; + } } memcpy(ptr->self_state, state, sizeof(state)); @@ -550,6 +553,10 @@ static bool netplay_poll(netplay_t *netplay) get_self_input_state(netplay); + /* No network side in spectate mode */ + if (netplay_is_server(netplay) && netplay->spectate.enabled) + return true; + /* 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)); @@ -899,6 +906,41 @@ static bool init_socket(netplay_t *netplay, const char *server, uint16_t port) return true; } +static bool netplay_init_buffers(netplay_t *netplay, unsigned frames) +{ + unsigned i; + retro_ctx_size_info_t info; + + if (!netplay) + return false; + + /* * 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; + + netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, + sizeof(*netplay->buffer)); + + if (!netplay->buffer) + return false; + + core_serialize_size(&info); + + netplay->state_size = info.size; + + for (i = 0; i < netplay->buffer_size; i++) + { + netplay->buffer[i].state = calloc(netplay->state_size, 1); + + if (!netplay->buffer[i].state) + return false; + } + + return true; +} + /** * netplay_new: * @server : IP address of server. @@ -931,6 +973,12 @@ netplay_t *netplay_new(const char *server, uint16_t port, netplay->stall_frames = frames; netplay->check_frames = check_frames; + if (!netplay_init_buffers(netplay, frames)) + { + free(netplay); + return NULL; + } + if(spectate) netplay->net_cbs = netplay_get_cbs_spectate(); else @@ -1064,56 +1112,6 @@ void netplay_free(netplay_t *netplay) free(netplay); } - -static void netplay_set_spectate_input(netplay_t *netplay, int16_t input) -{ - if (netplay->spectate.input_ptr >= netplay->spectate.input_sz) - { - netplay->spectate.input_sz++; - netplay->spectate.input_sz *= 2; - netplay->spectate.input = (uint16_t*)realloc(netplay->spectate.input, - netplay->spectate.input_sz * sizeof(uint16_t)); - } - - netplay->spectate.input[netplay->spectate.input_ptr++] = swap_if_big16(input); -} - -int16_t input_state_spectate(unsigned port, unsigned device, - unsigned idx, unsigned id) -{ - netplay_t *netplay = (netplay_t*)netplay_data; - int16_t res = netplay->cbs.state_cb(port, device, idx, id); - - netplay_set_spectate_input(netplay, res); - return res; -} - -static int16_t netplay_get_spectate_input(netplay_t *netplay, bool port, - unsigned device, unsigned idx, unsigned id) -{ - int16_t inp; - retro_ctx_input_state_info_t input_info; - - if (socket_receive_all_blocking(netplay->fd, (char*)&inp, sizeof(inp))) - return swap_if_big16(inp); - - RARCH_ERR("Connection with host was cut.\n"); - runloop_msg_queue_push("Connection with host was cut.", 1, 180, true); - - input_info.cb = netplay->cbs.state_cb; - - core_set_input_state(&input_info); - - return netplay->cbs.state_cb(port, device, idx, id); -} - -int16_t input_state_spectate_client(unsigned port, unsigned device, - unsigned idx, unsigned id) -{ - return netplay_get_spectate_input((netplay_t*)netplay_data, port, - device, idx, id); -} - /** * netplay_pre_frame: * @netplay : pointer to netplay object @@ -1132,7 +1130,8 @@ bool netplay_pre_frame(netplay_t *netplay) /* FIXME: This is an ugly way to learn we're not paused anymore */ netplay_frontend_paused(netplay, false); } - netplay->net_cbs->pre_frame(netplay); + if (!netplay->net_cbs->pre_frame(netplay)) + return false; return (!netplay->has_connection || (!netplay->stall && !netplay->remote_paused)); } @@ -1164,7 +1163,7 @@ void netplay_frontend_paused(netplay_t *netplay, bool paused) return; netplay->local_paused = paused; - if (netplay->has_connection) + if (netplay->has_connection && !netplay->spectate.enabled) netplay_send_raw_cmd(netplay, paused ? NETPLAY_CMD_PAUSE : NETPLAY_CMD_RESUME, NULL, 0); } diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 7ed2400841..ecac020d55 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -125,12 +125,6 @@ void audio_sample_net(int16_t left, int16_t right); size_t audio_sample_batch_net(const int16_t *data, size_t frames); -int16_t input_state_spectate(unsigned port, unsigned device, - unsigned idx, unsigned id); - -int16_t input_state_spectate_client(unsigned port, unsigned device, - unsigned idx, unsigned id); - /** * netplay_new: * @server : IP address of server. diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index 225135416a..56ede8892e 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -52,7 +52,7 @@ static void netplay_handle_frame_hash(netplay_t *netplay, struct delta_frame *de * * Pre-frame for Netplay (normal version). **/ -static void netplay_net_pre_frame(netplay_t *netplay) +static bool netplay_net_pre_frame(netplay_t *netplay) { retro_ctx_serialize_info_t serial_info; @@ -83,6 +83,8 @@ static void netplay_net_pre_frame(netplay_t *netplay) netplay->can_poll = true; input_poll_net(); + + return true; } /** @@ -190,34 +192,6 @@ static void netplay_net_post_frame(netplay_t *netplay) core_unserialize(&serial_info); } } -static bool netplay_net_init_buffers(netplay_t *netplay) -{ - unsigned i; - retro_ctx_size_info_t info; - - if (!netplay) - return false; - - netplay->buffer = (struct delta_frame*)calloc(netplay->buffer_size, - sizeof(*netplay->buffer)); - - if (!netplay->buffer) - return false; - - core_serialize_size(&info); - - netplay->state_size = info.size; - - for (i = 0; i < netplay->buffer_size; i++) - { - netplay->buffer[i].state = calloc(netplay->state_size, 1); - - if (!netplay->buffer[i].state) - return false; - } - - return true; -} static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) { @@ -232,15 +206,6 @@ static bool netplay_net_info_cb(netplay_t* netplay, unsigned frames) return false; } - /* * 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; - netplay->has_connection = true; return true; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index 0730ae4af3..49fe4fd280 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -66,7 +66,7 @@ struct delta_frame }; struct netplay_callbacks { - void (*pre_frame) (netplay_t *netplay); + bool (*pre_frame) (netplay_t *netplay); void (*post_frame)(netplay_t *netplay); bool (*info_cb) (netplay_t *netplay, unsigned frames); }; @@ -138,6 +138,7 @@ struct netplay struct { bool enabled; int fds[MAX_SPECTATORS]; + uint32_t frames[MAX_SPECTATORS]; uint16_t *input; size_t input_ptr; size_t input_sz; diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index c122b20b88..e6dbbd8aac 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.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- @@ -14,10 +15,8 @@ * If not, see . */ +#include #include -#include -#include -#include #include #include @@ -25,157 +24,266 @@ #include "netplay_private.h" -#include "../../runloop.h" +#include "retro_assert.h" + +#include "../../autosave.h" /** - * netplay_pre_frame_spectate: + * netplay_spectate_pre_frame: * @netplay : pointer to netplay object * - * Pre-frame for Netplay (spectate mode version). + * Pre-frame for Netplay (spectator version). **/ -static void netplay_spectate_pre_frame(netplay_t *netplay) +static bool netplay_spectate_pre_frame(netplay_t *netplay) { - unsigned i; - uint32_t *header; - int new_fd, idx, bufsize; - size_t header_size; - struct sockaddr_storage their_addr; - socklen_t addr_size; - fd_set fds; - struct timeval tmp_tv = {0}; - - if (!netplay_is_server(netplay)) - return; - - FD_ZERO(&fds); - FD_SET(netplay->fd, &fds); - - if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) - return; - - if (!FD_ISSET(netplay->fd, &fds)) - return; - - addr_size = sizeof(their_addr); - new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size); - if (new_fd < 0) + if (netplay_is_server(netplay)) { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_ACCEPT_INCOMING_SPECTATOR)); - return; - } + fd_set fds; + struct timeval tmp_tv = {0}; + int new_fd, idx, i; + struct sockaddr_storage their_addr; + socklen_t addr_size; + retro_ctx_serialize_info_t serial_info; + uint32_t header[3]; - idx = -1; - for (i = 0; i < MAX_SPECTATORS; i++) - { - if (netplay->spectate.fds[i] == -1) + netplay->can_poll = true; + input_poll_net(); + + /* Send our input to any connected spectators */ + for (i = 0; i < MAX_SPECTATORS; i++) { - idx = i; - break; + if (netplay->spectate.fds[i] >= 0) + { + netplay->packet_buffer[2] = htonl(netplay->self_frame_count - netplay->spectate.frames[i]); + if (!socket_send_all_blocking(netplay->spectate.fds[i], netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) + { + socket_close(netplay->spectate.fds[i]); + netplay->spectate.fds[i] = -1; + } + } } - } - /* No vacant client streams :( */ - if (idx == -1) + /* Check for connections */ + FD_ZERO(&fds); + FD_SET(netplay->fd, &fds); + if (socket_select(netplay->fd + 1, &fds, NULL, NULL, &tmp_tv) <= 0) + return true; + + if (!FD_ISSET(netplay->fd, &fds)) + return true; + + addr_size = sizeof(their_addr); + new_fd = accept(netplay->fd, (struct sockaddr*)&their_addr, &addr_size); + if (new_fd < 0) + { + RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_ACCEPT_INCOMING_SPECTATOR)); + return true; + } + + idx = -1; + for (i = 0; i < MAX_SPECTATORS; i++) + { + if (netplay->spectate.fds[i] == -1) + { + idx = i; + break; + } + } + + /* No vacant client streams :( */ + if (idx == -1) + { + socket_close(new_fd); + return true; + } + + if (!netplay_get_nickname(netplay, new_fd)) + { + RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT)); + socket_close(new_fd); + return true; + } + + if (!netplay_send_nickname(netplay, new_fd)) + { + RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT)); + socket_close(new_fd); + return true; + } + + /* Start them at the current frame */ + netplay->spectate.frames[idx] = netplay->self_frame_count; + 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)) + { + /* Send them the savestate */ + header[0] = htonl(NETPLAY_CMD_LOAD_SAVESTATE); + header[1] = htonl(serial_info.size + sizeof(uint32_t)); + header[2] = htonl(0); + if (!socket_send_all_blocking(new_fd, header, sizeof(header), false)) + { + socket_close(new_fd); + return true; + } + + if (!socket_send_all_blocking(new_fd, serial_info.data, serial_info.size, false)) + { + socket_close(new_fd); + return true; + } + } + + /* And send them this frame's input */ + netplay->packet_buffer[2] = htonl(0); + if (!socket_send_all_blocking(new_fd, netplay->packet_buffer, sizeof(netplay->packet_buffer), false)) + { + socket_close(new_fd); + return true; + } + + netplay->spectate.fds[idx] = new_fd; + + } + else { - socket_close(new_fd); - return; + if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count)) + { + /* Mark our own data as already read, so we ignore local input */ + netplay->buffer[netplay->self_ptr].have_local = true; + } + + netplay->can_poll = true; + input_poll_net(); + + /* Only proceed if we have data */ + if (netplay->read_frame_count <= netplay->self_frame_count) + return false; + } - if (!netplay_get_nickname(netplay, new_fd)) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GET_NICKNAME_FROM_CLIENT)); - socket_close(new_fd); - return; - } - - if (!netplay_send_nickname(netplay, new_fd)) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_NICKNAME_TO_CLIENT)); - socket_close(new_fd); - return; - } - - header = netplay_bsv_header_generate(&header_size, - netplay_impl_magic()); - - if (!header) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_GENERATE_BSV_HEADER)); - socket_close(new_fd); - return; - } - - bufsize = header_size; - setsockopt(new_fd, SOL_SOCKET, SO_SNDBUF, (const char*)&bufsize, - sizeof(int)); - - if (!socket_send_all_blocking(new_fd, header, header_size, false)) - { - RARCH_ERR("%s\n", msg_hash_to_str(MSG_FAILED_TO_SEND_HEADER_TO_CLIENT)); - socket_close(new_fd); - free(header); - return; - } - - free(header); - netplay->spectate.fds[idx] = new_fd; - -#ifndef HAVE_SOCKET_LEGACY - netplay_log_connection(&their_addr, idx, netplay->other_nick); -#endif + return true; } /** - * netplay_post_frame_spectate: + * netplay_spectate_post_frame: * @netplay : pointer to netplay object * - * Post-frame for Netplay (spectate mode version). - * We check if we have new input and replay from recorded input. + * Post-frame for Netplay (spectator version). + * Not much here, just fast forward if we're behind the server. **/ static void netplay_spectate_post_frame(netplay_t *netplay) { - unsigned i; + netplay->self_ptr = NEXT_PTR(netplay->self_ptr); + netplay->self_frame_count++; - if (!netplay_is_server(netplay)) - return; - - for (i = 0; i < MAX_SPECTATORS; i++) + if (netplay_is_server(netplay)) { - char msg[128]; + /* Not expecting any client data */ + netplay->read_ptr = netplay->other_ptr = netplay->self_ptr; + netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count; - if (netplay->spectate.fds[i] == -1) - continue; - - if (socket_send_all_blocking(netplay->spectate.fds[i], - netplay->spectate.input, - netplay->spectate.input_ptr * sizeof(int16_t), - false)) - continue; - - RARCH_LOG("Client (#%u) disconnected ...\n", i); - - snprintf(msg, sizeof(msg), "Client (#%u) disconnected.", i); - runloop_msg_queue_push(msg, 1, 180, false); - - socket_close(netplay->spectate.fds[i]); - netplay->spectate.fds[i] = -1; - break; } + else + { + /* If we must rewind, it's because we got a save state */ + if (netplay->force_rewind) + { + retro_ctx_serialize_info_t serial_info; - netplay->spectate.input_ptr = 0; + /* Replay frames. */ + netplay->is_replay = true; + netplay->replay_ptr = netplay->other_ptr; + netplay->replay_frame_count = netplay->other_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) + { +#if defined(HAVE_THREADS) + autosave_lock(); +#endif + core_run(); +#if defined(HAVE_THREADS) + autosave_unlock(); +#endif + netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); + netplay->replay_frame_count++; + } + + netplay->is_replay = false; + netplay->force_rewind = false; + } + + /* We're in sync by definition */ + if (netplay->read_frame_count < netplay->self_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; + } + + /* If the server gets significantly ahead, skip to catch up */ + if (netplay->self_frame_count + netplay->stall_frames <= netplay->read_frame_count) + { + /* "Replay" into the future */ + netplay->is_replay = true; + netplay->replay_ptr = netplay->self_ptr; + netplay->replay_frame_count = netplay->self_frame_count; + + while (netplay->replay_frame_count < netplay->read_frame_count - 1) + { +#if defined(HAVE_THREADS) + autosave_lock(); +#endif + core_run(); +#if defined(HAVE_THREADS) + autosave_unlock(); +#endif + + netplay->replay_ptr = NEXT_PTR(netplay->replay_ptr); + netplay->replay_frame_count++; + netplay->self_ptr = netplay->replay_ptr; + netplay->self_frame_count = netplay->replay_frame_count; + } + + netplay->is_replay = false; + } + + } } -static bool netplay_spectate_info_cb(netplay_t *netplay, unsigned frames) +static bool netplay_spectate_info_cb(netplay_t* netplay, unsigned frames) { - unsigned i; - if(netplay_is_server(netplay)) + if (netplay_is_server(netplay)) { - if(!netplay_get_info(netplay)) + int i; + for (i = 0; i < MAX_SPECTATORS; i++) + { + netplay->spectate.fds[i] = -1; + } + + } + else + { + if (!netplay_send_nickname(netplay, netplay->fd)) + return false; + + if (!netplay_get_nickname(netplay, netplay->fd)) return false; } - for (i = 0; i < MAX_SPECTATORS; i++) - netplay->spectate.fds[i] = -1; + netplay->has_connection = true; + return true; } @@ -186,6 +294,5 @@ struct netplay_callbacks* netplay_get_cbs_spectate(void) &netplay_spectate_post_frame, &netplay_spectate_info_cb }; - return &cbs; }