Unsafe rewinding

Unsafe rewinding is unsafe, but in exchange, a lot faster than normal
save/load cycle.
This commit is contained in:
Ilari Liusvaara 2012-05-19 14:37:49 +03:00
parent d66ddeb7dc
commit 6f796f9601
15 changed files with 277 additions and 6 deletions

View file

@ -15,4 +15,12 @@ void set_jukebox_names(const std::vector<std::string>& newj);
void update_movie_state();
extern std::string msu1_base_path;
/**
* Signal that fast rewind operation is needed.
*
* Parameter ptr: If NULL, saves a state and calls lua_callback_do_unsafe_rewind() with quicksave, movie and NULL.
* If non-NULL, calls lua_callback_do_unsafe_rewind() with movie and this parameter, but without quicksave.
*/
void mainloop_signal_need_rewind(void* ptr);
#endif

View file

@ -228,6 +228,15 @@ public:
* movie, reads all released.
*/
controller_frame read_subframe(uint64_t frame, uint64_t subframe) throw();
/**
* Fast save.
*/
void fast_save(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector<uint32_t>& counters);
/**
* Fast load.
*/
void fast_load(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector<uint32_t>& counters);
private:
//TRUE if readonly mode is active.
bool readonly;

View file

@ -33,4 +33,14 @@ std::string translate_name_mprefix(std::string original, bool forio = false);
extern std::string last_save;
extern movie_logic movb;
/**
* Restore the actual core state from quicksave. Only call in rewind callback.
*
* Parameter state: The state to restore.
* Parameter secs: The seconds counter.
* Parameter ssecs: The subsecond counter.
*/
void mainloop_restore_state(const std::vector<char>& state, uint64_t secs, uint64_t ssecs);
#endif

View file

