diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/emu/debug/debugcmd.cpp | 23 | ||||
-rw-r--r-- | src/emu/debug/debugcmd.h | 1 | ||||
-rw-r--r-- | src/emu/debug/debugcpu.cpp | 8 | ||||
-rw-r--r-- | src/emu/emuopts.cpp | 2 | ||||
-rw-r--r-- | src/emu/emuopts.h | 4 | ||||
-rw-r--r-- | src/emu/inpttype.h | 7 | ||||
-rw-r--r-- | src/emu/ioport.h | 1 | ||||
-rw-r--r-- | src/emu/machine.cpp | 39 | ||||
-rw-r--r-- | src/emu/machine.h | 5 | ||||
-rw-r--r-- | src/emu/save.cpp | 465 | ||||
-rw-r--r-- | src/emu/save.h | 68 | ||||
-rw-r--r-- | src/frontend/mame/ui/submenu.cpp | 2 | ||||
-rw-r--r-- | src/frontend/mame/ui/ui.cpp | 5 |
13 files changed, 603 insertions, 27 deletions
diff --git a/src/emu/debug/debugcmd.cpp b/src/emu/debug/debugcmd.cpp index 38bff17b8c4..dd0c918ae6e 100644 --- a/src/emu/debug/debugcmd.cpp +++ b/src/emu/debug/debugcmd.cpp @@ -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(); @@ -1638,6 +1641,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> ¶ms) +{ + 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 -------------------------------------------------*/ diff --git a/src/emu/debug/debugcmd.h b/src/emu/debug/debugcmd.h index 98b6d8ae03d..41405ec0a71 100644 --- a/src/emu/debug/debugcmd.h +++ b/src/emu/debug/debugcmd.h @@ -131,6 +131,7 @@ private: void execute_hotspot(int ref, const std::vector<std::string> ¶ms); void execute_statesave(int ref, const std::vector<std::string> ¶ms); void execute_stateload(int ref, const std::vector<std::string> ¶ms); + void execute_rewind(int ref, const std::vector<std::string> ¶ms); void execute_save(int ref, const std::vector<std::string> ¶ms); void execute_load(int ref, const std::vector<std::string> ¶ms); void execute_dump(int ref, const std::vector<std::string> ¶ms); diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp index eab6f7771e7..4e03e09f54a 100644 --- a/src/emu/debug/debugcpu.cpp +++ b/src/emu/debug/debugcpu.cpp @@ -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); diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp index 11e369f0aca..12e674718d9 100644 --- a/src/emu/emuopts.cpp +++ b/src/emu/emuopts.cpp @@ -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)" }, diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h index a449c5d95a1..60de574bb22 100644 --- a/src/emu/emuopts.h +++ b/src/emu/emuopts.h @@ -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); } diff --git a/src/emu/inpttype.h b/src/emu/inpttype.h index b58ca37bec6..a1e5bef133b 100644 --- a/src/emu/inpttype.h +++ b/src/emu/inpttype.h @@ -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) ) diff --git a/src/emu/ioport.h b/src/emu/ioport.h index 722c028e4dc..a9f25e29184 100644 --- a/src/emu/ioport.h +++ b/src/emu/ioport.h @@ -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, diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp index fa6375ef937..f55bfb6c5ad 100644 --- a/src/emu/machine.cpp +++ b/src/emu/machine.cpp @@ -683,6 +683,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(); } diff --git a/src/emu/machine.h b/src/emu/machine.h index 4cf274c9617..efe464983db 100644 --- a/src/emu/machine.h +++ b/src/emu/machine.h @@ -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(); diff --git a/src/emu/save.cpp b/src/emu/save.cpp index dea222227ee..eff71cd6870 100644 --- a/src/emu/save.cpp +++ b/src/emu/save.cpp @@ -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 //------------------------------------------------- @@ -415,26 +419,451 @@ 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() diff --git a/src/emu/save.h b/src/emu/save.h index 4f112796eaf..e5c8c2fdbbf 100644 --- a/src/emu/save.h +++ b/src/emu/save.h @@ -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) diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp index 367fb429a53..33632cb4150 100644 --- a/src/frontend/mame/ui/submenu.cpp +++ b/src/frontend/mame/ui/submenu.cpp @@ -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 }, diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp index 026bf0c34f7..df5089cb1c0 100644 --- a/src/frontend/mame/ui/ui.cpp +++ b/src/frontend/mame/ui/ui.cpp @@ -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()); |