mirror of
https://github.com/holub/mame
synced 2025-06-30 07:58:56 +03:00
Merge pull request #2959 from vadosnaprimer/rewind
Rewind implementation fixes and improvements
This commit is contained in:
commit
ffc0dadaa8
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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> ¶ms)
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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));
|
||||
|
@ -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)" },
|
||||
|
@ -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) )
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
454
src/emu/save.cpp
454
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,34 +572,208 @@ 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)
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
const size_t total = m_capacity * 1024 * 1024;
|
||||
const size_t single = ram_state::get_size(m_save);
|
||||
|
||||
// can't set below zero, but allow commandline to override options' upper limit
|
||||
if (total < 0)
|
||||
m_capacity = 0;
|
||||
|
||||
// if capacity is below savestate size, can't save anything
|
||||
if (total < single)
|
||||
{
|
||||
// drop all states beyond capacity
|
||||
uint32_t count = m_state_list.size() - m_capacity;
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// invalidate - mark all the future states as
|
||||
// invalid to prevent loading them, as the
|
||||
// current input might have changed
|
||||
//-------------------------------------------------
|
||||
|
||||
void rewinder::invalidate()
|
||||
{
|
||||
if (!m_enabled)
|
||||
return;
|
||||
|
||||
// is there anything to invalidate?
|
||||
if (!current_index_is_last())
|
||||
{
|
||||
// all states starting from the current one will be invalid
|
||||
m_first_invalid_index = m_current_index;
|
||||
|
||||
// actually invalidate
|
||||
for (auto it = m_state_list.begin() + m_first_invalid_index; it < m_state_list.end(); ++it)
|
||||
it->get()->m_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// capture - record a single state, returns true
|
||||
// on success
|
||||
//-------------------------------------------------
|
||||
|
||||
bool rewinder::capture()
|
||||
{
|
||||
if (!m_enabled)
|
||||
{
|
||||
report_error(STATERR_DISABLED, rewind_operation::SAVE);
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
// internal error, complain and evacuate
|
||||
report_error(error, rewind_operation::SAVE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// invalidate the future states
|
||||
invalidate();
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we will fit in
|
||||
if (!check_size())
|
||||
// the list keeps growing
|
||||
m_current_index++;
|
||||
|
||||
// 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;
|
||||
|
||||
// success
|
||||
report_error(STATERR_NONE, rewind_operation::SAVE);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// step - single step back in time, returns true
|
||||
// on success
|
||||
//-------------------------------------------------
|
||||
|
||||
bool rewinder::step()
|
||||
{
|
||||
if (!m_enabled)
|
||||
{
|
||||
report_error(STATERR_DISABLED, rewind_operation::LOAD);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
// 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);
|
||||
|
||||
if (error == save_error::STATERR_NONE)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// check_size - shrink the state list if it is
|
||||
// about to hit the capacity. returns true if
|
||||
// the list got shrank
|
||||
//-------------------------------------------------
|
||||
|
||||
bool rewinder::check_size()
|
||||
{
|
||||
if (!m_enabled)
|
||||
return false;
|
||||
|
||||
// state sizes in bytes
|
||||
const size_t singlesize = ram_state::get_size(m_save);
|
||||
size_t totalsize = m_state_list.size() * singlesize;
|
||||
|
||||
// convert our limit from megabytes
|
||||
const size_t capsize = m_capacity * 1024 * 1024;
|
||||
|
||||
// safety check that shouldn't be allowed to trigger
|
||||
if (totalsize > capsize)
|
||||
{
|
||||
// states to remove
|
||||
const u32 count = (totalsize - capsize) / singlesize;
|
||||
|
||||
// drop everything that's beyond capacity
|
||||
m_state_list.erase(m_state_list.begin(), m_state_list.begin() + count);
|
||||
}
|
||||
|
||||
// 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();
|
||||
// update before new check
|
||||
totalsize = m_state_list.size() * singlesize;
|
||||
|
||||
// if we're not on top of the list, no need to move states around
|
||||
if (!last->m_valid)
|
||||
return;
|
||||
// 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;
|
||||
|
||||
// we can now get the first state and invalidate it
|
||||
std::unique_ptr<ram_state> first(std::move(m_state_list.front()));
|
||||
@ -603,246 +782,97 @@ void rewinder::check_size()
|
||||
// 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 (m_first_time_note)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// report_error - report rewind success or
|
||||
// error type
|
||||
// report_error - report rewind results
|
||||
//-------------------------------------------------
|
||||
|
||||
void rewinder::report_error(save_error error, rewind_operation operation, int index)
|
||||
void rewinder::report_error(save_error error, rewind_operation operation)
|
||||
{
|
||||
const char *const opname = (operation == rewind_operation::LOAD) ? "load" : "save";
|
||||
|
||||
switch (error)
|
||||
{
|
||||
// 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);
|
||||
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;
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
case STATERR_READ_ERROR:
|
||||
m_save.machine().popmessage("Error: Unable to %s state due to a read error.", opname);
|
||||
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().popmessage("Error: Unable to %s state due to a write error.", opname);
|
||||
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().popmessage("No rewind state to 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)
|
||||
{
|
||||
m_save.machine().logerror("Rewind error: Rewind is disabled.\n");
|
||||
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
|
||||
}
|
||||
break;
|
||||
|
||||
// success
|
||||
case STATERR_NONE:
|
||||
{
|
||||
const char *const warning = (m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE) ? ""
|
||||
: "\nWarning: Save states are not officially supported for this machine.";
|
||||
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";
|
||||
|
||||
// figure out the qantity of valid states
|
||||
int invalid = get_first_invalid_index();
|
||||
|
||||
// all states are valid
|
||||
if (invalid == REWIND_INDEX_NONE)
|
||||
invalid = m_state_list.size();
|
||||
|
||||
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);
|
||||
// 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)
|
||||
{
|
||||
m_save.machine().logerror(warning);
|
||||
m_first_time_warning = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// something that shouldn't be allowed to happen
|
||||
default:
|
||||
m_save.machine().popmessage("Error: Unknown error during state %s.", opname);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// get_current_index - get the index of the
|
||||
// state to assume as current
|
||||
//-------------------------------------------------
|
||||
|
||||
int rewinder::get_current_index()
|
||||
{
|
||||
// nowhere to search
|
||||
if (m_state_list.empty())
|
||||
return REWIND_INDEX_NONE;
|
||||
|
||||
// fetch the current machine time
|
||||
attotime curtime = m_save.machine().time();
|
||||
|
||||
// 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();
|
||||
|
||||
// all states are older
|
||||
return REWIND_INDEX_NONE;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// 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();
|
||||
|
||||
// all states are valid
|
||||
return REWIND_INDEX_NONE;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// invalidate - mark all the future states as
|
||||
// invalid
|
||||
//-------------------------------------------------
|
||||
|
||||
int rewinder::invalidate()
|
||||
{
|
||||
// fetch the current state index
|
||||
int index = get_current_index();
|
||||
|
||||
// more invalid states may be farther back, account for them too
|
||||
int invalid = get_first_invalid_index();
|
||||
|
||||
// roll back if we can
|
||||
if (invalid != REWIND_INDEX_NONE && (index == REWIND_INDEX_NONE || invalid < index))
|
||||
index = invalid;
|
||||
|
||||
if (index != REWIND_INDEX_NONE)
|
||||
{
|
||||
// if it's the last state in the list, skip further invalidation
|
||||
if (++index >= m_state_list.size())
|
||||
return index;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// index of the first invalid state
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// capture - record a single state
|
||||
//-------------------------------------------------
|
||||
|
||||
void rewinder::capture()
|
||||
{
|
||||
// fetch the current state index and invalidate the future states
|
||||
int index = invalidate();
|
||||
|
||||
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();
|
||||
|
||||
// validate the state
|
||||
if (error == STATERR_NONE)
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we still fit in
|
||||
check_size();
|
||||
|
||||
// success
|
||||
report_error(STATERR_NONE, rewind_operation::SAVE);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// step - single step back in time
|
||||
//-------------------------------------------------
|
||||
|
||||
void rewinder::step()
|
||||
{
|
||||
// check presence of states
|
||||
if (m_state_list.empty())
|
||||
{
|
||||
// no states, complain and evacuate
|
||||
report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch the current state index
|
||||
int index = get_current_index();
|
||||
|
||||
// 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)
|
||||
{
|
||||
// use the last consecutively valid state, to ensure rewinder integrity
|
||||
index = get_first_invalid_index();
|
||||
|
||||
if (index == REWIND_INDEX_NONE)
|
||||
// all states are valid
|
||||
index = m_state_list.size();
|
||||
else if (index == REWIND_INDEX_FIRST)
|
||||
{
|
||||
// no valid states, complain and evacuate
|
||||
report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// no valid states, complain
|
||||
report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// state_entry - constructor
|
||||
//-------------------------------------------------
|
||||
|
@ -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();
|
||||
};
|
||||
|
||||
|
||||
|
@ -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"));
|
||||
|
@ -70,6 +70,7 @@ protected:
|
||||
ID_STEP,
|
||||
ID_STEP_OVER,
|
||||
ID_STEP_OUT,
|
||||
ID_REWIND_STEP,
|
||||
ID_HARD_RESET,
|
||||
ID_SOFT_RESET,
|
||||
ID_EXIT,
|
||||
|
Loading…
Reference in New Issue
Block a user