Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding option to save and load state by timestamp #9

Merged
merged 1 commit into from
May 1, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Source/Core/Core/Src/ConfigManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ static const struct {

{ "ToggleFullscreen", 70 /* 'F' */, 2 /* wxMOD_CMD */ },
{ "Screenshot", 83 /* 'S' */, 2 /* wxMOD_CMD */ },
{ "Exit", 0, 0 /* wxMOD_NONE */ },

{ "Wiimote1Connect", 49 /* '1' */, 2 /* wxMOD_CMD */ },
{ "Wiimote2Connect", 50 /* '2' */, 2 /* wxMOD_CMD */ },
Expand All @@ -57,6 +58,7 @@ static const struct {

{ "ToggleFullscreen", 13 /* WXK_RETURN */, 1 /* wxMOD_ALT */ },
{ "Screenshot", 348 /* WXK_F9 */, 0 /* wxMOD_NONE */ },
{ "Exit", 0, 0 /* wxMOD_NONE */ },

{ "Wiimote1Connect", 344 /* WXK_F5 */, 1 /* wxMOD_ALT */ },
{ "Wiimote2Connect", 345 /* WXK_F6 */, 1 /* wxMOD_ALT */ },
Expand All @@ -81,6 +83,19 @@ static const struct {
{ "SaveStateSlot6", 345 /* WXK_F6 */, 4 /* wxMOD_SHIFT */ },
{ "SaveStateSlot7", 346 /* WXK_F7 */, 4 /* wxMOD_SHIFT */ },
{ "SaveStateSlot8", 347 /* WXK_F8 */, 4 /* wxMOD_SHIFT */ },

{ "LoadLastState1", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState2", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState3", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState4", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState5", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState6", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState7", 0, 0 /* wxMOD_NONE */ },
{ "LoadLastState8", 0, 0 /* wxMOD_NONE */ },

{ "SaveFirstState", 0, 0 /* wxMOD_NONE */ },
{ "UndoLoadState", 351 /* WXK_F12 */, 0 /* wxMOD_NONE */ },
{ "UndoSaveState", 351 /* WXK_F12 */, 4 /* wxMOD_SHIFT */ },
};

SConfig::SConfig()
Expand Down
14 changes: 14 additions & 0 deletions Source/Core/Core/Src/CoreParameter.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ enum Hotkey

HK_FULLSCREEN,
HK_SCREENSHOT,
HK_EXIT,

HK_WIIMOTE1_CONNECT,
HK_WIIMOTE2_CONNECT,
Expand All @@ -50,6 +51,19 @@ enum Hotkey
HK_SAVE_STATE_SLOT_7,
HK_SAVE_STATE_SLOT_8,

HK_LOAD_LAST_STATE_1,
HK_LOAD_LAST_STATE_2,
HK_LOAD_LAST_STATE_3,
HK_LOAD_LAST_STATE_4,
HK_LOAD_LAST_STATE_5,
HK_LOAD_LAST_STATE_6,
HK_LOAD_LAST_STATE_7,
HK_LOAD_LAST_STATE_8,

HK_SAVE_FIRST_STATE,
HK_UNDO_LOAD_STATE,
HK_UNDO_SAVE_STATE,

NUM_HOTKEYS,
};

Expand Down
108 changes: 93 additions & 15 deletions Source/Core/Core/Src/State.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// Refer to the license.txt file included.

#include "Common.h"
#include "Timer.h"
#include "State.h"
#include "Core.h"
#include "ConfigManager.h"
Expand Down Expand Up @@ -58,13 +59,7 @@ static Common::Event g_compressAndDumpStateSyncEvent;
static std::thread g_save_thread;

// Don't forget to increase this after doing changes on the savestate system
static const u32 STATE_VERSION = 16;

struct StateHeader
{
u8 gameID[6];
size_t size;
};
static const u32 STATE_VERSION = 17;

enum
{
Expand Down Expand Up @@ -159,17 +154,59 @@ void VerifyBuffer(std::vector<u8>& buffer)
Core::PauseAndLock(false, wasUnpaused);
}

// return state number not in map
int GetEmptySlot(std::map<double, int> m)
{
for (int i = 1; i <= NUM_STATES; i++)
{
bool found = false;
for (std::map<double, int>::iterator it = m.begin(); it != m.end(); it++)
{
if (it->second == i)
{
found = true;
break;
}
}
if (!found) return i;
}
return -1;
}

// read state timestamps
std::map<double, int> GetSavedStates()
{
StateHeader header;
std::map<double, int> m;
for (int i = 1; i <= NUM_STATES; i++)
{
if (File::Exists(MakeStateFilename(i)))
{
if (ReadHeader(MakeStateFilename(i), header))
{
double d = Common::Timer::GetDoubleTime() - header.time;
// increase time until unique value is obtained
while (m.find(d) != m.end()) d += .001;
m.insert(std::pair<double,int>(d, i));
}
}
}
return m;
}

struct CompressAndDumpState_args
{
std::vector<u8>* buffer_vector;
std::mutex* buffer_mutex;
std::string filename;
bool wait;
};

void CompressAndDumpState(CompressAndDumpState_args save_args)
{
std::lock_guard<std::mutex> lk(*save_args.buffer_mutex);
g_compressAndDumpStateSyncEvent.Set();
if (!save_args.wait)
g_compressAndDumpStateSyncEvent.Set();

const u8* const buffer_data = &(*(save_args.buffer_vector))[0];
const size_t buffer_size = (save_args.buffer_vector)->size();
Expand Down Expand Up @@ -201,13 +238,15 @@ void CompressAndDumpState(CompressAndDumpState_args save_args)
if (!f)
{
Core::DisplayMessage("Could not save state", 2000);
g_compressAndDumpStateSyncEvent.Set();
return;
}

// Setting up the header
StateHeader header;
memcpy(header.gameID, SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), 6);
header.size = g_use_compression ? buffer_size : 0;
header.time = Common::Timer::GetDoubleTime();

f.WriteArray(&header, 1);

Expand Down Expand Up @@ -244,9 +283,10 @@ void CompressAndDumpState(CompressAndDumpState_args save_args)

Core::DisplayMessage(StringFromFormat("Saved State to %s",
filename.c_str()).c_str(), 2000);
g_compressAndDumpStateSyncEvent.Set();
}

