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
This commit is contained in:
nitsuja- 2010-04-17 21:02:03 +00:00
parent 3fb80c52af
commit 766818baa8
12 changed files with 379 additions and 60 deletions

View File

@ -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<unsigned int, std::string>::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;

View File

@ -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)

View File

@ -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);

View File

@ -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)
{

View File

@ -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();

View File

@ -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)

View File

@ -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);

View File

@ -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},

View File

@ -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

View File

@ -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

View File

@ -17,6 +17,7 @@
#include "LuaWindow.h"
#include "LuaInterface.h"
#include "../../Core/Src/CoreTiming.h"
#include <map>
@ -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<int, wxLuaWindow *> 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;
}
}

View File

@ -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();
};