mirror of
https://github.com/libretro/RetroArch
synced 2025-02-06 09:40:06 +00:00
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:
parent
79eba578ff
commit
7271d1c3fa
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user