mirror of https://github.com/stella-emu/stella.git
implement saving/loading all TM states to/from disk
This commit is contained in:
parent
ad6e6f1855
commit
67db29e826
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
#include "StateManager.hxx"
|
||||
|
||||
#define STATE_HEADER "06000003state"
|
||||
// #define MOVIE_HEADER "03030000movie"
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
#ifndef STATE_MANAGER_HXX
|
||||
#define STATE_MANAGER_HXX
|
||||
|
||||
#define STATE_HEADER "06000003state"
|
||||
|
||||
class OSystem;
|
||||
class RewindManager;
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ class Event
|
|||
Rewind1Menu, Rewind10Menu, RewindAllMenu,
|
||||
Unwind1Menu, Unwind10Menu, UnwindAllMenu,
|
||||
|
||||
StartPauseMode,
|
||||
StartPauseMode, SaveAllStates, LoadAllStates,
|
||||
|
||||
LastType
|
||||
};
|
||||
|
|
|
@ -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", "" },
|
||||
|
|
|
@ -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
|
||||
;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue