mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
* 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
430 lines
12 KiB
C++
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();
|
|
}
|
|
|