Files
archived-pcsx2/pcsx2/RecoverySystem.cpp
Jake.Stine c17455c702 User Interface code cleanups and bugfixes. some highlights:
* Configuration panels are all modal-less now, so that you can open the config panel and leave it open while running games.
 * Handful of thread sync improvements.
 * Fixed on-the-fly interpreter/recompiler configuration.
 * Fixed plugin hotswapping (mostly works, but still a little funny at times)
 * All new assertion dialogs and popup message handlers.
 * RecentIsoList defaults to 12 instead of 6

Dev Notes:
 * I had to create a new set of assertion functions called pxAssume*.  Originally I hoped to avoid that complexity, and just use a single one-assert-fits-all case, but turned out blanketly using __assume() for all assertion cases wasn't reliable.
 * wxGuiTools: Replaced the operator, with operator& -- the latter has proper order of precedence, the former required () to scope correctly. >_<

git-svn-id: http://pcsx2.googlecode.com/svn/trunk@2339 96395faa-99c1-11dd-bbfe-3dabce05a288
2009-12-14 12:18:55 +00:00

430 lines
12 KiB
C++

/* PCSX2 - PS2 Emulator for PCs
* Copyright (C) 2002-2009 PCSX2 Dev Team
*
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Found-
* ation, either version 3 of te License, or (at your option) any later version.
*
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with PCSX2.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include "PrecompiledHeader.h"
#include "zlib/zlib.h"
#include "App.h"
#include "HostGui.h"
class _BaseStateThread;
// Used to hold the current state backup (fullcopy of PS2 memory and plugin states).
static SafeArray<u8> state_buffer;
_BaseStateThread* current_state_thread = NULL;
// Simple lock boolean for the state buffer being in use by a thread.
static NonblockingMutex state_buffer_lock;
// This boolean is to keep the system from resuming emulation until the current state has completely
// uploaded or downloaded itself. It is only modified from the main thread, and should only be read
// from the main thread.
int sys_resume_lock = 0;
static FnType_OnThreadComplete* Callback_FreezeFinished = NULL;
static void __evt_fastcall StateThread_OnAppStatus( void* thr, AppEventType& stat )
{
if( (thr == NULL) || (stat != AppStatus_Exiting) ) return;
((PersistentThread*)thr)->Cancel();
}
enum
{
StateThreadAction_None = 0,
StateThreadAction_Create,
StateThreadAction_Restore,
StateThreadAction_ZipToDisk,
StateThreadAction_UnzipFromDisk,
};
class _BaseStateThread : public PersistentThread
{
typedef PersistentThread _parent;
protected:
EventListenerBinding<AppEventType> m_bind_OnExit;
bool m_isStarted;
// Holds the pause/suspend state of the emulator when the state load/stave chain of action is started,
// so that the proper state can be restoed automatically on completion.
bool m_resume_when_done;
public:
virtual ~_BaseStateThread() throw()
{
if( !m_isStarted ) return;
// Assertion fails because C++ changes the 'this' pointer to the base class since
// derived classes have been deallocated at this point the destructor!
//pxAssumeDev( current_state_thread == this, wxCharNull );
current_state_thread = NULL;
state_buffer_lock.Release(); // just in case;
}
protected:
_BaseStateThread( const char* name, FnType_OnThreadComplete* onFinished ) :
m_bind_OnExit( wxGetApp().Source_AppStatus(), EventListener<AppEventType>( this, StateThread_OnAppStatus ) )
{
Callback_FreezeFinished = onFinished;
m_name = L"StateThread::" + fromUTF8(name);
m_isStarted = false;
m_resume_when_done = false;
}
void OnStart()
{
if( !state_buffer_lock.TryAcquire() )
throw Exception::CancelEvent( m_name + L"request ignored: state copy buffer is already locked!" );
current_state_thread = this;
m_isStarted = true;
_parent::OnStart();
}
void SendFinishEvent( int type )
{
wxCommandEvent evt( pxEVT_FreezeThreadFinished );
evt.SetClientData( this );
evt.SetInt( type );
evt.SetExtraLong( m_resume_when_done );
wxGetApp().AddPendingEvent( evt );
}
};
// --------------------------------------------------------------------------------------
// StateThread_Freeze
// --------------------------------------------------------------------------------------
class StateThread_Freeze : public _BaseStateThread
{
typedef _BaseStateThread _parent;
public:
StateThread_Freeze( FnType_OnThreadComplete* onFinished ) : _BaseStateThread( "Freeze", onFinished )
{
if( !SysHasValidState() )
throw Exception::RuntimeError( L"Cannot complete state freeze request; the virtual machine state is reset.", _("You'll need to start a new virtual machine before you can save its state.") );
}
protected:
void OnStart()
{
_parent::OnStart();
++sys_resume_lock;
m_resume_when_done = CoreThread.Pause();
}
void ExecuteTaskInThread()
{
memSavingState( state_buffer ).FreezeAll();
}
void OnCleanupInThread()
{
SendFinishEvent( StateThreadAction_Create );
_parent::OnCleanupInThread();
}
};
// --------------------------------------------------------------------------------------
// StateThread_ZipToDisk
// --------------------------------------------------------------------------------------
class StateThread_ZipToDisk : public _BaseStateThread
{
typedef _BaseStateThread _parent;
protected:
const wxString m_filename;
gzFile m_gzfp;
public:
StateThread_ZipToDisk( FnType_OnThreadComplete* onFinished, bool resume_done, const wxString& file )
: _BaseStateThread( "ZipToDisk", onFinished )
, m_filename( file )
{
m_gzfp = NULL;
m_resume_when_done = resume_done;
}
virtual ~StateThread_ZipToDisk() throw()
{
if( m_gzfp != NULL ) gzclose( m_gzfp );
}
protected:
void OnStart()
{
_parent::OnStart();
m_gzfp = gzopen( m_filename.ToUTF8(), "wb" );
if( m_gzfp == NULL )
throw Exception::CreateStream( m_filename, "Cannot create savestate file for writing." );
}
void ExecuteTaskInThread()
{
Yield( 3 );
static const int BlockSize = 0x10000;
int curidx = 0;
do
{
int thisBlockSize = std::min( BlockSize, state_buffer.GetSizeInBytes() - curidx );
if( gzwrite( m_gzfp, state_buffer.GetPtr(curidx), thisBlockSize ) < thisBlockSize )
throw Exception::BadStream( m_filename );
curidx += thisBlockSize;
Yield( 1 );
} while( curidx < state_buffer.GetSizeInBytes() );
}
void OnCleanupInThread()
{
SendFinishEvent( StateThreadAction_ZipToDisk );
_parent::OnCleanupInThread();
}
};
// --------------------------------------------------------------------------------------
// StateThread_UnzipFromDisk
// --------------------------------------------------------------------------------------
class StateThread_UnzipFromDisk : public _BaseStateThread
{
typedef _BaseStateThread _parent;
protected:
const wxString m_filename;
gzFile m_gzfp;
// set true only once the whole file has finished loading. IF the thread is canceled or
// an error occurs, this will remain false.
bool m_finished;
public:
StateThread_UnzipFromDisk( FnType_OnThreadComplete* onFinished, bool resume_done, const wxString& file )
: _BaseStateThread( "UnzipFromDisk", onFinished )
, m_filename( file )
{
m_gzfp = NULL;
m_finished = false;
m_resume_when_done = resume_done;
}
virtual ~StateThread_UnzipFromDisk() throw()
{
if( m_gzfp != NULL ) gzclose( m_gzfp );
}
protected:
void OnStart()
{
_parent::OnStart();
m_gzfp = gzopen( m_filename.ToUTF8(), "rb" );
if( m_gzfp == NULL )
throw Exception::CreateStream( m_filename, "Cannot open savestate file for reading." );
}
void ExecuteTaskInThread()
{
// fixme: should start initially with the file size, and then grow from there.
static const int BlockSize = 0x100000;
state_buffer.MakeRoomFor( 0x800000 ); // start with an 8 meg buffer to avoid frequent reallocation.
int curidx = 0;
do
{
state_buffer.MakeRoomFor( curidx+BlockSize );
gzread( m_gzfp, state_buffer.GetPtr(curidx), BlockSize );
curidx += BlockSize;
TestCancel();
} while( !gzeof(m_gzfp) );
m_finished = true;
}
void OnCleanupInThread()
{
SendFinishEvent( StateThreadAction_UnzipFromDisk );
_parent::OnCleanupInThread();
}
};
void Pcsx2App::OnFreezeThreadFinished( wxCommandEvent& evt )
{
// clear the OnFreezeFinished to NULL now, in case of error.
// (but only actually run it if no errors occur)
FnType_OnThreadComplete* fn_tmp = Callback_FreezeFinished;
Callback_FreezeFinished = NULL;
{
ScopedPtr<PersistentThread> thr( (PersistentThread*)evt.GetClientData() );
if( !pxAssertDev( thr != NULL, "NULL thread handle on freeze finished?" ) ) return;
state_buffer_lock.Release();
--sys_resume_lock;
thr->RethrowException();
}
if( fn_tmp != NULL ) fn_tmp( evt );
}
static void OnFinished_Resume( const wxCommandEvent& evt )
{
CoreThread.RecoverState();
if( evt.GetExtraLong() ) CoreThread.Resume();
}
static wxString zip_dest_filename;
static void OnFinished_ZipToDisk( const wxCommandEvent& evt )
{
if( !pxAssertDev( evt.GetInt() == StateThreadAction_Create, "Unexpected StateThreadAction value, aborting save." ) ) return;
if( zip_dest_filename.IsEmpty() )
{
Console.Warning( "Cannot save state to disk: empty filename specified." );
return;
}
// Phase 2: Record to disk!!
(new StateThread_ZipToDisk( NULL, !!evt.GetExtraLong(), zip_dest_filename ))->Start();
CoreThread.Resume();
}
// =====================================================================================================
// StateCopy Public Interface
// =====================================================================================================
void StateCopy_SaveToFile( const wxString& file )
{
if( state_buffer_lock.IsLocked() ) return;
zip_dest_filename = file;
(new StateThread_Freeze( OnFinished_ZipToDisk ))->Start();
Console.WriteLn( Color_StrongGreen, L"Saving savestate to file: %s", zip_dest_filename.c_str() );
}
void StateCopy_LoadFromFile( const wxString& file )
{
if( state_buffer_lock.IsLocked() ) return;
bool resume_when_done = CoreThread.Pause();
(new StateThread_UnzipFromDisk( OnFinished_Resume, resume_when_done, file ))->Start();
}
// Saves recovery state info to the given saveslot, or saves the active emulation state
// (if one exists) and no recovery data was found. This is needed because when a recovery
// state is made, the emulation state is usually reset so the only persisting state is
// the one in the memory save. :)
void StateCopy_SaveToSlot( uint num )
{
if( state_buffer_lock.IsLocked() ) return;
zip_dest_filename = SaveStateBase::GetFilename( num );
(new StateThread_Freeze( OnFinished_ZipToDisk ))->Start();
Console.WriteLn( Color_StrongGreen, "Saving savestate to slot %d...", num );
Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", zip_dest_filename.c_str() );
}
void StateCopy_LoadFromSlot( uint slot )
{
if( state_buffer_lock.IsLocked() ) return;
wxString file( SaveStateBase::GetFilename( slot ) );
if( !wxFileExists( file ) )
{
Console.Warning( "Savestate slot %d is empty.", slot );
return;
}
Console.WriteLn( Color_StrongGreen, "Loading savestate from slot %d...", slot );
Console.Indent().WriteLn( Color_StrongGreen, L"filename: %s", file.c_str() );
bool resume_when_done = CoreThread.Pause();
(new StateThread_UnzipFromDisk( OnFinished_Resume, resume_when_done, file ))->Start();
}
bool StateCopy_IsValid()
{
return !state_buffer.IsDisposed();
}
const SafeArray<u8>* StateCopy_GetBuffer()
{
if( state_buffer_lock.IsLocked() || state_buffer.IsDisposed() ) return NULL;
return &state_buffer;
}
void StateCopy_FreezeToMem()
{
if( state_buffer_lock.IsLocked() ) return;
(new StateThread_Freeze( OnFinished_Resume ))->Start();
}
static void _acquire_and_block()
{
if( state_buffer_lock.TryAcquire() ) return;
/*
// If the state buffer is locked and we're being called from the main thread then we need
// to cancel the current action. This is needed because state_buffer_lock is only updated
// from events handled on the main thread.
if( wxThread::IsMain() )
throw Exception::CancelEvent( "Blocking ThawFromMem canceled due to existing state buffer lock." );
else*/
{
pxAssume( current_state_thread != NULL );
do {
current_state_thread->Block();
wxGetApp().ProcessPendingEvents(); // Trying this for now, may or may not work due to recursive pitfalls (see above)
} while ( !state_buffer_lock.TryAcquire() );
}
}
void StateCopy_FreezeToMem_Blocking()
{
_acquire_and_block();
memSavingState( state_buffer ).FreezeAll();
state_buffer_lock.Release();
}
// Copies the saved state into the active VM, and automatically free's the saved state data.
void StateCopy_ThawFromMem_Blocking()
{
_acquire_and_block();
memLoadingState( state_buffer ).FreezeAll();
state_buffer.Dispose();
state_buffer_lock.Release();
}
void StateCopy_Clear()
{
if( state_buffer_lock.IsLocked() ) return;
state_buffer.Dispose();
}
bool StateCopy_IsBusy()
{
return state_buffer_lock.IsLocked();
}