Rewind feature and RAM savestates.

This starts the work requested in #2398.

How RAM states work.

Implemented using util::vectorstream. Instead of dumping m_save.m_entry_list to file, it writes them as binary to vectorstream. Compression is not used, as it would slow down the process. The header is written as usual, also in binary. When a state is loaded, the savestate data gets binary-read from vectorstream.

How rewind works.

Rewind is optional, it can be turned off through MAME GUI while not running. Rewind capacity is available there too. Rewind step hotkey is available from the standard hotkey menu. In the debugger you have the "rewind" command ("rw" shortcut) that works the same as the hotkey.

Every time you advance a frame (pause step), rewinder captures a RAM savestate of the frame you were at. It does the same when you do step into/over/out in the debugger. Every time it captures a new state (and when you unpause), it marks as invalid all its states that go after the current machine time, because input might change, so they are not relevant anymore. It keeps their buffers allocated though, for future use. When rewinder runs out of allowed amount of savestates it can have, it invalidates the first state in the list and tosses its unique_ptr to the end of the list, then it uses its buffer to capture a new state. When you hit the rewind step key, or use "rewind" command in the debugger, it loads a state that is immediately before the current machine time. Invalid states between valid ones are not allowed to appear, as that breaks rewinder integrity and causes problems. Rewinder keeps its own set of ram states as a vector of unique_ptr's. All rewinder operations and errors get reported using machine().popmessage().
This commit is contained in:
vadosnaprimer 2017-10-10 21:31:01 +03:00
parent 51c06e670f
commit a66cb36cc7
13 changed files with 603 additions and 27 deletions

View File

