Merge pull request #1 from rz5/master

rz5 implementation of undo
This commit is contained in:
Andrés 2016-06-08 22:27:21 -05:00
commit 035f6ca210
7 changed files with 309 additions and 83 deletions

106
command.c
View File

@ -1578,25 +1578,6 @@ static void command_event_save_state(const char *path,
char *s, size_t len)
{
settings_t *settings = config_get_ptr();
char buf[PATH_MAX_LENGTH] = {0};
/* if a save state already exists rename it to .last before saving
* so it can be recovered */
if (path_file_exists(path))
{
strlcpy(buf, path, sizeof(buf));
snprintf(buf, sizeof(buf), "%s", path);
path_remove_extension(buf);
snprintf(buf, sizeof(buf), "%s.last", buf);
if (!content_rename_state(path, buf))
{
snprintf(s, len, "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_SAVE_UNDO),
path);
return;
}
}
if (!content_save_state(path))
{
@ -1614,6 +1595,22 @@ static void command_event_save_state(const char *path,
settings->state_slot);
}
static void command_event_undo_save_state(char *s, size_t len)
{
if (!content_undo_save_state())
{
snprintf(s, len, "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE),
"RAM");
return;
}
/* TODO/FIXME - use msg_hash_to_str here and there */
snprintf(s, len, "%s",
"Restored save state.");
}
/**
* event_load_state
* @path : Path to state.
@ -1622,29 +1619,9 @@ static void command_event_save_state(const char *path,
*
* Loads a state with path being @path.
**/
static void command_event_load_state(const char *path, char *s, size_t len, bool undo)
static void command_event_load_state(const char *path, char *s, size_t len)
{
settings_t *settings = config_get_ptr();
char buf[PATH_MAX_LENGTH] = {0};
/* save a state before loading (unless it's an undo operation already)
* so the state can be recovered
*/
if (!undo)
{
strlcpy(buf, path, sizeof(buf));
snprintf(buf, sizeof(buf), "%s", path);
path_remove_extension(buf);
snprintf(buf, sizeof(buf), "%s.undo", buf);
if (!content_save_state(buf))
{
snprintf(s, len, "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_SAVE_UNDO),
path);
return;
}
}
if (!content_load_state(path))
{
@ -1657,18 +1634,29 @@ static void command_event_load_state(const char *path, char *s, size_t len, bool
if (settings->state_slot < 0)
snprintf(s, len, "%s #-1 (auto).",
msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT));
else if (!undo)
else
snprintf(s, len, "%s #%d.", msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT),
settings->state_slot);
else
snprintf(s, len, "%s #-1 (undo).", msg_hash_to_str(MSG_LOADED_STATE_FROM_SLOT));
}
static void command_event_undo_load_state(char *s, size_t len)
{
if (!content_undo_load_state())
{
snprintf(s, len, "%s \"%s\".",
msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE),
"RAM");
return;
}
/* TODO/FIXME - use msg_hash_to_str here and there */
snprintf(s, len, "%s",
"Undid load state.");
}
static void command_event_main_state(unsigned cmd)
{
retro_ctx_size_info_t info;
char path[PATH_MAX_LENGTH] = {0};
char buf[PATH_MAX_LENGTH] = {0};
char msg[128] = {0};
global_t *global = global_get_ptr();
settings_t *settings = config_get_ptr();
@ -1692,33 +1680,13 @@ static void command_event_main_state(unsigned cmd)
command_event_save_state(path, msg, sizeof(msg));
break;
case CMD_EVENT_LOAD_STATE:
command_event_load_state(path, msg, sizeof(msg), false);
command_event_load_state(path, msg, sizeof(msg));
break;
case CMD_EVENT_UNDO_LOAD_STATE:
strlcpy(buf, path, sizeof(buf));
path_remove_extension(buf);
snprintf(buf, sizeof(buf), "%s.undo", buf);
if (path_file_exists(buf))
command_event_load_state(buf, msg, sizeof(msg), true);
else
{
snprintf(msg, sizeof(msg), "%s.",
msg_hash_to_str(MSG_FAILED_TO_LOAD_UNDO));
}
command_event_undo_load_state(msg, sizeof(msg));
break;
case CMD_EVENT_UNDO_SAVE_STATE:
strlcpy(buf, path, sizeof(buf));
path_remove_extension(buf);
snprintf(buf, sizeof(buf), "%s.last", buf);
if (path_file_exists(buf))
command_event_load_state(buf, msg, sizeof(msg), true);
else
{
snprintf(msg, sizeof(msg), "%s.",
msg_hash_to_str(MSG_FAILED_TO_LOAD_UNDO));
}
command_event_undo_save_state(msg, sizeof(msg));
break;
}
}
@ -1821,6 +1789,7 @@ bool command_event(enum event_command cmd, void *data)
#ifndef HAVE_DYNAMIC
command_event(CMD_EVENT_QUIT, NULL);
#endif
content_reset_savestate_backups();
break;
case CMD_EVENT_LOAD_STATE:
/* Immutable - disallow savestate load when
@ -2120,9 +2089,12 @@ bool command_event(enum event_command cmd, void *data)
if (hwr)
memset(hwr, 0, sizeof(*hwr));
content_reset_savestate_backups();
break;
}
case CMD_EVENT_CORE_INIT:
content_reset_savestate_backups();
if (!command_event_init_core((enum rarch_core_type*)data))
return false;
break;

View File

@ -49,6 +49,7 @@ enum event_command
CMD_EVENT_UNLOAD_CORE,
CMD_EVENT_LOAD_STATE,
CMD_EVENT_UNDO_LOAD_STATE,
/* Rewrites a savestate on disk */
CMD_EVENT_UNDO_SAVE_STATE,
CMD_EVENT_SAVE_STATE,
CMD_EVENT_SAVE_STATE_DECREMENT,

