summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author R. Belmont <rb6502@users.noreply.github.com>2017-12-22 13:01:05 -0500
committer GitHub <noreply@github.com>2017-12-22 13:01:05 -0500
commitffc0dadaa86ce9a68d3c6c9ef97c84eb3e709828 (patch)
treec914b1ff420f271a78bd0974cc5e83e291c35e44
parent4ddb642cf9bea51d4476c9700b73ac9a2139bf5c (diff)
parentc4ca6d02c05e5f74239ae30865d05d7aa439082b (diff)
Merge pull request #2959 from vadosnaprimer/rewind
Rewind implementation fixes and improvements
-rw-r--r--docs/source/commandline/commandline-all.rst10
-rw-r--r--docs/source/usingmame/defaultkeys.rst3
-rw-r--r--src/emu/debug/debugcmd.cpp25
-rw-r--r--src/emu/debug/debughlp.cpp2
-rw-r--r--src/emu/diexec.cpp3
-rw-r--r--src/emu/emuopts.cpp2
-rw-r--r--src/emu/inpttype.h2
-rw-r--r--src/emu/machine.cpp16
-rw-r--r--src/emu/machine.h4
-rw-r--r--src/emu/save.cpp410
-rw-r--r--src/emu/save.h41
-rw-r--r--src/osd/modules/debugger/win/debugwininfo.cpp20
-rw-r--r--src/osd/modules/debugger/win/debugwininfo.h1
13 files changed, 298 insertions, 241 deletions
diff --git a/docs/source/commandline/commandline-all.rst b/docs/source/commandline/commandline-all.rst
index 37c04023356..27c761371eb 100644
--- a/docs/source/commandline/commandline-all.rst
+++ b/docs/source/commandline/commandline-all.rst
@@ -334,6 +334,16 @@ Core Output Directory Options
Core state/playback options
---------------------------
+**-[no]rewind**
+
+ When enabled and emulation is paused, automatically creates a save state in memory every time a frame is advanced. Rewind save states can then be loaded consecutively by pressing the rewind single step shortcut key (*Left Shift + Tilde by default*). The default rewind value is OFF (-norewind).
+
+ If debugger is in a 'break' state, a save state is instead created every time step in, step over, or step out occurs. In that mode, rewind save states can be loaded by executing the debugger 'rewind'(or 'rw') command.
+
+**-rewind_capacity** *<value>*
+
+ Sets the rewind capacity value, in megabytes. It is the total amount of memory rewind savestates can occupy. When capacity is hit, old savestates get erased as new ones are captured. Setting capacity lower than the current savestate size disables rewind. Values below 0 are automatically clamped to 0.
+
**-state** *<slot>*
Immediately after starting the specified game, will cause the save state in the specified <slot> to be loaded.
diff --git a/docs/source/usingmame/defaultkeys.rst b/docs/source/usingmame/defaultkeys.rst
index de6cdade16f..dabfb6c5474 100644
--- a/docs/source/usingmame/defaultkeys.rst
+++ b/docs/source/usingmame/defaultkeys.rst
@@ -27,7 +27,8 @@ All the keys below are fully configurable in the user interface. This list shows
|
| If you are running with -debug, this key sends a 'break' in emulation.
**P** | Pauses the game.
-**Shift+P** | While paused, advances to next frame.
+**Shift+P** | While paused, advances to next frame. If rewind is enabled, a new rewind save state is also captured.
+**Shift+~** | While paused, loads the most recent rewind save state.
**F2** | Service Mode for games that support it.
**F3** | Resets the game.
**Shift+F3** | Performs a "hard reset", which tears everything down and re-creates it
diff --git a/src/emu/debug/debugcmd.cpp b/src/emu/debug/debugcmd.cpp
index c3b794fd525..c14d74a4299 100644
--- a/src/emu/debug/debugcmd.cpp
+++ b/src/emu/debug/debugcmd.cpp
@@ -1646,21 +1646,16 @@ void debugger_commands::execute_stateload(int ref, const std::vector<std::string
void debugger_commands::execute_rewind(int ref, const std::vector<std::string> &params)
{
- if (!m_machine.save().rewind()->enabled())
- {
- m_console.printf("Rewind not enabled\n");
- return;
- }
-
- m_machine.rewind_step();
-
- // clear all PC & memory tracks
- for (device_t &device : device_iterator(m_machine.root_device()))
- {
- device.debug()->track_pc_data_clear();
- device.debug()->track_mem_data_clear();
- }
- m_console.printf("Rewind step attempted. Please refer to window message popup for results.\n");
+ bool success = m_machine.rewind_step();
+ if (success)
+ // clear all PC & memory tracks
+ for (device_t &device : device_iterator(m_machine.root_device()))
+ {
+ device.debug()->track_pc_data_clear();
+ device.debug()->track_mem_data_clear();
+ }
+ else
+ m_console.printf("Rewind error occured. See error.log for details.\n");
}
diff --git a/src/emu/debug/debughlp.cpp b/src/emu/debug/debughlp.cpp
index f25d52b3ae7..4eff7e4e8e4 100644
--- a/src/emu/debug/debughlp.cpp
+++ b/src/emu/debug/debughlp.cpp
@@ -463,7 +463,7 @@ static const help_item static_help_list[] =
"The rewind command loads the most recent RAM-based state. Rewind states, when enabled, are "
"saved when \"step\", \"over\", or \"out\" command gets executed, storing the machine state as "
"of the moment before actually stepping. Consecutively loading rewind states can work like "
- "reverse execution. Depending on which steps forward were taken previously, the bahavior can "
+ "reverse execution. Depending on which steps forward were taken previously, the behavior can "
"be similar to GDB's \"reverse-stepi\" or \"reverse-next\". All output for this command is "
"currently echoed into the running machine window. Previous memory and PC tracking statistics "
"are cleared, actual reverse execution does not occur.\n"
diff --git a/src/emu/diexec.cpp b/src/emu/diexec.cpp
index d84f3b8f4be..0958108fe42 100644
--- a/src/emu/diexec.cpp
+++ b/src/emu/diexec.cpp
@@ -499,9 +499,6 @@ void device_execute_interface::interface_post_start()
assert_always(m_icountptr != nullptr, "m_icountptr never initialized!");
// register for save states
- device().save_pointer(NAME(m_icountptr), 1);
- device().save_item(NAME(m_cycles_running));
- device().save_item(NAME(m_cycles_stolen));
device().save_item(NAME(m_suspend));
device().save_item(NAME(m_nextsuspend));
device().save_item(NAME(m_eatcycles));
diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp
index 4fd91155fd2..0cb37e6895e 100644
--- a/src/emu/emuopts.cpp
+++ b/src/emu/emuopts.cpp
@@ -63,7 +63,7 @@ const options_entry emu_options::s_option_entries[] =
{ OPTION_STATE, nullptr, OPTION_STRING, "saved state to load" },
{ OPTION_AUTOSAVE, "0", OPTION_BOOLEAN, "enable automatic restore at startup, and automatic save at exit time" },
{ OPTION_REWIND, "0", OPTION_BOOLEAN, "enable rewind savestates" },
- { OPTION_REWIND_CAPACITY "(1-500)", "100", OPTION_INTEGER, "quantity of states rewind buffer can contain" },
+ { OPTION_REWIND_CAPACITY "(1-2048)", "100", OPTION_INTEGER, "rewind buffer size in megabytes" },
{ OPTION_PLAYBACK ";pb", nullptr, OPTION_STRING, "playback an input file" },
{ OPTION_RECORD ";rec", nullptr, OPTION_STRING, "record an input file" },
{ OPTION_RECORD_TIMECODE, "0", OPTION_BOOLEAN, "record an input timecode file (requires -record option)" },
diff --git a/src/emu/inpttype.h b/src/emu/inpttype.h
index a1e5bef133b..cf7cef9150d 100644
--- a/src/emu/inpttype.h
+++ b/src/emu/inpttype.h
@@ -825,7 +825,7 @@ inline void construct_core_types_UI(simple_list<input_type_entry> &typelist)
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_CONFIGURE, "Config Menu", input_seq(KEYCODE_TAB) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE, "Pause", input_seq(KEYCODE_P, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE_SINGLE, "Pause - Single Step", input_seq(KEYCODE_P, KEYCODE_LSHIFT) )
- INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_REWIND_SINGLE, "Rewind - Single Step", input_seq(KEYCODE_TILDE, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_TILDE, KEYCODE_RSHIFT) )
+ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_REWIND_SINGLE, "Rewind - Single Step", input_seq(KEYCODE_TILDE, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RESET_MACHINE, "Reset Machine", input_seq(KEYCODE_F3, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SOFT_RESET, "Soft Reset", input_seq(KEYCODE_F3, input_seq::not_code, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SHOW_GFX, "Show Gfx", input_seq(KEYCODE_F4) )
diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp
index 48edd645ac1..c8ed052ad98 100644
--- a/src/emu/machine.cpp
+++ b/src/emu/machine.cpp
@@ -687,10 +687,9 @@ void running_machine::immediate_load(const char *filename)
// state to the rewind list
//-------------------------------------------------
-void running_machine::rewind_capture()
+bool running_machine::rewind_capture()
{
- if (m_save.rewind()->enabled())
- m_save.rewind()->capture();
+ return m_save.rewind()->capture();
}
@@ -699,10 +698,9 @@ void running_machine::rewind_capture()
// rewind states
//-------------------------------------------------
-void running_machine::rewind_step()
+bool running_machine::rewind_step()
{
- if (m_save.rewind()->enabled())
- m_save.rewind()->step();
+ return m_save.rewind()->step();
}
@@ -713,8 +711,7 @@ void running_machine::rewind_step()
void running_machine::rewind_invalidate()
{
- if (m_save.rewind()->enabled())
- m_save.rewind()->invalidate();
+ m_save.rewind()->invalidate();
}
@@ -960,6 +957,9 @@ void running_machine::handle_saveload()
if (saverr != STATERR_NONE && m_saveload_schedule == saveload_schedule::SAVE)
file.remove_on_close();
}
+ else if (openflags == OPEN_FLAG_READ && filerr == osd_file::error::NOT_FOUND)
+ // attempt to load a non-existent savestate, report empty slot
+ popmessage("Error: No savestate file to load.", opname);
else
popmessage("Error: Failed to open file for %s operation.", opname);
}
diff --git a/src/emu/machine.h b/src/emu/machine.h
index efe464983db..098d677b077 100644
--- a/src/emu/machine.h
+++ b/src/emu/machine.h
@@ -230,8 +230,8 @@ public:
void immediate_load(const char *filename);
// rewind operations
- void rewind_capture();
- void rewind_step();
+ bool rewind_capture();
+ bool rewind_step();
void rewind_invalidate();
// scheduled operations
diff --git a/src/emu/save.cpp b/src/emu/save.cpp
index eff71cd6870..24dbff90c93 100644
--- a/src/emu/save.cpp
+++ b/src/emu/save.cpp
@@ -79,7 +79,12 @@ void save_manager::allow_registration(bool allowed)
// allow/deny registration
m_reg_allowed = allowed;
if (!allowed)
+ {
dump_registry();
+
+ // everything is registered by now, evaluate the savestate size
+ m_rewind->clamp_capacity();
+ }
}
@@ -567,279 +572,304 @@ rewinder::rewinder(save_manager &save)
: m_save(save)
, m_enabled(save.machine().options().rewind())
, m_capacity(save.machine().options().rewind_capacity())
+ , m_current_index(REWIND_INDEX_NONE)
+ , m_first_invalid_index(REWIND_INDEX_NONE)
+ , m_first_time_warning(true)
+ , m_first_time_note(true)
{
}
//-------------------------------------------------
-// check_size - shrink the state list if it is
-// about to hit the capacity
+// clamp_capacity - safety checks for commandline
+// override
//-------------------------------------------------
-void rewinder::check_size()
+void rewinder::clamp_capacity()
{
- // safety check that shouldn't be allowed to trigger
- if (m_state_list.size() > m_capacity)
- {
- // drop all states beyond capacity
- uint32_t count = m_state_list.size() - m_capacity;
- m_state_list.erase(m_state_list.begin(), m_state_list.begin() + count);
- }
+ if (!m_enabled)
+ return;
- // check if we're about to hit capacity
- if (m_state_list.size() == m_capacity)
- {
- // check the last state
- ram_state *last = m_state_list.back().get();
+ const size_t total = m_capacity * 1024 * 1024;
+ const size_t single = ram_state::get_size(m_save);
- // if we're not on top of the list, no need to move states around
- if (!last->m_valid)
- return;
+ // can't set below zero, but allow commandline to override options' upper limit
+ if (total < 0)
+ m_capacity = 0;
- // we can now get the first state and invalidate it
- std::unique_ptr<ram_state> first(std::move(m_state_list.front()));
- first->m_valid = false;
-
- // move it to the end for future use
- m_state_list.push_back(std::move(first));
- m_state_list.erase(m_state_list.begin());
+ // if capacity is below savestate size, can't save anything
+ if (total < single)
+ {
+ m_enabled = false;
+ m_save.machine().logerror("Rewind has been disabled, because rewind capacity is smaller than savestate size.\n");
+ m_save.machine().logerror("Rewind buffer size: %d bytes. Savestate size: %d bytes.\n", total, single);
+ m_save.machine().popmessage("Rewind has been disabled. See error.log for details");
}
}
//-------------------------------------------------
-// report_error - report rewind success or
-// error type
+// invalidate - mark all the future states as
+// invalid to prevent loading them, as the
+// current input might have changed
//-------------------------------------------------
-void rewinder::report_error(save_error error, rewind_operation operation, int index)
+void rewinder::invalidate()
{
- const char *const opname = (operation == rewind_operation::LOAD) ? "load" : "save";
+ if (!m_enabled)
+ return;
- switch (error)
+ // is there anything to invalidate?
+ if (!current_index_is_last())
{
- // internal saveload failures
- case STATERR_ILLEGAL_REGISTRATIONS:
- m_save.machine().popmessage("Error: Unable to %s state due to illegal registrations. See error.log for details.", opname);
- break;
+ // all states starting from the current one will be invalid
+ m_first_invalid_index = m_current_index;
- case STATERR_INVALID_HEADER:
- m_save.machine().popmessage("Error: Unable to %s state due to an invalid header. Make sure the save state is correct for this machine.", opname);
- break;
+ // actually invalidate
+ for (auto it = m_state_list.begin() + m_first_invalid_index; it < m_state_list.end(); ++it)
+ it->get()->m_valid = false;
+ }
+}
- case STATERR_READ_ERROR:
- m_save.machine().popmessage("Error: Unable to %s state due to a read error.", opname);
- break;
- case STATERR_WRITE_ERROR:
- m_save.machine().popmessage("Error: Unable to %s state due to a write error.", opname);
- break;
+//-------------------------------------------------
+// capture - record a single state, returns true
+// on success
+//-------------------------------------------------
- // external saveload failures
- case STATERR_NOT_FOUND:
- if (operation == rewind_operation::LOAD)
- m_save.machine().popmessage("No rewind state to load.");
- break;
+bool rewinder::capture()
+{
+ if (!m_enabled)
+ {
+ report_error(STATERR_DISABLED, rewind_operation::SAVE);
+ return false;
+ }
- // success
- case STATERR_NONE:
+ if (current_index_is_last())
+ {
+ // we need to create a new state
+ std::unique_ptr<ram_state> state = std::make_unique<ram_state>(m_save);
+ const save_error error = state->save();
+
+ // validate the state
+ if (error == STATERR_NONE)
+ // it's safe to append
+ m_state_list.push_back(std::move(state));
+ else
{
- const char *const warning = (m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE) ? ""
- : "\nWarning: Save states are not officially supported for this machine.";
+ // internal error, complain and evacuate
+ report_error(error, rewind_operation::SAVE);
+ return false;
+ }
+ }
+ else
+ {
+ // invalidate the future states
+ invalidate();
- // figure out the qantity of valid states
- int invalid = get_first_invalid_index();
+ // update the existing state
+ ram_state *state = m_state_list.at(m_current_index).get();
+ const save_error error = state->save();
+
+ // validate the state
+ if (error != STATERR_NONE)
+ {
+ // internal error, complain and evacuate
+ report_error(error, rewind_operation::SAVE);
+ return false;
+ }
+ }
- // all states are valid
- if (invalid == REWIND_INDEX_NONE)
- invalid = m_state_list.size();
+ // make sure we will fit in
+ if (!check_size())
+ // the list keeps growing
+ m_current_index++;
- if (operation == rewind_operation::SAVE)
- m_save.machine().popmessage("Rewind state %i captured.\nRewind state count: %i.%s", invalid - 1, invalid, warning);
- else
- m_save.machine().popmessage("Rewind state %i loaded.\nRewind state count: %i.%s", index, invalid, warning);
- }
- break;
+ // update first invalid index
+ if (current_index_is_last())
+ m_first_invalid_index = REWIND_INDEX_NONE;
+ else
+ m_first_invalid_index = m_current_index + 1;
- // something that shouldn't be allowed to happen
- default:
- m_save.machine().popmessage("Error: Unknown error during state %s.", opname);
- break;
- }
+ // success
+ report_error(STATERR_NONE, rewind_operation::SAVE);
+ return true;
}
//-------------------------------------------------
-// get_current_index - get the index of the
-// state to assume as current
+// step - single step back in time, returns true
+// on success
//-------------------------------------------------
-int rewinder::get_current_index()
+bool rewinder::step()
{
- // nowhere to search
- if (m_state_list.empty())
- return REWIND_INDEX_NONE;
+ if (!m_enabled)
+ {
+ report_error(STATERR_DISABLED, rewind_operation::LOAD);
+ return false;
+ }
- // fetch the current machine time
- attotime curtime = m_save.machine().time();
+ // do we have states to load?
+ if (m_current_index <= REWIND_INDEX_FIRST || m_first_invalid_index == REWIND_INDEX_FIRST)
+ {
+ // no valid states, complain and evacuate
+ report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
+ return false;
+ }
- // find the state at the current time, or at least the first one after
- for (auto it = m_state_list.begin(); it < m_state_list.end(); ++it)
- if (it->get()->m_time >= curtime)
- return it - m_state_list.begin();
+ // prepare to load the last valid index if we're too far ahead
+ if (m_first_invalid_index > REWIND_INDEX_NONE && m_current_index > m_first_invalid_index)
+ m_current_index = m_first_invalid_index;
- // all states are older
- return REWIND_INDEX_NONE;
-}
+ // step back and obtain the state pointer
+ ram_state *state = m_state_list.at(--m_current_index).get();
+ // try to load and report the result
+ const save_error error = state->load();
+ report_error(error, rewind_operation::LOAD);
-//-------------------------------------------------
-// get_first_invalid_index - get the index of the
-// first invalid state
-//-------------------------------------------------
-
-int rewinder::get_first_invalid_index()
-{
- for (auto it = m_state_list.begin(); it < m_state_list.end(); ++it)
- if (!it->get()->m_valid)
- return it - m_state_list.begin();
+ if (error == save_error::STATERR_NONE)
+ return true;
- // all states are valid
- return REWIND_INDEX_NONE;
+ return false;
}
//-------------------------------------------------
-// invalidate - mark all the future states as
-// invalid
+// check_size - shrink the state list if it is
+// about to hit the capacity. returns true if
+// the list got shrank
//-------------------------------------------------
-int rewinder::invalidate()
+bool rewinder::check_size()
{
- // fetch the current state index
- int index = get_current_index();
+ if (!m_enabled)
+ return false;
- // more invalid states may be farther back, account for them too
- int invalid = get_first_invalid_index();
+ // state sizes in bytes
+ const size_t singlesize = ram_state::get_size(m_save);
+ size_t totalsize = m_state_list.size() * singlesize;
- // roll back if we can
- if (invalid != REWIND_INDEX_NONE && (index == REWIND_INDEX_NONE || invalid < index))
- index = invalid;
+ // convert our limit from megabytes
+ const size_t capsize = m_capacity * 1024 * 1024;
- if (index != REWIND_INDEX_NONE)
+ // safety check that shouldn't be allowed to trigger
+ if (totalsize > capsize)
{
- // if it's the last state in the list, skip further invalidation
- if (++index >= m_state_list.size())
- return index;
+ // states to remove
+ const u32 count = (totalsize - capsize) / singlesize;
- // invalidate all the future states, as the current input might have changed
- for (auto it = m_state_list.begin() + index; it < m_state_list.end(); ++it)
- it->get()->m_valid = false;
+ // drop everything that's beyond capacity
+ m_state_list.erase(m_state_list.begin(), m_state_list.begin() + count);
}
- // index of the first invalid state
- return index;
-}
+ // update before new check
+ totalsize = m_state_list.size() * singlesize;
+ // check if capacity will be hit by the newly captured state
+ if (totalsize + singlesize >= capsize)
+ {
+ // check if we have spare states ahead
+ if (!current_index_is_last())
+ // no need to move states around
+ return false;
-//-------------------------------------------------
-// capture - record a single state
-//-------------------------------------------------
-
-void rewinder::capture()
-{
- // fetch the current state index and invalidate the future states
- int index = invalidate();
+ // we can now get the first state and invalidate it
+ std::unique_ptr<ram_state> first(std::move(m_state_list.front()));
+ first->m_valid = false;
- if (index == REWIND_INDEX_NONE)
- {
- // no current state, create one
- std::unique_ptr<ram_state> state = std::make_unique<ram_state>(m_save);
- save_error error = state->save();
+ // move it to the end for future use
+ m_state_list.push_back(std::move(first));
+ m_state_list.erase(m_state_list.begin());
- // validate the state
- if (error == STATERR_NONE)
+ if (m_first_time_note)
{
- // it's safe to append
- m_state_list.push_back(std::move(state));
- }
- else
- {
- // internal error, complain and evacuate
- report_error(error, rewind_operation::SAVE);
- return;
- }
- }
- else
- {
- // update the existing state
- ram_state *state = m_state_list.at(--index).get();
- save_error error = state->save();
-
- // validate the state
- if (error != STATERR_NONE)
- {
- // internal error, complain and evacuate
- report_error(error, rewind_operation::SAVE);
- return;
+ m_save.machine().logerror("Rewind note: Capacity has been reached. Old savestates will be erased.\n");
+ m_save.machine().logerror("Capacity: %d bytes. Savestate size: %d bytes. Savestate count: %d.\n",
+ totalsize, singlesize, m_state_list.size());
+ m_first_time_note = false;
}
- }
- // make sure we still fit in
- check_size();
+ return true;
+ }
- // success
- report_error(STATERR_NONE, rewind_operation::SAVE);
+ return false;
}
//-------------------------------------------------
-// step - single step back in time
+// report_error - report rewind results
//-------------------------------------------------
-void rewinder::step()
+void rewinder::report_error(save_error error, rewind_operation operation)
{
- // check presence of states
- if (m_state_list.empty())
+ const char *const opname = (operation == rewind_operation::LOAD) ? "load" : "save";
+ switch (error)
{
- // no states, complain and evacuate
- report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
- return;
- }
+ // internal saveload failures
+ case STATERR_ILLEGAL_REGISTRATIONS:
+ m_save.machine().logerror("Rewind error: Unable to %s state due to illegal registrations.", opname);
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ break;
- // fetch the current state index
- int index = get_current_index();
+ case STATERR_INVALID_HEADER:
+ m_save.machine().logerror("Rewind error: Unable to %s state due to an invalid header. "
+ "Make sure the save state is correct for this machine.\n", opname);
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ break;
- // if there is room, retreat
- if (index != REWIND_INDEX_FIRST)
- {
- // we may be on top of the list, when all states are older
- if (index == REWIND_INDEX_NONE)
+ case STATERR_READ_ERROR:
+ m_save.machine().logerror("Rewind error: Unable to %s state due to a read error.\n", opname);
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ break;
+
+ case STATERR_WRITE_ERROR:
+ m_save.machine().logerror("Rewind error: Unable to %s state due to a write error.\n", opname);
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ break;
+
+ // external saveload failures
+ case STATERR_NOT_FOUND:
+ if (operation == rewind_operation::LOAD)
+ {
+ m_save.machine().logerror("Rewind error: No rewind state to load.\n");
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ }
+ break;
+
+ case STATERR_DISABLED:
+ if (operation == rewind_operation::LOAD)
{
- // use the last consecutively valid state, to ensure rewinder integrity
- index = get_first_invalid_index();
+ m_save.machine().logerror("Rewind error: Rewind is disabled.\n");
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ }
+ break;
- if (index == REWIND_INDEX_NONE)
- // all states are valid
- index = m_state_list.size();
- else if (index == REWIND_INDEX_FIRST)
+ // success
+ case STATERR_NONE:
+ {
+ const u64 supported = m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE;
+ const char *const warning = supported || !m_first_time_warning ? "" :
+ "Rewind warning: Save states are not officially supported for this machine.\n";
+ const char *const opnamed = (operation == rewind_operation::LOAD) ? "loaded" : "captured";
+
+ // for rewinding outside of debugger, give some indication that rewind has worked, as screen doesn't update
+ m_save.machine().popmessage("Rewind state %i %s.\n%s", m_current_index + 1, opnamed, warning);
+ if (m_first_time_warning && operation == rewind_operation::LOAD && !supported)
{
- // no valid states, complain and evacuate
- report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
- return;
+ m_save.machine().logerror(warning);
+ m_first_time_warning = false;
}
}
+ break;
- // obtain the state pointer
- ram_state *state = m_state_list.at(--index).get();
-
- // try to load and report the result
- report_error(state->load(), rewind_operation::LOAD, index);
- return;
+ // something that shouldn't be allowed to happen
+ default:
+ m_save.machine().logerror("Error: Unknown error during state %s.\n", opname);
+ m_save.machine().popmessage("Rewind error occured. See error.log for details.");
+ break;
}
-
- // no valid states, complain
- report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
}
diff --git a/src/emu/save.h b/src/emu/save.h
index e5c8c2fdbbf..baf18258d09 100644
--- a/src/emu/save.h
+++ b/src/emu/save.h
@@ -30,7 +30,8 @@ enum save_error
STATERR_ILLEGAL_REGISTRATIONS,
STATERR_INVALID_HEADER,
STATERR_READ_ERROR,
- STATERR_WRITE_ERROR
+ STATERR_WRITE_ERROR,
+ STATERR_DISABLED
};
@@ -181,7 +182,7 @@ private:
running_machine & m_machine; // reference to our machine
std::unique_ptr<rewinder> m_rewind; // rewinder
bool m_reg_allowed; // are registrations allowed?
- int m_illegal_regs; // number of illegal registrations
+ s32 m_illegal_regs; // number of illegal registrations
std::vector<std::unique_ptr<state_entry>> m_entry_list; // list of registered entries
std::vector<std::unique_ptr<ram_state>> m_ramstate_list; // list of ram states
@@ -191,12 +192,12 @@ private:
class ram_state
{
- save_manager & m_save; // reference to save_manager
- util::vectorstream m_data; // save data buffer
+ save_manager & m_save; // reference to save_manager
+ util::vectorstream m_data; // save data buffer
public:
- bool m_valid; // can we load this state?
- attotime m_time; // machine timestamp
+ bool m_valid; // can we load this state?
+ attotime m_time; // machine timestamp
ram_state(save_manager &save);
static size_t get_size(save_manager &save);
@@ -206,9 +207,13 @@ public:
class rewinder
{
- save_manager & m_save; // reference to save_manager
- bool m_enabled; // enable rewind savestates
- uint32_t m_capacity; // imposed limit of total states (1-500)
+ save_manager & m_save; // reference to save_manager
+ bool m_enabled; // enable rewind savestates
+ size_t m_capacity; // total memory rewind states can occupy (MB, limited to 1-2048 in options)
+ s32 m_current_index; // where we are in time
+ s32 m_first_invalid_index; // all states before this one are guarateed to be valid
+ bool m_first_time_warning; // keep track of warnings we report
+ bool m_first_time_note; // keep track of notes
std::vector<std::unique_ptr<ram_state>> m_state_list; // rewinder's own ram states
// load/save management
@@ -221,20 +226,20 @@ class rewinder
enum
{
REWIND_INDEX_NONE = -1,
- REWIND_INDEX_FIRST = 0
+ REWIND_INDEX_FIRST
};
-
- int get_current_index();
- int get_first_invalid_index();
- void check_size();
- void report_error(save_error type, rewind_operation operation, int index = REWIND_INDEX_FIRST);
+
+ bool check_size();
+ bool current_index_is_last() { return m_current_index == m_state_list.size() - 1; }
+ void report_error(save_error type, rewind_operation operation);
public:
rewinder(save_manager &save);
bool enabled() { return m_enabled; }
- int invalidate();
- void capture();
- void step();
+ void clamp_capacity();
+ void invalidate();
+ bool capture();
+ bool step();
};
diff --git a/src/osd/modules/debugger/win/debugwininfo.cpp b/src/osd/modules/debugger/win/debugwininfo.cpp
index 2f2b43a429a..ac60b980439 100644
--- a/src/osd/modules/debugger/win/debugwininfo.cpp
+++ b/src/osd/modules/debugger/win/debugwininfo.cpp
@@ -201,8 +201,10 @@ bool debugwin_info::handle_key(WPARAM wparam, LPARAM lparam)
return true;
case VK_F11:
- if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
+ if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) && ((GetAsyncKeyState(VK_CONTROL) & 0x8000) == 0))
SendMessage(m_wnd, WM_COMMAND, ID_STEP_OUT, 0);
+ else if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
+ SendMessage(m_wnd, WM_COMMAND, ID_REWIND_STEP, 0);
else
SendMessage(m_wnd, WM_COMMAND, ID_STEP, 0);
return true;
@@ -325,6 +327,21 @@ bool debugwin_info::handle_command(WPARAM wparam, LPARAM lparam)
machine().debugger().cpu().get_visible_cpu()->debug()->single_step_out();
return true;
+ case ID_REWIND_STEP:
+ machine().rewind_step();
+
+ // clear all PC & memory tracks
+ for (device_t &device : device_iterator(machine().root_device()))
+ {
+ device.debug()->track_pc_data_clear();
+ device.debug()->track_mem_data_clear();
+ }
+
+ // update debugger and emulator window
+ machine().debug_view().update_all();
+ machine().debugger().refresh_display();
+ return true;
+
case ID_HARD_RESET:
machine().schedule_hard_reset();
return true;
@@ -533,6 +550,7 @@ HMENU debugwin_info::create_standard_menubar()
AppendMenu(debugmenu, MF_ENABLED, ID_STEP, TEXT("Step Into\tF11"));
AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OVER, TEXT("Step Over\tF10"));
AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OUT, TEXT("Step Out\tShift+F11"));
+ AppendMenu(debugmenu, MF_ENABLED, ID_REWIND_STEP, TEXT("Rewind Step\tCtrl+F11"));
AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
AppendMenu(debugmenu, MF_ENABLED, ID_SOFT_RESET, TEXT("Soft Reset\tF3"));
AppendMenu(debugmenu, MF_ENABLED, ID_HARD_RESET, TEXT("Hard Reset\tShift+F3"));
diff --git a/src/osd/modules/debugger/win/debugwininfo.h b/src/osd/modules/debugger/win/debugwininfo.h
index c967329447b..0d871f79b6f 100644
--- a/src/osd/modules/debugger/win/debugwininfo.h
+++ b/src/osd/modules/debugger/win/debugwininfo.h
@@ -70,6 +70,7 @@ protected:
ID_STEP,
ID_STEP_OVER,
ID_STEP_OUT,
+ ID_REWIND_STEP,
ID_HARD_RESET,
ID_SOFT_RESET,
ID_EXIT,