From 4d7f4e4cddf348a2ce7a3e5546002e81c59fe0a2 Mon Sep 17 00:00:00 2001 From: Glenn Maynard Date: Sat, 5 Feb 2005 08:02:43 +0000 Subject: [PATCH] split out generic worker thread code from ThreadedFileWorker, so memory cards can use it, too --- stepmania/src/RageFileDriverTimeout.cpp | 335 ++++++++---------------- stepmania/src/RageUtil_WorkerThread.cpp | 189 +++++++++++++ stepmania/src/RageUtil_WorkerThread.h | 87 ++++++ stepmania/src/StepMania.vcproj | 6 + 4 files changed, 386 insertions(+), 231 deletions(-) create mode 100644 stepmania/src/RageUtil_WorkerThread.cpp create mode 100644 stepmania/src/RageUtil_WorkerThread.h diff --git a/stepmania/src/RageFileDriverTimeout.cpp b/stepmania/src/RageFileDriverTimeout.cpp index f5d3291e56..c59dd4bfc1 100644 --- a/stepmania/src/RageFileDriverTimeout.cpp +++ b/stepmania/src/RageFileDriverTimeout.cpp @@ -44,6 +44,7 @@ #include "RageFileDriverTimeout.h" #include "RageUtil.h" #include "RageUtil_FileDB.h" +#include "RageUtil_WorkerThread.h" #include "RageLog.h" #include @@ -58,21 +59,15 @@ enum ThreadRequest REQ_COPY, REQ_POPULATE_FILE_SET, REQ_FLUSH_DIR_CACHE, - - REQ_SHUTDOWN, - REQ_INVALID, - NUM_REQUESTS, }; /* This is the class that does most of the work. */ -class ThreadedFileWorker +class ThreadedFileWorker: public WorkerThread { public: ThreadedFileWorker( CString sPath ); ~ThreadedFileWorker(); - void SetTimeout( float fSeconds ); - /* Threaded operations. If a file operation times out, the caller loses all access * to the file and should fail all future operations; this is because the thread * is still trying to finish the operation. The thread will clean up afterwards. */ @@ -87,29 +82,18 @@ public: bool FlushDirCache( const CString &sPath ); bool PopulateFileSet( FileSet &fs, const CString &sPath ); +protected: + void RequestTimedOut(); + private: - static int StartWorkerMain( void *pThis ) { ((ThreadedFileWorker *) (pThis))->WorkerMain(); return 0; } - void WorkerMain(); - - /* Run the given request. Return true if the operation completed, false on timeout. - * All other return values (including error returns from the request) are returned - * below. */ - bool DoRequest( ThreadRequest r ); - - RageThread m_WorkerThread; - RageEvent m_WorkerEvent; - - RageFileDriver *m_pChildDriver; - - RageTimer m_Timeout; + void HandleRequest( int iRequest ); /* All requests: */ - ThreadRequest m_Request; - bool m_bRequestFinished; - bool m_bTimedOut; + RageFileDriver *m_pChildDriver; /* List of files to delete: */ vector m_apDeletedFiles; + RageMutex m_DeletedFilesLock; /* REQ_OPEN, REQ_POPULATE_FILE_SET, REQ_FLUSH_DIR_CACHE: */ CString m_sRequestPath; /* in */ @@ -154,47 +138,28 @@ void RageFileDriverTimeout::SetTimeout( float fSeconds ) ThreadedFileWorker::ThreadedFileWorker( CString sPath ): - m_WorkerEvent( "\"" + sPath + "\" worker event" ) + WorkerThread( sPath ), + m_DeletedFilesLock( sPath + "DeletedFilesLock" ) { /* Grab a reference to the child driver. We'll operate on it directly. */ m_pChildDriver = FILEMAN->GetFileDriver( sPath ); if( m_pChildDriver == NULL ) LOG->Warn( "ThreadedFileWorker: Mountpoint \"%s\" not found", sPath.c_str() ); - m_Timeout.SetZero(); - m_Request = REQ_INVALID; - m_bTimedOut = false; m_pResultFile = NULL; m_pRequestFile = NULL; m_pResultBuffer = NULL; - m_WorkerThread.SetName( "ThreadedFileWorker fileset worker \"" + sPath + "\"" ); - m_WorkerThread.Create( StartWorkerMain, this ); - g_apWorkersMutex.Lock(); g_apWorkers.push_back( this ); g_apWorkersMutex.Unlock(); + + StartThread(); } ThreadedFileWorker::~ThreadedFileWorker() { - /* If we're timed out, wait. */ - m_WorkerEvent.Lock(); - if( m_bTimedOut ) - { - LOG->Trace( "Waiting for timed-out fs \"%s\" to complete ..." ); - while( m_bTimedOut ) - m_WorkerEvent.Wait(); - } - m_WorkerEvent.Unlock(); - - /* Disable the timeout. This will ensure that we really wait for the worker - * thread to shut down. */ - SetTimeout( -1 ); - - /* Shut down. */ - DoRequest( REQ_SHUTDOWN ); - m_WorkerThread.Wait(); + StopThread(); if( m_pChildDriver != NULL ) FILEMAN->ReleaseFileDriver( m_pChildDriver ); @@ -212,183 +177,88 @@ ThreadedFileWorker::~ThreadedFileWorker() g_apWorkersMutex.Unlock(); } -void ThreadedFileWorker::SetTimeout( float fSeconds ) +void ThreadedFileWorker::HandleRequest( int iRequest ) { - m_WorkerEvent.Lock(); - if( fSeconds == -1 ) - m_Timeout.SetZero(); - else { - m_Timeout.Touch(); - m_Timeout += fSeconds; - } - m_WorkerEvent.Unlock(); -} - -bool ThreadedFileWorker::DoRequest( ThreadRequest r ) -{ - ASSERT( !m_bTimedOut ); - ASSERT( m_Request == REQ_INVALID ); - - /* Set the request, and wake up the worker thread. */ - m_WorkerEvent.Lock(); - m_Request = r; - m_WorkerEvent.Broadcast(); - - /* Wait for it to complete or time out. */ - while( !m_bRequestFinished ) - { - bool bTimedOut = !m_WorkerEvent.Wait( &m_Timeout ); - if( bTimedOut ) - break; - } - - /* Don't depend on bTimedOut to tell if we timed out; only look at m_bRequestFinished, - * to avoid a race condition where the event times out and the operation completes - * simultaneously. */ - bool bRequestFinished = m_bRequestFinished; - if( !m_bRequestFinished ) - { - /* Set m_bTimedOut true. It'll be set to false when the operation actually - * completes. */ - m_bTimedOut = true; - } - else - { - /* The operation completed; make sure that the worker thread doesn't erase the file. */ - m_pRequestFile = NULL; - m_bRequestFinished = false; - } - m_WorkerEvent.Unlock(); - - return bRequestFinished; -} - -void ThreadedFileWorker::WorkerMain() -{ - while(1) - { - m_WorkerEvent.Lock(); - while( m_Request == REQ_INVALID && m_apDeletedFiles.empty() ) - m_WorkerEvent.Wait(); - const ThreadRequest r = m_Request; - m_Request = REQ_INVALID; - - /* We're going to clean up any deleted files. Make a copy of the list; don't - * hold the lock while we delete. */ + m_DeletedFilesLock.Lock(); vector apDeletedFiles = m_apDeletedFiles; m_apDeletedFiles.clear(); - - m_WorkerEvent.Unlock(); + m_DeletedFilesLock.Unlock(); for( unsigned i = 0; i < apDeletedFiles.size(); ++i ) delete apDeletedFiles[i]; - apDeletedFiles.clear(); - - if( r == REQ_INVALID ) - continue; - - /* We have a request. */ - switch( r ) - { - case REQ_OPEN: - ASSERT( m_pResultFile == NULL ); - ASSERT( !m_sRequestPath.empty() ); - m_iResultRequest = 0; - m_pResultFile = m_pChildDriver->Open( m_sRequestPath, m_iRequestMode, m_iResultRequest ); - break; - - case REQ_CLOSE: - ASSERT( m_pRequestFile != NULL ); - m_apDeletedFiles.push_back( m_pRequestFile ); - break; - - case REQ_GET_FILE_SIZE: - ASSERT( m_pRequestFile != NULL ); - m_iResultRequest = m_pRequestFile->GetFileSize(); - break; - - case REQ_READ: - ASSERT( m_pRequestFile != NULL ); - ASSERT( m_pResultBuffer != NULL ); - m_iResultRequest = m_pRequestFile->Read( m_pResultBuffer, m_iRequestSize ); - m_sResultError = m_pRequestFile->GetError(); - break; - - case REQ_WRITE: - ASSERT( m_pRequestFile != NULL ); - ASSERT( m_pRequestBuffer != NULL ); - m_iResultRequest = m_pRequestFile->Write( m_pRequestBuffer, m_iRequestSize ); - m_sResultError = m_pRequestFile->GetError(); - break; - - case REQ_FLUSH: - ASSERT( m_pRequestFile != NULL ); - m_iResultRequest = m_pRequestFile->Flush(); - m_sResultError = m_pRequestFile->GetError(); - break; - - case REQ_COPY: - ASSERT( m_pRequestFile != NULL ); - m_pResultFile = m_pRequestFile->Copy(); - break; - - case REQ_POPULATE_FILE_SET: - ASSERT( !m_bRequestFinished ); - ASSERT( !m_sRequestPath.empty() ); - - m_ResultFileSet = FileSet(); - m_pChildDriver->FDB->GetFileSetCopy( m_sRequestPath, m_ResultFileSet ); - break; - - case REQ_FLUSH_DIR_CACHE: - m_pChildDriver->FlushDirCache( m_sRequestPath ); - break; - - case REQ_SHUTDOWN: - break; - - default: - FAIL_M( ssprintf("%i", r) ); - } - - m_WorkerEvent.Lock(); - - if( m_bTimedOut ) - { - /* The event timed out. Clean up any residue from the last action. */ - if( m_pRequestFile != NULL ) - { - m_apDeletedFiles.push_back( m_pResultFile ); - m_pResultFile = NULL; - break; - } - - if( m_pResultFile != NULL ) - { - m_apDeletedFiles.push_back( m_pResultFile ); - m_pResultFile = NULL; - break; - } - - delete m_pRequestBuffer; - m_pRequestBuffer = NULL; - delete m_pResultBuffer; - m_pResultBuffer = NULL; - - /* Clear the time-out flag, indicating that we can work again. */ - m_bTimedOut = false; - } - else - m_bRequestFinished = true; - - /* We're finished. Wake up the requester (if he's still around). */ - m_WorkerEvent.Broadcast(); - m_WorkerEvent.Unlock(); - - if( r == REQ_SHUTDOWN ) - break; } + + /* We have a request. */ + switch( iRequest ) + { + case REQ_OPEN: + ASSERT( m_pResultFile == NULL ); + ASSERT( !m_sRequestPath.empty() ); + m_iResultRequest = 0; + m_pResultFile = m_pChildDriver->Open( m_sRequestPath, m_iRequestMode, m_iResultRequest ); + break; + + case REQ_CLOSE: + ASSERT( m_pRequestFile != NULL ); + delete m_pRequestFile; + m_pRequestFile = NULL; + + break; + + case REQ_GET_FILE_SIZE: + ASSERT( m_pRequestFile != NULL ); + m_iResultRequest = m_pRequestFile->GetFileSize(); + break; + + case REQ_READ: + ASSERT( m_pRequestFile != NULL ); + ASSERT( m_pResultBuffer != NULL ); + m_iResultRequest = m_pRequestFile->Read( m_pResultBuffer, m_iRequestSize ); + m_sResultError = m_pRequestFile->GetError(); + break; + + case REQ_WRITE: + ASSERT( m_pRequestFile != NULL ); + ASSERT( m_pRequestBuffer != NULL ); + m_iResultRequest = m_pRequestFile->Write( m_pRequestBuffer, m_iRequestSize ); + m_sResultError = m_pRequestFile->GetError(); + break; + + case REQ_FLUSH: + ASSERT( m_pRequestFile != NULL ); + m_iResultRequest = m_pRequestFile->Flush(); + m_sResultError = m_pRequestFile->GetError(); + break; + + case REQ_COPY: + ASSERT( m_pRequestFile != NULL ); + m_pResultFile = m_pRequestFile->Copy(); + break; + + case REQ_POPULATE_FILE_SET: + ASSERT( !m_sRequestPath.empty() ); + + m_ResultFileSet = FileSet(); + m_pChildDriver->FDB->GetFileSetCopy( m_sRequestPath, m_ResultFileSet ); + break; + + case REQ_FLUSH_DIR_CACHE: + m_pChildDriver->FlushDirCache( m_sRequestPath ); + break; + + default: + FAIL_M( ssprintf("%i", iRequest) ); + } +} + +void ThreadedFileWorker::RequestTimedOut() +{ + /* The event timed out. Clean up any residue from the last action. */ + SAFE_DELETE( m_pRequestFile ); + SAFE_DELETE( m_pResultFile ); + SAFE_DELETE( m_pRequestBuffer ); + SAFE_DELETE( m_pResultBuffer ); } RageFileBasic *ThreadedFileWorker::Open( const CString &sPath, int iMode, int &iErr ) @@ -400,7 +270,7 @@ RageFileBasic *ThreadedFileWorker::Open( const CString &sPath, int iMode, int &i } /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) { iErr = EFAULT; /* Win32 has no ETIMEDOUT */ return NULL; @@ -412,7 +282,8 @@ RageFileBasic *ThreadedFileWorker::Open( const CString &sPath, int iMode, int &i if( !DoRequest(REQ_OPEN) ) { LOG->Trace( "Open(%s) timed out", sPath.c_str() ); - return false; + iErr = EFAULT; /* Win32 has no ETIMEDOUT */ + return NULL; } iErr = m_iResultRequest; @@ -426,20 +297,20 @@ void ThreadedFileWorker::Close( RageFileBasic *pFile ) { ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */ - if( !m_bTimedOut ) + if( IsTimedOut() ) { /* If we're not in a timed-out state, try to wait for the deletion to complete * before continuing. */ m_pRequestFile = pFile; DoRequest( REQ_CLOSE ); + m_pRequestFile = NULL; } else { /* Delete the file when the timeout completes. */ - m_WorkerEvent.Lock(); + m_DeletedFilesLock.Lock(); m_apDeletedFiles.push_back( pFile ); - m_WorkerEvent.Broadcast(); - m_WorkerEvent.Unlock(); + m_DeletedFilesLock.Unlock(); } } @@ -448,7 +319,7 @@ int ThreadedFileWorker::GetFileSize( RageFileBasic *&pFile ) ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */ /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) { this->Close( pFile ); pFile = NULL; @@ -476,7 +347,7 @@ int ThreadedFileWorker::Read( RageFileBasic *&pFile, void *pBuf, int iSize, CStr ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */ /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) { this->Close( pFile ); pFile = NULL; @@ -515,7 +386,7 @@ int ThreadedFileWorker::Write( RageFileBasic *&pFile, const void *pBuf, int iSiz ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */ /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) { this->Close( pFile ); pFile = NULL; @@ -554,7 +425,7 @@ int ThreadedFileWorker::Flush( RageFileBasic *&pFile, CString &sError ) ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */ /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) { this->Close( pFile ); pFile = NULL; @@ -576,6 +447,8 @@ int ThreadedFileWorker::Flush( RageFileBasic *&pFile, CString &sError ) return -1; } + m_pRequestFile = NULL; + return m_iResultRequest; } @@ -584,7 +457,7 @@ RageFileBasic *ThreadedFileWorker::Copy( RageFileBasic *&pFile, CString &sError ASSERT( m_pChildDriver != NULL ); /* how did you get a file to begin with? */ /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) { this->Close( pFile ); pFile = NULL; @@ -606,6 +479,7 @@ RageFileBasic *ThreadedFileWorker::Copy( RageFileBasic *&pFile, CString &sError } RageFileBasic *pRet = m_pResultFile; + m_pRequestFile = NULL; m_pResultFile = NULL; return pRet; @@ -618,12 +492,11 @@ bool ThreadedFileWorker::PopulateFileSet( FileSet &fs, const CString &sPath ) return false; /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) return false; /* Fill in a different FileSet; copy the results in on success. */ m_sRequestPath = sPath; - m_bRequestFinished = false; /* Kick off the worker thread, and wait for it to finish. */ if( !DoRequest(REQ_POPULATE_FILE_SET) ) @@ -642,7 +515,7 @@ bool ThreadedFileWorker::FlushDirCache( const CString &sPath ) return false; /* If we're currently in a timed-out state, fail. */ - if( m_bTimedOut ) + if( IsTimedOut() ) return false; m_sRequestPath = sPath; diff --git a/stepmania/src/RageUtil_WorkerThread.cpp b/stepmania/src/RageUtil_WorkerThread.cpp new file mode 100644 index 0000000000..c2510da6d0 --- /dev/null +++ b/stepmania/src/RageUtil_WorkerThread.cpp @@ -0,0 +1,189 @@ +#include "global.h" +#include "RageUtil_WorkerThread.h" +#include "RageLog.h" + +WorkerThread::WorkerThread( const CString &sName ): + m_WorkerEvent( "\"" + sName + "\" worker event" ) +{ + m_Timeout.SetZero(); + m_iRequest = REQ_NONE; + m_bTimedOut = false; + m_fHeartbeat = -1; + m_bRequestFinished = false; + + m_WorkerThread.SetName( "ThreadedFileWorker fileset worker \"" + sName + "\"" ); +} + +WorkerThread::~WorkerThread() +{ + /* The worker thread must be stopped by the derived class. */ + ASSERT( !m_WorkerThread.IsCreated() ); +} + +void WorkerThread::SetTimeout( float fSeconds ) +{ + m_WorkerEvent.Lock(); + if( fSeconds == -1 ) + m_Timeout.SetZero(); + else + { + m_Timeout.Touch(); + m_Timeout += fSeconds; + } + m_WorkerEvent.Unlock(); +} + + +void WorkerThread::StartThread() +{ + ASSERT( !m_WorkerThread.IsCreated() ); + + m_WorkerThread.Create( StartWorkerMain, this ); +} + +void WorkerThread::StopThread() +{ + /* If we're timed out, wait. */ + m_WorkerEvent.Lock(); + if( m_bTimedOut ) + { + LOG->Trace( "Waiting for timed-out fs \"%s\" to complete ..." ); + while( m_bTimedOut ) + m_WorkerEvent.Wait(); + } + m_WorkerEvent.Unlock(); + + /* Disable the timeout. This will ensure that we really wait for the worker + * thread to shut down. */ + SetTimeout( -1 ); + + /* Shut down. */ + DoRequest( REQ_SHUTDOWN ); + m_WorkerThread.Wait(); +} + +bool WorkerThread::DoRequest( int iRequest ) +{ + ASSERT( !m_bTimedOut ); + ASSERT( m_iRequest == REQ_NONE ); + + /* Set the request, and wake up the worker thread. */ + m_WorkerEvent.Lock(); + + m_iRequest = iRequest; + m_WorkerEvent.Broadcast(); + + /* Wait for it to complete or time out. */ + while( !m_bRequestFinished ) + { + bool bTimedOut = !m_WorkerEvent.Wait( &m_Timeout ); + if( bTimedOut ) + break; + } + + const bool bRequestFinished = m_bRequestFinished; + if( m_bRequestFinished ) + { + /* The request finished successfully. It's the calling function's + * responsibility to clean up. */ + m_bRequestFinished = false; + } + else + { + /* The request hasn't finished yet. Set m_bTimedOut true. This tells the + * still-running request that it timed out, and so it's the thread's + * responsibility to clean up--we can't do it, since we'd collide with the + * request. */ + m_bTimedOut = true; + } + m_WorkerEvent.Unlock(); + + return bRequestFinished; +} + +void WorkerThread::WorkerMain() +{ + while(1) + { + bool bTimeToRunHeartbeat = false; + m_WorkerEvent.Lock(); + while( m_iRequest == REQ_NONE && !bTimeToRunHeartbeat ) //&& m_apDeletedFiles.empty() ) + { + if( !m_WorkerEvent.Wait( m_fHeartbeat != -1? &m_NextHeartbeat:NULL ) ) + bTimeToRunHeartbeat = true; + } + const int iRequest = m_iRequest; + m_iRequest = REQ_NONE; + + m_WorkerEvent.Unlock(); + + if( iRequest == REQ_SHUTDOWN ) + break; + + /* If it's time to run a heartbeat, do so. */ + if( bTimeToRunHeartbeat ) + { + DoHeartbeat(); + + m_NextHeartbeat.Touch(); + m_NextHeartbeat += m_fHeartbeat; + } + + if( iRequest != REQ_NONE ) + { + /* Handle the request. */ + HandleRequest( iRequest ); + + /* Lock the mutex, to keep DoRequest where it is (if it's still running). */ + /* The request is finished. If it timed out, clear the timeout flag and + * call RequestTimedOut, to allow cleaning up. */ + m_WorkerEvent.Lock(); + + if( m_bTimedOut ) + { + /* The calling thread timed out. It's already gone and moved on, so + * it's our responsibility to clean up. No new requests will come in + * until we clear m_bTimedOut, so we can safely unlock and clean up. */ + m_WorkerEvent.Unlock(); + + RequestTimedOut(); + + /* Clear the time-out flag, indicating that we can work again. */ + m_bTimedOut = false; + } + else + { + m_bRequestFinished = true; + + /* We're finished. Wake up the requester. */ + m_WorkerEvent.Broadcast(); + m_WorkerEvent.Unlock(); + } + } + } +} + +/* + * (c) 2005 Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/stepmania/src/RageUtil_WorkerThread.h b/stepmania/src/RageUtil_WorkerThread.h new file mode 100644 index 0000000000..69840336bd --- /dev/null +++ b/stepmania/src/RageUtil_WorkerThread.h @@ -0,0 +1,87 @@ +/* WorkerThread - a worker thread for operations that are allowed to time out. */ + +#ifndef RAGE_UTIL_WORKER_THREAD_H +#define RAGE_UTIL_WORKER_THREAD_H + +#include "RageThreads.h" +#include "RageTimer.h" + +class WorkerThread +{ +public: + WorkerThread( const CString &sName ); + virtual ~WorkerThread(); + + /* Call SetTimeout(10) to start a timeout period of 10 seconds. This is not a + * per-request timeout; you have 10 seconds to do your work, at which point all + * requests time out until SetTimeout is called again. */ + void SetTimeout( float fSeconds ); + +protected: + /* Call this in the derived class to start and stop the thread. */ + void StartThread(); + void StopThread(); + + /* Run the given request. Return true if the operation completed, false on timeout. + * Always call IsTimedOut() first; if true is returned, the thread is currently + * timed out and DoRequest() must not be called. */ + bool DoRequest( int iRequest ); + + /* Return true if the last operation has timed out and has not yet recovered. */ + bool IsTimedOut() const { return m_bTimedOut; } + + /* Overload this in the derived class to handle requests. */ + virtual void HandleRequest( int iRequest ) = 0; + + /* If DoRequest times out, this will be called in the thread after completion. + * Clean up. No new requests will be allowed until this completes. */ + virtual void RequestTimedOut() { } + + /* Enable a heartbeat. DoHeartbeat will be called every fSeconds while idle. + * DoHeartbeat may safely time out; if DoRequest tries to start a request in + * the main thread, it'll simply time out. */ + void SetHeartbeat( float fSeconds ) { m_fHeartbeat = fSeconds; m_NextHeartbeat.Touch(); } + virtual void DoHeartbeat() { } + +private: + static int StartWorkerMain( void *pThis ) { ((WorkerThread *) (pThis))->WorkerMain(); return 0; } + void WorkerMain(); + + enum { REQ_SHUTDOWN = -1, REQ_NONE = -2 }; + RageThread m_WorkerThread; + RageEvent m_WorkerEvent; + int m_iRequest; + bool m_bRequestFinished; + bool m_bTimedOut; + RageTimer m_Timeout; + + float m_fHeartbeat; + RageTimer m_NextHeartbeat; +}; + +#endif + +/* + * (c) 2005 Glenn Maynard + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/stepmania/src/StepMania.vcproj b/stepmania/src/StepMania.vcproj index 407029cb6f..e997809ea7 100644 --- a/stepmania/src/StepMania.vcproj +++ b/stepmania/src/StepMania.vcproj @@ -2162,6 +2162,12 @@ cl /Zl /nologo /c verstub.cpp /Fo"$(IntDir)"\ + + + +