View File

@ -46,16 +46,21 @@ bool content_load_ram_file(unsigned slot);
bool content_save_ram_file(unsigned slot);
/* Load a state from disk to memory. */
bool content_load_state(const char *path);
bool content_load_state(const char* path);
bool content_load_state_with_backup(const char* path, bool load_to_backup_buffer);
/* Save a state from memory to disk. */
bool content_save_state(const char *path);
bool content_save_state_with_backup(const char *path, bool save_to_disk);
/* Copy a save state. */
bool content_rename_state(const char *origin, const char *dest);
/* Load a state backup from disk to memory. */
bool content_undo_load_state(const char *path);
/* Undoes the last load state operation that was done */
bool content_undo_load_state();
/* Restores the last savestate file which was overwritten */
bool content_undo_save_state();
bool content_does_not_need_content(void);
@ -73,6 +78,9 @@ void content_deinit(void);
* selected libretro core. */
bool content_init(void);
/* Resets the state and savefile backup buffers */
bool content_reset_savestate_backups();
RETRO_END_DECLS
#endif

BIN
internal buffer Normal file

Binary file not shown.

View File

@ -112,13 +112,13 @@ const char *msg_hash_to_str_us(uint32_t hash)
case MSG_RESET:
return "Reset";
case MSG_FAILED_TO_LOAD_STATE:
return "Failed to load state from";
return "Nothing to undo.";
case MSG_FAILED_TO_SAVE_STATE_TO:
return "Failed to save state to";
case MSG_FAILED_TO_LOAD_UNDO:
return "No undo state found";
case MSG_FAILED_TO_SAVE_UNDO:
return "Failed to save undo information";
case MSG_FAILED_TO_UNDO_LOAD_STATE:
return "Failed to undo load state.";
case MSG_FAILED_TO_UNDO_SAVE_STATE:
return "Failed to undo save state.";
case MSG_FAILED_TO_SAVE_SRAM:
return "Failed to save SRAM";
case MSG_STATE_SIZE:

View File

@ -125,8 +125,8 @@
#define MSG_CORE_DOES_NOT_SUPPORT_SAVESTATES 0xd50adf46U
#define MSG_FAILED_TO_LOAD_STATE 0x91f348ebU
#define MSG_FAILED_TO_LOAD_UNDO 0xb6e2fc55U
#define MSG_FAILED_TO_SAVE_UNDO 0xf2e29478U
#define MSG_FAILED_TO_UNDO_LOAD_STATE 0xb6e2fc55U
#define MSG_FAILED_TO_UNDO_SAVE_STATE 0xf2e29478U
#define MSG_RESET 0x10474288U

