Merge pull request #3700 from GregorR/netplay-serialization-quirks

Serialization quirks
This commit is contained in:
Alcaro 2016-10-06 00:59:14 +02:00 committed by GitHub
commit 46ab267f40
9 changed files with 246 additions and 37 deletions

4
core.h
View File

@ -178,6 +178,10 @@ bool core_set_environment(retro_ctx_environ_info_t *info);
bool core_serialize_size(retro_ctx_size_info_t *info);
uint64_t core_serialization_quirks(void);
void core_set_serialization_quirks(uint64_t quirks);
bool core_serialize(retro_ctx_serialize_info_t *info);
bool core_unserialize(retro_ctx_serialize_info_t *info);

View File

@ -52,6 +52,7 @@ static bool core_game_loaded = false;
static bool core_input_polled = false;
static bool core_has_set_input_descriptors = false;
static struct retro_callbacks retro_ctx;
static uint64_t core_serialization_quirks_v = 0;
static void core_input_state_poll_maybe(void)
{
@ -308,6 +309,16 @@ bool core_serialize_size(retro_ctx_size_info_t *info)
return true;
}
uint64_t core_serialization_quirks(void)
{
return core_serialization_quirks_v;
}
void core_set_serialization_quirks(uint64_t quirks)
{
core_serialization_quirks_v = quirks;
}
bool core_frame(retro_ctx_frame_info_t *info)
{
if (!info || !retro_ctx.frame_cb)

View File

@ -1608,6 +1608,13 @@ bool rarch_environment_cb(unsigned cmd, void *data)
break;
}
case RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS:
{
uint64_t *quirks = (uint64_t *) data;
core_set_serialization_quirks(*quirks);
break;
}
/* Default */
default:
RARCH_LOG("Environ UNSUPPORTED (#%u).\n", cmd);

View File

@ -976,6 +976,36 @@ struct retro_hw_render_context_negotiation_interface
* so it will be used after SET_HW_RENDER, but before the context_reset callback.
*/
/* Serialized state is incomplete in some way. Set if serialization is
* usable in typical end-user cases but should not be relied upon to
* implement frame-sensitive frontend features such as netplay or
* rerecording. */
#define RETRO_SERIALIZATION_QUIRK_INCOMPLETE (1 << 0)
/* The core must spend some time initializing before serialization is
* supported. */
#define RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE (1 << 1)
/* Serialization size may change within a session. */
#define RETRO_SERIALIZATION_QUIRK_CORE_VARIABLE_SIZE (1 << 2)
/* Set by the frontend to acknowledge that it supports variable-sized
* states. */
#define RETRO_SERIALIZATION_QUIRK_FRONT_VARIABLE_SIZE (1 << 3)
/* Serialized state can only be loaded during the same session. */
#define RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION (1 << 4)
/* Serialized state cannot be loaded on an architecture with a different
* endianness from the one it was saved on. */
#define RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT (1 << 5)
/* Serialized state cannot be loaded on a different platform from the one it
* was saved on for reasons other than endianness, such as word size
* dependence */
#define RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT (1 << 6)
#define RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS 44
/* uint64_t * --
* Sets quirk flags associated with serialization. The frontend will zero any flags it doesn't
* recognize or support. Should be set in either retro_init or retro_load_game, but not both.
*/
#define RETRO_MEMDESC_CONST (1 << 0) /* The frontend will never change this memory area once retro_load_game has returned. */
#define RETRO_MEMDESC_BIGENDIAN (1 << 1) /* The memory area contains big endian data. Default is little endian. */
#define RETRO_MEMDESC_ALIGN_2 (1 << 16) /* All memory access in this area is aligned to their own size, or 2, whichever is smaller. */

View File

