/*] Copyright (c) 2009-2010, Charles McGarvey [************************** **] All rights reserved. * * vi:ts=4 sw=4 tw=75 * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * **************************************************************************/ /** * \file Thread.hh * Light C++ wrapper around the SDL threads API. */ #ifndef _MOOF_THREAD_HH_ #define _MOOF_THREAD_HH_ #include #include #include #include namespace Mf { /** * Represents a thread which may be running. You cannot instantiate a * thread object directly; new threads are created by detaching functions * using the detach() method. Once a thread is detached, it will continue * running until the function returns. You don't need to keep the thread * object you want to wait() or kill() the thread later. */ class Thread { public: typedef boost::function Function; /** * Construct an invalid thread object which has no association with any * real thread. */ Thread() : mThread(0) {} /** * Execute a function in a new thread. * \param function The function to execute. * \return The new thread, or an invalid thread if an error occurred. */ static Thread detach(const Function& function) { Function* fcopy = new Function(function); SDL_Thread* thread = SDL_CreateThread(&Thread::run, (void*)fcopy); if (thread == 0) delete fcopy; return Thread(thread); } /** * Wait for the thread to terminate, getting its return value. The * thread will be invalidated. * \return The integer value returned by the detached function. */ int wait() { int i; SDL_WaitThread(mThread, &i); mThread = 0; return i; } /** * Forcefully kill the thread without giving it a chance to clean up * after itself. The thread will be invalidated. Don't use this. */ void kill() { SDL_KillThread(mThread); mThread = 0; } /** * Get whether or not the thread object is associated with a real * thread. * \return True if the thread is valid, false otherwise. */ bool isValid() const { return mThread != 0; } /** * Get a unique identifier for this thread, if it is valid. * \return The identifier. */ uint32_t identifier() const { return SDL_GetThreadID(mThread); } /** * Get the unique identifier of the calling thread. * \return The identifier. */ static uint32_t currentIdentifier() { return SDL_ThreadID(); } private: Thread(SDL_Thread* thread) : mThread(thread) {} static int run(void* arg) { int code = (*(Function*)arg)(); delete (Function*)arg; return code; } SDL_Thread* mThread; }; /** * An abstract class representing some task that is to be run * asynchronously. */ class AsyncTask { public: /** * Deconstruct the task. */ virtual ~AsyncTask() {} /** * Get whether or not the task is done. * \return True if the task is done, false otherwise. */ virtual bool isDone() const = 0; /** * Begin the task. */ virtual void run() = 0; /** * Block the current thread until the task is finished. * \return A value representing the state of the finished task. */ virtual int wait() = 0; }; /** * An asynchronous task that is run to be executed in a separated thread. */ class ThreadedTask { public: /** * Get the thread object the task is executing in. * \return The thread. */ const Thread& thread() const { return mThread; } /** * Block the current thread until the task thread is finished. * \return The integer value returned by the task function. */ int wait() { return mThread.wait(); } protected: Thread mThread; }; /** * A mutex to protect sensitive sections of code from threads which might * otherwise cause unpredictable results. */ class Mutex { public: /** * Construct a mutex. */ Mutex() : mMutex(SDL_CreateMutex()) {} /** * Deconstruct a mutex. */ ~Mutex() { SDL_DestroyMutex(mMutex); } /** * Block until the calling thread can secure exclusive access to the * code protected by the mutex. * \return True if the lock was acquired, false otherwise. * \see Lock */ bool acquireLock() { return (SDL_LockMutex(mMutex) == 0); } /** * Unlock the mutex. Call this after the sensitive block of code to * allow another thread to acquire the lock. * \return True if the mutex was unlocked, false otherwise. * \see Lock */ bool releaseLock() { return (SDL_UnlockMutex(mMutex) == 0); } /** * As an alternative method for locking, objects of this class will * automagically release the lock if it is still locked at * deconstruction. Therefore, it's generally safer to use this method * since it makes it much more difficult to forget to unlock a mutex. */ class Lock { public: /** * Construct a lock. * \param mutex The mutex. */ explicit Lock(Mutex& mutex) : mMutex(mutex), mIsLocked(false) {} /** * Deconstruct a lock. The lock is automagically released if it is * still locked. */ ~Lock() { if (mIsLocked) release(); } /** * Try to acquire a lock on the mutex. * \return True if the mutex was locked, false otherwise. */ bool acquire() { return (mIsLocked = mMutex.acquireLock()); } /** * Release the lock. * \return True if the mutex was unlocked, false otherwise. */ bool release() { bool result = mMutex.releaseLock(); mIsLocked = false; return result; } /** * Get whether or not the mutex is locked. * \return True if the mutex is locked, false otherwise. */ bool isLocked() const { return mIsLocked; } protected: Mutex& mMutex; bool mIsLocked; friend class Condition; }; /** * This type of lock tries to acquire a lock on the mutex during * construction and releases the lock on deconstruction. */ class ScopedLock : private Lock { public: /** * Construct a lock. * \param mutex The mutex. */ explicit ScopedLock(Mutex& mutex) : Lock(mutex) { acquire(); } /** * Get whether or not the mutex is locked. * \return True if the mutex is locked, false otherwise. */ bool isLocked() const { return Lock::isLocked(); } }; private: SDL_mutex* mMutex; friend class Condition; }; /** * A class representing a condition variable. */ class Condition { public: /** * Construct a condition. */ Condition() { mCondition = SDL_CreateCond(); } /** * Deconstruct a condition. */ ~Condition() { SDL_DestroyCond(mCondition); } /** * Unlock the mutex and wait for another thread to notify the thread, * at which point the mutex will be re-locked and the method will * return. * \param mutex The mutex. * \return True if the thread was notified, false otherwise. */ bool wait(Mutex& mutex) { return (SDL_CondWait(mCondition, mutex.mMutex) == 0); } /** * Unlock the mutex associated with a lock and wait for another thread * to notify the thread, at which point the lock will be re-locked and * the method will return. * \param lock The lock. * \return True if the thread was notified, false otherwise. */ bool wait(Mutex::Lock& lock) { return (SDL_CondWait(mCondition, lock.mMutex.mMutex) == 0); } /** * Unlock the mutex and wait for another thread to notify the thread, * at which point the mutex will be re-locked and the method will * return. If the thread was not notified before a certain number of * seconds, the method will return anyway. * \param mutex The mutex. * \param timeout Number of seconds to wait. * \return True if the thread was notified, false otherwise. */ bool wait(Mutex& mutex, Scalar timeout) { Uint32 ms = timeout * SCALAR(1000.0); return (SDL_CondWaitTimeout(mCondition, mutex.mMutex, ms) == 0); } /** * Unlock the mutex associated with a lock and wait for another thread * to notify the thread, at which point the lock will be re-locked and * the method will return. If the thread was not notified before a * certain number of seconds, the method will return anyway. * \param lock The lock. * \param timeout Number of seconds to wait. * \return True if the thread was notified, false otherwise. */ bool wait(Mutex::Lock& lock, Scalar timeout) { Uint32 ms = timeout * SCALAR(1000.0); return (SDL_CondWaitTimeout(mCondition, lock.mMutex.mMutex, ms) == 0); } /** * Notify one other thread that is waiting on the condition. * \return True on success, false otherwise. */ bool notify() { return (SDL_CondSignal(mCondition) == 0); } /** * Notify all other threads that are waiting on the condition. * \return True on success, false otherwise. */ bool notifyAll() { return (SDL_CondBroadcast(mCondition) == 0); } private: SDL_cond* mCondition; }; /** * A semaphore class. */ class Semaphore { public: /** * Construct a semaphore. * \param value The initial value of the semaphore. */ explicit Semaphore(uint32_t value) { mSemaphore = SDL_CreateSemaphore(value); } /** * Deconstruct a semaphore. */ ~Semaphore() { SDL_DestroySemaphore(mSemaphore); } /** * Block until the calling thread can secure exclusive access to the * code protected by the semaphore. * \return True if the lock was acquired, false otherwise. * \see Lock */ bool acquireLock() { return (SDL_SemWait(mSemaphore) == 0); } /** * Block until the calling thread can secure exclusive access to the * code protected by the semaphore, or until the timeout expires. * \param timeout Number of seconds to try. * \return True if the lock was acquired, false otherwise. */ bool acquireLock(Scalar timeout) { Uint32 ms = timeout * SCALAR(1000.0); return (SDL_SemWaitTimeout(mSemaphore, ms) == 0); } /** * Unlock the semaphore. Call this after the sensitive block of code * to allow another thread to acquire the lock. * \return True if the semaphore was unlocked, false otherwise. * \see Lock */ bool releaseLock() { return (SDL_SemPost(mSemaphore) == 0); } /** * Try to lock the semaphore, but don't block if the lock is not * immediately available. * \return True if the semaphore was locked, false otherwise. */ bool tryLock() { return (SDL_SemTryWait(mSemaphore) == 0); } /** * As an alternative method for locking, objects of this class will * automagically release the lock if it is still locked at * deconstruction. Therefore, it's generally safer to use this method * since it makes it much more difficult to forget to unlock a * semaphore. */ class Lock { public: /** * Construct a lock. * \param semaphore The semaphore. */ explicit Lock(Semaphore& semaphore) : mSemaphore(semaphore), mIsLocked(false) {} /** * Deconstruct a lock. The lock is automagically released if it is * still locked. */ ~Lock() { if (mIsLocked) release(); } /** * Try to acquire a lock on the semaphore. * \return True if the semaphore was locked, false otherwise. */ bool acquire() { return (mIsLocked = mSemaphore.acquireLock()); } /** * Release the lock. * \return True if the semaphore was unlocked, false otherwise. */ bool release() { bool result = mSemaphore.releaseLock(); mIsLocked = false; return result; } /** * Get whether or not the semaphore is locked. * \return True if the semaphore is locked, false otherwise. */ bool isLocked() const { return mIsLocked; } protected: Semaphore& mSemaphore; bool mIsLocked; }; /** * This type of lock tries to acquire a lock on the semaphore during * construction and releases the lock on deconstruction. */ class ScopedLock : private Lock { public: /** * Construct a lock. * \param semaphore The semaphore. */ explicit ScopedLock(Semaphore& semaphore) : Lock(semaphore) { acquire(); } /** * Get whether or not the semaphore is locked. * \return True if the semaphore is locked, false otherwise. */ bool isLocked() const { return Lock::isLocked(); } }; private: SDL_sem* mSemaphore; }; } // namespace Mf #endif // _MOOF_THREAD_HH_