implement saving/loading all TM states to/from disk

This commit is contained in:
thrust26 2019-05-31 14:44:46 +02:00
parent ad6e6f1855
commit 67db29e826
10 changed files with 233 additions and 18 deletions

View File

@ -144,8 +144,10 @@ void PhysicalKeyboardHandler::setDefaultMapping(Event::Type event, EventMode mod
setDefaultKey(Event::ConsoleRightDiffA , KBDK_F7);
setDefaultKey(Event::ConsoleRightDiffB , KBDK_F8);
setDefaultKey(Event::SaveState , KBDK_F9);
setDefaultKey(Event::SaveAllStates , KBDK_F9, KBDM_ALT);
setDefaultKey(Event::ChangeState , KBDK_F10);
setDefaultKey(Event::LoadState , KBDK_F11);
setDefaultKey(Event::LoadAllStates , KBDK_F11, KBDM_ALT);
setDefaultKey(Event::TakeSnapshot , KBDK_F12);
setDefaultKey(Event::Fry , KBDK_BACKSPACE);
setDefaultKey(Event::TogglePauseMode , KBDK_PAUSE);

View File

@ -36,6 +36,7 @@ RewindManager::RewindManager(OSystem& system, StateManager& statemgr)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RewindManager::setup()
{
myStateSize = 0;
myLastTimeMachineAdd = false;
const string& prefix = myOSystem.settings().getBool("dev.settings") ? "dev." : "plr.";
@ -128,6 +129,7 @@ bool RewindManager::addState(const string& message, bool timeMachine)
s.rewind(); // rewind Serializer internal buffers
if(myStateManager.saveState(s) && myOSystem.console().tia().saveDisplay(s))
{
myStateSize = std::max(myStateSize, uInt32(s.size()));
state.message = message;
state.cycles = myOSystem.console().tia().cycles();
myLastTimeMachineAdd = timeMachine;
@ -218,6 +220,129 @@ uInt32 RewindManager::windStates(uInt32 numStates, bool unwind)
return rewindStates(numStates);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string RewindManager::saveAllStates()
{
uInt8* buffer = NULL;
try
{
ostringstream buf;
buf << myOSystem.stateDir()
<< myOSystem.console().properties().get(PropType::Cart_Name)
<< ".sta";
// Truncate existing file to 0
FILE* fp;
errno_t err = fopen_s(&fp, buf.str().c_str(), "w");
// Make sure the file can be opened for writing
if (err != NULL)
return "Can't save to all states file";
fclose(fp);
Serializer out(buf.str());
int numStates = rewindStates(1000) + 1;
// Save header
buf.str("");
out.putString(STATE_HEADER);
out.putShort(numStates);
out.putInt(myStateSize);
buffer = new uInt8[myStateSize];
for (int i = 0; i < numStates; i++)
{
RewindState& state = myStateList.current();
Serializer& s = state.data;
// Rewind Serializer internal buffers
s.rewind();
// Save state
s.getByteArray(buffer, myStateSize);
out.putByteArray(buffer, myStateSize);
out.putString(state.message);
out.putLong(state.cycles);
if (i < numStates)
unwindStates(1);
}
delete[] buffer;
buf.str("");
buf << "Saved " << numStates << " states";
return buf.str();
}
catch (...)
{
if (buffer)
delete[] buffer;
return "Error loading all states";
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string RewindManager::loadAllStates()
{
uInt8* buffer = NULL;
try
{
ostringstream buf;
buf << myOSystem.stateDir()
<< myOSystem.console().properties().get(PropType::Cart_Name)
<< ".sta";
// Make sure the file can be opened for reading
Serializer in(buf.str(), true);
if (!in)
return "Can't load from all states file";
clear();
int numStates;
// Load header
buf.str("");
// Check compatibility
if (in.getString() != STATE_HEADER)
return "Incompatible all states file";
numStates = in.getShort();
myStateSize = in.getInt();
buffer = new uInt8[myStateSize];
for (int i = 0; i < numStates; i++)
{
if (myStateList.full())
compressStates();
// Add new state at the end of the list (queue adds at end)
// This updates the 'current' iterator inside the list
myStateList.addLast();
RewindState& state = myStateList.current();
Serializer& s = state.data;
// Rewind Serializer internal buffers
s.rewind();
// Fill new state with saved values
in.getByteArray(buffer, myStateSize);
s.putByteArray(buffer, myStateSize);
state.message = in.getString();
state.cycles = in.getLong();
}
delete[] buffer;
// initialize current state (parameters ignored)
loadState(0, 0);
buf.str("");
buf << "Loaded " << numStates << " states";
return buf.str();
}
catch (...)
{
if (buffer)
delete[] buffer;
return "Error saving all states";
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void RewindManager::compressStates()

View File

@ -136,10 +136,16 @@ class RewindManager
*/
uInt32 windStates(uInt32 numStates, bool unwind);
string saveAllStates();
string loadAllStates();
bool atFirst() const { return myStateList.atFirst(); }
bool atLast() const { return myStateList.atLast(); }
void resize(uInt32 size) { myStateList.resize(size); }
void clear() { myStateList.clear(); }
void clear() {
myStateSize = 0;
myStateList.clear();
}
/**
Convert the cycles into a unit string.
@ -169,6 +175,7 @@ class RewindManager
uInt64 myHorizon;
double myFactor;
bool myLastTimeMachineAdd;
uInt32 myStateSize;
struct RewindState {
Serializer data; // actual save state

View File

@ -27,7 +27,6 @@
#include "StateManager.hxx"
#define STATE_HEADER "06000003state"
// #define MOVIE_HEADER "03030000movie"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -18,6 +18,8 @@
#ifndef STATE_MANAGER_HXX
#define STATE_MANAGER_HXX
#define STATE_HEADER "06000003state"
class OSystem;
class RewindManager;

View File

@ -103,7 +103,7 @@ class Event
Rewind1Menu, Rewind10Menu, RewindAllMenu,
Unwind1Menu, Unwind10Menu, UnwindAllMenu,
StartPauseMode,
StartPauseMode, SaveAllStates, LoadAllStates,
LastType
};

View File

@ -35,6 +35,7 @@
#include "Settings.hxx"
#include "Sound.hxx"
#include "StateManager.hxx"
#include "RewindManager.hxx"
#include "TimerManager.hxx"
#include "Switches.hxx"
#include "M6532.hxx"
@ -612,6 +613,11 @@ void EventHandler::handleEvent(Event::Type event, bool pressed)
if(pressed) myOSystem.state().saveState();
return;
case Event::SaveAllStates:
if (pressed)
myOSystem.frameBuffer().showMessage(myOSystem.state().rewindManager().saveAllStates());
return;
case Event::ChangeState:
if(pressed) myOSystem.state().changeState();
return;
@ -620,6 +626,11 @@ void EventHandler::handleEvent(Event::Type event, bool pressed)
if(pressed) myOSystem.state().loadState();
return;
case Event::LoadAllStates:
if (pressed)
myOSystem.frameBuffer().showMessage(myOSystem.state().rewindManager().loadAllStates());
return;
case Event::Rewind:
if (pressed) myOSystem.state().rewindStates();
return;
@ -1687,6 +1698,8 @@ EventHandler::ActionList EventHandler::ourEmulActionList[EMUL_ACTIONLIST_SIZE] =
{ Event::Unwind1Menu, "Unwind one state & enter TM UI", "" },
{ Event::Unwind10Menu, "Unwind 10 states & enter TM UI", "" },
{ Event::UnwindAllMenu, "Unwind all states & enter TM UI", "" },
{ Event::SaveAllStates, "Save all TM states of current game", "" },
{ Event::LoadAllStates, "Load saved TM states for current game", "" },
{ Event::Combo1, "Combo 1", "" },
{ Event::Combo2, "Combo 2", "" },

View File

@ -368,10 +368,11 @@ class EventHandler
COMBO_SIZE = 16,
EVENTS_PER_COMBO = 8,
#ifdef PNG_SUPPORT
EMUL_ACTIONLIST_SIZE = 136 + COMBO_SIZE,
PNG_SIZE = 2,
#else
EMUL_ACTIONLIST_SIZE = 136 - 2 + COMBO_SIZE,
PNG_SIZE = 0,
#endif
EMUL_ACTIONLIST_SIZE = 136 + PNG_SIZE + COMBO_SIZE,
MENU_ACTIONLIST_SIZE = 18
;

View File

@ -158,6 +158,41 @@ static uInt32 UNWIND_ALL[BUTTON_H] =
0b11000011000011,
0
};
static uInt32 SAVE_ALL[BUTTON_H] =
{
0b00000111100000,
0b00000111100000,
0b00000111100000,
0b00000111100000,
0b11111111111111,
0b01111111111110,
0b00111111111100,
0b00011111111000,
0b00001111110000,
0b00000111100000,
0b00000011000000,
0b00000000000000,
0b11111111111111,
0b11111111111111,
};
static uInt32 LOAD_ALL[BUTTON_H] =
{
0b00000011000000,
0b00000111100000,
0b00001111110000,
0b00011111111000,
0b00111111111100,
0b01111111111110,
0b11111111111111,
0b00000111100000,
0b00000111100000,
0b00000111100000,
0b00000111100000,
0b00000000000000,
0b11111111111111,
0b11111111111111,
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent,
@ -232,6 +267,14 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent,
BUTTON_W, BUTTON_H, kUnwindAll);
xpos = myUnwindAllWidget->getRight() + BUTTON_GAP * 4;
mySaveAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, SAVE_ALL,
BUTTON_W, BUTTON_H, kSaveAll);
xpos = mySaveAllWidget->getRight() + BUTTON_GAP;
myLoadAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, LOAD_ALL,
BUTTON_W, BUTTON_H, kLoadAll);
xpos = myLoadAllWidget->getRight() + BUTTON_GAP * 4;
// Add message
myMessageWidget = new StaticTextWidget(this, font, xpos, ypos + 3, " ",
TextAlign::Left, kBGColor);
@ -250,14 +293,6 @@ void TimeMachineDialog::center()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TimeMachineDialog::loadConfig()
{
RewindManager& r = instance().state().rewindManager();
IntArray cycles = r.cyclesList();
// Set range and intervals for timeline
uInt32 maxValue = cycles.size() > 1 ? uInt32(cycles.size() - 1) : 0;
myTimeline->setMaxValue(maxValue);
myTimeline->setStepValues(cycles);
// Enable blending (only once is necessary)
if(!surface().attributes().blending)
{
@ -266,10 +301,7 @@ void TimeMachineDialog::loadConfig()
surface().applyAttributes();
}
myMessageWidget->setLabel("");
handleWinds(_enterWinds);
_enterWinds = 0;
handleToggle();
initBar();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -355,11 +387,38 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd,
handleWinds(1000);
break;
case kSaveAll:
instance().frameBuffer().showMessage(instance().state().rewindManager().saveAllStates());
break;
case kLoadAll:
instance().frameBuffer().showMessage(instance().state().rewindManager().loadAllStates());
initBar();
break;
default:
Dialog::handleCommand(sender, cmd, data, 0);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void TimeMachineDialog::initBar()
{
RewindManager& r = instance().state().rewindManager();
IntArray cycles = r.cyclesList();
// Set range and intervals for timeline
uInt32 maxValue = cycles.size() > 1 ? uInt32(cycles.size() - 1) : 0;
myTimeline->setMaxValue(maxValue);
myTimeline->setStepValues(cycles);
myMessageWidget->setLabel("");
handleWinds(_enterWinds);
_enterWinds = 0;
handleToggle();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string TimeMachineDialog::getTimeString(uInt64 cycles)
{
@ -416,6 +475,7 @@ void TimeMachineDialog::handleWinds(Int32 numWinds)
myRewind1Widget->setEnabled(!r.atFirst());
myUnwindAllWidget->setEnabled(!r.atLast());
myUnwind1Widget->setEnabled(!r.atLast());
mySaveAllWidget->setEnabled(r.getLastIdx() != 0);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -424,4 +484,3 @@ void TimeMachineDialog::handleToggle()
myToggleWidget->setBitmap(instance().state().mode() == StateManager::Mode::Off ? RECORD : STOP,
BUTTON_W, BUTTON_H);
}

View File

@ -40,6 +40,9 @@ class TimeMachineDialog : public Dialog
void handleKeyDown(StellaKey key, StellaMod mod) override;
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
/** initialize timeline bar */
void initBar();
/** This dialog uses its own positioning, so we override Dialog::center() */
void center() override;
@ -62,6 +65,8 @@ class TimeMachineDialog : public Dialog
kUnwindAll = 'TMua',
kUnwind10 = 'TMu1',
kUnwind1 = 'TMun',
kSaveAll = 'TMsv',
kLoadAll = 'TMld',
};
TimeLineWidget* myTimeline;
@ -72,6 +77,8 @@ class TimeMachineDialog : public Dialog
ButtonWidget* myRewind1Widget;
ButtonWidget* myUnwind1Widget;
ButtonWidget* myUnwindAllWidget;
ButtonWidget* mySaveAllWidget;
ButtonWidget* myLoadAllWidget;
StaticTextWidget* myCurrentTimeWidget;
StaticTextWidget* myLastTimeWidget;