diff --git a/src/common/RewindManager.cxx b/src/common/RewindManager.cxx index 0a0a265e1..5debdfc1e 100644 --- a/src/common/RewindManager.cxx +++ b/src/common/RewindManager.cxx @@ -15,6 +15,8 @@ // this file, and for a DISCLAIMER OF ALL WARRANTIES. //============================================================================ +#include + #include "OSystem.hxx" #include "Serializer.hxx" #include "StateManager.hxx" @@ -25,15 +27,19 @@ // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - RewindManager::RewindManager(OSystem& system, StateManager& statemgr) : myOSystem(system), - myStateManager(statemgr) + myStateManager(statemgr), + myIsNTSC(true) // TODO { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool RewindManager::addState(const string& message) { + // TODO: remove following (preceding???) (all invalid) states + // myStateList.removeToFront(); from current() + 1 (or - 1???) + if(myStateList.full()) - myStateList.removeLast(); // remove the oldest state file + compressStates(); RewindState& state = myStateList.addFirst(); Serializer& s = state.data; @@ -41,7 +47,8 @@ bool RewindManager::addState(const string& message) s.reset(); // rewind Serializer internal buffers if(myStateManager.saveState(s) && myOSystem.console().tia().saveDisplay(s)) { - state.message = "Rewind " + message; + state.message = message; + state.cycle = myOSystem.console().tia().cycles(); return true; } return false; @@ -52,15 +59,19 @@ bool RewindManager::rewindState() { if(!myStateList.empty()) { + // TODO: get state previous to the current state instead of first() RewindState& state = myStateList.first(); Serializer& s = state.data; + string message = getMessage(state); s.reset(); // rewind Serializer internal buffers myStateManager.loadState(s); myOSystem.console().tia().loadDisplay(s); // Show message indicating the rewind state - myOSystem.frameBuffer().showMessage(state.message); + myOSystem.frameBuffer().showMessage(message); + + // TODO: Do NOT remove state (TODO later somewhere else: stop emulation) myStateList.removeFirst(); return true; @@ -68,3 +79,80 @@ bool RewindManager::rewindState() else return false; } + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool RewindManager::unwindState() +{ + if(!atFirst()) // or last??? + { + // TODO: get state next to the current state + /*RewindState& state = myStateList.???() + Serializer& s = state.data; + string message = getMessage(state); + + s.reset(); // rewind Serializer internal buffers + myStateManager.loadState(s); + myOSystem.console().tia().loadDisplay(s); + + // Show message indicating the rewind state + myOSystem.frameBuffer().showMessage(message);*/ + return true; + } + return false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RewindManager::compressStates() +{ + myStateList.removeLast(); // remove the oldest state file + // TODO: add smart state removal +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string RewindManager::getMessage(RewindState& state) +{ + const Int64 NTSC_FREQ = 1193182; + const Int64 PAL_FREQ = 1182298; + Int64 diff = myOSystem.console().tia().cycles() - state.cycle, + freq = myIsNTSC ? NTSC_FREQ : PAL_FREQ, + diffUnit; + stringstream message; + string unit; + + message << (diff >= 0 ? "Rewind" : "Unwind"); + diff = abs(diff); + + if(diff < 76 * 2) + { + unit = "cycle"; + diffUnit = diff; + } + else if(diff < 76 * 262 * 2) + { + unit = "scanline"; + diffUnit = diff / 76; + } + else if(diff < NTSC_FREQ * 2) + { + unit = "frame"; + diffUnit = diff / (76 * 262); + } + else if(diff < NTSC_FREQ * 60 * 2) + { + unit = "second"; + diffUnit = diff / NTSC_FREQ; + } + else + { + unit = "minute"; + diffUnit = diff / (NTSC_FREQ * 60); + } + message << " " << diffUnit << " " << unit; + if(diffUnit != 1) + message << "s"; + + // add optional message (TODO: when smart removal works, we have to do something smart with this part too) + if(!state.message.empty()) + message << " (" << state.message << ")"; + return message.str(); +} diff --git a/src/common/RewindManager.hxx b/src/common/RewindManager.hxx index d8c238edf..d964b1297 100644 --- a/src/common/RewindManager.hxx +++ b/src/common/RewindManager.hxx @@ -49,7 +49,14 @@ class RewindManager */ bool rewindState(); - bool empty() const { return myStateList.empty(); } + /** + Unwind one level of the state list, and display the message associated + with that state. + */ + bool unwindState(); + + bool atLast() const { return myStateList.empty(); } + bool atFirst() const { return false; } // TODO void clear() { myStateList.clear(); } private: @@ -62,6 +69,7 @@ class RewindManager struct RewindState { Serializer data; string message; + uInt64 cycle; // We do nothing on object instantiation or copy RewindState() { } @@ -70,6 +78,13 @@ class RewindManager Common::LinkedObjectPool myStateList; + bool myIsNTSC; + + void compressStates(); + + string getMessage(RewindState& state); + + private: // Following constructors and assignment operators not supported RewindManager() = delete; diff --git a/src/debugger/Debugger.cxx b/src/debugger/Debugger.cxx index a12aed36b..ce7a8755e 100644 --- a/src/debugger/Debugger.cxx +++ b/src/debugger/Debugger.cxx @@ -451,22 +451,34 @@ void Debugger::nextFrame(int frames) lockBankswitchState(); } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool Debugger::rewindState() +bool Debugger::windState(bool unwind) { RewindManager& r = myOSystem.state().rewindManager(); mySystem.clearDirtyPages(); unlockBankswitchState(); - bool result = r.rewindState(); + bool result = unwind ? r.unwindState() : r.rewindState(); lockBankswitchState(); - myDialog->rewindButton().setEnabled(!r.empty()); + myDialog->rewindButton().setEnabled(!r.atLast()); + myDialog->unwindButton().setEnabled(!r.atFirst()); return result; } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Debugger::rewindState() +{ + return windState(false); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool Debugger::unwindState() +{ + return windState(true); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void Debugger::clearAllBreakPoints() { @@ -505,7 +517,8 @@ void Debugger::saveOldState(string rewindMsg) { RewindManager& r = myOSystem.state().rewindManager(); r.addState(rewindMsg); - myDialog->rewindButton().setEnabled(!r.empty()); + myDialog->rewindButton().setEnabled(!r.atLast()); + myDialog->unwindButton().setEnabled(!r.atFirst()); } } @@ -519,7 +532,8 @@ void Debugger::setStartState() RewindManager& r = myOSystem.state().rewindManager(); if(myOSystem.state().mode() == StateManager::Mode::Off) r.clear(); - myDialog->rewindButton().setEnabled(!r.empty()); + myDialog->rewindButton().setEnabled(!r.atLast()); + myDialog->unwindButton().setEnabled(!r.atFirst()); // Save initial state, but don't add it to the rewind list saveOldState(); diff --git a/src/debugger/Debugger.hxx b/src/debugger/Debugger.hxx index 77a8f9efd..45f891bf2 100644 --- a/src/debugger/Debugger.hxx +++ b/src/debugger/Debugger.hxx @@ -270,6 +270,7 @@ class Debugger : public DialogContainer void nextScanline(int lines); void nextFrame(int frames); bool rewindState(); + bool unwindState(); void toggleBreakPoint(uInt16 bp); @@ -326,6 +327,8 @@ class Debugger : public DialogContainer static PseudoRegister ourPseudoRegisters[NUM_PSEUDO_REGS]; private: + bool windState(bool unwind); + // Following constructors and assignment operators not supported Debugger() = delete; Debugger(const Debugger&) = delete; diff --git a/src/debugger/DebuggerParser.cxx b/src/debugger/DebuggerParser.cxx index c18ed9a7c..472b83487 100644 --- a/src/debugger/DebuggerParser.cxx +++ b/src/debugger/DebuggerParser.cxx @@ -1837,6 +1837,19 @@ void DebuggerParser::executeUndef() commandResult << red("no such label"); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// "unwind" +void DebuggerParser::executeUnwind() +{ + if(debugger.unwindState()) + { + debugger.rom().invalidate(); + commandResult << "unwind by one level"; + } + else + commandResult << "no states left to rewind"; +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // "v" void DebuggerParser::executeV() @@ -2611,6 +2624,16 @@ DebuggerParser::Command DebuggerParser::commands[kNumCommands] = { std::mem_fn(&DebuggerParser::executeUndef) }, + { + "unwind", + "Unwind state to last step/trace/scanline/frame", + "Unwind currently only works in the debugger", + false, + true, + { kARG_END_ARGS }, + std::mem_fn(&DebuggerParser::executeUnwind) + }, + { "v", "Overflow Flag: set (0 or 1), or toggle (no arg)", diff --git a/src/debugger/DebuggerParser.hxx b/src/debugger/DebuggerParser.hxx index c423a22e0..3aa977eef 100644 --- a/src/debugger/DebuggerParser.hxx +++ b/src/debugger/DebuggerParser.hxx @@ -68,7 +68,7 @@ class DebuggerParser string saveScriptFile(string file); private: - enum { kNumCommands = 76 }; + enum { kNumCommands = 77 }; // Constants for argument processing enum { @@ -209,6 +209,7 @@ class DebuggerParser void executeType(); void executeUHex(); void executeUndef(); + void executeUnwind(); void executeV(); void executeWatch(); void executeX(); diff --git a/src/debugger/gui/DebuggerDialog.cxx b/src/debugger/gui/DebuggerDialog.cxx index 31c3c8786..8c2ec368b 100644 --- a/src/debugger/gui/DebuggerDialog.cxx +++ b/src/debugger/gui/DebuggerDialog.cxx @@ -92,7 +92,10 @@ void DebuggerDialog::handleKeyDown(StellaKey key, StellaMod mod) switch(key) { case KBDK_R: - doRewind(); + if(!instance().eventHandler().kbdShift(mod)) + doRewind(); + else + doUnwind(); break; case KBDK_S: doStep(); @@ -141,6 +144,10 @@ void DebuggerDialog::handleCommand(CommandSender* sender, int cmd, doRewind(); break; + case kDDUnwindCmd: + doUnwind(); + break; + case kDDExitCmd: doExitDebugger(); break; @@ -189,6 +196,12 @@ void DebuggerDialog::doRewind() instance().debugger().parser().run("rewind"); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void DebuggerDialog::doUnwind() +{ + instance().debugger().parser().run("unwind"); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void DebuggerDialog::doExitDebugger() { @@ -375,7 +388,7 @@ void DebuggerDialog::addRomArea() bwidth, bheight, "Exit", kDDExitCmd); bwidth = myLFont->getStringWidth("< ") + 4; - bheight = bheight * 5 + 4*4; + bheight = bheight * 3 + 4 * 2; buttonX -= (bwidth + 5); buttonY = r.top + 5; myRewindButton = @@ -383,6 +396,14 @@ void DebuggerDialog::addRomArea() bwidth, bheight, "<", kDDRewindCmd); myRewindButton->clearFlags(WIDGET_ENABLED); + buttonY += bheight + 4; + bheight = (myLFont->getLineHeight() + 2) * 2 + 4 * 1; + myUnwindButton = + new ButtonWidget(this, *myLFont, buttonX, buttonY, + bwidth, bheight, ">", kDDUnwindCmd); + myUnwindButton->clearFlags(WIDGET_ENABLED); + + int xpos = buttonX - 8*myLFont->getMaxCharWidth() - 20, ypos = 20; DataGridOpsWidget* ops = new DataGridOpsWidget(this, *myLFont, xpos, ypos); diff --git a/src/debugger/gui/DebuggerDialog.hxx b/src/debugger/gui/DebuggerDialog.hxx index 8b72071ff..9606db3a1 100644 --- a/src/debugger/gui/DebuggerDialog.hxx +++ b/src/debugger/gui/DebuggerDialog.hxx @@ -62,6 +62,7 @@ class DebuggerDialog : public Dialog CartRamWidget& cartRam() const { return *myCartRam; } EditTextWidget& message() const { return *myMessageBox; } ButtonWidget& rewindButton() const { return *myRewindButton; } + ButtonWidget& unwindButton() const { return *myUnwindButton; } void showFatalMessage(const string& msg); @@ -75,6 +76,7 @@ class DebuggerDialog : public Dialog void doScanlineAdvance(); void doAdvance(); void doRewind(); + void doUnwind(); void doExitDebugger(); void doExitRom(); @@ -96,6 +98,7 @@ class DebuggerDialog : public Dialog kDDAdvCmd = 'DDav', kDDSAdvCmd = 'DDsv', kDDRewindCmd = 'DDrw', + kDDUnwindCmd = 'DDuw', kDDExitCmd = 'DDex', kDDExitFatalCmd = 'DDer' }; @@ -113,6 +116,7 @@ class DebuggerDialog : public Dialog CartRamWidget* myCartRam; EditTextWidget* myMessageBox; ButtonWidget* myRewindButton; + ButtonWidget* myUnwindButton; unique_ptr myFatalError; unique_ptr myLFont; // used for labels