Added rewind feature to GTK version. Patch by Juha Laukkanen.

There are two config params:

rewind_count_max => how many blocks are reserved maximum, higher value leads to greater memory usage naturally but longer rewind log
rewind_interval => default interval is 165ms and higher value leads to more inaccurate rewind but longer rewind log

Also fixes memtell() telling incorrect size because data is not flushed.
wxWidgets front end having too small buffer for rewinds resulting overflows.
This commit is contained in:
skidau 2015-05-18 00:58:16 +00:00
parent 6605d4eb6d
commit 4116a72ae7
13 changed files with 178 additions and 24 deletions

View File

@ -31,13 +31,13 @@ struct EmulatedSystem {
// save state
bool (*emuWriteState)(const char *);
#endif
// load memory state (rewind)
bool (*emuReadMemState)(char *, int);
// write memory state (rewind)
bool (*emuWriteMemState)(char *, int);
// write PNG file
bool (*emuWritePNG)(const char *);
// write BMP file
// load memory state (rewind)
bool (*emuReadMemState)(char *, int);
// write memory state (rewind)
bool (*emuWriteMemState)(char *, int, long&);
// write PNG file
bool (*emuWritePNG)(const char *);
// write BMP file
bool (*emuWriteBMP)(const char *);
// emulator update CPSR (ARM only)
void (*emuUpdateCPSR)();

View File

@ -691,6 +691,7 @@ int ZEXPORT memgzclose (file)
long ZEXPORT memtell(file)
gzFile file;
{
do_flush (file, Z_FULL_FLUSH); // makes memtell to tell truth
mem_stream *s = (mem_stream*)file;
if (s == NULL) return Z_STREAM_ERROR;

View File

@ -3929,7 +3929,7 @@ static bool gbWriteSaveState(gzFile gzFile)
return true;
}
bool gbWriteMemSaveState(char *memory, int available)
bool gbWriteMemSaveState(char *memory, int available, long& reserved)
{
gzFile gzFile = utilMemGzOpen(memory, available, "w");
@ -3939,9 +3939,9 @@ bool gbWriteMemSaveState(char *memory, int available)
bool res = gbWriteSaveState(gzFile);
long pos = utilGzMemTell(gzFile)+8;
reserved = utilGzMemTell(gzFile)+8;
if(pos >= (available))
if(reserved >= (available))
res = false;
utilGzClose(gzFile);

View File

@ -35,7 +35,7 @@ bool gbWriteBatteryFile(const char *);
bool gbWriteBatteryFile(const char *, bool);
bool gbReadBatteryFile(const char *);
bool gbWriteSaveState(const char *);
bool gbWriteMemSaveState(char *, int);
bool gbWriteMemSaveState(char *, int, long&);
bool gbReadSaveState(const char *);
bool gbReadMemSaveState(char *, int);
void gbSgbRenderBorder();

View File

@ -621,7 +621,7 @@ unsigned int CPUWriteState(u8* data, unsigned size)
return (ptrdiff_t)data - (ptrdiff_t)orig;
}
bool CPUWriteMemState(char *memory, int available)
bool CPUWriteMemState(char *memory, int available, long& reserved)
{
return false;
}
@ -684,7 +684,7 @@ bool CPUWriteState(const char *file)
}
bool CPUWriteMemState(char *memory, int available)
bool CPUWriteMemState(char *memory, int available, long& reserved)
{
gzFile gzFile = utilMemGzOpen(memory, available, "w");
@ -694,9 +694,9 @@ bool CPUWriteMemState(char *memory, int available)
bool res = CPUWriteState(gzFile);
long pos = utilGzMemTell(gzFile)+8;
reserved = utilGzMemTell(gzFile)+8;
if(pos >= (available))
if(reserved >= (available))
res = false;
utilGzClose(gzFile);

View File

@ -104,7 +104,8 @@ Window::Window(GtkWindow * _pstWindow, const Glib::RefPtr<Gtk::Builder> & _poXml
m_iJoypadMax (PAD_4),
m_iVideoOutputMin (OutputCairo),
m_iVideoOutputMax (OutputOpenGL),
m_bFullscreen (false)
m_bFullscreen (false),
m_psavestate (NULL)
{
m_poXml = _poXml;
m_poFileOpenDialog = NULL;
@ -526,7 +527,12 @@ void Window::vInitConfig()
m_poCoreConfig->vSetKey("emulator_type", EmulatorAuto );
m_poCoreConfig->vSetKey("pause_when_inactive", true );
m_poCoreConfig->vSetKey("show_speed", ShowPercentage );
// Rewind
//
m_poCoreConfig->vSetKey("rewind_count_max", STATE_MAX_DEFAULT);
m_poCoreConfig->vSetKey("rewind_interval", STATE_INTERVAL_DEFAULT);
// Display section
//
m_poDisplayConfig = m_oConfig.poAddSection("Display");
@ -569,6 +575,7 @@ void Window::vCheckConfig()
{
int iValue;
int iAdjusted;
unsigned short i16Value;
float fValue;
float fAdjusted;
std::string sValue;
@ -665,6 +672,22 @@ void Window::vCheckConfig()
m_poCoreConfig->vSetKey("emulator_type", iAdjusted);
}
// Rewind feature
// move to value change cb
i16Value = m_poCoreConfig->oGetKey<unsigned short>("rewind_count_max");
if (i16Value > 65535u)
{
m_poCoreConfig->vSetKey("rewind_count_max", STATE_MAX_DEFAULT);
}
m_state_count_max = m_poCoreConfig->oGetKey<unsigned short>("rewind_count_max");
iValue = m_poCoreConfig->oGetKey<unsigned short>("rewind_interval");
if (i16Value > 65535u)
{
m_poCoreConfig->vSetKey("rewind_interval", STATE_INTERVAL_DEFAULT);
}
m_rewind_interval = m_poCoreConfig->oGetKey<unsigned short>("rewind_interval");
// Display section
//
iValue = m_poDisplayConfig->oGetKey<int>("scale");
@ -1108,6 +1131,15 @@ bool Window::bLoadROM(const std::string & _rsFile)
vOnLoadGameMostRecent();
}
// reserve rewind space for write operation
// this is used as work space
// actual state blocks are reserved in bOnEmuSaveStateRewind()
// when resulted size is known
//
if (m_state_count_max > 0 && m_psavestate == NULL) {
m_psavestate = new char[SZSTATE];
}
vStartEmu();
return true;
@ -1414,6 +1446,9 @@ void Window::vSaveCheats()
void Window::vStartEmu()
{
m_oEmuRewindSig.disconnect();
m_oEmuRewindSig = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::bOnEmuSaveStateRewind), m_rewind_interval);
if (m_oEmuSig.connected())
{
return;
@ -1426,6 +1461,7 @@ void Window::vStartEmu()
void Window::vStopEmu()
{
m_oEmuSig.disconnect();
m_oEmuRewindSig.disconnect();
m_bWasEmulating = false;
}

View File

@ -166,6 +166,8 @@ protected:
virtual void vOnCheatDisableToggled(Gtk::CheckMenuItem * _poCMI);
virtual void vOnHelpAbout();
virtual bool bOnEmuIdle();
virtual bool bOnEmuSaveStateRewind();
virtual bool bOnEmuRewind();
virtual bool on_focus_in_event(GdkEventFocus * _pstEvent);
virtual bool on_focus_out_event(GdkEventFocus * _pstEvent);
@ -242,7 +244,7 @@ private:
std::list<Gtk::Widget *> m_listSensitiveWhenPlaying;
sigc::connection m_oEmuSig;
sigc::connection m_oEmuSig, m_oEmuRewindSig;
int m_bFullscreen;
int m_iScreenWidth;
@ -257,6 +259,16 @@ private:
bool m_bAutoFrameskip;
EShowSpeed m_eShowSpeed;
/* State saving into memory & rewind to saved state */
u16 m_state_count_max;
u16 m_rewind_interval;
static const u32 SZSTATE = 1024*512;
static const u16 STATE_MAX_DEFAULT = 180u;
static const u16 STATE_INTERVAL_DEFAULT = 165u;
std::deque<char*> m_rewind_load_q;
char *m_psavestate;
void vInitSystem();
void vUnInitSystem();
void vInitSDL();

View File

@ -18,6 +18,8 @@
#include "window.h"
#include <deque>
#include <gtkmm/stock.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/aboutdialog.h>
@ -341,6 +343,13 @@ void Window::vOnFileClose()
m_eCartridge = CartridgeNone;
emulating = 0;
while (!m_rewind_load_q.empty()) {
delete[] m_rewind_load_q.front();
m_rewind_load_q.pop_front();
}
delete[] m_psavestate;
m_psavestate = NULL;
vUpdateGameSlots();
for (std::list<Gtk::Widget *>::iterator it = m_listSensitiveWhenPlaying.begin();
@ -534,6 +543,57 @@ void Window::vOnHelpAbout()
oAboutDialog.run();
}
bool Window::bOnEmuRewind()
{
if( !m_rewind_load_q.empty() ) {
// load a rewind save state
char *psavestate = m_rewind_load_q.front();
//memset(m_psavestate, 0x0, SZSTATE);
long szstate = *((long*)psavestate); // first there is size
//memmove(m_psavestate, psavestate+sizeof(szstate), szstate-sizeof(szstate));
if (m_stEmulator.emuReadMemState(psavestate+sizeof(szstate), szstate)) {
// the save state is now used so delete it and we have more space
m_rewind_load_q.pop_front();
delete[] psavestate;
//printf ("Restored %p! (%li bytes)\n", psavestate, szstate);
}
return true;
} else {
// no more save states; either disabled, too early, or rewinded all the way to start
return false;
}
}
bool Window::bOnEmuSaveStateRewind() {
// check if we're disabled
char *psavestate;
if (m_state_count_max == 0u) {
return false;
} else if (m_rewind_load_q.size() >= m_state_count_max) { // check if we can reserve more memory for save states
// if we can't reserve more memory let's take away used save states starting from oldest
psavestate = m_rewind_load_q.back();
m_rewind_load_q.pop_back();
delete[] psavestate;
} // otherwise we can reserve more memory
// Do the actual saving
long ressize;
if (m_stEmulator.emuWriteMemState(m_psavestate, SZSTATE, ressize)) {
/*ressize*=2; // if tell does not return correct size this leverage factor is needed
if (ressize > SZSTATE) ressize = SZSTATE;*/
g_assert( ressize <= SZSTATE );
ressize+=(sizeof(ressize)*8); // some leverage
psavestate = new char[ressize];
memmove(psavestate, &ressize, sizeof(ressize)); // pack size first
memmove(psavestate+sizeof(ressize), m_psavestate, ressize-sizeof(ressize)); // then actual save data
//printf("Wrote %p (%li bytes %i %i)\n", psavestate, ressize, *((long*)psavestate), sizeof(ressize));
m_rewind_load_q.push_front(psavestate);
return true;
} else {
return false;
}
}
bool Window::bOnEmuIdle()
{
vSDLPollEvents();
@ -573,6 +633,17 @@ bool Window::on_key_press_event(GdkEventKey * _pstEvent)
return true;
}
// Rewind key CTRL+B
if (m_state_count_max > 0u && (_pstEvent->state & GDK_CONTROL_MASK) && _pstEvent->keyval == GDK_b) {
// disable saves first and then connect new handler
if (m_oEmuRewindSig.connected()) m_oEmuRewindSig.disconnect();
m_state_count_max = 0u;
//return this->bOnEmuRewind();
m_oEmuRewindSig = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::bOnEmuRewind),
65u);
return true;
}
// Forward the keyboard event to the input module by faking a SDL event
SDL_Event event;
event.type = SDL_KEYDOWN;
@ -584,6 +655,15 @@ bool Window::on_key_press_event(GdkEventKey * _pstEvent)
bool Window::on_key_release_event(GdkEventKey * _pstEvent)
{
// Rewind key CTRL+B
if (_pstEvent->keyval == GDK_b /*&& !(_pstEvent->state & GDK_CONTROL_MASK)*/) {
// connect save handler back
if (m_oEmuRewindSig.connected()) m_oEmuRewindSig.disconnect();
m_state_count_max = m_poCoreConfig->oGetKey<unsigned short>("rewind_count_max");
m_oEmuRewindSig = Glib::signal_timeout().connect(sigc::mem_fun(*this, &Window::bOnEmuSaveStateRewind), m_rewind_interval);
return true;
}
// Forward the keyboard event to the input module by faking a SDL event
SDL_Event event;
event.type = SDL_KEYUP;

View File

@ -1438,13 +1438,14 @@ void handleRewinds()
rewindCount = REWIND_NUM;
curSavePos = (rewindTopPos + 1) % rewindCount; // [1] depends on previous
long ressize;
if(
emulator.emuWriteMemState
&&
emulator.emuWriteMemState(
&rewindMemory[curSavePos*REWIND_SIZE],
REWIND_SIZE
REWIND_SIZE, /* available*/
ressize /* actual size */
)
) {
char rewMsgBuf[100];
@ -2048,6 +2049,16 @@ void systemScreenCapture(int a)
systemScreenMessage("Screen capture");
}
void systemSaveOldest()
{
// I need to be implemented
}
void systemLoadRecent()
{
// I need to be implemented
}
u32 systemGetClock()
{
return SDL_GetTicks();

View File

@ -1316,8 +1316,9 @@ BOOL VBA::OnIdle(LONG lCount)
rewindCount++;
if(rewindCount > 8)
rewindCount = 8;
long ressize;
if(emulator.emuWriteMemState(&rewindMemory[rewindPos*REWIND_SIZE],
REWIND_SIZE)) {
REWIND_SIZE, ressize)) { /* available and actual size */
rewindPos = ++rewindPos & 7;
if(rewindCount == 8)
rewindTopPos = ++rewindTopPos & 7;

View File

@ -1081,8 +1081,10 @@ void GameArea::OnIdle(wxIdleEvent &event)
return;
}
long ressize;
if (!emusys->emuWriteMemState(&rewind_mem[REWIND_SIZE * next_rewind_state],
REWIND_SIZE))
REWIND_SIZE, ressize /* actual size */))
// if you see a lot of these, maybe increase REWIND_SIZE
wxLogInfo(_("Error writing rewind state"));
else

View File

@ -494,6 +494,17 @@ void systemScreenCapture(int num)
systemScreenMessage(msg);
}
void systemSaveOldest()
{
// I need to be implemented
}
void systemLoadRecent()
{
// I need to be implemented
}
u32 systemGetClock()
{
return wxGetApp().timer.Time();

View File

@ -452,9 +452,9 @@ public:
u32 rom_size;
// FIXME: size this properly
#define REWIND_SIZE 400000
// FIXME: make this a config option
#define NUM_REWINDS 8
#define REWIND_SIZE 1024*512*NUM_REWINDS
// FIXME: make this a config option
void ShowFullScreen(bool full);
bool IsFullScreen() { return fullscreen; }