void SaveAs(const std::string& filename)
void SaveAs(const std::string& filename, bool wait)
{
// Pause the core while we save the state
bool wasUnpaused = Core::PauseAndLock(true);
Expand Down Expand Up @@ -274,6 +314,7 @@ void SaveAs(const std::string& filename)
save_args.buffer_vector = &g_current_buffer;
save_args.buffer_mutex = &g_cs_current_buffer;
save_args.filename = filename;
save_args.wait = wait;

Flush();
g_save_thread = std::thread(CompressAndDumpState, save_args);
Expand All @@ -291,6 +332,20 @@ void SaveAs(const std::string& filename)
Core::PauseAndLock(false, wasUnpaused);
}

bool ReadHeader(const std::string filename, StateHeader& header)
{
Flush();
File::IOFile f(filename, "rb");
if (!f)
{
Core::DisplayMessage("State not found", 2000);
return false;
}

f.ReadArray(&header, 1);
return true;
}

void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_data)
{
Flush();
Expand Down Expand Up @@ -496,9 +551,9 @@ static std::string MakeStateFilename(int number)
SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), number);
}

void Save(int slot)
void Save(int slot, bool wait)
{
SaveAs(MakeStateFilename(slot));
SaveAs(MakeStateFilename(slot), wait);
}

void Load(int slot)
Expand All @@ -511,12 +566,35 @@ void Verify(int slot)
VerifyAt(MakeStateFilename(slot));
}

void LoadLastSaved()
void LoadLastSaved(int i)
{
if (g_last_filename.empty())
Core::DisplayMessage("There is no last saved state", 2000);
std::map<double, int> savedStates = GetSavedStates();

if (i > savedStates.size())
Core::DisplayMessage("State doesn't exist", 2000);
else
LoadAs(g_last_filename);
{
std::map<double, int>::iterator it = savedStates.begin();
std::advance(it, i-1);
Load(it->second);
}
}

// must wait for state to be written because it must know if all slots are taken
void SaveFirstSaved()
{
std::map<double, int> savedStates = GetSavedStates();

// save to an empty slot
if (savedStates.size() < NUM_STATES)
Save(GetEmptySlot(savedStates), true);
// overwrite the oldest state
else
{
std::map<double, int>::iterator it = savedStates.begin();
std::advance(it, savedStates.size()-1);
Save(it->second, true);
}
}

void Flush()
Expand Down
21 changes: 18 additions & 3 deletions Source/Core/Core/Src/State.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,45 @@
namespace State
{

// number of states
static const u32 NUM_STATES = 8;

struct StateHeader
{
u8 gameID[6];
u32 size;
double time;
};

void Init();

void Shutdown();

void EnableCompression(bool compression);

bool ReadHeader(const std::string filename, StateHeader& header);

// These don't happen instantly - they get scheduled as events.
// ...But only if we're not in the main cpu thread.
// If we're in the main cpu thread then they run immediately instead
// because some things (like Lua) need them to run immediately.
// Slots from 0-99.
void Save(int slot);
void Save(int slot, bool wait = false);
void Load(int slot);
void Verify(int slot);

void SaveAs(const std::string &filename);
void SaveAs(const std::string &filename, bool wait = false);
void LoadAs(const std::string &filename);
void VerifyAt(const std::string &filename);

void SaveToBuffer(std::vector<u8>& buffer);
void LoadFromBuffer(std::vector<u8>& buffer);
void VerifyBuffer(std::vector<u8>& buffer);

void LoadLastSaved();
static std::string MakeStateFilename(int number);

void LoadLastSaved(int i = 1);
void SaveFirstSaved();
void UndoSaveState();
void UndoLoadState();

Expand Down
Loading