From 27188e102d4aebabb8ac2caa8ed04070fd2139e8 Mon Sep 17 00:00:00 2001 From: Gregor Richards Date: Wed, 14 Sep 2016 18:03:40 -0400 Subject: [PATCH] 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];