From 4116a72ae70e9de4db2625a1c987b2e050078ac0 Mon Sep 17 00:00:00 2001 From: skidau Date: Mon, 18 May 2015 00:58:16 +0000 Subject: [PATCH] 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. --- src/System.h | 14 +++---- src/common/memgzio.c | 1 + src/gb/GB.cpp | 6 +-- src/gb/gb.h | 2 +- src/gba/GBA.cpp | 8 ++-- src/gtk/window.cpp | 40 ++++++++++++++++++- src/gtk/window.h | 14 ++++++- src/gtk/windowcallbacks.cpp | 80 +++++++++++++++++++++++++++++++++++++ src/sdl/SDL.cpp | 15 ++++++- src/win32/VBA.cpp | 3 +- src/wx/panel.cpp | 4 +- src/wx/sys.cpp | 11 +++++ src/wx/wxvbam.h | 4 +- 13 files changed, 178 insertions(+), 24 deletions(-) diff --git a/src/System.h b/src/System.h index 110e3162..ec8c7e62 100644 --- a/src/System.h +++ b/src/System.h @@ -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)(); diff --git a/src/common/memgzio.c b/src/common/memgzio.c index 529cc832..8d225d3d 100644 --- a/src/common/memgzio.c +++ b/src/common/memgzio.c @@ -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; diff --git a/src/gb/GB.cpp b/src/gb/GB.cpp index c532379c..34df6281 100644 --- a/src/gb/GB.cpp +++ b/src/gb/GB.cpp @@ -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); diff --git a/src/gb/gb.h b/src/gb/gb.h index c2556144..d14a0e53 100644 --- a/src/gb/gb.h +++ b/src/gb/gb.h @@ -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(); diff --git a/src/gba/GBA.cpp b/src/gba/GBA.cpp index 6b491ef0..a5f32600 100644 --- a/src/gba/GBA.cpp +++ b/src/gba/GBA.cpp @@ -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); diff --git a/src/gtk/window.cpp b/src/gtk/window.cpp index fc69903f..f1d09a6e 100644 --- a/src/gtk/window.cpp +++ b/src/gtk/window.cpp @@ -104,7 +104,8 @@ Window::Window(GtkWindow * _pstWindow, const Glib::RefPtr & _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("rewind_count_max"); + if (i16Value > 65535u) + { + m_poCoreConfig->vSetKey("rewind_count_max", STATE_MAX_DEFAULT); + } + m_state_count_max = m_poCoreConfig->oGetKey("rewind_count_max"); + + iValue = m_poCoreConfig->oGetKey("rewind_interval"); + if (i16Value > 65535u) + { + m_poCoreConfig->vSetKey("rewind_interval", STATE_INTERVAL_DEFAULT); + } + m_rewind_interval = m_poCoreConfig->oGetKey("rewind_interval"); + // Display section // iValue = m_poDisplayConfig->oGetKey("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; } diff --git a/src/gtk/window.h b/src/gtk/window.h index 77652499..ad0a50cc 100644 --- a/src/gtk/window.h +++ b/src/gtk/window.h @@ -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 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 m_rewind_load_q; + char *m_psavestate; + void vInitSystem(); void vUnInitSystem(); void vInitSDL(); diff --git a/src/gtk/windowcallbacks.cpp b/src/gtk/windowcallbacks.cpp index e504d352..09558c06 100644 --- a/src/gtk/windowcallbacks.cpp +++ b/src/gtk/windowcallbacks.cpp @@ -18,6 +18,8 @@ #include "window.h" +#include + #include #include #include @@ -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::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("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; diff --git a/src/sdl/SDL.cpp b/src/sdl/SDL.cpp index b0aee232..14534a5a 100644 --- a/src/sdl/SDL.cpp +++ b/src/sdl/SDL.cpp @@ -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(); diff --git a/src/win32/VBA.cpp b/src/win32/VBA.cpp index 3c0ea6e0..0ef8fd4c 100644 --- a/src/win32/VBA.cpp +++ b/src/win32/VBA.cpp @@ -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; diff --git a/src/wx/panel.cpp b/src/wx/panel.cpp index c5b98468..ba6b089d 100644 --- a/src/wx/panel.cpp +++ b/src/wx/panel.cpp @@ -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 diff --git a/src/wx/sys.cpp b/src/wx/sys.cpp index ff3de061..6efda6ce 100644 --- a/src/wx/sys.cpp +++ b/src/wx/sys.cpp @@ -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(); diff --git a/src/wx/wxvbam.h b/src/wx/wxvbam.h index c6f476ac..6b0e9556 100644 --- a/src/wx/wxvbam.h +++ b/src/wx/wxvbam.h @@ -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; }