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.
This commit is contained in:
Gregor Richards 2016-09-14 23:19:47 -04:00
parent 79eba578ff
commit 7271d1c3fa
5 changed files with 162 additions and 16 deletions

View File

@ -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:

View File

@ -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:

View File

@ -20,6 +20,8 @@
#include "netplay_private.h"
#include <net/net_socket.h>
#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);
}

View File

@ -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;

View File

@ -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