/* 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 the 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 . */ #include "PrecompiledHeader.h" #include "App.h" #include "HostGui.h" #include "zlib/zlib.h" class _BaseStateThread; static SafeArray state_buffer; // 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 m_bind_OnExit; public: virtual ~_BaseStateThread() throw() { state_buffer_lock.Release(); // just in case; } protected: _BaseStateThread( const char* name, FnType_OnThreadComplete* onFinished ) : m_bind_OnExit( wxGetApp().Source_AppStatus(), EventListener( this, StateThread_OnAppStatus ) ) { Callback_FreezeFinished = onFinished; m_name = L"StateThread::" + fromUTF8(name); } void OnStart() { if( !state_buffer_lock.TryAquire() ) throw Exception::CancelEvent( m_name + L"request ignored: state copy buffer is already locked!" ); _parent::OnStart(); } void SendFinishEvent( int type ) { wxCommandEvent evt( pxEVT_FreezeThreadFinished ); evt.SetClientData( this ); evt.SetInt( type ); 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; CoreThread.Pause(); } void ExecuteTaskInThread() { memSavingState( state_buffer ).FreezeAll(); } void OnCleanupInThread() { SendFinishEvent( StateThreadAction_Create ); _parent::OnCleanupInThread(); } }; // -------------------------------------------------------------------------------------- // StateThread_Thaw // -------------------------------------------------------------------------------------- class StateThread_Thaw : public _BaseStateThread { typedef _BaseStateThread _parent; public: StateThread_Thaw( FnType_OnThreadComplete* onFinished ) : _BaseStateThread( "Thaw", onFinished ) { } protected: void OnStart() { _parent::OnStart(); if( state_buffer.IsDisposed() ) { state_buffer_lock.Release(); throw Exception::RuntimeError( "ThawState request made, but no valid state exists!" ); } ++sys_resume_lock; CoreThread.Pause(); } void ExecuteTaskInThread() { memLoadingState( state_buffer ).FreezeAll(); } void OnCleanupInThread() { SendFinishEvent( StateThreadAction_Restore ); _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, const wxString& file ) : _BaseStateThread( "ZipToDisk", onFinished ) , m_filename( file ) , m_gzfp( NULL ) { } ~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 ); //if( gzwrite( m_gzfp, state_buffer.GetPtr(), state_buffer.GetSizeInBytes() ) < state_buffer.GetSizeInBytes() ) // throw Exception::BadStream(); 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, const wxString& file ) : _BaseStateThread( "UnzipFromDisk", onFinished ) , m_filename( file ) , m_gzfp( NULL ) , m_finished( false ) { } ~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 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 ); //m_evtsrc_FreezeThreadFinished.Dispatch( evt ); } void OnFinished_Resume( const wxCommandEvent& evt ) { if( evt.GetInt() == StateThreadAction_Restore ) { // Successfully restored state, so remove the copy. Don't remove it sooner // because the thread may have failed with some exception/error. state_buffer.Dispose(); SysClearExecutionCache(); } CoreThread.Resume(); } void OnFinished_Dispose( const wxCommandEvent& evt ) { state_buffer.Dispose(); } static wxString zip_dest_filename; 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( OnFinished_Dispose, zip_dest_filename ))->Start(); CoreThread.Resume(); } void OnFinished_Restore( const wxCommandEvent& evt ) { if( !pxAssertDev( evt.GetInt() == StateThreadAction_UnzipFromDisk, "Unexpected StateThreadAction value, aborting restore." ) ) return; // Phase 2: Restore over existing VM state!! (new StateThread_Thaw( OnFinished_Resume ))->Start(); } // ===================================================================================================== // 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; CoreThread.Pause(); (new StateThread_UnzipFromDisk( OnFinished_Restore, 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.WriteLn( Color_StrongGreen, L"\tfilename: %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.WriteLn( Color_StrongGreen, L"\tfilename: %s", file.c_str() ); CoreThread.Pause(); (new StateThread_UnzipFromDisk( OnFinished_Restore, file ))->Start(); } bool StateCopy_IsValid() { return !state_buffer.IsDisposed(); } bool StateCopy_HasFullState() { return false; } bool StateCopy_HasPartialState() { return false; } void StateCopy_FreezeToMem() { if( state_buffer_lock.IsLocked() ) return; (new StateThread_Freeze( OnFinished_Restore ))->Start(); } void StateCopy_ThawFromMem() { if( state_buffer_lock.IsLocked() ) return; new StateThread_Thaw( OnFinished_Restore ); } void State_ThawFromMem_Blocking() { if( !state_buffer_lock.TryAquire() ) memLoadingState( state_buffer ).FreezeAll(); state_buffer_lock.Release(); } void StateCopy_Clear() { if( state_buffer_lock.IsLocked() ) return; state_buffer.Dispose(); } bool StateCopy_IsBusy() { return state_buffer_lock.IsLocked(); }