@ -469,7 +469,7 @@ std::map<std::string, std::vector<char>> load_sram_commandline(const std::vector
* returns: The saved state.
* throws std::bad_alloc: Not enough memory.
*/
std::vector<char> save_core_state() throw(std::bad_alloc);
std::vector<char> save_core_state(bool nochecksum = false) throw(std::bad_alloc);
/**
* Loads core state from buffer.
@ -477,7 +477,7 @@ std::vector<char> save_core_state() throw(std::bad_alloc);
* parameter buf: The buffer containing the state.
* throws std::runtime_error: Loading state failed.
*/
void load_core_state(const std::vector<char>& buf) throw(std::runtime_error);
void load_core_state(const std::vector<char>& buf, bool nochecksum = false) throw(std::runtime_error);
/**
* Read index of ROMs and add ROMs found to content-searchable storage.

View file

@ -4,6 +4,7 @@
#include "core/controllerframe.hpp"
#include "core/keymapper.hpp"
#include "core/render.hpp"
#include "core/movie.hpp"
struct lua_State;
@ -88,6 +89,7 @@ void lua_callback_post_save(const std::string& name, bool is_state) throw();
void lua_callback_snoop_input(uint32_t port, uint32_t controller, uint32_t index, short value) throw();
void lua_callback_quit() throw();
void lua_callback_keyhook(const std::string& key, const struct keygroup::parameters& p) throw();
void lua_callback_do_unsafe_rewind(const std::vector<char>& save, uint64_t secs, uint64_t ssecs, movie& mov, void* u);
#define LUA_TIMED_HOOK_IDLE 0
#define LUA_TIMED_HOOK_TIMER 1

View file

@ -0,0 +1,15 @@
#ifndef _lua__unsaferewind__hpp__included__
#define _lua__unsaferewind__hpp__included__
struct lua_unsaferewind
{
std::vector<char> state;
uint64_t frame;
uint64_t lag;
uint64_t ptr;
uint64_t secs;
uint64_t ssecs;
std::vector<uint32_t> pollcounters;
};
#endif

View file

@ -2918,6 +2918,51 @@ Read specifed subframe in specified frame and return data as array (100
elements, numbered 0-99 currently).
\end_layout
\begin_layout Subsubsection
movie.unsafe_rewind([UNSAFEREWIND state])
\end_layout
\begin_layout Standard
Start setting point for unsafe rewind or jump to point of unsafe rewind.
\end_layout
\begin_layout Itemize
If called without argument, causes emulator to start process of setting
unsafe rewind point.
When this has finished, callback on_set_rewind occurs, passing the rewind
state to lua script.
\end_layout
\begin_layout Itemize
If called with argument, causes emulator rewind to passed rewind point as
soon as possible.
Readwrite mode is implicitly activated.
\end_layout
\begin_layout Standard
The following warnings apply to unsafe rewinding:
\end_layout
\begin_layout Itemize
There are no safety checks against misuse (that's what
\begin_inset Quotes eld
\end_inset
unsafe
\begin_inset Quotes erd
\end_inset
comes from)!
\end_layout
\begin_layout Itemize
Only call rewind from timeline rewind point was set from.
\end_layout
\begin_layout Itemize
Only call rewind from after the rewind point was set.
\end_layout
\begin_layout Subsection
Table settings
\end_layout
@ -3341,6 +3386,30 @@ Called when requested by set_idle_timeout() and the timeout has expired
(regardless if emulator is waiting).
\end_layout
\begin_layout Subsubsection
Callback: on_set_rewind(UNSAFEREWIND r)
\end_layout
\begin_layout Standard
Called when unsafe rewind object has been constructed.
\end_layout
\begin_layout Subsubsection
Callback: on_pre_rewind()
\end_layout
\begin_layout Standard
Called just before unsafe rewind is about to occur.
\end_layout
\begin_layout Subsubsection
Callback: on_post_rewind()
\end_layout
\begin_layout Standard
Called just after unsafe rewind has occured.
\end_layout
\begin_layout Section
Memory watch expression syntax
\end_layout

View file

@ -1476,6 +1476,28 @@ Count number of subframes in specified frame (frame numbers are
Read specifed subframe in specified frame and return data as
array (100 elements, numbered 0-99 currently).
8.6.7 movie.unsafe_rewind([UNSAFEREWIND state])
Start setting point for unsafe rewind or jump to point of unsafe
rewind.
• If called without argument, causes emulator to start process of
setting unsafe rewind point. When this has finished, callback
on_set_rewind occurs, passing the rewind state to lua script.
• If called with argument, causes emulator rewind to passed
rewind point as soon as possible. Readwrite mode is implicitly
activated.
The following warnings apply to unsafe rewinding:
• There are no safety checks against misuse (that's what “unsafe”
comes from)!
• Only call rewind from timeline rewind point was set from.
• Only call rewind from after the rewind point was set.
8.7 Table settings
Routines for settings manipulation
@ -1712,6 +1734,18 @@ expired and emulator is waiting.
Called when requested by set_idle_timeout() and the timeout has
expired (regardless if emulator is waiting).
8.10.21 Callback: on_set_rewind(UNSAFEREWIND r)
Called when unsafe rewind object has been constructed.
8.10.22 Callback: on_pre_rewind()
Called just before unsafe rewind is about to occur.
8.10.23 Callback: on_post_rewind()
Called just after unsafe rewind has occured.
9 Memory watch expression syntax
Memory watch expressions are in RPN (Reverse Polish Notation). At

View file

@ -79,6 +79,11 @@ namespace
//Delay reset.
unsigned long long delayreset_cycles_run;
unsigned long long delayreset_cycles_target;
//Unsafe rewind.
bool do_unsafe_rewind = false;
void* unsafe_rewind_obj = NULL;
enum advance_mode old_mode;
bool delayreset_fn()
{
@ -96,6 +101,16 @@ namespace
path_setting firmwarepath_setting("firmwarepath");
void mainloop_signal_need_rewind(void* ptr)
{
if(ptr) {
old_mode = amode;
amode = ADVANCE_LOAD;
}
do_unsafe_rewind = true;
unsafe_rewind_obj = ptr;
}
controller_frame movie_logic::update_controls(bool subframe) throw(std::bad_alloc, std::runtime_error)
{
if(lua_requests_subframe_paint)
@ -164,7 +179,6 @@ controller_frame movie_logic::update_controls(bool subframe) throw(std::bad_allo
namespace
{
enum advance_mode old_mode;
//Do pending load (automatically unpauses).
void mark_pending_load(const std::string& filename, int lmode)
@ -774,6 +788,18 @@ namespace
//failing.
int handle_load()
{
if(do_unsafe_rewind && unsafe_rewind_obj) {
uint64_t t = get_utime();
std::vector<char> s;
lua_callback_do_unsafe_rewind(s, 0, 0, movb.get_movie(), unsafe_rewind_obj);
information_dispatch::do_mode_change(false);
do_unsafe_rewind = false;
our_movie.is_savestate = true;
location_special = SPECIAL_SAVEPOINT;
update_movie_state();
messages << "Rewind done in " << (get_utime() - t) << " usec." << std::endl;
return 1;
}
if(pending_load != "") {
system_corrupt = false;
if(loadmode != LOAD_STATE_BEGINNING && !do_load_state(pending_load, loadmode)) {
@ -801,12 +827,21 @@ namespace
//If there are pending saves, perform them.
void handle_saves()
{
if(!queued_saves.empty()) {
if(!queued_saves.empty() || (do_unsafe_rewind && !unsafe_rewind_obj)) {
stepping_into_save = true;
SNES::system.runtosave();
stepping_into_save = false;
for(auto i : queued_saves)
do_save_state(i);
if(do_unsafe_rewind && !unsafe_rewind_obj) {
uint64_t t = get_utime();
std::vector<char> s = save_core_state(true);
uint64_t secs = our_movie.rtc_second;
uint64_t ssecs = our_movie.rtc_subsecond;
lua_callback_do_unsafe_rewind(s, secs, ssecs, movb.get_movie(), NULL);
do_unsafe_rewind = false;
messages << "Rewind point set in " << (get_utime() - t) << " usec." << std::endl;
}
}
queued_saves.clear();
}

View file

@ -455,6 +455,25 @@ void movie::reset_state() throw()
clear_caches();
}
void movie::fast_save(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector<uint32_t>& _counters)
{
pollcounters.save_state(_counters);
_frame = current_frame;
_ptr = current_frame_first_subframe;
_lagc = lag_frames;
}
void movie::fast_load(uint64_t& _frame, uint64_t& _ptr, uint64_t& _lagc, std::vector<uint32_t>& _counters)
{
readonly = true;
current_frame = _frame;
current_frame_first_subframe = (_ptr <= movie_data.size()) ? _ptr : movie_data.size();
lag_frames = _lagc;
pollcounters.load_state(_counters);
readonly_mode(false);
}
movie_logic::movie_logic() throw()
{
}

View file

@ -495,3 +495,11 @@ bool do_load_state(const std::string& filename, int lmode)
}
return true;
}
void mainloop_restore_state(const std::vector<char>& state, uint64_t secs, uint64_t ssecs)
{
rrdata::add_internal();
our_movie.rtc_second = secs;
our_movie.rtc_subsecond = ssecs;
load_core_state(state, true);
}

View file

@ -600,12 +600,14 @@ std::map<std::string, std::vector<char>> load_sram_commandline(const std::vector
return ret;
}
std::vector<char> save_core_state() throw(std::bad_alloc)
std::vector<char> save_core_state(bool nochecksum) throw(std::bad_alloc)
{
std::vector<char> ret;
serializer s = SNES::system.serialize();
ret.resize(s.size());
memcpy(&ret[0], s.data(), s.size());
if(nochecksum)
return ret;
size_t offset = ret.size();
unsigned char tmp[32];
sha256::hash(tmp, ret);
@ -614,8 +616,15 @@ std::vector<char> save_core_state() throw(std::bad_alloc)
return ret;
}
void load_core_state(const std::vector<char>& buf) throw(std::runtime_error)
void load_core_state(const std::vector<char>& buf, bool nochecksum) throw(std::runtime_error)
{
if(nochecksum) {
serializer s(reinterpret_cast<const uint8_t*>(&buf[0]), buf.size());
if(!SNES::system.unserialize(s))
throw std::runtime_error("SNES core rejected savestate");
return;
}
if(buf.size() < 32)
throw std::runtime_error("Savestate corrupt");
unsigned char tmp[32];

View file

@ -27,6 +27,8 @@ void lua_callback_keyhook(const std::string& key, const struct keygroup::paramet
void init_lua() throw() {}
void quit_lua() throw() {}
uint64_t lua_timed_hook(int timer) throw() { return 0x7EFFFFFFFFFFFFFFULL; }
void lua_callback_do_unsafe_rewind(const std::vector<char>& save, movie& mov, void* u) {}
bool lua_requests_repaint = false;
bool lua_requests_subframe_paint = false;

View file

@ -2,8 +2,10 @@
#include "core/globalwrap.hpp"
#include "lua/internal.hpp"
#include "lua/lua.hpp"
#include "lua/unsaferewind.hpp"
#include "core/mainloop.hpp"
#include "core/memorymanip.hpp"
#include "core/moviedata.hpp"
#include "core/misc.hpp"
#include <map>
@ -555,7 +557,38 @@ uint64_t lua_timed_hook(int timer) throw()
}
}
void lua_callback_do_unsafe_rewind(const std::vector<char>& save, uint64_t secs, uint64_t ssecs, movie& mov, void* u)
{
if(u) {
lua_unsaferewind* u2 = reinterpret_cast<lua_obj_pin<lua_unsaferewind>*>(u)->object();
//Load.
try {
if(callback_exists("on_pre_rewind"))
run_lua_cb(0);
mainloop_restore_state(u2->state, u2->secs, u2->ssecs);
mov.fast_load(u2->frame, u2->ptr, u2->lag, u2->pollcounters);
if(callback_exists("on_post_rewind"))
run_lua_cb(0);
} catch(...) {
return;
}
} else {
//Save
if(callback_exists("on_set_rewind")) {
lua_unsaferewind* u2 = lua_class<lua_unsaferewind>::create(L);
u2->state = save;
u2->secs = secs,
u2->ssecs = ssecs;
mov.fast_save(u2->frame, u2->ptr, u2->lag, u2->pollcounters);
run_lua_cb(1);
}
}
}
bool lua_requests_repaint = false;
bool lua_requests_subframe_paint = false;
bool lua_supported = true;
DECLARE_LUACLASS(lua_unsaferewind, "UNSAFEREWIND");

View file

@ -1,6 +1,8 @@
#include "lua/internal.hpp"
#include "lua/unsaferewind.hpp"
#include "core/movie.hpp"
#include "core/moviedata.hpp"
#include "core/mainloop.hpp"
namespace
{
@ -68,4 +70,20 @@ namespace
lua_pushnumber(LS, our_movie.rtc_subsecond);
return 2;
});
function_ptr_luafun musv("movie.unsafe_rewind", [](lua_State* LS, const std::string& fname) -> int {
if(lua_isnoneornil(LS, 1)) {
//Start process to mark save.
mainloop_signal_need_rewind(NULL);
} else if(lua_class<lua_unsaferewind>::is(LS, 1)) {
//Load the save.
lua_obj_pin<lua_unsaferewind>* u = lua_class<lua_unsaferewind>::pin(LS, 1, fname.c_str());
mainloop_signal_need_rewind(u);
} else {
lua_pushstring(LS, "movie.unsafe_rewind: Expected nil or UNSAFEREWIND as 1st argument");
lua_error(LS);
return 0;
}
});
}