Unsafe rewinding
Unsafe rewinding is unsafe, but in exchange, a lot faster than normal save/load cycle.
This commit is contained in:
parent
d66ddeb7dc
commit
6f796f9601
15 changed files with 277 additions and 6 deletions
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
15
include/lua/unsaferewind.hpp
Normal file
15
include/lua/unsaferewind.hpp
Normal 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
|
69
manual.lyx
69
manual.lyx
|
@ -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
|
||||
|
|
34
manual.txt
34
manual.txt
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue