mirror of
https://github.com/libretro/RetroArch
synced 2025-02-02 05:54:16 +00:00
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.
This commit is contained in:
parent
4d11b1d674
commit
27188e102d
@ -1919,11 +1919,6 @@ bool command_event(enum event_command cmd, void *data)
|
|||||||
if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL))
|
if (bsv_movie_ctl(BSV_MOVIE_CTL_IS_INITED, NULL))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
#ifdef HAVE_NETPLAY
|
|
||||||
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_DATA_INITED, NULL))
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef HAVE_CHEEVOS
|
#ifdef HAVE_CHEEVOS
|
||||||
if (settings->cheevos.hardcore_mode_enable)
|
if (settings->cheevos.hardcore_mode_enable)
|
||||||
return false;
|
return false;
|
||||||
|
@ -242,7 +242,12 @@ static bool netplay_get_cmd(netplay_t *netplay)
|
|||||||
for (i = 0; i < WORDS_PER_FRAME; i++)
|
for (i = 0; i < WORDS_PER_FRAME; i++)
|
||||||
buffer[i] = ntohl(buffer[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 */
|
/* Out of order = out of luck */
|
||||||
return netplay_cmd_nak(netplay);
|
return netplay_cmd_nak(netplay);
|
||||||
@ -295,8 +300,58 @@ static bool netplay_get_cmd(netplay_t *netplay)
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case NETPLAY_CMD_LOAD_SAVESTATE:
|
case NETPLAY_CMD_LOAD_SAVESTATE:
|
||||||
RARCH_ERR("NETPLAY_CMD_LOAD_SAVESTATE unimplemented.\n");
|
{
|
||||||
|
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);
|
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:
|
case NETPLAY_CMD_PAUSE:
|
||||||
netplay->remote_paused = true;
|
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_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)
|
void deinit_netplay(void)
|
||||||
{
|
{
|
||||||
netplay_t *netplay = (netplay_t*)netplay_data;
|
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:
|
case RARCH_NETPLAY_CTL_UNPAUSE:
|
||||||
netplay_frontend_paused((netplay_t*)netplay_data, false);
|
netplay_frontend_paused((netplay_t*)netplay_data, false);
|
||||||
break;
|
break;
|
||||||
|
case RARCH_NETPLAY_CTL_LOAD_SAVESTATE:
|
||||||
|
netplay_load_savestate((netplay_t*)netplay_data, (retro_ctx_serialize_info_t*)data);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
case RARCH_NETPLAY_CTL_NONE:
|
case RARCH_NETPLAY_CTL_NONE:
|
||||||
break;
|
break;
|
||||||
|
@ -37,7 +37,8 @@ enum rarch_netplay_ctl_state
|
|||||||
RARCH_NETPLAY_CTL_PRE_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_PAUSE,
|
||||||
RARCH_NETPLAY_CTL_UNPAUSE
|
RARCH_NETPLAY_CTL_UNPAUSE,
|
||||||
|
RARCH_NETPLAY_CTL_LOAD_SAVESTATE
|
||||||
};
|
};
|
||||||
|
|
||||||
enum netplay_cmd
|
enum netplay_cmd
|
||||||
@ -77,6 +78,7 @@ enum netplay_cmd
|
|||||||
|
|
||||||
/* Send a savestate for the client to load */
|
/* Send a savestate for the client to load */
|
||||||
NETPLAY_CMD_LOAD_SAVESTATE = 0x0012,
|
NETPLAY_CMD_LOAD_SAVESTATE = 0x0012,
|
||||||
|
|
||||||
/* Sends over cheats enabled on client */
|
/* Sends over cheats enabled on client */
|
||||||
NETPLAY_CMD_CHEATS = 0x0013,
|
NETPLAY_CMD_CHEATS = 0x0013,
|
||||||
|
|
||||||
@ -181,6 +183,15 @@ void netplay_post_frame(netplay_t *handle);
|
|||||||
**/
|
**/
|
||||||
void netplay_frontend_paused(netplay_t *netplay, bool paused);
|
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:
|
* init_netplay:
|
||||||
*
|
*
|
||||||
|
@ -69,6 +69,8 @@ static void netplay_net_post_frame(netplay_t *netplay)
|
|||||||
if (!netplay->has_connection)
|
if (!netplay->has_connection)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!netplay->force_rewind)
|
||||||
|
{
|
||||||
/* Skip ahead if we predicted correctly.
|
/* Skip ahead if we predicted correctly.
|
||||||
* Skip until our simulation failed. */
|
* Skip until our simulation failed. */
|
||||||
while (netplay->other_frame_count < netplay->read_frame_count &&
|
while (netplay->other_frame_count < netplay->read_frame_count &&
|
||||||
@ -83,10 +85,12 @@ static void netplay_net_post_frame(netplay_t *netplay)
|
|||||||
netplay->other_ptr = NEXT_PTR(netplay->other_ptr);
|
netplay->other_ptr = NEXT_PTR(netplay->other_ptr);
|
||||||
netplay->other_frame_count++;
|
netplay->other_frame_count++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Now replay the real input if we've gotten ahead of it */
|
/* Now replay the real input if we've gotten ahead of it */
|
||||||
if (netplay->other_frame_count < netplay->read_frame_count &&
|
if (netplay->force_rewind ||
|
||||||
netplay->other_frame_count < netplay->self_frame_count)
|
(netplay->other_frame_count < netplay->read_frame_count &&
|
||||||
|
netplay->other_frame_count < netplay->self_frame_count))
|
||||||
{
|
{
|
||||||
retro_ctx_serialize_info_t serial_info;
|
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->other_frame_count = netplay->self_frame_count;
|
||||||
}
|
}
|
||||||
netplay->is_replay = false;
|
netplay->is_replay = false;
|
||||||
|
netplay->force_rewind = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we're supposed to stall, rewind (we shouldn't get this far if we're
|
/* If we're supposed to stall, rewind (we shouldn't get this far if we're
|
||||||
|
@ -103,10 +103,13 @@ struct netplay
|
|||||||
|
|
||||||
/* Are we replaying old frames? */
|
/* Are we replaying old frames? */
|
||||||
bool is_replay;
|
bool is_replay;
|
||||||
|
|
||||||
/* We don't want to poll several times on a frame. */
|
/* We don't want to poll several times on a frame. */
|
||||||
bool can_poll;
|
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. */
|
/* A buffer for outgoing input packets. */
|
||||||
uint32_t packet_buffer[2 + WORDS_PER_FRAME];
|
uint32_t packet_buffer[2 + WORDS_PER_FRAME];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user