Support for reset in netplay

This patch transfers core_reset across netplay. Resets effectively
worked before thanks to check_frames, but this makes resets work even
without check_frames, and in particular should allow resets to force
sync in savestateless cores, bringing them one step closer to actually
being usable by non-experts.
This commit is contained in:
Gregor Richards 2017-02-15 14:40:37 -05:00
parent d0b29f3d8e
commit 4c1abfaa71
7 changed files with 155 additions and 67 deletions

View File

@ -1918,6 +1918,9 @@ bool command_event(enum event_command cmd, void *data)
core_reset();
#ifdef HAVE_CHEEVOS
cheevos_reset_game();
#endif
#if HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_RESET, NULL);
#endif
break;
case CMD_EVENT_SAVE_STATE:

View File

@ -316,6 +316,14 @@ Payload:
Description:
Request that a client stall for the given number of frames.
Command: RESET
Payload:
{
frame: uint32
}
Description:
Indicate that the core was reset at the beginning of the given frame.
Command: CHEATS
Unused

View File

@ -44,6 +44,7 @@ enum rarch_netplay_ctl_state
RARCH_NETPLAY_CTL_PAUSE,
RARCH_NETPLAY_CTL_UNPAUSE,
RARCH_NETPLAY_CTL_LOAD_SAVESTATE,
RARCH_NETPLAY_CTL_RESET,
RARCH_NETPLAY_CTL_DISCONNECT
};

View File