@ -206,6 +206,9 @@ debugger_commands::debugger_commands(running_machine& machine, debugger_cpu& cpu
m_console.register_command("stateload", CMDFLAG_NONE, 0, 1, 1, std::bind(&debugger_commands::execute_stateload, this, _1, _2));
m_console.register_command("sl", CMDFLAG_NONE, 0, 1, 1, std::bind(&debugger_commands::execute_stateload, this, _1, _2));
m_console.register_command("rewind", CMDFLAG_NONE, 0, 0, 0, std::bind(&debugger_commands::execute_rewind, this, _1, _2));
m_console.register_command("rw", CMDFLAG_NONE, 0, 0, 0, std::bind(&debugger_commands::execute_rewind, this, _1, _2));
m_console.register_command("save", CMDFLAG_NONE, AS_PROGRAM, 3, 4, std::bind(&debugger_commands::execute_save, this, _1, _2));
m_console.register_command("saved", CMDFLAG_NONE, AS_DATA, 3, 4, std::bind(&debugger_commands::execute_save, this, _1, _2));
m_console.register_command("savei", CMDFLAG_NONE, AS_IO, 3, 4, std::bind(&debugger_commands::execute_save, this, _1, _2));
@ -1627,7 +1630,7 @@ void debugger_commands::execute_stateload(int ref, const std::vector<std::string
const std::string &filename(params[0]);
m_machine.immediate_load(filename.c_str());
// Clear all PC & memory tracks
// clear all PC & memory tracks
for (device_t &device : device_iterator(m_machine.root_device()))
{
device.debug()->track_pc_data_clear();
@ -1637,6 +1640,24 @@ void debugger_commands::execute_stateload(int ref, const std::vector<std::string
}
/*-------------------------------------------------
execute_rewind - execute the rewind command
-------------------------------------------------*/
void debugger_commands::execute_rewind(int ref, const std::vector<std::string> &params)
{
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");
}
/*-------------------------------------------------
execute_save - execute the save command
-------------------------------------------------*/

View File

@ -131,6 +131,7 @@ private:
void execute_hotspot(int ref, const std::vector<std::string> &params);
void execute_statesave(int ref, const std::vector<std::string> &params);
void execute_stateload(int ref, const std::vector<std::string> &params);
void execute_rewind(int ref, const std::vector<std::string> &params);
void execute_save(int ref, const std::vector<std::string> &params);
void execute_load(int ref, const std::vector<std::string> &params);
void execute_dump(int ref, const std::vector<std::string> &params);

View File

@ -1902,6 +1902,7 @@ void device_debug::single_step(int numsteps)
{
assert(m_exec != nullptr);
m_device.machine().rewind_capture();
m_stepsleft = numsteps;
m_stepaddr = ~0;
m_flags |= DEBUG_FLAG_STEPPING;
@ -1918,6 +1919,7 @@ void device_debug::single_step_over(int numsteps)
{
assert(m_exec != nullptr);
m_device.machine().rewind_capture();
m_stepsleft = numsteps;
m_stepaddr = ~0;
m_flags |= DEBUG_FLAG_STEPPING_OVER;
@ -1934,6 +1936,7 @@ void device_debug::single_step_out()
{
assert(m_exec != nullptr);
m_device.machine().rewind_capture();
m_stepsleft = 100;
m_stepaddr = ~0;
m_flags |= DEBUG_FLAG_STEPPING_OUT;
@ -1950,6 +1953,7 @@ void device_debug::go(offs_t targetpc)
{
assert(m_exec != nullptr);
m_device.machine().rewind_invalidate();
m_stopaddr = targetpc;
m_flags |= DEBUG_FLAG_STOP_PC;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
@ -1964,6 +1968,7 @@ void device_debug::go_vblank()
{
assert(m_exec != nullptr);
m_device.machine().rewind_invalidate();
m_flags |= DEBUG_FLAG_STOP_VBLANK;
m_device.machine().debugger().cpu().go_vblank();
}
@ -1978,6 +1983,7 @@ void device_debug::go_interrupt(int irqline)
{
assert(m_exec != nullptr);
m_device.machine().rewind_invalidate();
m_stopirq = irqline;
m_flags |= DEBUG_FLAG_STOP_INTERRUPT;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
@ -1997,6 +2003,7 @@ void device_debug::go_exception(int exception)
{
assert(m_exec != nullptr);
m_device.machine().rewind_invalidate();
m_stopexception = exception;
m_flags |= DEBUG_FLAG_STOP_EXCEPTION;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
@ -2012,6 +2019,7 @@ void device_debug::go_milliseconds(u64 milliseconds)
{
assert(m_exec != nullptr);
m_device.machine().rewind_invalidate();
m_stoptime = m_device.machine().time() + attotime::from_msec(milliseconds);
m_flags |= DEBUG_FLAG_STOP_TIME;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);

View File

@ -62,6 +62,8 @@ const options_entry emu_options::s_option_entries[] =
{ nullptr, nullptr, OPTION_HEADER, "CORE STATE/PLAYBACK OPTIONS" },
{ 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, "1", OPTION_BOOLEAN, "enable rewind savestates" },
{ OPTION_REWIND_CAPACITY "(1-500)", "100", OPTION_INTEGER, "quantity of states rewind buffer can contain" },
{ 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)" },

View File

@ -51,6 +51,8 @@
// core state/playback options
#define OPTION_STATE "state"
#define OPTION_AUTOSAVE "autosave"
#define OPTION_REWIND "rewind"
#define OPTION_REWIND_CAPACITY "rewind_capacity"
#define OPTION_PLAYBACK "playback"
#define OPTION_RECORD "record"
#define OPTION_RECORD_TIMECODE "record_timecode"
@ -327,6 +329,8 @@ public:
// core state/playback options
const char *state() const { return value(OPTION_STATE); }
bool autosave() const { return bool_value(OPTION_AUTOSAVE); }
int rewind() const { return bool_value(OPTION_REWIND); }
int rewind_capacity() const { return int_value(OPTION_REWIND_CAPACITY); }
const char *playback() const { return value(OPTION_PLAYBACK); }
const char *record() const { return value(OPTION_RECORD); }
bool record_timecode() const { return bool_value(OPTION_RECORD_TIMECODE); }

View File

@ -820,11 +820,12 @@ inline void construct_core_types_keypad(simple_list<input_type_entry> &typelist)
inline void construct_core_types_UI(simple_list<input_type_entry> &typelist)
{
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ON_SCREEN_DISPLAY,"On Screen Display", input_seq(KEYCODE_TILDE) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DEBUG_BREAK, "Break in Debugger", input_seq(KEYCODE_TILDE) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ON_SCREEN_DISPLAY,"On Screen Display", input_seq(KEYCODE_TILDE, input_seq::not_code, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DEBUG_BREAK, "Break in Debugger", input_seq(KEYCODE_TILDE, input_seq::not_code, KEYCODE_LSHIFT) )
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_seq::or_code, KEYCODE_P, 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_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) )

View File

@ -322,6 +322,7 @@ enum ioport_type
IPT_UI_DEBUG_BREAK,
IPT_UI_PAUSE,
IPT_UI_PAUSE_SINGLE,
IPT_UI_REWIND_SINGLE,
IPT_UI_RESET_MACHINE,
IPT_UI_SOFT_RESET,
IPT_UI_SHOW_GFX,

View File

@ -682,6 +682,42 @@ void running_machine::immediate_load(const char *filename)
}
//-------------------------------------------------
// rewind_capture - capture and append a new
// state to the rewind list
//-------------------------------------------------
void running_machine::rewind_capture()
{
if (m_save.rewind()->enabled())
m_save.rewind()->capture();
}
//-------------------------------------------------
// rewind_step - a single step back through
// rewind states
//-------------------------------------------------
void running_machine::rewind_step()
{
if (m_save.rewind()->enabled())
m_save.rewind()->step();
}
//-------------------------------------------------
// rewind_invalidate - mark all the future rewind
// states as invalid
//-------------------------------------------------
void running_machine::rewind_invalidate()
{
if (m_save.rewind()->enabled())
m_save.rewind()->invalidate();
}
//-------------------------------------------------
// pause - pause the system
//-------------------------------------------------
@ -721,7 +757,10 @@ void running_machine::resume()
void running_machine::toggle_pause()
{
if (paused())
{
rewind_invalidate();
resume();
}
else
pause();
}

View File

@ -229,6 +229,11 @@ public:
void immediate_save(const char *filename);
void immediate_load(const char *filename);
// rewind operations
void rewind_capture();
void rewind_step();
void rewind_invalidate();
// scheduled operations
void schedule_exit();
void schedule_hard_reset();

View File

@ -23,6 +23,7 @@
***************************************************************************/
#include "emu.h"
#include "emuopts.h"
#include "coreutil.h"
@ -60,10 +61,11 @@ enum
//-------------------------------------------------
save_manager::save_manager(running_machine &machine)
: m_machine(machine),
m_reg_allowed(true),
m_illegal_regs(0)
: m_machine(machine)
, m_reg_allowed(true)
, m_illegal_regs(0)
{
m_rewind = std::make_unique<rewinder>(*this);
}
@ -169,8 +171,8 @@ void save_manager::save_memory(device_t *device, const char *module, const char
totalname = string_format("%s/%X/%s", module, index, name);
// look for duplicates and an entry to insert in front of
std::vector<std::unique_ptr<state_entry>>::iterator insert_after = m_entry_list.begin();
for (auto it = m_entry_list.begin(); it!= m_entry_list.end(); ++it)
std::vector<std::unique_ptr<state_entry>>::iterator insert_after = m_entry_list.begin();
for (auto it = m_entry_list.begin(); it != m_entry_list.end(); ++it)
{
// stop when we find an entry whose name is after ours
if (it->get()->m_name.compare(totalname)>0)
@ -183,7 +185,7 @@ void save_manager::save_memory(device_t *device, const char *module, const char
}
// insert us into the list
m_entry_list.insert(insert_after,std::make_unique<state_entry>(val, totalname.c_str(), device, module, tag ? tag : "", index, valsize, valcount));
m_entry_list.insert(insert_after, std::make_unique<state_entry>(val, totalname.c_str(), device, module, tag ? tag : "", index, valsize, valcount));
}
@ -213,18 +215,19 @@ save_error save_manager::check_file(running_machine &machine, emu_file &file, co
return validate_header(header, gamename, sig, errormsg, "");
}
//-------------------------------------------------
// dispatch_postload - invoke all registered
// postload callbacks for updates
//-------------------------------------------------
void save_manager::dispatch_postload()
{
for (auto &func : m_postload_list)
func->m_func();
}
//-------------------------------------------------
// read_file - read the data from a file
//-------------------------------------------------
@ -269,18 +272,19 @@ save_error save_manager::read_file(emu_file &file)
return STATERR_NONE;
}
//-------------------------------------------------
// dispatch_presave - invoke all registered
// presave callbacks for updates
//-------------------------------------------------
void save_manager::dispatch_presave()
{
for (auto &func : m_presave_list)
func->m_func();
}
//-------------------------------------------------
// write_file - writes the data to a file
//-------------------------------------------------
@ -414,27 +418,452 @@ save_manager::state_callback::state_callback(save_prepost_delegate callback)
}
//-------------------------------------------------
// ram_state - constructor
//-------------------------------------------------
ram_state::ram_state(save_manager &save)
: m_save(save)
, m_data()
, m_valid(false)
, m_time(m_save.machine().time())
{
m_data.reserve(get_size(save));
m_data.clear();
m_data.rdbuf()->clear();
m_data.seekp(0);
m_data.seekg(0);
}
//-------------------------------------------------
// get_size - utility function to get the
// uncompressed size of a state
//-------------------------------------------------
size_t ram_state::get_size(save_manager &save)
{
size_t totalsize = 0;
for (auto &entry : save.m_entry_list)
{
totalsize += entry->m_typesize * entry->m_typecount;
}
return totalsize + HEADER_SIZE;
}
//-------------------------------------------------
// save - write the current machine state to the
// allocated stream
//-------------------------------------------------
save_error ram_state::save()
{
// initialize
m_valid = false;
m_data.seekp(0);
// if we have illegal registrations, return an error
if (m_save.m_illegal_regs > 0)
return STATERR_ILLEGAL_REGISTRATIONS;
// generate the header
u8 header[HEADER_SIZE];
memcpy(&header[0], STATE_MAGIC_NUM, 8);
header[8] = SAVE_VERSION;
header[9] = NATIVE_ENDIAN_VALUE_LE_BE(0, SS_MSB_FIRST);
strncpy((char *)&header[0x0a], m_save.machine().system().name, 0x1c - 0x0a);
u32 sig = m_save.signature();
*(u32 *)&header[0x1c] = little_endianize_int32(sig);
// write the header
m_data.write((char *)header, sizeof(header));
// check for any errors
if (!m_data)
return STATERR_WRITE_ERROR;
// call the pre-save functions
m_save.dispatch_presave();
// write all the data
for (auto &entry : m_save.m_entry_list)
{
u32 totalsize = entry->m_typesize * entry->m_typecount;
m_data.write((char *)entry->m_data, totalsize);
// check for any errors
if (!m_data)
return STATERR_WRITE_ERROR;
}
// final confirmation
m_valid = true;
m_time = m_save.machine().time();
return STATERR_NONE;
}
//-------------------------------------------------
// load - restore the machine state from the
// stream
//-------------------------------------------------
save_error ram_state::load()
{
// initialize
m_data.seekg(0);
// if we have illegal registrations, return an error
if (m_save.m_illegal_regs > 0)
return STATERR_ILLEGAL_REGISTRATIONS;
// read the header
u8 header[HEADER_SIZE];
m_data.read((char *)header, sizeof(header));
// check for any errors
if (!m_data)
return STATERR_READ_ERROR;
// verify the header and report an error if it doesn't match
u32 sig = m_save.signature();
if (m_save.validate_header(header, m_save.machine().system().name, sig, nullptr, "Error: ") != STATERR_NONE)
return STATERR_INVALID_HEADER;
// determine whether or not to flip the data when done
bool flip = NATIVE_ENDIAN_VALUE_LE_BE((header[9] & SS_MSB_FIRST) != 0, (header[9] & SS_MSB_FIRST) == 0);
// read all the data, flipping if necessary
for (auto &entry : m_save.m_entry_list)
{
u32 totalsize = entry->m_typesize * entry->m_typecount;
m_data.read((char *)entry->m_data, totalsize);
// check for any errors
if (!m_data)
return STATERR_READ_ERROR;
// handle flipping
if (flip)
entry->flip_data();
}
// call the post-load functions
m_save.dispatch_postload();
return STATERR_NONE;
}
//-------------------------------------------------
// rewinder - constuctor
//-------------------------------------------------
rewinder::rewinder(save_manager &save)
: m_save(save)
, m_enabled(save.machine().options().rewind())
, m_capacity(save.machine().options().rewind_capacity())
{
}
//-------------------------------------------------
// check_size - shrink the state list if it is
// about to hit the capacity
//-------------------------------------------------
void rewinder::check_size()
{
// 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);
}
// 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();
// if we're not on top of the list, no need to move states around
if (!last->m_valid)
return;
// 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());
}
}
//-------------------------------------------------
// report_error - report rewind success or
// error type
//-------------------------------------------------
void rewinder::report_error(save_error error, rewind_operation operation, int index)
{
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);
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);
break;
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;
// external saveload failures
case STATERR_NOT_FOUND:
if (operation == rewind_operation::LOAD)
m_save.machine().popmessage("No rewind state to load.");
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.";
// 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);
}
break;
// something that shouldn't be allowed to happen
default:
m_save.machine().popmessage("Error: Unknown error during state %s.", opname);
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
//-------------------------------------------------
state_entry::state_entry(void *data, const char *name, device_t *device, const char *module, const char *tag, int index, u8 size, u32 count)
: m_data(data),
m_name(name),
m_device(device),
m_module(module),
m_tag(tag),
m_index(index),
m_typesize(size),
m_typecount(count),
m_offset(0)
: m_data(data)
, m_name(name)
, m_device(device)
, m_module(module)
, m_tag(tag)
, m_index(index)
, m_typesize(size)
, m_typecount(count)
, m_offset(0)
{
}
//-------------------------------------------------
// flip_data - reverse the endianness of a
// block of data
// block of data
//-------------------------------------------------
void state_entry::flip_data()

View File

@ -26,6 +26,7 @@
enum save_error
{
STATERR_NONE,
STATERR_NOT_FOUND,
STATERR_ILLEGAL_REGISTRATIONS,
STATERR_INVALID_HEADER,
STATERR_READ_ERROR,
@ -79,18 +80,25 @@ public:
u32 m_offset; // offset within the final structure
};
class ram_state;
class rewinder;
class save_manager
{
// type_checker is a set of templates to identify valid save types
template<typename _ItemType> struct type_checker { static const bool is_atom = false; static const bool is_pointer = false; };
template<typename _ItemType> struct type_checker<_ItemType*> { static const bool is_atom = false; static const bool is_pointer = true; };
friend class ram_state;
friend class rewinder;
public:
// construction/destruction
save_manager(running_machine &machine);
// getters
running_machine &machine() const { return m_machine; }
rewinder *rewind() { return m_rewind.get(); }
int registration_count() const { return m_entry_list.size(); }
bool registration_allowed() const { return m_reg_allowed; }
@ -166,19 +174,69 @@ private:
// construction/destruction
state_callback(save_prepost_delegate callback);
save_prepost_delegate m_func; // delegate
save_prepost_delegate m_func; // delegate
};
// internal state
running_machine & m_machine; // reference to our machine
bool m_reg_allowed; // are registrations allowed?
int m_illegal_regs; // number of illegal registrations
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
std::vector<std::unique_ptr<state_entry>> m_entry_list; // list of registered entries
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
std::vector<std::unique_ptr<state_callback>> m_presave_list; // list of pre-save functions
std::vector<std::unique_ptr<state_callback>> m_postload_list; // list of post-load functions
};
class ram_state
{
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
ram_state(save_manager &save);
static size_t get_size(save_manager &save);
save_error save();
save_error load();
};
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)
std::vector<std::unique_ptr<ram_state>> m_state_list; // rewinder's own ram states
// load/save management
enum class rewind_operation
{
SAVE,
LOAD
};
enum
{
REWIND_INDEX_NONE = -1,
REWIND_INDEX_FIRST = 0
};
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);
public:
rewinder(save_manager &save);
bool enabled() { return m_enabled; }
int invalidate();
void capture();
void step();
};
// template specializations to enumerate the fundamental atomic types you are allowed to save
ALLOW_SAVE_TYPE_AND_ARRAY(char)

View File

@ -63,6 +63,8 @@ std::vector<submenu::option> const submenu::advanced_options = {
{ submenu::option_type::HEAD, __("State/Playback Options") },
{ submenu::option_type::EMU, __("Automatic save/restore"), OPTION_AUTOSAVE },
{ submenu::option_type::EMU, __("Rewind"), OPTION_REWIND },
{ submenu::option_type::EMU, __("Rewind capacity"), OPTION_REWIND_CAPACITY },
{ submenu::option_type::EMU, __("Bilinear snapshot"), OPTION_SNAPBILINEAR },
{ submenu::option_type::EMU, __("Burn-in"), OPTION_BURNIN },

View File

@ -1200,10 +1200,15 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container)
// pause single step
if (machine().ui_input().pressed(IPT_UI_PAUSE_SINGLE))
{
machine().rewind_capture();
set_single_step(true);
machine().resume();
}
// rewind single step
if (machine().ui_input().pressed(IPT_UI_REWIND_SINGLE))
machine().rewind_step();
// handle a toggle cheats request
if (machine().ui_input().pressed(IPT_UI_TOGGLE_CHEAT))
mame_machine_manager::instance()->cheat().set_enable(!mame_machine_manager::instance()->cheat().enabled());