mirror of
https://github.com/libretro/RetroArch
synced 2025-03-29 22:20:21 +00:00
Add checkpointing feature for replay recordings. (#15072)
If cores are not deterministic, or if they only have bounded determinism, we can obtain less drift if replay files also contain periodic checkpoint states. These are configured by the new retroarch stting replay_checkpoint_interval (measured in seconds). States are inserted into the replay file in between frames. This patch also fixes the settings display for the replay autoincrement max keep setting.
This commit is contained in:
parent
297aa1ff72
commit
81b3e128ac
@ -1279,6 +1279,10 @@
|
||||
* replays will be deleted in this case) */
|
||||
#define DEFAULT_REPLAY_MAX_KEEP 0
|
||||
|
||||
/* Specifies how often checkpoints will be saved to replay files during recording.
|
||||
* > Setting value to zero disables recording checkpoints. */
|
||||
#define DEFAULT_REPLAY_CHECKPOINT_INTERVAL 0
|
||||
|
||||
/* Automatically saves a savestate at the end of RetroArch's lifetime.
|
||||
* The path is $SRAM_PATH.auto.
|
||||
* RetroArch will automatically load any savestate with this path on
|
||||
|
@ -2202,6 +2202,7 @@ static struct config_uint_setting *populate_settings_uint(
|
||||
SETTING_UINT("rewind_buffer_size_step", &settings->uints.rewind_buffer_size_step, true, DEFAULT_REWIND_BUFFER_SIZE_STEP, false);
|
||||
SETTING_UINT("autosave_interval", &settings->uints.autosave_interval, true, DEFAULT_AUTOSAVE_INTERVAL, false);
|
||||
SETTING_UINT("replay_max_keep", &settings->uints.replay_max_keep, true, DEFAULT_REPLAY_MAX_KEEP, false);
|
||||
SETTING_UINT("replay_checkpoint_interval", &settings->uints.replay_checkpoint_interval, true, DEFAULT_REPLAY_CHECKPOINT_INTERVAL, false);
|
||||
SETTING_UINT("savestate_max_keep", &settings->uints.savestate_max_keep, true, DEFAULT_SAVESTATE_MAX_KEEP, false);
|
||||
SETTING_UINT("frontend_log_level", &settings->uints.frontend_log_level, true, DEFAULT_FRONTEND_LOG_LEVEL, false);
|
||||
SETTING_UINT("libretro_log_level", &settings->uints.libretro_log_level, true, DEFAULT_LIBRETRO_LOG_LEVEL, false);
|
||||
|
@ -204,6 +204,7 @@ typedef struct settings
|
||||
unsigned rewind_granularity;
|
||||
unsigned rewind_buffer_size_step;
|
||||
unsigned autosave_interval;
|
||||
unsigned replay_checkpoint_interval;
|
||||
unsigned replay_max_keep;
|
||||
unsigned savestate_max_keep;
|
||||
unsigned network_cmd_port;
|
||||
|
@ -4784,7 +4784,8 @@ void bsv_movie_finish_rewind(input_driver_state_t *input_st)
|
||||
}
|
||||
void bsv_movie_next_frame(input_driver_state_t *input_st)
|
||||
{
|
||||
/* Used for rewinding while playback/record. */
|
||||
settings_t *settings = config_get_ptr();
|
||||
unsigned checkpoint_interval = settings->uints.replay_checkpoint_interval;
|
||||
bsv_movie_t *handle = input_st->bsv_movie_state_handle;
|
||||
if (!handle)
|
||||
return;
|
||||
@ -4799,6 +4800,32 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
|
||||
intfstream_write(handle->file, &(handle->key_events[i]), sizeof(bsv_key_data_t));
|
||||
}
|
||||
bsv_movie_handle_clear_key_events(handle);
|
||||
/* maybe record checkpoint */
|
||||
if (checkpoint_interval != 0 && handle->frame_ptr > 0 && (handle->frame_ptr % (checkpoint_interval*60) == 0))
|
||||
{
|
||||
retro_ctx_size_info_t info;
|
||||
retro_ctx_serialize_info_t serial_info;
|
||||
uint8_t *st;
|
||||
uint64_t size;
|
||||
uint8_t frame_tok = REPLAY_TOKEN_CHECKPOINT_FRAME;
|
||||
core_serialize_size(&info);
|
||||
size = info.size;
|
||||
st = (uint8_t*)malloc(info.size);
|
||||
serial_info.data = st;
|
||||
serial_info.size = info.size;
|
||||
core_serialize(&serial_info);
|
||||
/* "next frame is a checkpoint" */
|
||||
intfstream_write(handle->file, (uint8_t *)(&frame_tok), sizeof(uint8_t));
|
||||
intfstream_write(handle->file, &(swap_if_big64(size)), sizeof(uint64_t));
|
||||
intfstream_write(handle->file, st, info.size);
|
||||
free(st);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t frame_tok = REPLAY_TOKEN_REGULAR_FRAME;
|
||||
/* write "next frame is not a checkpoint" */
|
||||
intfstream_write(handle->file, (uint8_t *)(&frame_tok), sizeof(uint8_t));
|
||||
}
|
||||
}
|
||||
if (input_st->bsv_movie_state.flags & BSV_FLAG_MOVIE_PLAYBACK)
|
||||
{
|
||||
@ -4816,6 +4843,8 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
|
||||
{
|
||||
/* Unnatural EOF */
|
||||
RARCH_ERR("[Replay] Keyboard replay ran out of keyboard inputs too early\n");
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4824,6 +4853,52 @@ void bsv_movie_next_frame(input_driver_state_t *input_st)
|
||||
RARCH_LOG("[Replay] EOF after buttons\n",handle->key_event_count);
|
||||
/* Natural(?) EOF */
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
|
||||
return;
|
||||
}
|
||||
{
|
||||
uint8_t next_frame_type=REPLAY_TOKEN_INVALID;
|
||||
if (intfstream_read(handle->file, (uint8_t *)(&next_frame_type), sizeof(uint8_t)) != sizeof(uint8_t))
|
||||
{
|
||||
/* Unnatural EOF */
|
||||
RARCH_ERR("[Replay] Replay ran out of frames\n");
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
|
||||
return;
|
||||
}
|
||||
else if(next_frame_type == REPLAY_TOKEN_REGULAR_FRAME)
|
||||
{
|
||||
/* do nothing */
|
||||
}
|
||||
else if(next_frame_type == REPLAY_TOKEN_CHECKPOINT_FRAME)
|
||||
{
|
||||
uint64_t size;
|
||||
if (intfstream_read(handle->file, &(size), sizeof(uint64_t)) != sizeof(uint64_t))
|
||||
{
|
||||
RARCH_ERR("[Replay] Replay ran out of frames\n");
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
retro_ctx_serialize_info_t serial_info;
|
||||
uint8_t *st;
|
||||
size = swap_if_big64(size);
|
||||
st = (uint8_t*)malloc(size);
|
||||
if(intfstream_read(handle->file, st, size) != size)
|
||||
{
|
||||
RARCH_ERR("[Replay] Replay checkpoint truncated\n");
|
||||
input_st->bsv_movie_state.flags |= BSV_FLAG_MOVIE_END;
|
||||
free(st);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
serial_info.data_const = st;
|
||||
serial_info.size = size;
|
||||
core_unserialize(&serial_info);
|
||||
free(st);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,6 +163,10 @@ struct bsv_movie
|
||||
};
|
||||
|
||||
typedef struct bsv_movie bsv_movie_t;
|
||||
|
||||
#define REPLAY_TOKEN_INVALID '\0'
|
||||
#define REPLAY_TOKEN_REGULAR_FRAME 'f'
|
||||
#define REPLAY_TOKEN_CHECKPOINT_FRAME 'c'
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
@ -337,6 +337,10 @@ MSG_HASH(
|
||||
MENU_ENUM_LABEL_AUTOSAVE_INTERVAL,
|
||||
"autosave_interval"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_REPLAY_CHECKPOINT_INTERVAL,
|
||||
"replay_checkpoint_interval"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_AUTO_OVERRIDES_ENABLE,
|
||||
"auto_overrides_enable"
|
||||
@ -3269,6 +3273,10 @@ MSG_HASH(
|
||||
MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP,
|
||||
"savestate_max_keep"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_REPLAY_MAX_KEEP,
|
||||
"replay_max_keep"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_SAVESTATE_DIRECTORY,
|
||||
"savestate_directory"
|
||||
|
@ -265,6 +265,9 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len)
|
||||
case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL:
|
||||
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_AUTOSAVE_INTERVAL), len);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_REPLAY_CHECKPOINT_INTERVAL:
|
||||
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_REPLAY_CHECKPOINT_INTERVAL), len);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_VALUE_INPUT_ADC_TYPE:
|
||||
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_INPUT_ADC_TYPE), len);
|
||||
break;
|
||||
|
@ -4170,6 +4170,18 @@ MSG_HASH(
|
||||
MENU_ENUM_LABEL_HELP_AUTOSAVE_INTERVAL,
|
||||
"Autosaves the non-volatile SRAM at a regular interval. This is disabled by default unless set otherwise. The interval is measured in seconds. A value of 0 disables autosave."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_REPLAY_CHECKPOINT_INTERVAL,
|
||||
"Replay Checkpoint Interval"
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_SUBLABEL_REPLAY_CHECKPOINT_INTERVAL,
|
||||
"Automatically bookmark the game state during replay recording at a regular interval (in seconds)."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_HELP_REPLAY_CHECKPOINT_INTERVAL,
|
||||
"Autosaves the game state during replay recording at a regular interval. This is disabled by default unless set otherwise. The interval is measured in seconds. A value of 0 disables checkpoint recording."
|
||||
)
|
||||
MSG_HASH(
|
||||
MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_INDEX,
|
||||
"Increment Save State Index Automatically"
|
||||
|
@ -731,6 +731,8 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_save_file_compression, MENU_
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_savestate_file_compression, MENU_ENUM_SUBLABEL_SAVESTATE_FILE_COMPRESSION)
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_savestate_max_keep, MENU_ENUM_SUBLABEL_SAVESTATE_MAX_KEEP)
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_autosave_interval, MENU_ENUM_SUBLABEL_AUTOSAVE_INTERVAL)
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_replay_max_keep, MENU_ENUM_SUBLABEL_REPLAY_MAX_KEEP)
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_replay_checkpoint_interval, MENU_ENUM_SUBLABEL_REPLAY_CHECKPOINT_INTERVAL)
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_remap_binds_enable, MENU_ENUM_SUBLABEL_INPUT_REMAP_BINDS_ENABLE)
|
||||
DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_autodetect_enable, MENU_ENUM_SUBLABEL_INPUT_AUTODETECT_ENABLE)
|
||||
#if defined(HAVE_DINPUT) || defined(HAVE_WINRAWINPUT)
|
||||
@ -3699,9 +3701,15 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
|
||||
case MENU_ENUM_LABEL_AUTOSAVE_INTERVAL:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_autosave_interval);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_REPLAY_CHECKPOINT_INTERVAL:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_replay_checkpoint_interval);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_max_keep);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_REPLAY_MAX_KEEP:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_replay_max_keep);
|
||||
break;
|
||||
case MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE:
|
||||
BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_thumbnail_enable);
|
||||
break;
|
||||
|
@ -9736,6 +9736,7 @@ unsigned menu_displaylist_build_list(
|
||||
case DISPLAYLIST_SAVING_SETTINGS_LIST:
|
||||
{
|
||||
bool savestate_auto_index = settings->bools.savestate_auto_index;
|
||||
bool replay_auto_index = settings->bools.replay_auto_index;
|
||||
|
||||
menu_displaylist_build_info_selective_t build_list[] = {
|
||||
{MENU_ENUM_LABEL_SORT_SAVEFILES_ENABLE, PARSE_ONLY_BOOL, true},
|
||||
@ -9751,6 +9752,7 @@ unsigned menu_displaylist_build_list(
|
||||
{MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE, PARSE_ONLY_BOOL, true},
|
||||
{MENU_ENUM_LABEL_REPLAY_AUTO_INDEX, PARSE_ONLY_BOOL, true},
|
||||
{MENU_ENUM_LABEL_REPLAY_MAX_KEEP, PARSE_ONLY_UINT, false},
|
||||
{MENU_ENUM_LABEL_REPLAY_CHECKPOINT_INTERVAL, PARSE_ONLY_UINT, true},
|
||||
{MENU_ENUM_LABEL_SAVE_FILE_COMPRESSION, PARSE_ONLY_BOOL, true},
|
||||
{MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION, PARSE_ONLY_BOOL, true},
|
||||
{MENU_ENUM_LABEL_SORT_SCREENSHOTS_BY_CONTENT_ENABLE, PARSE_ONLY_BOOL, true},
|
||||
@ -9769,6 +9771,9 @@ unsigned menu_displaylist_build_list(
|
||||
case MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP:
|
||||
build_list[i].checked = savestate_auto_index;
|
||||
break;
|
||||
case MENU_ENUM_LABEL_REPLAY_MAX_KEEP:
|
||||
build_list[i].checked = replay_auto_index;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -6700,6 +6700,22 @@ static void setting_get_string_representation_uint_autosave_interval(
|
||||
}
|
||||
#endif
|
||||
|
||||
static void setting_get_string_representation_uint_replay_checkpoint_interval(
|
||||
rarch_setting_t *setting,
|
||||
char *s, size_t len)
|
||||
{
|
||||
if (!setting)
|
||||
return;
|
||||
|
||||
if (*setting->value.target.unsigned_integer)
|
||||
{
|
||||
snprintf(s, len, "%u ", *setting->value.target.unsigned_integer);
|
||||
strlcat(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SECONDS), len);
|
||||
}
|
||||
else
|
||||
strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_OFF), len);
|
||||
}
|
||||
|
||||
#if defined(HAVE_NETWORKING)
|
||||
static void setting_get_string_representation_netplay_mitm_server(
|
||||
rarch_setting_t *setting,
|
||||
@ -11160,6 +11176,22 @@ static bool setting_append_list(
|
||||
general_read_handler);
|
||||
(*list)[list_info->index - 1].action_ok = &setting_action_ok_uint;
|
||||
menu_settings_list_current_add_range(list, list_info, 0, 999, 1, true, true);
|
||||
|
||||
CONFIG_UINT(
|
||||
list, list_info,
|
||||
&settings->uints.replay_checkpoint_interval,
|
||||
MENU_ENUM_LABEL_REPLAY_CHECKPOINT_INTERVAL,
|
||||
MENU_ENUM_LABEL_VALUE_REPLAY_CHECKPOINT_INTERVAL,
|
||||
DEFAULT_REPLAY_CHECKPOINT_INTERVAL,
|
||||
&group_info,
|
||||
&subgroup_info,
|
||||
parent_group,
|
||||
general_write_handler,
|
||||
general_read_handler);
|
||||
(*list)[list_info->index - 1].action_ok = &setting_action_ok_uint;
|
||||
(*list)[list_info->index - 1].get_string_representation =
|
||||
&setting_get_string_representation_uint_replay_checkpoint_interval;
|
||||
menu_settings_list_current_add_range(list, list_info, 0, 3600, 60, true, false);
|
||||
#endif
|
||||
|
||||
CONFIG_BOOL(
|
||||
|
@ -2358,6 +2358,7 @@ enum msg_hash_enums
|
||||
MENU_LABEL(FRONTEND_LOG_LEVEL),
|
||||
MENU_LBL_H(LIBRETRO_LOG_LEVEL),
|
||||
MENU_LBL_H(AUTOSAVE_INTERVAL),
|
||||
MENU_LBL_H(REPLAY_CHECKPOINT_INTERVAL),
|
||||
MENU_LBL_H(CONFIG_SAVE_ON_EXIT),
|
||||
MENU_LABEL(REMAP_SAVE_ON_EXIT),
|
||||
MENU_LABEL(CONFIGURATION_LIST),
|
||||
|
Loading…
x
Reference in New Issue
Block a user