View File

@ -35,6 +35,26 @@
#include "../verbosity.h"
#include "tasks_internal.h"
struct save_state_buf
{
void* data;
char path[PATH_MAX_LENGTH];
size_t size;
};
/*
Holds a savestate which was stored on disk and was lost when
content_save_state() wrote over it.
Can be restored to disk with undo_save_state().
*/
static struct save_state_buf old_save_file;
/*
Represents the state which was lost when load_state() was called.
Can be restored with undo_load_state().
*/
static struct save_state_buf old_state_buf;
struct sram_block
{
unsigned type;
@ -42,17 +62,163 @@ struct sram_block
size_t size;
};
bool content_undo_load_state()
{
if (old_state_buf.data == NULL || old_state_buf.size == 0)
return false;
unsigned i;
//ssize_t size;
retro_ctx_serialize_info_t serial_info;
unsigned num_blocks = 0;
//void *buf = NULL;
struct sram_block *blocks = NULL;
settings_t *settings = config_get_ptr();
global_t *global = global_get_ptr();
//bool ret = filestream_read_file(path, &buf, &size);
RARCH_LOG("%s: \"%s\".\n",
msg_hash_to_str(MSG_LOADING_STATE),
"RAM");
RARCH_LOG("%s: %u %s.\n",
msg_hash_to_str(MSG_STATE_SIZE),
old_state_buf.size,
msg_hash_to_str(MSG_BYTES));
/* TODO/FIXME - This checking of SRAM overwrite, the backing up of it and
its flushing could all be in their own functions... */
if (settings->block_sram_overwrite && global->savefiles
&& global->savefiles->size)
{
RARCH_LOG("%s.\n",
msg_hash_to_str(MSG_BLOCKING_SRAM_OVERWRITE));
blocks = (struct sram_block*)
calloc(global->savefiles->size, sizeof(*blocks));
if (blocks)
{
num_blocks = global->savefiles->size;
for (i = 0; i < num_blocks; i++)
blocks[i].type = global->savefiles->elems[i].attr.i;
}
}
for (i = 0; i < num_blocks; i++)
{
retro_ctx_memory_info_t mem_info;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
blocks[i].size = mem_info.size;
}
for (i = 0; i < num_blocks; i++)
if (blocks[i].size)
blocks[i].data = malloc(blocks[i].size);
/* Backup current SRAM which is overwritten by unserialize. */
for (i = 0; i < num_blocks; i++)
{
if (blocks[i].data)
{
retro_ctx_memory_info_t mem_info;
const void *ptr = NULL;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
ptr = mem_info.data;
if (ptr)
memcpy(blocks[i].data, ptr, blocks[i].size);
}
}
/* We need to make a temporary copy of the buffer, to allow the swap below */
void* temp_data = malloc(old_state_buf.size);
size_t temp_data_size = old_state_buf.size;
memcpy(temp_data, old_state_buf.data, old_state_buf.size);
serial_info.data_const = temp_data;
serial_info.size = temp_data_size;
/* Swap the current state with the backup state. This way, we can undo
what we're undoing */
content_save_state_with_backup("RAM", false);
bool ret = core_unserialize(&serial_info);
/* Clean up the temporary copy */
free(temp_data);
temp_data = NULL;
temp_data_size = 0;
/* Flush back. */
for (i = 0; i < num_blocks; i++)
{
if (blocks[i].data)
{
retro_ctx_memory_info_t mem_info;
void *ptr = NULL;
mem_info.id = blocks[i].type;
core_get_memory(&mem_info);
ptr = mem_info.data;
if (ptr)
memcpy(ptr, blocks[i].data, blocks[i].size);
}
}
for (i = 0; i < num_blocks; i++)
free(blocks[i].data);
free(blocks);
if (!ret)
RARCH_ERR("%s \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_UNDO_LOAD_STATE),
"RAM");
return ret;
}
bool content_undo_save_state()
{
bool ret = filestream_write_file(old_save_file.path, old_save_file.data, old_save_file.size);
/* Wipe the save file buffer as it's intended to be one use only */
old_save_file.path[0] = '\0';
if (old_save_file.data) {
free(old_save_file.data);
old_save_file.data = NULL;
}
old_save_file.data = 0;
if (!ret)
RARCH_ERR("%s \"%s\".\n",
msg_hash_to_str(MSG_FAILED_TO_UNDO_SAVE_STATE),
"RAM");
return ret;
}
/* TODO/FIXME - turn this into actual task */
/**
* save_state:
* @path : path of saved state that shall be written to.
*
* @save_to_disk: If false, saves the state onto old_state_buf.
* Save a state from memory to disk.
*
* Returns: true if successful, false otherwise.
**/
bool content_save_state(const char *path)
bool content_save_state(const char *path) { content_save_state_with_backup(path, true);}
bool content_save_state_with_backup(const char *path, bool save_to_disk)
{
retro_ctx_serialize_info_t serial_info;
retro_ctx_size_info_t info;
@ -82,8 +248,31 @@ bool content_save_state(const char *path)
serial_info.size = info.size;
ret = core_serialize(&serial_info);
if (ret)
ret = filestream_write_file(path, data, info.size);
if (ret) {
if (save_to_disk) {
if (path_file_exists(path)) {
content_load_state_with_backup(path, true);
}
ret = filestream_write_file(path, data, info.size);
}
/* save_to_disk is false, which means we are saving the state
in old_state_buf to allow content_undo_load_state() to restore it */
else
{
old_state_buf.path[0] = '\0';
/* If we were holding onto an old state already, clean it up first */
if (old_state_buf.data) {
free(old_state_buf.data);
old_state_buf.data = NULL;
}
old_state_buf.data = malloc(info.size);
memcpy(old_state_buf.data, data, info.size);
old_state_buf.size = info.size;
}
}
else
{
RARCH_ERR("%s \"%s\".\n",
@ -99,12 +288,15 @@ bool content_save_state(const char *path)
/**
* content_load_state:
* @path : path that state will be loaded from.
*
* @load_to_backup_buffer: If true, the state will be loaded into old_save_file.
* Load a state from disk to memory.
*
* Returns: true if successful, false otherwise.
*
*
**/
bool content_load_state(const char *path)
bool content_load_state(const char* path) { content_load_state_with_backup(path, false); }
bool content_load_state_with_backup(const char *path, bool load_to_backup_buffer)
{
unsigned i;
ssize_t size;
@ -128,6 +320,26 @@ bool content_load_state(const char *path)
(unsigned)size,
msg_hash_to_str(MSG_BYTES));
/* This means we're backing up the file in memory, so content_undo_save_state()
can restore it */
if (load_to_backup_buffer) {
strcpy(old_save_file.path, path);
/* If we were previously backing up a file, let go of it first */
if (old_save_file.data) {
free(old_save_file.data);
old_save_file.data = NULL;
}
old_save_file.data = malloc(size);
memcpy(old_save_file.data, buf, size);
old_save_file.size = size;
free(buf);
return true;
}
if (settings->block_sram_overwrite && global->savefiles
&& global->savefiles->size)
{
@ -179,6 +391,9 @@ bool content_load_state(const char *path)
serial_info.data_const = buf;
serial_info.size = size;
/* Backup the current state so we can undo this load */
content_save_state_with_backup("RAM", false);
ret = core_unserialize(&serial_info);
/* Flush back. */
@ -231,3 +446,33 @@ bool content_rename_state(const char *origin, const char *dest)
RARCH_LOG ("Error %d renaming file %s", ret, origin);
return false;
}
/*
*
* TODO/FIXME: Figure out when and where this should be called
*
*/
bool content_reset_savestate_backups()
{
printf("Resetting undo buffers.\n");
if (old_save_file.data)
{
free(old_save_file.data);
old_save_file.data = NULL;
}
old_save_file.path[0] = '\0';
old_save_file.size = 0;
if (old_state_buf.data)
{
free(old_state_buf.data);
old_state_buf.data = NULL;
}
old_state_buf.path[0] = '\0';
old_state_buf.size = 0;
return true;
}