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:
parent
6605d4eb6d
commit
4116a72ae7
|
@ -34,7 +34,7 @@ struct EmulatedSystem {
|
|||
// load memory state (rewind)
|
||||
bool (*emuReadMemState)(char *, int);
|
||||
// write memory state (rewind)
|
||||
bool (*emuWriteMemState)(char *, int);
|
||||
bool (*emuWriteMemState)(char *, int, long&);
|
||||
// write PNG file
|
||||
bool (*emuWritePNG)(const char *);
|
||||
// write BMP 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
@ -527,6 +528,11 @@ void Window::vInitConfig()
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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; }
|
||||
|
|
Loading…
Reference in New Issue