From 766818baa8eedc946f2df71428260121179f63c6 Mon Sep 17 00:00:00 2001 From: nitsuja- Date: Sat, 17 Apr 2010 21:02:03 +0000 Subject: [PATCH] possible savestate memory leak fix in DoBuffer made Lua run on the CPU thread to fix unreliable script execution issues several fixes so the Lua savestate functions can actually work added Lua function savestate.verify to help with catching desyncs implemented FailVerifyAtFrameBoundary in Lua interface added a Clear button to the LuaWindow git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@5382 8ced0084-cf51-0410-be5f-012b33b47a6e --- Source/Core/Common/Src/ChunkFile.h | 15 +- Source/Core/Common/Src/Thread.cpp | 14 +- Source/Core/Common/Src/Thread.h | 1 + Source/Core/Core/Src/Core.cpp | 10 +- Source/Core/Core/Src/Core.h | 1 + Source/Core/Core/Src/CoreTiming.cpp | 16 ++ Source/Core/Core/Src/CoreTiming.h | 1 + Source/Core/Core/Src/LuaInterface.cpp | 98 +++++++++-- Source/Core/Core/Src/State.cpp | 210 ++++++++++++++++++++---- Source/Core/Core/Src/State.h | 7 + Source/Core/DolphinWX/Src/LuaWindow.cpp | 55 ++++++- Source/Core/DolphinWX/Src/LuaWindow.h | 11 +- 12 files changed, 379 insertions(+), 60 deletions(-) diff --git a/Source/Core/Common/Src/ChunkFile.h b/Source/Core/Common/Src/ChunkFile.h index 00b50b4a8d..a9ec34adc6 100644 --- a/Source/Core/Common/Src/ChunkFile.h +++ b/Source/Core/Common/Src/ChunkFile.h @@ -46,10 +46,11 @@ struct LinkedListItem : public T class PointerWrap { public: - enum Mode { - MODE_READ = 1, - MODE_WRITE, - MODE_MEASURE, + enum Mode { + MODE_READ = 1, // load + MODE_WRITE, // save + MODE_MEASURE, // calculate size + MODE_VERIFY, // compare }; u8 **ptr; @@ -69,6 +70,7 @@ public: case MODE_READ: memcpy(data, *ptr, size); break; case MODE_WRITE: memcpy(*ptr, data, size); break; case MODE_MEASURE: break; // MODE_MEASURE - don't need to do anything + case MODE_VERIFY: for(int i = 0; i < size; i++) _dbg_assert_msg_(COMMON, ((u8*)data)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at 0x%X) != %d (0x%X) (at 0x%X).\n", ((u8*)data)[i], ((u8*)data)[i], &((u8*)data)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break; default: break; // throw an error? } (*ptr) += size; @@ -102,6 +104,7 @@ public: break; case MODE_WRITE: case MODE_MEASURE: + case MODE_VERIFY: { std::map::iterator itr = x.begin(); while (number > 0) @@ -133,6 +136,7 @@ public: case MODE_READ: x = (char*)*ptr; break; case MODE_WRITE: memcpy(*ptr, x.c_str(), stringLen); break; case MODE_MEASURE: break; + case MODE_VERIFY: _dbg_assert_msg_(COMMON, !strcmp(x.c_str(), (char*)*ptr), "Savestate verification failure: \"%s\" != \"%s\" (at 0x%X).\n", x.c_str(), (char*)*ptr, ptr); break; } (*ptr) += stringLen; } @@ -143,9 +147,10 @@ public: if (_Size > 0) { switch (mode) { - case MODE_READ: *pBuffer = new u8[_Size]; memcpy(*pBuffer, *ptr, _Size); break; + case MODE_READ: delete[] *pBuffer; *pBuffer = new u8[_Size]; memcpy(*pBuffer, *ptr, _Size); break; case MODE_WRITE: memcpy(*ptr, *pBuffer, _Size); break; case MODE_MEASURE: break; + case MODE_VERIFY: if(*pBuffer) for(u32 i = 0; i < _Size; i++) _dbg_assert_msg_(COMMON, (*pBuffer)[i] == (*ptr)[i], "Savestate verification failure: %d (0x%X) (at 0x%X) != %d (0x%X) (at 0x%X).\n", (*pBuffer)[i], (*pBuffer)[i], &(*pBuffer)[i], (*ptr)[i], (*ptr)[i], &(*ptr)[i]); break; } } else { *pBuffer = NULL; diff --git a/Source/Core/Common/Src/Thread.cpp b/Source/Core/Common/Src/Thread.cpp index 8346ccb592..13ee37cd89 100644 --- a/Source/Core/Common/Src/Thread.cpp +++ b/Source/Core/Common/Src/Thread.cpp @@ -117,7 +117,12 @@ void Thread::SetCurrentThreadAffinity(int mask) SetThreadAffinityMask(GetCurrentThread(), mask); } -#ifdef _WIN32 +bool Thread::IsCurrentThread() +{ + return GetCurrentThreadId() == m_threadId; +} + + EventEx::EventEx() { InterlockedExchange(&m_Lock, 1); @@ -169,7 +174,7 @@ bool EventEx::MsgWait() } return true; } -#endif + // Regular same thread loop based waiting Event::Event() @@ -397,6 +402,11 @@ void Thread::SetCurrentThreadAffinity(int mask) #endif } +bool Thread::IsCurrentThread() +{ + return pthread_equal(pthread_self(), thread_id) != 0; +} + void InitThreading() { static int thread_init_done = 0; if (thread_init_done) diff --git a/Source/Core/Common/Src/Thread.h b/Source/Core/Common/Src/Thread.h index f667cc170c..6905d32eb0 100644 --- a/Source/Core/Common/Src/Thread.h +++ b/Source/Core/Common/Src/Thread.h @@ -110,6 +110,7 @@ public: void SetAffinity(int mask); static void SetCurrentThreadAffinity(int mask); static int CurrentId(); + bool IsCurrentThread(); #ifdef _WIN32 void SetPriority(int priority); DWORD WaitForDeath(const int iWait = INFINITE); diff --git a/Source/Core/Core/Src/Core.cpp b/Source/Core/Core/Src/Core.cpp index f12bec8af7..270ab1d717 100644 --- a/Source/Core/Core/Src/Core.cpp +++ b/Source/Core/Core/Src/Core.cpp @@ -101,12 +101,16 @@ void *g_pXWindow = NULL; #endif Common::Thread* g_EmuThread = NULL; +static Common::Thread* cpuThread = NULL; + SCoreStartupParameter g_CoreStartupParameter; // This event is set when the emuthread starts. Common::Event emuThreadGoing; Common::Event cpuRunloopQuit; + + // Display messages and return values // Formatted stop message @@ -162,6 +166,10 @@ bool isRunning() return (GetState() != CORE_UNINITIALIZED) || g_bHwInit; } +bool IsRunningInCurrentThread() +{ + return isRunning() && ((cpuThread == NULL) || cpuThread->IsCurrentThread()); +} // This is called from the GUI thread. See the booting call schedule in // BootManager.cpp @@ -423,7 +431,7 @@ THREAD_RETURN EmuThread(void *pArg) PowerPC::SetMode(PowerPC::MODE_INTERPRETER); // Spawn the CPU thread - Common::Thread *cpuThread = NULL; + _dbg_assert_(HLE, cpuThread == NULL); // ENTER THE VIDEO THREAD LOOP if (_CoreParameter.bCPUThread) { diff --git a/Source/Core/Core/Src/Core.h b/Source/Core/Core/Src/Core.h index 4096cc191f..b6c13aff35 100644 --- a/Source/Core/Core/Src/Core.h +++ b/Source/Core/Core/Src/Core.h @@ -48,6 +48,7 @@ namespace Core std::string StopMessage(bool, std::string); bool isRunning(); + bool IsRunningInCurrentThread(); // this tells us whether we are in the cpu thread. void SetState(EState _State); EState GetState(); diff --git a/Source/Core/Core/Src/CoreTiming.cpp b/Source/Core/Core/Src/CoreTiming.cpp index 3e1ae9eeb6..19ff68be89 100644 --- a/Source/Core/Core/Src/CoreTiming.cpp +++ b/Source/Core/Core/Src/CoreTiming.cpp @@ -20,6 +20,7 @@ #include "Thread.h" #include "PowerPC/PowerPC.h" #include "CoreTiming.h" +#include "Core.h" #include "StringUtil.h" #define MAX_SLICE_LENGTH 20000 @@ -174,6 +175,7 @@ void DoState(PointerWrap &p) } break; case PointerWrap::MODE_MEASURE: + case PointerWrap::MODE_VERIFY: case PointerWrap::MODE_WRITE: { Event *ev = first; @@ -217,6 +219,20 @@ void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata externalEventSection.Leave(); } +// Same as ScheduleEvent_Threadsafe(0, ...) EXCEPT if we are already on the main thread +// in which case the event will get handled immediately, before returning. +void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata) +{ + if(Core::IsRunningInCurrentThread()) + { + externalEventSection.Enter(); + event_types[event_type].callback(userdata, 0); + externalEventSection.Leave(); + } + else + ScheduleEvent_Threadsafe(0, event_type, userdata); +} + void ClearPendingEvents() { while (first) diff --git a/Source/Core/Core/Src/CoreTiming.h b/Source/Core/Core/Src/CoreTiming.h index d8176daab7..2665d39f43 100644 --- a/Source/Core/Core/Src/CoreTiming.h +++ b/Source/Core/Core/Src/CoreTiming.h @@ -58,6 +58,7 @@ void UnregisterAllEvents(); // when we implement state saves. void ScheduleEvent(int cyclesIntoFuture, int event_type, u64 userdata=0); void ScheduleEvent_Threadsafe(int cyclesIntoFuture, int event_type, u64 userdata=0); +void ScheduleEvent_Threadsafe_Immediate(int event_type, u64 userdata=0); // We only permit one event of each type in the queue at a time. void RemoveEvent(int event_type); diff --git a/Source/Core/Core/Src/LuaInterface.cpp b/Source/Core/Core/Src/LuaInterface.cpp index 0779f1a89d..4bc4a80e4d 100644 --- a/Source/Core/Core/Src/LuaInterface.cpp +++ b/Source/Core/Core/Src/LuaInterface.cpp @@ -78,9 +78,9 @@ struct LuaContextInfo { bool ranExit; // used to prevent a registered exit callback from ever getting called more than once bool guiFuncsNeedDeferring; // true whenever GUI drawing would be cleared by the next emulation update before it would be visible, and thus needs to be deferred until after the next emulation update int numDeferredGUIFuncs; // number of deferred function calls accumulated, used to impose an arbitrary limit to avoid running out of memory - bool ranFrameAdvance; // false if emulua.frameadvance() hasn't been called yet + bool ranFrameAdvance; // false if emu.frameadvance() hasn't been called yet int transparencyModifier; // values less than 255 will scale down the opacity of whatever the GUI renders, values greater than 255 will increase the opacity of anything transparent the GUI renders - SpeedMode speedMode; // determines how emulua.frameadvance() acts + SpeedMode speedMode; // determines how emu.frameadvance() acts char panicMessage [72]; // a message to print if the script terminates due to panic being set std::string lastFilename; // path to where the script last ran from so that restart can work (note: storing the script in memory instead would not be useful because we always want the most up-to-date script from file) std::string nextFilename; // path to where the script should run from next, mainly used in case the restart flag is true @@ -500,7 +500,7 @@ static char* ConstructScriptSaveDataPath(char* output, int bufferSize, LuaContex return rv; } -// emulua.persistglobalvariables({ +// emu.persistglobalvariables({ // variable1 = defaultvalue1, // variable2 = defaultvalue2, // etc @@ -580,7 +580,7 @@ DEFINE_LUA_FUNCTION(emulua_persistglobalvariables, "variabletable") } else { - luaL_error(L, "'%s' = '%s' entries are not allowed in the table passed to emulua.persistglobalvariables()", lua_typename(L,keyType), lua_typename(L,valueType)); + luaL_error(L, "'%s' = '%s' entries are not allowed in the table passed to emu.persistglobalvariables()", lua_typename(L,keyType), lua_typename(L,valueType)); } int varNameIndex = valueIndex; @@ -1101,10 +1101,10 @@ DEFINE_LUA_FUNCTION(bitbit, "whichbit") return 1; } -// tells emulua to wait while the script is doing calculations -// can call this periodically instead of emulua.frameadvance +// tells dolphin to wait while the script is doing calculations +// can call this periodically instead of emu.frameadvance // note that the user can use hotkeys at this time -// (e.g. a savestate could possibly get loaded before emulua.wait() returns) +// (e.g. a savestate could possibly get loaded before emu.wait() returns) DEFINE_LUA_FUNCTION(emulua_wait, "") { /*LuaContextInfo& info = GetCurrentInfo(); @@ -1211,9 +1211,33 @@ void printfToOutput(const char* fmt, ...) delete[] str; } -// What is THAT? bool FailVerifyAtFrameBoundary(lua_State* L, const char* funcName, int unstartedSeverity=2, int inframeSeverity=2) { + if (!Core::isRunning()) + { + static const char* msg = "cannot call %s() when emulation has not started."; + switch(unstartedSeverity) + { + case 0: break; + case 1: printfToOutput(msg, funcName); break; + default: case 2: luaL_error(L, msg, funcName); break; + } + return true; + } + // "if the current thread is not the cpu thread or if our caller is the cpu emulation itself" + // TODO: implement the second part of the condition here. + // it might boil down to "is CallRegisteredLuaMemHook on the callstack" in the case of Dolphin. + if(!Core::IsRunningInCurrentThread() /*|| CallerIsCpuEmulation()*/) + { + static const char* msg = "cannot call %s() inside an emulation frame."; + switch(inframeSeverity) + { + case 0: break; + case 1: printfToOutput(msg, funcName); break; + default: case 2: luaL_error(L, msg, funcName); break; + } + return true; + } return false; } @@ -1223,7 +1247,7 @@ bool FailVerifyAtFrameBoundary(lua_State* L, const char* funcName, int unstarted // except without the user being able to activate emulator commands DEFINE_LUA_FUNCTION(emulua_emulateframe, "") { - if(FailVerifyAtFrameBoundary(L, "emulua.emulateframe", 0,1)) + if(FailVerifyAtFrameBoundary(L, "emu.emulateframe", 0,1)) return 0; Update_Emulation_One((HWND)Core::g_CoreStartupParameter.hMainWindow); @@ -1237,7 +1261,7 @@ DEFINE_LUA_FUNCTION(emulua_emulateframe, "") // and the user is unable to activate emulator commands during it DEFINE_LUA_FUNCTION(emulua_emulateframefastnoskipping, "") { - if(FailVerifyAtFrameBoundary(L, "emulua.emulateframefastnoskipping", 0,1)) + if(FailVerifyAtFrameBoundary(L, "emu.emulateframefastnoskipping", 0,1)) return 0; Update_Emulation_One_Before((HWND)Core::g_CoreStartupParameter.hMainWindow); @@ -1253,7 +1277,7 @@ DEFINE_LUA_FUNCTION(emulua_emulateframefastnoskipping, "") // where the user is unable to activate emulator commands DEFINE_LUA_FUNCTION(emulua_emulateframefast, "") { - if(FailVerifyAtFrameBoundary(L, "emulua.emulateframefast", 0,1)) + if(FailVerifyAtFrameBoundary(L, "emu.emulateframefast", 0,1)) return 0; disableVideoLatencyCompensationCount = VideoLatencyCompensation + 1; @@ -1288,7 +1312,7 @@ DEFINE_LUA_FUNCTION(emulua_emulateframefast, "") // while the user continues to see and hear normal emulation DEFINE_LUA_FUNCTION(emulua_emulateframeinvisible, "") { - if(FailVerifyAtFrameBoundary(L, "emulua.emulateframeinvisible", 0,1)) + if(FailVerifyAtFrameBoundary(L, "emu.emulateframeinvisible", 0,1)) return 0; int oldDisableSound2 = disableSound2; @@ -1340,7 +1364,7 @@ DEFINE_LUA_FUNCTION(emulua_speedmode, "mode") DEFINE_LUA_FUNCTION(emulua_frameadvance, "") { - if(FailVerifyAtFrameBoundary(L, "emulua.frameadvance", 0,1)) + if(FailVerifyAtFrameBoundary(L, "emu.frameadvance", 0,1)) return emulua_wait(L); if(Core::GetState() == Core::CORE_UNINITIALIZED || Core::GetState() == Core::CORE_STOPPING) @@ -1655,6 +1679,9 @@ DEFINE_LUA_FUNCTION(state_create, "[location]") return 1; } + if(!Core::isRunning()) + luaL_error(L, "savestate.create cannot be called before emulation has started."); + size_t len = State_GetSize(); // allocate the in-memory/anonymous savestate @@ -1672,7 +1699,7 @@ DEFINE_LUA_FUNCTION(state_create, "[location]") // if option is "scriptdataonly" then the state will not actually be saved, but any save callbacks will still get called and their results will be saved (see savestate.registerload()/savestate.registersave()) DEFINE_LUA_FUNCTION(state_save, "location[,option]") { - const char* option = (lua_type(L,2) == LUA_TSTRING) ? lua_tostring(L,2) : NULL; + const char* option = (lua_type(L,2) == LUA_TSTRING) ? lua_tostring(L,2) : NULL; if(option) { if(!strcasecmp(option, "quiet")) // I'm not sure if saving can generate warning messages, but we might as well support suppressing them should they turn out to exist @@ -1691,6 +1718,7 @@ DEFINE_LUA_FUNCTION(state_save, "location[,option]") case LUA_TNUMBER: // numbered save file default: { + State_Flush(); State_Save((int)luaL_checkinteger(L,1)); return 0; } @@ -1701,6 +1729,7 @@ DEFINE_LUA_FUNCTION(state_save, "location[,option]") if(stateBuffer) { stateBuffer += ((16 - (u64)stateBuffer) & 15); // for performance alignment reasons + State_Flush(); State_SaveToBuffer(&stateBuffer); } return 0; @@ -1740,6 +1769,7 @@ DEFINE_LUA_FUNCTION(state_load, "location[,option]") LuaContextInfo& info = GetCurrentInfo(); if(info.rerecordCountingDisabled) SkipNextRerecordIncrement = true; + State_Flush(); State_Load((int)luaL_checkinteger(L,1)); return 0; @@ -1751,6 +1781,7 @@ DEFINE_LUA_FUNCTION(state_load, "location[,option]") if(stateBuffer) { stateBuffer += ((16 - (u64)stateBuffer) & 15); // for performance alignment reasons + State_Flush(); if(stateBuffer[0]) State_LoadFromBuffer(&stateBuffer); else // the first byte of a valid savestate is never 0 @@ -1761,6 +1792,40 @@ DEFINE_LUA_FUNCTION(state_load, "location[,option]") } } +// savestate.verify(location) +// verifies that the current emulation state matches the savestate that's already at the given location +// you can pass in either a savestate file number (an integer), +// OR you can pass in a savestate object that was returned by savestate.create() and has already saved to with savestate.save() +DEFINE_LUA_FUNCTION(state_verify, "location") +{ + int type = lua_type(L,1); + switch(type) + { + case LUA_TNUMBER: // numbered save file + default: + { + State_Flush(); + State_Verify((int)luaL_checkinteger(L,1)); + return 0; + } + + case LUA_TUSERDATA: // in-memory save slot + { + unsigned char* stateBuffer = (unsigned char*)lua_touserdata(L,1); + if(stateBuffer) + { + stateBuffer += ((16 - (u64)stateBuffer) & 15); // for performance alignment reasons + State_Flush(); + if(stateBuffer[0]) + State_VerifyBuffer(&stateBuffer); + else // the first byte of a valid savestate is never 0 + luaL_error(L, "attempted to verify an anonymous savestate before saving it"); + } + return 0; + } + } +} + // savestate.loadscriptdata(location) // returns the user data associated with the given savestate // without actually loading the rest of that savestate or calling any callbacks. @@ -2170,8 +2235,8 @@ DEFINE_LUA_FUNCTION(gui_parsecolor, "color") DEFINE_LUA_FUNCTION(gui_text, "x,y,str[,color=\"white\"[,outline=\"black\"]]") { if(DeferGUIFuncIfNeeded(L)) - return 0; // we have to wait until later to call this function because emulua hasn't emulated the next frame yet - // (the only way to avoid this deferring is to be in a gui.register or emulua.registerafter callback) + return 0; // we have to wait until later to call this function because dolphin hasn't emulated the next frame yet + // (the only way to avoid this deferring is to be in a gui.register or emu.registerafter callback) int x = (int)luaL_checkinteger(L,1) & 0xFFFF; int y = (int)luaL_checkinteger(L,2) & 0xFFFF; @@ -3045,6 +3110,7 @@ static const struct luaL_reg statelib [] = {"create", state_create}, {"save", state_save}, {"load", state_load}, + {"verify", state_verify}, {"loadscriptdata", state_loadscriptdata}, {"savescriptdata", state_savescriptdata}, {"registersave", state_registersave}, diff --git a/Source/Core/Core/Src/State.cpp b/Source/Core/Core/Src/State.cpp index 4f285b28ea..1da95de50f 100644 --- a/Source/Core/Core/Src/State.cpp +++ b/Source/Core/Core/Src/State.cpp @@ -61,6 +61,7 @@ static bool state_op_in_progress = false; static int ev_Save, ev_BufferSave; static int ev_Load, ev_BufferLoad; +static int ev_Verify, ev_BufferVerify; static std::string cur_filename, lastFilename; static u8 **cur_buffer = NULL; @@ -97,6 +98,11 @@ void DoState(PointerWrap &p) PowerPC::DoState(p); HW::DoState(p); CoreTiming::DoState(p); + + // TODO: it's a GIGANTIC waste of time and space to savestate the following + // (adds 128MB of mostly-empty cache data to every savestate). + // it seems to be unnecessary as far as I can tell, + // but I can't prove it is yet so I'll leave it here for now... #ifdef JIT_UNLIMITED_ICACHE p.DoVoid(jit->GetBlockCache()->GetICache(), JIT_ICACHE_SIZE); p.DoVoid(jit->GetBlockCache()->GetICacheEx(), JIT_ICACHEEX_SIZE); @@ -123,8 +129,6 @@ void LoadBufferStateCallback(u64 userdata, int cyclesLate) void SaveBufferStateCallback(u64 userdata, int cyclesLate) { - size_t sz; - if (!cur_buffer) { Core::DisplayMessage("Error saving state", 1000); return; @@ -135,13 +139,21 @@ void SaveBufferStateCallback(u64 userdata, int cyclesLate) u8 *ptr = NULL; PointerWrap p(&ptr, PointerWrap::MODE_MEASURE); - DoState(p); - sz = (size_t)ptr; - if (*cur_buffer) - delete[] (*cur_buffer); + if (!*cur_buffer) + { + // if we got passed an empty buffer, + // allocate it with new[] + // (and the caller is responsible for delete[]ing it later) + DoState(p); + size_t sz = (size_t)ptr; + *cur_buffer = new u8[sz]; + } + else + { + // otherwise the caller is telling us that they have already allocated it with enough space + } - *cur_buffer = new u8[sz]; ptr = *cur_buffer; p.SetMode(PointerWrap::MODE_WRITE); DoState(p); @@ -149,6 +161,23 @@ void SaveBufferStateCallback(u64 userdata, int cyclesLate) state_op_in_progress = false; } +void VerifyBufferStateCallback(u64 userdata, int cyclesLate) +{ + if (!cur_buffer || !*cur_buffer) { + Core::DisplayMessage("State does not exist", 1000); + return; + } + + jit->ClearCache(); + + u8 *ptr = *cur_buffer; + PointerWrap p(&ptr, PointerWrap::MODE_VERIFY); + DoState(p); + + Core::DisplayMessage("Verified state", 2000); + state_op_in_progress = false; +} + THREAD_RETURN CompressAndDumpState(void *pArgs) { saveStruct *saveArg = (saveStruct *)pArgs; @@ -224,12 +253,7 @@ THREAD_RETURN CompressAndDumpState(void *pArgs) void SaveStateCallback(u64 userdata, int cyclesLate) { - // If already saving state, wait for it to finish - if (saveThread) - { - delete saveThread; - saveThread = NULL; - } + State_Flush(); jit->ClearCache(); @@ -258,16 +282,17 @@ void LoadStateCallback(u64 userdata, int cyclesLate) { bool bCompressedState; - // If saving state, wait for it to finish - if (saveThread) - { - delete saveThread; - saveThread = NULL; - } + State_Flush(); // Save temp buffer for undo load state - cur_buffer = &undoLoad; - SaveBufferStateCallback(userdata, cyclesLate); + // TODO: this should be controlled by a user option, + // because it slows down every savestate load to provide an often-unused feature. + { + delete[] undoLoad; + undoLoad = NULL; + cur_buffer = &undoLoad; + SaveBufferStateCallback(userdata, cyclesLate); + } FILE *f = fopen(cur_filename.c_str(), "rb"); if (!f) @@ -356,12 +381,108 @@ void LoadStateCallback(u64 userdata, int cyclesLate) delete[] buffer; } +void VerifyStateCallback(u64 userdata, int cyclesLate) +{ + bool bCompressedState; + + State_Flush(); + + FILE *f = fopen(cur_filename.c_str(), "rb"); + if (!f) + { + Core::DisplayMessage("State not found", 2000); + return; + } + + u8 *buffer = NULL; + state_header header; + size_t sz; + + fread(&header, sizeof(state_header), 1, f); + + if (memcmp(SConfig::GetInstance().m_LocalCoreStartupParameter.GetUniqueID().c_str(), header.gameID, 6)) + { + char gameID[7] = {0}; + memcpy(gameID, header.gameID, 6); + Core::DisplayMessage(StringFromFormat("State belongs to a different game (ID %s)", + gameID), 2000); + + fclose(f); + + return; + } + + sz = header.sz; + bCompressedState = (sz != 0); + if (bCompressedState) + { + Core::DisplayMessage("Decompressing State...", 500); + + lzo_uint i = 0; + buffer = new u8[sz]; + if (!buffer) { + PanicAlert("Error allocating buffer"); + return; + } + while (true) + { + lzo_uint cur_len = 0; + lzo_uint new_len = 0; + if (fread(&cur_len, 1, sizeof(int), f) == 0) + break; + if (feof(f)) + break; // don't know if this happens. + fread(out, 1, cur_len, f); + int res = lzo1x_decompress(out, cur_len, (buffer + i), &new_len, NULL); + if (res != LZO_E_OK) + { + // This doesn't seem to happen anymore. + PanicAlert("Internal LZO Error - decompression failed (%d) (%d, %d) \n" + "Try verifying the state again", res, i, new_len); + fclose(f); + delete [] buffer; + return; + } + + // The size of the data to read to our buffer is 'new_len' + i += new_len; + } + } + else + { + fseek(f, 0, SEEK_END); + sz = (int)(ftell(f) - sizeof(int)); + fseek(f, sizeof(int), SEEK_SET); + buffer = new u8[sz]; + int x; + if ((x = (int)fread(buffer, 1, sz, f)) != (int)sz) + PanicAlert("wtf? %d %d", x, sz); + } + + fclose(f); + + jit->ClearCache(); + + u8 *ptr = buffer; + PointerWrap p(&ptr, PointerWrap::MODE_VERIFY); + DoState(p); + + if (p.GetMode() == PointerWrap::MODE_READ) + Core::DisplayMessage(StringFromFormat("Verified state at %s", cur_filename.c_str()).c_str(), 2000); + else + Core::DisplayMessage("Unable to Verify : Can't verify state from other revisions !", 4000); + + delete [] buffer; +} + void State_Init() { ev_Load = CoreTiming::RegisterEvent("LoadState", &LoadStateCallback); ev_Save = CoreTiming::RegisterEvent("SaveState", &SaveStateCallback); + ev_Verify = CoreTiming::RegisterEvent("VerifyState", &VerifyStateCallback); ev_BufferLoad = CoreTiming::RegisterEvent("LoadBufferState", &LoadBufferStateCallback); ev_BufferSave = CoreTiming::RegisterEvent("SaveBufferState", &SaveBufferStateCallback); + ev_BufferVerify = CoreTiming::RegisterEvent("VerifyBufferState", &VerifyBufferStateCallback); if (lzo_init() != LZO_E_OK) PanicAlert("Internal LZO Error - lzo_init() failed"); @@ -369,11 +490,7 @@ void State_Init() void State_Shutdown() { - if (saveThread) - { - delete saveThread; - saveThread = NULL; - } + State_Flush(); if (undoLoad) { @@ -394,7 +511,7 @@ void State_SaveAs(const std::string &filename) state_op_in_progress = true; cur_filename = filename; lastFilename = filename; - CoreTiming::ScheduleEvent_Threadsafe(0, ev_Save); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_Save); } void State_Save(int slot) @@ -408,7 +525,7 @@ void State_LoadAs(const std::string &filename) return; state_op_in_progress = true; cur_filename = filename; - CoreTiming::ScheduleEvent_Threadsafe(0, ev_Load); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_Load); } void State_Load(int slot) @@ -416,6 +533,20 @@ void State_Load(int slot) State_LoadAs(MakeStateFilename(slot)); } +void State_VerifyAt(const std::string &filename) +{ + if (state_op_in_progress) + return; + state_op_in_progress = true; + cur_filename = filename; + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_Verify); +} + +void State_Verify(int slot) +{ + State_VerifyAt(MakeStateFilename(slot)); +} + void State_LoadLastSaved() { if (lastFilename.empty()) @@ -430,7 +561,7 @@ void State_LoadFromBuffer(u8 **buffer) return; state_op_in_progress = true; cur_buffer = buffer; - CoreTiming::ScheduleEvent_Threadsafe(0, ev_BufferLoad); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_BufferLoad); } void State_SaveToBuffer(u8 **buffer) @@ -439,7 +570,26 @@ void State_SaveToBuffer(u8 **buffer) return; state_op_in_progress = true; cur_buffer = buffer; - CoreTiming::ScheduleEvent_Threadsafe(0, ev_BufferSave); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_BufferSave); +} + +void State_VerifyBuffer(u8 **buffer) +{ + if (state_op_in_progress) + return; + state_op_in_progress = true; + cur_buffer = buffer; + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_BufferVerify); +} + +void State_Flush() +{ + // If already saving state, wait for it to finish + if (saveThread) + { + delete saveThread; + saveThread = NULL; + } } // Load the last state before loading the state diff --git a/Source/Core/Core/Src/State.h b/Source/Core/Core/Src/State.h index 0f6400f021..6102740946 100644 --- a/Source/Core/Core/Src/State.h +++ b/Source/Core/Core/Src/State.h @@ -32,21 +32,28 @@ void State_Init(); void State_Shutdown(); // These don't happen instantly - they get scheduled as events. +// ...But only if we're not in the main cpu thread. +// If we're in the main cpu thread then they run immediately instead +// because some things (like Lua) need them to run immediately. // Slots from 0-99. void State_Save(int slot); void State_Load(int slot); +void State_Verify(int slot); void State_SaveAs(const std::string &filename); void State_LoadAs(const std::string &filename); +void State_VerifyAt(const std::string &filename); void State_LoadFromBuffer(u8 **buffer); void State_SaveToBuffer(u8 **buffer); +void State_VerifyBuffer(u8 **buffer); void State_LoadLastSaved(); void State_UndoSaveState(); void State_UndoLoadState(); size_t State_GetSize(); +void State_Flush(); // wait until previously scheduled savestate event (if any) is done typedef struct diff --git a/Source/Core/DolphinWX/Src/LuaWindow.cpp b/Source/Core/DolphinWX/Src/LuaWindow.cpp index 5699c91e2b..f1c9f00f10 100644 --- a/Source/Core/DolphinWX/Src/LuaWindow.cpp +++ b/Source/Core/DolphinWX/Src/LuaWindow.cpp @@ -17,6 +17,7 @@ #include "LuaWindow.h" #include "LuaInterface.h" +#include "../../Core/Src/CoreTiming.h" #include @@ -33,10 +34,14 @@ BEGIN_EVENT_TABLE(wxLuaWindow, wxWindow) EVT_BUTTON(ID_BUTTON_LOAD, wxLuaWindow::OnEvent_ScriptLoad_Press) EVT_BUTTON(ID_BUTTON_RUN, wxLuaWindow::OnEvent_ScriptRun_Press) EVT_BUTTON(ID_BUTTON_STOP, wxLuaWindow::OnEvent_ScriptStop_Press) + EVT_BUTTON(ID_BUTTON_CLEAR, wxLuaWindow::OnEvent_ButtonClear_Press) END_EVENT_TABLE() std::map g_contextMap; +static int ev_LuaOpen, ev_LuaClose, ev_LuaStart, ev_LuaStop; + + void LuaPrint(int uid, const char *msg) { g_contextMap[uid]->PrintMessage(msg); @@ -55,11 +60,13 @@ void LuaStop(int uid, bool ok) wxLuaWindow::wxLuaWindow(wxFrame* parent, const wxPoint& pos, const wxSize& size) : wxFrame(parent, wxID_ANY, _T("Lua Script Console"), pos, size, wxDEFAULT_FRAME_STYLE | wxNO_FULL_REPAINT_ON_RESIZE) { + LuaWindow_InitFirstTime(); + // Create Lua context luaID = luaCount; - Lua::OpenLuaContext(luaID, LuaPrint, NULL, LuaStop); - g_contextMap[luaID] = this; luaCount++; + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_LuaOpen, luaID); + g_contextMap[luaID] = this; bScriptRunning = false; // Create the GUI controls @@ -76,7 +83,7 @@ wxLuaWindow::wxLuaWindow(wxFrame* parent, const wxPoint& pos, const wxSize& size wxLuaWindow::~wxLuaWindow() { // On Disposal - Lua::CloseLuaContext(luaID); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_LuaClose, luaID); g_contextMap.erase(luaID); } @@ -104,6 +111,7 @@ void wxLuaWindow::InitGUIControls() m_Button_LoadScript = new wxButton(this, ID_BUTTON_LOAD, _T("Load Script..."), wxDefaultPosition, wxDefaultSize); m_Button_Run = new wxButton(this, ID_BUTTON_RUN, _T("Run"), wxDefaultPosition, wxDefaultSize); m_Button_Stop = new wxButton(this, ID_BUTTON_STOP, _T("Stop"), wxDefaultPosition, wxDefaultSize); + m_Button_Clear = new wxButton(this, ID_BUTTON_CLEAR, _T("Clear"), wxDefaultPosition, wxDefaultSize); wxBoxSizer* sButtons = new wxBoxSizer(wxHORIZONTAL); m_Button_Run->Disable(); @@ -113,6 +121,7 @@ void wxLuaWindow::InitGUIControls() sButtons->Add(m_Button_LoadScript, 0, wxALL, 5); sButtons->Add(m_Button_Run, 0, wxALL, 5); sButtons->Add(m_Button_Stop, 0, wxALL, 5); + sButtons->Add(m_Button_Clear, 0, wxALL, 5); wxBoxSizer* sMain = new wxBoxSizer(wxVERTICAL); sMain->Add(m_Tab_Log, 1, wxEXPAND|wxALL, 5); @@ -157,12 +166,12 @@ void wxLuaWindow::OnEvent_ScriptRun_Press(wxCommandEvent& WXUNUSED(event)) m_Button_Run->Disable(); m_Button_Stop->Enable(); - Lua::RunLuaScriptFile(luaID, (const char *)currentScript.mb_str()); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_LuaStart, luaID); } void wxLuaWindow::OnEvent_ScriptStop_Press(wxCommandEvent& WXUNUSED(event)) { - Lua::StopLuaScript(luaID); + CoreTiming::ScheduleEvent_Threadsafe_Immediate(ev_LuaStop, luaID); OnStop(); PrintMessage("Script stopped!\n"); } @@ -175,6 +184,11 @@ void wxLuaWindow::OnStop() m_Button_Stop->Disable(); } +void wxLuaWindow::OnEvent_ButtonClear_Press(wxCommandEvent& WXUNUSED (event)) +{ + m_TextCtrl_Log->Clear(); +} + void wxLuaWindow::OnEvent_Window_Resize(wxSizeEvent& WXUNUSED (event)) { Layout(); @@ -188,3 +202,34 @@ void wxLuaWindow::OnEvent_Window_Close(wxCloseEvent& WXUNUSED (event)) Destroy(); } + +// this layer of event stuff is because Lua needs to run on the CPU thread +void wxLuaWindow::LuaOpenCallback(u64 userdata, int) +{ + Lua::OpenLuaContext((int)userdata, LuaPrint, NULL, LuaStop); +} +void wxLuaWindow::LuaCloseCallback(u64 userdata, int) +{ + Lua::CloseLuaContext((int)userdata); +} +void wxLuaWindow::LuaStartCallback(u64 userdata, int) +{ + int luaID = (int)userdata; + Lua::RunLuaScriptFile(luaID, (const char *)g_contextMap[luaID]->currentScript.mb_str()); +} +void wxLuaWindow::LuaStopCallback(u64 userdata, int) +{ + Lua::StopLuaScript((int)userdata); +} +void wxLuaWindow::LuaWindow_InitFirstTime() +{ + static bool initialized = false; + if(!initialized) + { + ev_LuaOpen = CoreTiming::RegisterEvent("LuaOpen", &wxLuaWindow::LuaOpenCallback); + ev_LuaClose = CoreTiming::RegisterEvent("LuaClose", &wxLuaWindow::LuaCloseCallback); + ev_LuaStart = CoreTiming::RegisterEvent("LuaStart", &wxLuaWindow::LuaStartCallback); + ev_LuaStop = CoreTiming::RegisterEvent("LuaStop", &wxLuaWindow::LuaStopCallback); + initialized = true; + } +} diff --git a/Source/Core/DolphinWX/Src/LuaWindow.h b/Source/Core/DolphinWX/Src/LuaWindow.h index 00938b52af..be51929da3 100644 --- a/Source/Core/DolphinWX/Src/LuaWindow.h +++ b/Source/Core/DolphinWX/Src/LuaWindow.h @@ -60,7 +60,7 @@ class wxLuaWindow : public wxFrame wxPanel *m_Tab_Log; wxButton *m_Button_Close, *m_Button_LoadScript, *m_Button_Run, - *m_Button_Stop; + *m_Button_Stop, *m_Button_Clear; wxTextCtrl *m_TextCtrl_Log; @@ -72,6 +72,7 @@ class wxLuaWindow : public wxFrame ID_BUTTON_LOAD, ID_BUTTON_RUN, ID_BUTTON_STOP, + ID_BUTTON_CLEAR, ID_TEXTCTRL_LOG }; @@ -88,7 +89,15 @@ class wxLuaWindow : public wxFrame void OnEvent_ScriptLoad_Press(wxCommandEvent& event); void OnEvent_ScriptRun_Press(wxCommandEvent& event); void OnEvent_ScriptStop_Press(wxCommandEvent& event); + void OnEvent_ButtonClear_Press(wxCommandEvent& event); + // -- CoreTiming-style event handlers -- + static void LuaOpenCallback(u64 userdata, int cyclesLate); + static void LuaCloseCallback(u64 userdata, int cyclesLate); + static void LuaStartCallback(u64 userdata, int cyclesLate); + static void LuaStopCallback(u64 userdata, int cyclesLate); + + static void LuaWindow_InitFirstTime(); };