@ -31,6 +31,7 @@
#include "netplay_private.h"
#include "../../autosave.h"
#include "../../configuration.h"
#include "../../command.h"
#include "../../movie.h"
@ -556,6 +557,23 @@ static bool netplay_get_cmd(netplay_t *netplay)
{
uint32_t frame;
/* Make sure we're ready for it */
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
{
if (!netplay->is_replay)
{
netplay->is_replay = true;
netplay->replay_ptr = netplay->self_ptr;
netplay->replay_frame_count = netplay->self_frame_count;
netplay_wait_and_init_serialization(netplay);
netplay->is_replay = false;
}
else
{
netplay_wait_and_init_serialization(netplay);
}
}
/* There is a subtlty in whether the load comes before or after the
* current frame:
*
@ -729,9 +747,11 @@ static bool netplay_poll(void)
/* Read Netplay input, block if we're configured to stall for input every
* frame */
res = poll_input(netplay_data,
(netplay_data->stall_frames == 0)
&& (netplay_data->read_frame_count <= netplay_data->self_frame_count));
if (netplay_data->stall_frames == 0 &&
netplay_data->read_frame_count <= netplay_data->self_frame_count)
res = poll_input(netplay_data, true);
else
res = poll_input(netplay_data, false);
if (res == -1)
{
hangup(netplay_data);
@ -970,6 +990,82 @@ void netplay_log_connection(const struct sockaddr_storage *their_addr,
bool netplay_try_init_serialization(netplay_t *netplay)
{
retro_ctx_serialize_info_t serial_info;
if (netplay->state_size)
return true;
if (!netplay_init_serialization(netplay))
return false;
/* Check if we can actually save */
serial_info.data_const = NULL;
serial_info.data = netplay->buffer[netplay->self_ptr].state;
serial_info.size = netplay->state_size;
if (!core_serialize(&serial_info))
return false;
/* Once initialized, we no longer exhibit this quirk */
netplay->quirks &= ~((uint64_t) NETPLAY_QUIRK_INITIALIZATION);
return true;
}
bool netplay_wait_and_init_serialization(netplay_t *netplay)
{
int frame;
if (netplay->state_size)
return true;
/* Wait a maximum of 60 frames */
for (frame = 0; frame < 60; frame++) {
if (netplay_try_init_serialization(netplay))
return true;
#if defined(HAVE_THREADS)
autosave_lock();
#endif
core_run();
#if defined(HAVE_THREADS)
autosave_unlock();
#endif
}
return false;
}
bool netplay_init_serialization(netplay_t *netplay)
{
unsigned i;
retro_ctx_size_info_t info;
if (netplay->state_size)
return true;
core_serialize_size(&info);
if (!info.size)
return false;
netplay->state_size = info.size;
for (i = 0; i < netplay->buffer_size; i++)
{
netplay->buffer[i].state = calloc(netplay->state_size, 1);
if (!netplay->buffer[i].state)
{
netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
return false;
}
}
return true;
}
static bool netplay_init_buffers(netplay_t *netplay, unsigned frames)
{
@ -988,25 +1084,8 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames)
if (!netplay->buffer)
return false;
{
unsigned i;
retro_ctx_size_info_t info;
core_serialize_size(&info);
netplay->state_size = info.size;
for (i = 0; i < netplay->buffer_size; i++)
{
netplay->buffer[i].state = calloc(netplay->state_size, 1);
if (!netplay->buffer[i].state)
{
netplay->savestates_work = false;
netplay->stall_frames = 0;
}
}
}
if (!(netplay->quirks & NETPLAY_QUIRK_INITIALIZATION))
netplay_init_serialization(netplay);
return true;
}
@ -1020,6 +1099,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames)
* @cb : Libretro callbacks.
* @spectate : If true, enable spectator mode.
* @nick : Nickname of user.
* @quirks : Netplay quirks required for this session.
*
* Creates a new netplay handle. A NULL host means we're
* hosting (user 1).
@ -1028,7 +1108,7 @@ static bool netplay_init_buffers(netplay_t *netplay, unsigned frames)
**/
netplay_t *netplay_new(const char *server, uint16_t port,
unsigned frames, unsigned check_frames, const struct retro_callbacks *cb,
bool spectate, const char *nick)
bool spectate, const char *nick, uint64_t quirks)
{
netplay_t *netplay = (netplay_t*)calloc(1, sizeof(*netplay));
if (!netplay)
@ -1040,10 +1120,10 @@ netplay_t *netplay_new(const char *server, uint16_t port,
netplay->port = server ? 0 : 1;
netplay->spectate.enabled = spectate;
netplay->is_server = server == NULL;
netplay->savestates_work = true;
strlcpy(netplay->nick, nick, sizeof(netplay->nick));
netplay->stall_frames = frames;
netplay->check_frames = check_frames;
netplay->quirks = quirks;
if (!netplay_init_buffers(netplay, frames))
{
@ -1205,6 +1285,12 @@ bool netplay_pre_frame(netplay_t *netplay)
if (netplay->local_paused)
netplay_frontend_paused(netplay, false);
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
{
/* Are we ready now? */
netplay_try_init_serialization(netplay);
}
if (!netplay->net_cbs->pre_frame(netplay))
return false;
@ -1298,6 +1384,10 @@ void netplay_load_savestate(netplay_t *netplay, retro_ctx_serialize_info_t *seri
netplay->other_frame_count = netplay->self_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|NETPLAY_QUIRK_NO_TRANSMISSION))
return;
/* 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));
@ -1353,6 +1443,8 @@ bool init_netplay(bool is_spectate, const char *server, unsigned port)
{
struct retro_callbacks cbs = {0};
settings_t *settings = config_get_ptr();
uint64_t serialization_quirks = 0;
uint64_t quirks = 0;
if (!netplay_enabled)
return false;
@ -1366,6 +1458,20 @@ bool init_netplay(bool is_spectate, const char *server, unsigned port)
core_set_default_callbacks(&cbs);
/* Map the core's quirks to our quirks */
serialization_quirks = core_serialization_quirks();
if (serialization_quirks & ~((uint64_t) NETPLAY_QUIRK_MAP_UNDERSTOOD))
{
/* Quirks we don't support! Just disable everything. */
quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
}
if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_SAVESTATES)
quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
if (serialization_quirks & NETPLAY_QUIRK_MAP_NO_TRANSMISSION)
quirks |= NETPLAY_QUIRK_NO_TRANSMISSION;
if (serialization_quirks & NETPLAY_QUIRK_MAP_INITIALIZATION)
quirks |= NETPLAY_QUIRK_INITIALIZATION;
if (netplay_is_client)
{
RARCH_LOG("Connecting to netplay host...\n");
@ -1382,7 +1488,7 @@ bool init_netplay(bool is_spectate, const char *server, unsigned port)
netplay_is_client ? server : NULL,
port ? port : RARCH_DEFAULT_PORT,
settings->netplay.sync_frames, settings->netplay.check_frames, &cbs,
is_spectate, settings->username);
is_spectate, settings->username, quirks);
if (netplay_data)
return true;