@ -740,6 +740,48 @@ void netplay_post_frame(netplay_t *netplay)
}
}
/**
* netplay_force_future
* @netplay : pointer to netplay object
*
* Force netplay to ignore all past input, typically because we've just loaded
* a state or reset.
*/
static void netplay_force_future(netplay_t *netplay)
{
/* Wherever we're inputting, that's where we consider our state to be loaded */
netplay->run_ptr = netplay->self_ptr;
netplay->run_frame_count = netplay->self_frame_count;
/* We need to ignore any intervening data from the other side,
* and never rewind past this */
netplay_update_unread_ptr(netplay);
if (netplay->unread_frame_count < netplay->run_frame_count)
{
uint32_t player;
for (player = 0; player < MAX_USERS; player++)
{
if (!(netplay->connected_players & (1<<player))) continue;
if (netplay->read_frame_count[player] < netplay->run_frame_count)
{
netplay->read_ptr[player] = netplay->run_ptr;
netplay->read_frame_count[player] = netplay->run_frame_count;
}
}
if (netplay->server_frame_count < netplay->run_frame_count)
{
netplay->server_ptr = netplay->run_ptr;
netplay->server_frame_count = netplay->run_frame_count;
}
netplay_update_unread_ptr(netplay);
}
if (netplay->other_frame_count < netplay->run_frame_count)
{
netplay->other_ptr = netplay->run_ptr;
netplay->other_frame_count = netplay->run_frame_count;
}
}
/**
* netplay_send_savestate
* @netplay : pointer to netplay object
@ -808,10 +850,7 @@ void netplay_load_savestate(netplay_t *netplay,
{
retro_ctx_serialize_info_t tmp_serial_info;
/* Wherever we're inputting, that's where we consider our state to be loaded
* (FIXME: Need to be more careful about saving it?) */
netplay->run_ptr = netplay->self_ptr;
netplay->run_frame_count = netplay->self_frame_count;
netplay_force_future(netplay);
/* Record it in our own buffer */
if (save || !serial_info)
@ -844,34 +883,6 @@ void netplay_load_savestate(netplay_t *netplay,
}
}
/* We need to ignore any intervening data from the other side,
* and never rewind past this */
netplay_update_unread_ptr(netplay);
if (netplay->unread_frame_count < netplay->run_frame_count)
{
uint32_t player;
for (player = 0; player < MAX_USERS; player++)
{
if (!(netplay->connected_players & (1<<player))) continue;
if (netplay->read_frame_count[player] < netplay->run_frame_count)
{
netplay->read_ptr[player] = netplay->run_ptr;
netplay->read_frame_count[player] = netplay->run_frame_count;
}
}
if (netplay->server_frame_count < netplay->run_frame_count)
{
netplay->server_ptr = netplay->run_ptr;
netplay->server_frame_count = netplay->run_frame_count;
}
netplay_update_unread_ptr(netplay);
}
if (netplay->other_frame_count < netplay->run_frame_count)
{
netplay->other_ptr = netplay->run_ptr;
netplay->other_frame_count = netplay->run_frame_count;
}
/* If we can't send it to the peer, loading a state was a bad idea */
if (netplay->quirks & (
NETPLAY_QUIRK_NO_SAVESTATES
@ -886,6 +897,37 @@ void netplay_load_savestate(netplay_t *netplay,
&netplay->compress_zlib);
}
/**
* netplay_core_reset
* @netplay : pointer to netplay object
*
* Indicate that the core has been reset to netplay peers
**/
static void netplay_core_reset(netplay_t *netplay)
{
uint32_t cmd[3];
size_t i;
/* Ignore past input */
netplay_force_future(netplay);
/* Request that our peers reset */
cmd[0] = htonl(NETPLAY_CMD_RESET);
cmd[1] = htonl(sizeof(uint32_t));
cmd[2] = htonl(netplay->self_frame_count);
for (i = 0; i < netplay->connections_size; i++)
{
struct netplay_connection *connection = &netplay->connections[i];
if (!connection->active ||
connection->mode < NETPLAY_CONNECTION_CONNECTED) continue;
if (!netplay_send(&connection->send_packet_buffer, connection->fd, cmd,
sizeof(cmd)))
netplay_hangup(netplay, connection);
}
}
/**
* netplay_toggle_play_spectate
*
@ -1145,6 +1187,9 @@ bool netplay_driver_ctl(enum rarch_netplay_ctl_state state, void *data)
case RARCH_NETPLAY_CTL_LOAD_SAVESTATE:
netplay_load_savestate(netplay_data, (retro_ctx_serialize_info_t*)data, true);
break;
case RARCH_NETPLAY_CTL_RESET:
netplay_core_reset(netplay_data);
break;
case RARCH_NETPLAY_CTL_DISCONNECT:
ret = netplay_disconnect(netplay_data);
goto done;

View File

@ -1022,6 +1022,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
break;
case NETPLAY_CMD_LOAD_SAVESTATE:
case NETPLAY_CMD_RESET:
{
uint32_t frame;
uint32_t isize;
@ -1071,7 +1072,11 @@ static bool netplay_get_cmd(netplay_t *netplay,
* (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->zbuffer_size + 2*sizeof(uint32_t))
/* Check the payload size */
if ((cmd == NETPLAY_CMD_LOAD_SAVESTATE &&
(cmd_size < 2*sizeof(uint32_t) || cmd_size > netplay->zbuffer_size + 2*sizeof(uint32_t))) ||
(cmd == NETPLAY_CMD_RESET && cmd_size != sizeof(uint32_t)))
{
RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected payload size.\n");
return netplay_cmd_nak(netplay, connection);
@ -1097,44 +1102,58 @@ static bool netplay_get_cmd(netplay_t *netplay,
goto shrt;
}
RECV(&isize, sizeof(isize))
/* Now we switch based on whether we're loading a state or resetting */
if (cmd == NETPLAY_CMD_LOAD_SAVESTATE)
{
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n");
return netplay_cmd_nak(netplay, connection);
}
isize = ntohl(isize);
RECV(&isize, sizeof(isize))
{
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive inflated size.\n");
return netplay_cmd_nak(netplay, connection);
}
isize = ntohl(isize);
if (isize != netplay->state_size)
{
RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n");
return netplay_cmd_nak(netplay, connection);
}
if (isize != netplay->state_size)
{
RARCH_ERR("CMD_LOAD_SAVESTATE received an unexpected save state size.\n");
return netplay_cmd_nak(netplay, connection);
}
RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t))
{
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n");
return netplay_cmd_nak(netplay, connection);
}
RECV(netplay->zbuffer, cmd_size - 2*sizeof(uint32_t))
{
RARCH_ERR("CMD_LOAD_SAVESTATE failed to receive savestate.\n");
return netplay_cmd_nak(netplay, connection);
}
/* And decompress it */
switch (connection->compression_supported)
{
case NETPLAY_COMPRESSION_ZLIB:
ctrans = &netplay->compress_zlib;
break;
default:
ctrans = &netplay->compress_nil;
}
ctrans->decompression_backend->set_in(ctrans->decompression_stream,
netplay->zbuffer, cmd_size - 2*sizeof(uint32_t));
ctrans->decompression_backend->set_out(ctrans->decompression_stream,
(uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state,
netplay->state_size);
ctrans->decompression_backend->trans(ctrans->decompression_stream,
true, &rd, &wn, NULL);
/* Force a rewind to the relevant frame */
netplay->force_rewind = true;
}
else
{
/* Resetting */
netplay->force_reset = true;
/* And decompress it */
switch (connection->compression_supported)
{
case NETPLAY_COMPRESSION_ZLIB:
ctrans = &netplay->compress_zlib;
break;
default:
ctrans = &netplay->compress_nil;
}
ctrans->decompression_backend->set_in(ctrans->decompression_stream,
netplay->zbuffer, cmd_size - 2*sizeof(uint32_t));
ctrans->decompression_backend->set_out(ctrans->decompression_stream,
(uint8_t*)netplay->buffer[netplay->read_ptr[connection->player]].state,
netplay->state_size);
ctrans->decompression_backend->trans(ctrans->decompression_stream,
true, &rd, &wn, NULL);
/* Skip ahead if it's past where we are */
if (frame > netplay->run_frame_count)
if (frame > netplay->run_frame_count ||
cmd == NETPLAY_CMD_RESET)
{
/* This is squirrely: We need to assure that when we advance the
* frame in post_frame, THEN we're referring to the frame to
@ -1161,8 +1180,7 @@ static bool netplay_get_cmd(netplay_t *netplay,
}
}
/* And force rewind to it */
netplay->force_rewind = true;
/* Make sure our states are correct */
netplay->savestate_request_outstanding = false;
netplay->other_ptr = netplay->read_ptr[connection->player];
netplay->other_frame_count = frame;

View File

@ -153,8 +153,11 @@ enum netplay_cmd
/* Request that a client stall because it's running fast */
NETPLAY_CMD_STALL = 0x0045,
/* Request a core reset */
NETPLAY_CMD_RESET = 0x0046,
/* Sends over cheats enabled on client (unsupported) */
NETPLAY_CMD_CHEATS = 0x0046,
NETPLAY_CMD_CHEATS = 0x0047,
/* Misc. commands */
@ -404,6 +407,9 @@ struct netplay
* events, such as player flipping or savestate loading. */
bool force_rewind;
/* Force a reset */
bool force_reset;
/* Quirks in the savestate implementation */
uint64_t quirks;

View File

@ -396,6 +396,13 @@ void netplay_sync_post_frame(netplay_t *netplay, bool stalled)
return;
}
/* Reset if it was requested */
if (netplay->force_reset)
{
core_reset();
netplay->force_reset = false;
}
#ifndef DEBUG_NONDETERMINISTIC_CORES
if (!netplay->force_rewind)
{