From 1c5099051c94a57ba15e6422219a1dd9df6a3c5a Mon Sep 17 00:00:00 2001 From: David Guillen Fandos Date: Fri, 20 Nov 2020 18:32:10 +0100 Subject: [PATCH] Adding savestate garbage collector for autoincrement stavestates As some issues indicate (#6195 #10471 #11008 #6861) there's an issue with the autoincrement save slot feature: slot index will increase and very old saves won't be deleted. This commit adds support to delete old save states with a user defined save state limit (global). Instead of wrapping around the slot counter it will simply delete the oldest save, since it is simpler. For now there's a limit of one deletion per save, which ensures a user cannot delete many saves by accident if they set the limit too low. --- config.def.h | 9 +++ configuration.c | 1 + configuration.h | 1 + intl/msg_hash_lbl.h | 4 ++ intl/msg_hash_us.h | 8 +++ menu/cbs/menu_cbs_sublabel.c | 4 ++ menu/menu_displaylist.c | 59 ++++++++++++------- menu/menu_setting.c | 97 ++++++++++++++++++------------ msg_hash.h | 1 + retroarch.c | 111 +++++++++++++++++++++++++++++++++-- 10 files changed, 233 insertions(+), 62 deletions(-) diff --git a/config.def.h b/config.def.h index 7cfdd1b7c5..2f940765b0 100644 --- a/config.def.h +++ b/config.def.h @@ -1020,6 +1020,15 @@ static const unsigned netplay_share_analog = RARCH_NETPLAY_SHARE_ANALOG_NO_PREFE * to the highest existing value. */ static const bool savestate_auto_index = false; +/* Specifies the maximum number of savestates to keep + * when savestate auto index is enabled + * > When limit is exceeded, savestate with the lowest + * index will be deleted automatically when creating + * a new savestate + * > Setting value to zero disables the limit (no + * savestates will be deleted in this case) */ +#define DEFAULT_SAVESTATE_MAX_KEEP 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 diff --git a/configuration.c b/configuration.c index ef5087cbdb..ad442c02fe 100644 --- a/configuration.c +++ b/configuration.c @@ -1878,6 +1878,7 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("rewind_granularity", &settings->uints.rewind_granularity, true, DEFAULT_REWIND_GRANULARITY, false); 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("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); SETTING_UINT("keyboard_gamepad_mapping_type",&settings->uints.input_keyboard_gamepad_mapping_type, true, 1, false); diff --git a/configuration.h b/configuration.h index 0119153fcc..f3a2d1db0a 100644 --- a/configuration.h +++ b/configuration.h @@ -170,6 +170,7 @@ typedef struct settings unsigned rewind_granularity; unsigned rewind_buffer_size_step; unsigned autosave_interval; + unsigned savestate_max_keep; unsigned network_cmd_port; unsigned network_remote_base_port; unsigned keymapper_port; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 3faa8004f2..4eea948c77 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -2586,6 +2586,10 @@ MSG_HASH( MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE, "savestate_auto_save" ) +MSG_HASH( + MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP, + "savestate_max_keep" + ) MSG_HASH( MENU_ENUM_LABEL_SAVESTATE_DIRECTORY, "savestate_directory" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 8e7b9e6e67..314567ec40 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -2962,6 +2962,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_SAVESTATE_AUTO_INDEX, "Before making a save state, the save state index is automatically increased. When loading content, the index will be set to the highest existing index." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_SAVESTATE_MAX_KEEP, + "Maximum Auto-Increment Save States to Keep" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_SAVESTATE_MAX_KEEP, + "Limits the number of save states that will be created when 'Increment Save State Index Automatically' is enabled. If limit is exceeded when saving a new state, the existing state with the lowest index will be deleted. A value of '0' means unlimited states will be recorded." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_SAVE, "Auto Save State" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 14b3057a7d..7177b306b5 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -491,6 +491,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_savestate_auto_load, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_savestate_thumbnail_enable, MENU_ENUM_SUBLABEL_SAVESTATE_THUMBNAIL_ENABLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_save_file_compression, MENU_ENUM_SUBLABEL_SAVE_FILE_COMPRESSION) 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_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) @@ -2876,6 +2877,9 @@ 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_SAVESTATE_MAX_KEEP: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_max_keep); + break; case MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_savestate_thumbnail_enable); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 9e42415025..c8716a291f 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8052,31 +8052,48 @@ unsigned menu_displaylist_build_list( break; case DISPLAYLIST_SAVING_SETTINGS_LIST: { - menu_displaylist_build_info_t build_list[] = { - {MENU_ENUM_LABEL_SORT_SAVEFILES_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SORT_SAVESTATES_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SORT_SAVEFILES_BY_CONTENT_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SORT_SAVESTATES_BY_CONTENT_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_BLOCK_SRAM_OVERWRITE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_AUTOSAVE_INTERVAL, PARSE_ONLY_UINT}, - {MENU_ENUM_LABEL_SAVESTATE_AUTO_INDEX, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVE_FILE_COMPRESSION, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVESTATE_FILE_COMPRESSION, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SORT_SCREENSHOTS_BY_CONTENT_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVEFILES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SAVESTATES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SYSTEMFILES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_SCREENSHOTS_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG, PARSE_ONLY_BOOL}, - {MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG_AGGREGATE, PARSE_ONLY_BOOL}, + settings_t *settings = config_get_ptr(); + bool savestate_auto_index = settings->bools.savestate_auto_index; + + menu_displaylist_build_info_selective_t build_list[] = { + {MENU_ENUM_LABEL_SORT_SAVEFILES_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SORT_SAVESTATES_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SORT_SAVEFILES_BY_CONTENT_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SORT_SAVESTATES_BY_CONTENT_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_BLOCK_SRAM_OVERWRITE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_AUTOSAVE_INTERVAL, PARSE_ONLY_UINT, true}, + {MENU_ENUM_LABEL_SAVESTATE_AUTO_INDEX, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP, PARSE_ONLY_UINT, false}, + {MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE, PARSE_ONLY_BOOL, 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}, + {MENU_ENUM_LABEL_SAVEFILES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SAVESTATES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SYSTEMFILES_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_SCREENSHOTS_IN_CONTENT_DIR_ENABLE, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG, PARSE_ONLY_BOOL, true}, + {MENU_ENUM_LABEL_CONTENT_RUNTIME_LOG_AGGREGATE, PARSE_ONLY_BOOL, true}, }; for (i = 0; i < ARRAY_SIZE(build_list); i++) { - if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + switch (build_list[i].enum_idx) + { + case MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP: + build_list[i].checked = savestate_auto_index; + break; + default: + break; + } + } + + for (i = 0; i < ARRAY_SIZE(build_list); i++) + { + if (build_list[i].checked && + MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, build_list[i].enum_idx, build_list[i].parse_type, false) == 0) count++; diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 1bef0cd76a..1233bb34de 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -9317,7 +9317,7 @@ static bool setting_append_list( case SETTINGS_LIST_SAVING: { uint8_t i; - struct bool_entry bool_entries[14]; + struct bool_entry bool_entries[13]; START_GROUP(list, list_info, &group_info, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_SAVING_SETTINGS), parent_group); parent_group = msg_hash_to_str(MENU_ENUM_LABEL_SAVING_SETTINGS); @@ -9361,54 +9361,48 @@ static bool setting_append_list( bool_entries[5].default_value = DEFAULT_BLOCK_SRAM_OVERWRITE; bool_entries[5].flags = SD_FLAG_NONE; - bool_entries[6].target = &settings->bools.savestate_auto_index; - bool_entries[6].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_AUTO_INDEX; - bool_entries[6].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_INDEX; - bool_entries[6].default_value = savestate_auto_index; + bool_entries[6].target = &settings->bools.savestate_auto_save; + bool_entries[6].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE; + bool_entries[6].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_SAVE; + bool_entries[6].default_value = savestate_auto_save; bool_entries[6].flags = SD_FLAG_NONE; - bool_entries[7].target = &settings->bools.savestate_auto_save; - bool_entries[7].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_AUTO_SAVE; - bool_entries[7].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_SAVE; - bool_entries[7].default_value = savestate_auto_save; + bool_entries[7].target = &settings->bools.savestate_auto_load; + bool_entries[7].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD; + bool_entries[7].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_LOAD; + bool_entries[7].default_value = savestate_auto_load; bool_entries[7].flags = SD_FLAG_NONE; - bool_entries[8].target = &settings->bools.savestate_auto_load; - bool_entries[8].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_AUTO_LOAD; - bool_entries[8].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_LOAD; - bool_entries[8].default_value = savestate_auto_load; - bool_entries[8].flags = SD_FLAG_NONE; + bool_entries[8].target = &settings->bools.savestate_thumbnail_enable; + bool_entries[8].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE; + bool_entries[8].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_THUMBNAIL_ENABLE; + bool_entries[8].default_value = savestate_thumbnail_enable; + bool_entries[8].flags = SD_FLAG_ADVANCED; - bool_entries[9].target = &settings->bools.savestate_thumbnail_enable; - bool_entries[9].name_enum_idx = MENU_ENUM_LABEL_SAVESTATE_THUMBNAIL_ENABLE; - bool_entries[9].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATE_THUMBNAIL_ENABLE; - bool_entries[9].default_value = savestate_thumbnail_enable; + bool_entries[9].target = &settings->bools.savefiles_in_content_dir; + bool_entries[9].name_enum_idx = MENU_ENUM_LABEL_SAVEFILES_IN_CONTENT_DIR_ENABLE; + bool_entries[9].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVEFILES_IN_CONTENT_DIR_ENABLE; + bool_entries[9].default_value = default_savefiles_in_content_dir; bool_entries[9].flags = SD_FLAG_ADVANCED; - bool_entries[10].target = &settings->bools.savefiles_in_content_dir; - bool_entries[10].name_enum_idx = MENU_ENUM_LABEL_SAVEFILES_IN_CONTENT_DIR_ENABLE; - bool_entries[10].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVEFILES_IN_CONTENT_DIR_ENABLE; - bool_entries[10].default_value = default_savefiles_in_content_dir; + bool_entries[10].target = &settings->bools.savestates_in_content_dir; + bool_entries[10].name_enum_idx = MENU_ENUM_LABEL_SAVESTATES_IN_CONTENT_DIR_ENABLE; + bool_entries[10].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATES_IN_CONTENT_DIR_ENABLE; + bool_entries[10].default_value = default_savestates_in_content_dir; bool_entries[10].flags = SD_FLAG_ADVANCED; - bool_entries[11].target = &settings->bools.savestates_in_content_dir; - bool_entries[11].name_enum_idx = MENU_ENUM_LABEL_SAVESTATES_IN_CONTENT_DIR_ENABLE; - bool_entries[11].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SAVESTATES_IN_CONTENT_DIR_ENABLE; - bool_entries[11].default_value = default_savestates_in_content_dir; + bool_entries[11].target = &settings->bools.systemfiles_in_content_dir; + bool_entries[11].name_enum_idx = MENU_ENUM_LABEL_SYSTEMFILES_IN_CONTENT_DIR_ENABLE; + bool_entries[11].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SYSTEMFILES_IN_CONTENT_DIR_ENABLE; + bool_entries[11].default_value = default_systemfiles_in_content_dir; bool_entries[11].flags = SD_FLAG_ADVANCED; - bool_entries[12].target = &settings->bools.systemfiles_in_content_dir; - bool_entries[12].name_enum_idx = MENU_ENUM_LABEL_SYSTEMFILES_IN_CONTENT_DIR_ENABLE; - bool_entries[12].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SYSTEMFILES_IN_CONTENT_DIR_ENABLE; - bool_entries[12].default_value = default_systemfiles_in_content_dir; + bool_entries[12].target = &settings->bools.screenshots_in_content_dir; + bool_entries[12].name_enum_idx = MENU_ENUM_LABEL_SCREENSHOTS_IN_CONTENT_DIR_ENABLE; + bool_entries[12].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SCREENSHOTS_IN_CONTENT_DIR_ENABLE; + bool_entries[12].default_value = default_screenshots_in_content_dir; bool_entries[12].flags = SD_FLAG_ADVANCED; - bool_entries[13].target = &settings->bools.screenshots_in_content_dir; - bool_entries[13].name_enum_idx = MENU_ENUM_LABEL_SCREENSHOTS_IN_CONTENT_DIR_ENABLE; - bool_entries[13].SHORT_enum_idx = MENU_ENUM_LABEL_VALUE_SCREENSHOTS_IN_CONTENT_DIR_ENABLE; - bool_entries[13].default_value = default_screenshots_in_content_dir; - bool_entries[13].flags = SD_FLAG_ADVANCED; - for (i = 0; i < ARRAY_SIZE(bool_entries); i++) { CONFIG_BOOL( @@ -9446,6 +9440,37 @@ static bool setting_append_list( (*list)[list_info->index - 1].get_string_representation = &setting_get_string_representation_uint_autosave_interval; #endif + CONFIG_BOOL( + list, list_info, + &settings->bools.savestate_auto_index, + MENU_ENUM_LABEL_SAVESTATE_AUTO_INDEX, + MENU_ENUM_LABEL_VALUE_SAVESTATE_AUTO_INDEX, + savestate_auto_index, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + (*list)[list_info->index - 1].action_ok = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_left = &setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_right = &setting_bool_action_right_with_refresh; + + CONFIG_UINT( + list, list_info, + &settings->uints.savestate_max_keep, + MENU_ENUM_LABEL_SAVESTATE_MAX_KEEP, + MENU_ENUM_LABEL_VALUE_SAVESTATE_MAX_KEEP, + DEFAULT_SAVESTATE_MAX_KEEP, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + 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_BOOL( list, list_info, diff --git a/msg_hash.h b/msg_hash.h index 925ceaec26..7c9264255b 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1736,6 +1736,7 @@ enum msg_hash_enums MENU_LABEL(CHEAT_DELETE_MATCH), MENU_LABEL(SCREEN_RESOLUTION), MENU_LABEL(SAVESTATE_AUTO_INDEX), + MENU_LABEL(SAVESTATE_MAX_KEEP), MENU_LABEL(SAVESTATE_AUTO_SAVE), MENU_LABEL(SAVESTATE_AUTO_LOAD), MENU_LABEL(SAVESTATE_THUMBNAIL_ENABLE), diff --git a/retroarch.c b/retroarch.c index 25f3429168..4f6479190c 100644 --- a/retroarch.c +++ b/retroarch.c @@ -11982,7 +11982,7 @@ static void command_event_load_auto_state( static void command_event_set_savestate_auto_index( settings_t *settings, - global_t *global, + const global_t *global, struct rarch_state *p_rarch) { size_t i; @@ -12047,6 +12047,96 @@ static void command_event_set_savestate_auto_index( max_idx); } +static void command_event_set_savestate_garbage_collect( + settings_t *settings, + const global_t *global, + struct rarch_state *p_rarch) +{ + size_t i, cnt = 0; + char state_dir[PATH_MAX_LENGTH]; + char state_base[PATH_MAX_LENGTH]; + + struct string_list *dir_list = NULL; + unsigned min_idx = UINT_MAX; + const char *oldest_save = NULL; + unsigned max_to_keep = settings->uints.savestate_max_keep; + bool show_hidden_files = settings->bools.show_hidden_files; + + if (!global || (max_to_keep == 0)) + return; + + state_dir[0] = '\0'; + state_base[0] = '\0'; + + /* Similar to command_event_set_savestate_auto_index(), + * this will find the lowest numbered save-state */ + fill_pathname_basedir(state_dir, global->name.savestate, + sizeof(state_dir)); + + dir_list = dir_list_new_special(state_dir, DIR_LIST_PLAIN, NULL, + show_hidden_files); + + if (!dir_list) + return; + + fill_pathname_base(state_base, global->name.savestate, + sizeof(state_base)); + + for (i = 0; i < dir_list->size; i++) + { + unsigned idx; + char elem_base[128]; + const char *end = NULL; + const char *dir_elem = dir_list->elems[i].data; + const char *ext = NULL; + + elem_base[0] = '\0'; + + if (string_is_empty(dir_elem)) + continue; + + fill_pathname_base(elem_base, dir_elem, sizeof(elem_base)); + + /* Only consider files with a '.state' extension + * > i.e. Ignore '.state.auto', '.state.bak', etc. */ + ext = path_get_extension(elem_base); + if (string_is_empty(ext) || + !string_starts_with_size(ext, "state", STRLEN_CONST("state"))) + continue; + + /* Check whether this file is associated with + * the current content */ + if (!string_starts_with(elem_base, state_base)) + continue; + + /* This looks like a valid save */ + cnt++; + + /* > Get index */ + end = dir_elem + strlen(dir_elem); + while ((end > dir_elem) && ISDIGIT((int)end[-1])) + end--; + + idx = string_to_unsigned(end); + + /* > Check if this is the lowest index so far */ + if (idx < min_idx) + { + min_idx = idx; + oldest_save = dir_elem; + } + } + + /* Only delete one save state per save action + * > Conservative behaviour, designed to minimise + * the risk of deleting multiple incorrect files + * in case of accident */ + if (!string_is_empty(oldest_save) && (cnt > max_to_keep)) + filestream_delete(oldest_save); + + dir_list_free(dir_list); +} + static bool event_init_content( settings_t *settings, struct rarch_state *p_rarch) @@ -12655,15 +12745,26 @@ static bool command_event_main_state( switch (cmd) { case CMD_EVENT_SAVE_STATE: - content_save_state(state_path, true, false); { + bool savestate_auto_index = + settings->bools.savestate_auto_index; + unsigned savestate_max_keep = + settings->uints.savestate_max_keep; bool frame_time_counter_reset_after_save_state = - settings->bools.frame_time_counter_reset_after_save_state; + settings->bools.frame_time_counter_reset_after_save_state; + + content_save_state(state_path, true, false); + + /* Clean up excess savestates if necessary */ + if (savestate_auto_index && (savestate_max_keep > 0)) + command_event_set_savestate_garbage_collect(settings, global, p_rarch); + if (frame_time_counter_reset_after_save_state) p_rarch->video_driver_frame_time_count = 0; + + ret = true; + push_msg = false; } - ret = true; - push_msg = false; break; case CMD_EVENT_LOAD_STATE: if (content_load_state(state_path, false, false))