diff --git a/core.h b/core.h index 0b80d39737..b7bf4657a8 100644 --- a/core.h +++ b/core.h @@ -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); diff --git a/core_impl.c b/core_impl.c index 683a821c57..9f1cb1ebbf 100644 --- a/core_impl.c +++ b/core_impl.c @@ -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) diff --git a/dynamic.c b/dynamic.c index 933f11f171..e6d1c45d2e 100644 --- a/dynamic.c +++ b/dynamic.c @@ -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); diff --git a/libretro-common/include/libretro.h b/libretro-common/include/libretro.h index a4df6be470..d8662e5ebd 100644 --- a/libretro-common/include/libretro.h +++ b/libretro-common/include/libretro.h @@ -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. */ diff --git a/network/netplay/netplay.c b/network/netplay/netplay.c index d8593fcd5f..9aea43cc03 100644 --- a/network/netplay/netplay.c +++ b/network/netplay/netplay.c @@ -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; diff --git a/network/netplay/netplay.h b/network/netplay/netplay.h index 6fb459b662..4900a85530 100644 --- a/network/netplay/netplay.h +++ b/network/netplay/netplay.h @@ -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: diff --git a/network/netplay/netplay_net.c b/network/netplay/netplay_net.c index dbb82e910b..10019ba242 100644 --- a/network/netplay/netplay_net.c +++ b/network/netplay/netplay_net.c @@ -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; diff --git a/network/netplay/netplay_private.h b/network/netplay/netplay_private.h index e792d94788..ae80b5eb53 100644 --- a/network/netplay/netplay_private.h +++ b/network/netplay/netplay_private.h @@ -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, diff --git a/network/netplay/netplay_spectate.c b/network/netplay/netplay_spectate.c index 53ad18b85a..fa05546268 100644 --- a/network/netplay/netplay_spectate.c +++ b/network/netplay/netplay_spectate.c @@ -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;