/*] 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 moof { /** * 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() : thread_(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) { thread::function* fcopy = new thread::function(function); SDL_Thread* thread = SDL_CreateThread(&thread::run, (void*)fcopy); if (thread == 0) delete fcopy; return moof::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(thread_, &i); thread_ = 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(thread_); thread_ = 0; } /** * Get whether or not the thread object is associated with a real * thread. * \return True if the thread is valid, false otherwise. */ bool is_valid() const { return thread_ != 0; } /** * Get a unique identifier for this thread, if it is valid. * \return The identifier. */ uint32_t identifier() const { return SDL_GetThreadID(thread_); } /** * Get the unique identifier of the calling thread. * \return The identifier. */ static uint32_t current_identifier() { return SDL_ThreadID(); } private: thread(SDL_Thread* thread) : thread_(thread) {} static int run(void* arg) { int code = (*(function*)arg)(); delete (function*)arg; return code; } SDL_Thread* thread_; }; /** * An abstract class representing some task that is to be run * asynchronously. */ class async_task { public: /** * Deconstruct the task. */ virtual ~async_task() {} /** * Get whether or not the task is done. * \return True if the task is done, false otherwise. */ virtual bool is_done() 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 threaded_task { public: /** * Get the thread object the task is executing in. * \return The thread. */ const class thread& thread() const { return thread_; } /** * Block the current thread until the task thread is finished. * \return The integer value returned by the task function. */ int wait() { return thread_.wait(); } protected: class thread thread_; }; /** * A mutex to protect sensitive sections of code from threads which might * otherwise cause unpredictable results. */ class mutex { public: /** * Construct a mutex. */ mutex() : mutex_(SDL_CreateMutex()) {} /** * Deconstruct a mutex. */ ~mutex() { SDL_DestroyMutex(mutex_); } /** * 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 acquire_lock() { return (SDL_LockMutex(mutex_) == 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 release_lock() { return (SDL_UnlockMutex(mutex_) == 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) : mutex_(mutex), is_locked_(false) {} /** * Deconstruct a lock. The lock is automagically released if it is * still locked. */ ~lock() { if (is_locked_) release(); } /** * Try to acquire a lock on the mutex. * \return True if the mutex was locked, false otherwise. */ bool acquire() { return (is_locked_ = mutex_.acquire_lock()); } /** * Release the lock. * \return True if the mutex was unlocked, false otherwise. */ bool release() { bool result = mutex_.release_lock(); is_locked_ = false; return result; } /** * Get whether or not the mutex is locked. * \return True if the mutex is locked, false otherwise. */ bool is_locked() const { return is_locked_; } protected: mutex& mutex_; bool is_locked_; friend class condition; }; /** * This type of lock tries to acquire a lock on the mutex during * construction and releases the lock on deconstruction. */ class scoped_lock : private lock { public: /** * Construct a lock. * \param mutex The mutex. */ explicit scoped_lock(mutex& mutex) : lock(mutex) { acquire(); } /** * Get whether or not the mutex is locked. * \return True if the mutex is locked, false otherwise. */ bool is_locked() const { return lock::is_locked(); } }; private: SDL_mutex* mutex_; friend class condition; }; /** * A class representing a condition variable. */ class condition { public: /** * Construct a condition. */ condition() { condition_ = SDL_CreateCond(); } /** * Deconstruct a condition. */ ~condition() { SDL_DestroyCond(condition_); } /** * 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(condition_, mutex.mutex_) == 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(condition_, lock.mutex_.mutex_) == 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(condition_, mutex.mutex_, 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(condition_, lock.mutex_.mutex_, ms) == 0); } /** * Notify one other thread that is waiting on the condition. * \return True on success, false otherwise. */ bool notify() { return (SDL_CondSignal(condition_) == 0); } /** * Notify all other threads that are waiting on the condition. * \return True on success, false otherwise. */ bool notify_all() { return (SDL_CondBroadcast(condition_) == 0); } private: SDL_cond* condition_; }; /** * A semaphore class. */ class semaphore { public: /** * Construct a semaphore. * \param value The initial value of the semaphore. */ explicit semaphore(uint32_t value) { semaphore_ = SDL_CreateSemaphore(value); } /** * Deconstruct a semaphore. */ ~semaphore() { SDL_DestroySemaphore(semaphore_); } /** * 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 acquire_lock() { return (SDL_SemWait(semaphore_) == 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 acquire_lock(scalar timeout) { Uint32 ms = timeout * SCALAR(1000.0); return (SDL_SemWaitTimeout(semaphore_, 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 release_lock() { return (SDL_SemPost(semaphore_) == 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 try_lock() { return (SDL_SemTryWait(semaphore_) == 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) : semaphore_(semaphore), is_locked_(false) {} /** * Deconstruct a lock. The lock is automagically released if it is * still locked. */ ~lock() { if (is_locked_) release(); } /** * Try to acquire a lock on the semaphore. * \return True if the semaphore was locked, false otherwise. */ bool acquire() { return (is_locked_ = semaphore_.acquire_lock()); } /** * Release the lock. * \return True if the semaphore was unlocked, false otherwise. */ bool release() { bool result = semaphore_.release_lock(); is_locked_ = false; return result; } /** * Get whether or not the semaphore is locked. * \return True if the semaphore is locked, false otherwise. */ bool is_locked() const { return is_locked_; } protected: semaphore& semaphore_; bool is_locked_; }; /** * This type of lock tries to acquire a lock on the semaphore during * construction and releases the lock on deconstruction. */ class scoped_lock : private lock { public: /** * Construct a lock. * \param semaphore The semaphore. */ explicit scoped_lock(semaphore& semaphore) : lock(semaphore) { acquire(); } /** * Get whether or not the semaphore is locked. * \return True if the semaphore is locked, false otherwise. */ bool is_locked() const { return lock::is_locked(); } }; private: SDL_sem* semaphore_; }; } // namespace moof #endif // _MOOF_THREAD_HH_