View File

@ -139,6 +139,7 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames);
* @cb : Libretro callbacks.
* @spectate : If true, enable spectator mode.
* @nick : Nickname of user.
* @quirks : Netplay quirks.
*
* Creates a new netplay handle. A NULL host means we're
* hosting (user 1).
@ -148,7 +149,7 @@ size_t audio_sample_batch_net(const int16_t *data, size_t frames);
netplay_t *netplay_new(const char *server,
uint16_t port, unsigned frames, unsigned check_frames,
const struct retro_callbacks *cb, bool spectate,
const char *nick);
const char *nick, uint64_t quirks);
/**
* netplay_free:

View File

@ -74,15 +74,18 @@ static bool netplay_net_pre_frame(netplay_t *netplay)
{
retro_ctx_serialize_info_t serial_info;
if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count) &&
netplay->self_frame_count > 0)
if (netplay_delta_frame_ready(netplay, &netplay->buffer[netplay->self_ptr], netplay->self_frame_count))
{
serial_info.data_const = NULL;
serial_info.data = netplay->buffer[netplay->self_ptr].state;
serial_info.size = netplay->state_size;
memset(serial_info.data, 0, serial_info.size);
if (netplay->savestates_work && core_serialize(&serial_info))
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
{
/* Don't serialize until it's safe */
}
else if (!(netplay->quirks & NETPLAY_QUIRK_NO_SAVESTATES) && core_serialize(&serial_info))
{
if (netplay->force_send_savestate && !netplay->stall)
{
@ -96,11 +99,14 @@ static bool netplay_net_pre_frame(netplay_t *netplay)
{
/* If the core can't serialize properly, we must stall for the
* remote input on EVERY frame, because we can't recover */
netplay->savestates_work = false;
netplay->quirks |= NETPLAY_QUIRK_NO_SAVESTATES;
netplay->stall_frames = 0;
if (!netplay->has_connection)
netplay->stall = RARCH_NETPLAY_STALL_NO_CONNECTION;
}
/* If we can't transmit savestates, we must stall until the client is ready */
if (!netplay->has_connection &&
(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)))
netplay->stall = RARCH_NETPLAY_STALL_NO_CONNECTION;
}
if (netplay->is_server && !netplay->has_connection)
@ -148,10 +154,8 @@ static bool netplay_net_pre_frame(netplay_t *netplay)
netplay->has_connection = true;
/* Send them the savestate */
if (netplay->savestates_work)
{
if (!(netplay->quirks & (NETPLAY_QUIRK_NO_SAVESTATES|NETPLAY_QUIRK_NO_TRANSMISSION)))
netplay_load_savestate(netplay, NULL, true);
}
/* And expect the current frame from the other side */
netplay->read_frame_count = netplay->other_frame_count = netplay->self_frame_count;
@ -230,6 +234,10 @@ 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->quirks & NETPLAY_QUIRK_INITIALIZATION)
/* Make sure we're initialized before we start loading things */
netplay_wait_and_init_serialization(netplay);
serial_info.data = NULL;
serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
serial_info.size = netplay->state_size;

