-/*******************************************************************************
-
- Copyright (c) 2009, Charles McGarvey
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-*******************************************************************************/
+/*] 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 <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <SDL/SDL.h>
+
+#include <Moof/Math.hh>
+
+
+namespace Mf {
+
+
/**
- * @file Thread.hh
- * Light C++ wrapper around SDL threads.
+ * 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:
-#include <boost/function.hpp>
+ typedef boost::function<int(void)> Function;
-#include <SDL/SDL.h>
+ /**
+ * Construct an invalid thread object which has no association with any
+ * real thread.
+ */
+ Thread() :
+ mThread(0) {}
-namespace Mf {
+ /**
+ * 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);
+ }
-//
-// The detach function detaches a separate thread by calling 'func' with
-// the 'arg' parameter.
-//
-typedef SDL_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;
+ }
-typedef boost::function<int(void)> Function;
+ /**
+ * 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;
+ }
-int detach_(void* arg)
-{
- Function function = *(Function*)arg;
- int ret = function();
- delete (Function*)arg;
- return ret;
-}
+ /**
+ * Get a unique identifier for this thread, if it is valid.
+ * \return The identifier.
+ */
+ uint32_t identifier() const
+ {
+ return SDL_GetThreadID(mThread);
+ }
-Thread detachFunction(const Function& function)
-{
- Thread thread;
- Function* fcopy = new Function(function);
+ /**
+ * Get the unique identifier of the calling thread.
+ * \return The identifier.
+ */
+ static uint32_t currentIdentifier()
+ {
+ return SDL_ThreadID();
+ }
- thread = SDL_CreateThread(detach_, (void*)fcopy);
- if (thread == 0) delete fcopy;
- return thread;
-}
+private:
-int waitOnThread(Thread thread)
-{
- int i;
- SDL_WaitThread(thread, &i);
- return i;
-}
+ Thread(SDL_Thread* thread) :
+ mThread(thread) {}
+
+ static int run(void* arg)
+ {
+ int code = (*(Function*)arg)();
+ delete (Function*)arg;
+ return code;
+ }
-void killThread(Thread thread)
-{
- SDL_KillThread(thread);
-}
+ SDL_Thread* mThread;
+};
-//
-// The identifier function returns a unique integer for the calling thread.
-//
-unsigned int getThreadIdentifier()
+/**
+ * An abstract class representing some task that is to be run
+ * asynchronously.
+ */
+class AsyncTask
{
- return SDL_ThreadID();
-}
+public:
-unsigned int getThreadIdentifier(Thread thread)
+ /**
+ * 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
{
- return SDL_GetThreadID(thread);
-}
+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
{
- friend class Condition;
-
public:
- Mutex()
- {
- mutex_ = SDL_CreateMutex();
- }
+
+ /**
+ * Construct a mutex.
+ */
+ Mutex() :
+ mMutex(SDL_CreateMutex()) {}
+
+ /**
+ * Deconstruct a mutex.
+ */
~Mutex()
{
- SDL_DestroyMutex(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(mutex_) == 0);
+ 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(mutex_) == 0);
+ 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
{
- friend class Condition;
-
public:
- Lock(Mutex& mutex)
- {
- mutex_ = &mutex;
- isLocked_ = false;
- }
+
+ /**
+ * Construct a lock.
+ * \param mutex The mutex.
+ */
+ Lock(Mutex& mutex) :
+ mMutex(mutex),
+ mIsLocked(false) {}
+
+ /**
+ * Deconstruct a lock. The lock is automagically released if it is
+ * still locked.
+ */
~Lock()
{
- if (isLocked_) release();
+ if (mIsLocked) release();
}
+
+ /**
+ * Try to acquire a lock on the mutex.
+ * \return True if the mutex was locked, false otherwise.
+ */
bool acquire()
{
- return (isLocked_ = mutex_->acquireLock());
+ return (mIsLocked = mMutex.acquireLock());
}
+
+ /**
+ * Release the lock.
+ * \return True if the mutex was unlocked, false otherwise.
+ */
bool release()
{
- return mutex_->releaseLock();
- isLocked_ = false;
+ 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 isLocked_;
+ return mIsLocked;
}
+
protected:
- Mutex* mutex_;
- bool isLocked_;
+
+ Mutex& mMutex;
+ bool mIsLocked;
+
+ friend class Condition;
};
- class ScopedLock : public Lock
+ /**
+ * 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.
+ */
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* mutex_;
+
+ SDL_mutex* mMutex;
+
+ friend class Condition;
};
+/**
+ * A class representing a condition variable.
+ */
class Condition
{
public:
+
+ /**
+ * Construct a condition.
+ */
Condition()
{
- condition_ = SDL_CreateCond();
+ mCondition = SDL_CreateCond();
}
+
+ /**
+ * Deconstruct a condition.
+ */
~Condition()
{
- SDL_DestroyCond(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(condition_, lock.mutex_->mutex_) == 0);
+ return (SDL_CondWait(mCondition, lock.mMutex.mMutex) == 0);
}
- bool wait(Mutex::Lock& lock, unsigned ms)
+
+ /**
+ * 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)
{
- // TODO for consistency, this function should take seconds
- return (SDL_CondWaitTimeout(condition_, lock.mutex_->mutex_, ms) == 0);
+ 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(condition_) == 0);
+ 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(condition_) == 0);
+ return (SDL_CondBroadcast(mCondition) == 0);
}
+
private:
- SDL_cond* condition_;
+
+ SDL_cond* mCondition;
};
+/**
+ * A semaphore class.
+ */
class Semaphore
{
public:
- Semaphore(unsigned int value)
+
+ /**
+ * Construct a semaphore.
+ * \param value The initial value of the semaphore.
+ */
+ Semaphore(uint32_t value)
{
- semaphore_ = SDL_CreateSemaphore(value);
+ mSemaphore = SDL_CreateSemaphore(value);
}
+
+ /**
+ * Deconstruct a semaphore.
+ */
~Semaphore()
{
- SDL_DestroySemaphore(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(semaphore_) == 0);
+ return (SDL_SemWait(mSemaphore) == 0);
}
- bool releaseLock()
+
+ /**
+ * 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)
{
- return (SDL_SemPost(semaphore_) == 0);
+ Uint32 ms = timeout * SCALAR(1000.0);
+ return (SDL_SemWaitTimeout(mSemaphore, ms) == 0);
}
- bool tryLock()
+ /**
+ * 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_SemTryWait(semaphore_) == 0);
+ return (SDL_SemPost(mSemaphore) == 0);
}
- bool tryLock(unsigned ms)
+
+ /**
+ * 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()
{
- // TODO for consistency, this function should take seconds
- return (SDL_SemWaitTimeout(semaphore_, ms) == 0);
+ 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:
- Lock(Semaphore& semaphore)
- {
- semaphore_ = &semaphore;
- isLocked_ = false;
- }
+
+ /**
+ * Construct a lock.
+ * \param semaphore The semaphore.
+ */
+ Lock(Semaphore& semaphore) :
+ mSemaphore(semaphore),
+ mIsLocked(false) {}
+
+ /**
+ * Deconstruct a lock. The lock is automagically released if it is
+ * still locked.
+ */
~Lock()
{
- if (isLocked_) release();
+ if (mIsLocked) release();
}
+
+ /**
+ * Try to acquire a lock on the semaphore.
+ * \return True if the semaphore was locked, false otherwise.
+ */
bool acquire()
{
- return (isLocked_ = semaphore_->acquireLock());
+ return (mIsLocked = mSemaphore.acquireLock());
}
+
+ /**
+ * Release the lock.
+ * \return True if the semaphore was unlocked, false otherwise.
+ */
bool release()
{
- return semaphore_->releaseLock(); isLocked_ = false;
+ 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 isLocked_;
+ return mIsLocked;
}
+
protected:
- Semaphore* semaphore_;
- bool isLocked_;
+
+ Semaphore& mSemaphore;
+ bool mIsLocked;
};
- class ScopedLock : public Lock
+ /**
+ * 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.
+ */
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* semaphore_;
+
+ SDL_sem* mSemaphore;
};
} // namespace Mf
-
#endif // _MOOF_THREAD_HH_
-/** vim: set ts=4 sw=4 tw=80: *************************************************/
-