View File

@ -40,6 +40,28 @@
#define PREV_PTR(x) ((x) == 0 ? netplay->buffer_size - 1 : (x) - 1)
#define NEXT_PTR(x) ((x + 1) % netplay->buffer_size)
/* Quirks mandated by how particular cores save states. This is distilled from
* the larger set of quirks that the quirks environment can communicate. */
#define NETPLAY_QUIRK_NO_SAVESTATES (1<<0)
#define NETPLAY_QUIRK_NO_TRANSMISSION (1<<1)
#define NETPLAY_QUIRK_INITIALIZATION (1<<2)
/* Mapping of serialization quirks to netplay quirks. */
#define NETPLAY_QUIRK_MAP_UNDERSTOOD \
(RETRO_SERIALIZATION_QUIRK_INCOMPLETE \
|RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE \
|RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION \
|RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT \
|RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT)
#define NETPLAY_QUIRK_MAP_NO_SAVESTATES \
(RETRO_SERIALIZATION_QUIRK_INCOMPLETE)
#define NETPLAY_QUIRK_MAP_NO_TRANSMISSION \
(RETRO_SERIALIZATION_QUIRK_SINGLE_SESSION \
|RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT \
|RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT)
#define NETPLAY_QUIRK_MAP_INITIALIZATION \
(RETRO_SERIALIZATION_QUIRK_MUST_INITIALIZE)
struct delta_frame
{
bool used; /* a bit derpy, but this is how we know if the delta's been used at all */
@ -118,8 +140,8 @@ struct netplay
* events, such as player flipping or savestate loading. */
bool force_rewind;
/* Does the core support savestates? */
bool savestates_work;
/* Quirks in the savestate implementation */
uint64_t quirks;
/* Force our state to be sent to the other side. Used when they request a
* savestate, to send at the next pre-frame. */
@ -177,6 +199,12 @@ struct netplay_callbacks* netplay_get_cbs_net(void);
struct netplay_callbacks* netplay_get_cbs_spectate(void);
/* Normally called at init time, unless the INITIALIZATION quirk is set */
bool netplay_init_serialization(netplay_t *netplay);
/* Force serialization to be ready by fast-forwarding the core */
bool netplay_wait_and_init_serialization(netplay_t *netplay);
void netplay_simulate_input(netplay_t *netplay, uint32_t sim_ptr);
void netplay_log_connection(const struct sockaddr_storage *their_addr,

View File

@ -111,6 +111,16 @@ static bool netplay_spectate_pre_frame(netplay_t *netplay)
return true;
}
/* Wait until it's safe to serialize */
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
{
netplay->is_replay = true;
netplay->replay_ptr = netplay->self_ptr;
netplay->replay_frame_count = netplay->self_frame_count;
netplay_wait_and_init_serialization(netplay);
netplay->is_replay = false;
}
/* Start them at the current frame */
netplay->spectate.frames[idx] = netplay->self_frame_count;
serial_info.data_const = NULL;
@ -197,6 +207,10 @@ static void netplay_spectate_post_frame(netplay_t *netplay)
netplay->replay_ptr = netplay->other_ptr;
netplay->replay_frame_count = netplay->other_frame_count;
/* Wait until it's safe to serialize */
if (netplay->quirks & NETPLAY_QUIRK_INITIALIZATION)
netplay_wait_and_init_serialization(netplay);
serial_info.data = NULL;
serial_info.data_const = netplay->buffer[netplay->replay_ptr].state;
serial_info.size = netplay->state_size;