WinRT: added a functional threading backend using C++11 apis

This commit is contained in:
David Ludwig 2012-11-24 11:17:23 -05:00
parent bc67a6617f
commit 2fde6ac33b
4 changed files with 176 additions and 182 deletions

View file

@ -20,21 +20,20 @@
*/
#include "SDL_config.h"
/* An implementation of condition variables using semaphores and mutexes */
/*
This implementation borrows heavily from the BeOS condition variable
implementation, written by Christopher Tate and Owen Smith. Thanks!
*/
extern "C" {
#include "SDL_thread.h"
}
#include <chrono>
#include <condition_variable>
#include <exception>
#include <ratio>
#include "SDL_sysmutex_c.h"
struct SDL_cond
{
SDL_mutex *lock;
int waiting;
int signals;
SDL_sem *wait_sem;
SDL_sem *wait_done;
std::condition_variable_any cpp_cond;
};
/* Create a condition variable */
@ -42,22 +41,17 @@ extern "C"
SDL_cond *
SDL_CreateCond(void)
{
SDL_cond *cond;
cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond));
if (cond) {
cond->lock = SDL_CreateMutex();
cond->wait_sem = SDL_CreateSemaphore(0);
cond->wait_done = SDL_CreateSemaphore(0);
cond->waiting = cond->signals = 0;
if (!cond->lock || !cond->wait_sem || !cond->wait_done) {
SDL_DestroyCond(cond);
cond = NULL;
}
} else {
SDL_OutOfMemory();
/* Allocate and initialize the condition variable */
try {
SDL_cond * cond = new SDL_cond;
return cond;
} catch (std::exception & ex) {
SDL_SetError("unable to create C++ condition variable: %s", ex.what());
return NULL;
} catch (...) {
SDL_SetError("unable to create C++ condition variable due to an unknown exception");
return NULL;
}
return (cond);
}
/* Destroy a condition variable */
@ -66,16 +60,11 @@ void
SDL_DestroyCond(SDL_cond * cond)
{
if (cond) {
if (cond->wait_sem) {
SDL_DestroySemaphore(cond->wait_sem);
try {
delete cond;
} catch (...) {
// catch any and all exceptions, just in case something happens
}
if (cond->wait_done) {
SDL_DestroySemaphore(cond->wait_done);
}
if (cond->lock) {
SDL_DestroyMutex(cond->lock);
}
SDL_free(cond);
}
}
@ -89,20 +78,14 @@ SDL_CondSignal(SDL_cond * cond)
return -1;
}
/* If there are waiting threads not already signalled, then
signal the condition and wait for the thread to respond.
*/
SDL_LockMutex(cond->lock);
if (cond->waiting > cond->signals) {
++cond->signals;
SDL_SemPost(cond->wait_sem);
SDL_UnlockMutex(cond->lock);
SDL_SemWait(cond->wait_done);
} else {
SDL_UnlockMutex(cond->lock);
try {
cond->cpp_cond.notify_one();
return 0;
} catch (...) {
// catch any and all exceptions, just in case something happens
SDL_SetError("unable to signal C++ condition variable due to an unknown exception");
return -1;
}
return 0;
}
/* Restart all threads that are waiting on the condition variable */
@ -115,30 +98,14 @@ SDL_CondBroadcast(SDL_cond * cond)
return -1;
}
/* If there are waiting threads not already signalled, then
signal the condition and wait for the thread to respond.
*/
SDL_LockMutex(cond->lock);
if (cond->waiting > cond->signals) {
int i, num_waiting;
num_waiting = (cond->waiting - cond->signals);
cond->signals = cond->waiting;
for (i = 0; i < num_waiting; ++i) {
SDL_SemPost(cond->wait_sem);
}
/* Now all released threads are blocked here, waiting for us.
Collect them all (and win fabulous prizes!) :-)
*/
SDL_UnlockMutex(cond->lock);
for (i = 0; i < num_waiting; ++i) {
SDL_SemWait(cond->wait_done);
}
} else {
SDL_UnlockMutex(cond->lock);
try {
cond->cpp_cond.notify_all();
return 0;
} catch (...) {
// catch any and all exceptions, just in case something happens
SDL_SetError("unable to broadcast C++ condition variable due to an unknown exception");
return -1;
}
return 0;
}
/* Wait on the condition variable for at most 'ms' milliseconds.
@ -166,56 +133,43 @@ extern "C"
int
SDL_CondWaitTimeout(SDL_cond * cond, SDL_mutex * mutex, Uint32 ms)
{
int retval;
if (!cond) {
SDL_SetError("Passed a NULL condition variable");
return -1;
}
/* Obtain the protection mutex, and increment the number of waiters.
This allows the signal mechanism to only perform a signal if there
are waiting threads.
*/
SDL_LockMutex(cond->lock);
++cond->waiting;
SDL_UnlockMutex(cond->lock);
/* Unlock the mutex, as is required by condition variable semantics */
SDL_UnlockMutex(mutex);
/* Wait for a signal */
if (ms == SDL_MUTEX_MAXWAIT) {
retval = SDL_SemWait(cond->wait_sem);
} else {
retval = SDL_SemWaitTimeout(cond->wait_sem, ms);
if (!mutex) {
SDL_SetError("Passed a NULL mutex variable");
return -1;
}
/* Let the signaler know we have completed the wait, otherwise
the signaler can race ahead and get the condition semaphore
if we are stopped between the mutex unlock and semaphore wait,
giving a deadlock. See the following URL for details:
http://www-classic.be.com/aboutbe/benewsletter/volume_III/Issue40.html
*/
SDL_LockMutex(cond->lock);
if (cond->signals > 0) {
/* If we timed out, we need to eat a condition signal */
if (retval > 0) {
SDL_SemWait(cond->wait_sem);
try {
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->cpp_mutex, std::defer_lock_t());
if (ms == SDL_MUTEX_MAXWAIT) {
cond->cpp_cond.wait(
cpp_lock
);
cpp_lock.release();
return 0;
} else {
auto wait_result = cond->cpp_cond.wait_for(
cpp_lock,
std::chrono::duration<Uint32, std::milli>(ms)
);
cpp_lock.release();
if (wait_result == std::cv_status::timeout) {
return SDL_MUTEX_TIMEDOUT;
} else {
return 0;
}
}
/* We always notify the signal thread that we are done */
SDL_SemPost(cond->wait_done);
/* Signal handshake complete */
--cond->signals;
} catch (std::exception & ex) {
SDL_SetError("unable to wait on C++ condition variable: %s", ex.what());
return -1;
} catch (...) {
SDL_SetError("unable to lock wait on C++ condition variable due to an unknown exception");
return -1;
}
--cond->waiting;
SDL_UnlockMutex(cond->lock);
/* Lock the mutex, as is required by condition variable semantics */
SDL_LockMutex(mutex);
return retval;
}
/* Wait on the condition variable forever */

View file

@ -20,41 +20,34 @@
*/
#include "SDL_config.h"
/* An implementation of mutexes using semaphores */
extern "C" {
#include "SDL_thread.h"
#include "SDL_systhread_c.h"
#include "SDL_log.h"
}
#include <exception>
#include "SDL_sysmutex_c.h"
#include <Windows.h>
struct SDL_mutex
{
int recursive;
SDL_threadID owner;
SDL_sem *sem;
};
/* Create a mutex */
extern "C"
SDL_mutex *
SDL_CreateMutex(void)
{
SDL_mutex *mutex;
/* Allocate mutex memory */
mutex = (SDL_mutex *) SDL_malloc(sizeof(*mutex));
if (mutex) {
/* Create the mutex semaphore, with initial value 1 */
mutex->sem = SDL_CreateSemaphore(1);
mutex->recursive = 0;
mutex->owner = 0;
if (!mutex->sem) {
SDL_free(mutex);
mutex = NULL;
}
} else {
SDL_OutOfMemory();
/* Allocate and initialize the mutex */
try {
SDL_mutex * mutex = new SDL_mutex;
return mutex;
} catch (std::exception & ex) {
SDL_SetError("unable to create C++ mutex: %s", ex.what());
return NULL;
} catch (...) {
SDL_SetError("unable to create C++ mutex due to an unknown exception");
return NULL;
}
return mutex;
}
/* Free the mutex */
@ -63,10 +56,11 @@ void
SDL_DestroyMutex(SDL_mutex * mutex)
{
if (mutex) {
if (mutex->sem) {
SDL_DestroySemaphore(mutex->sem);
try {
delete mutex;
} catch (...) {
// catch any and all exceptions, just in case something happens
}
SDL_free(mutex);
}
}
@ -75,31 +69,23 @@ extern "C"
int
SDL_mutexP(SDL_mutex * mutex)
{
#if SDL_THREADS_DISABLED
return 0;
#else
SDL_threadID this_thread;
SDL_threadID threadID = SDL_ThreadID();
DWORD realThreadID = GetCurrentThreadId();
if (mutex == NULL) {
SDL_SetError("Passed a NULL mutex");
return -1;
}
this_thread = SDL_ThreadID();
if (mutex->owner == this_thread) {
++mutex->recursive;
} else {
/* The order of operations is important.
We set the locking thread id after we obtain the lock
so unlocks from other threads will fail.
*/
SDL_SemWait(mutex->sem);
mutex->owner = this_thread;
mutex->recursive = 0;
try {
mutex->cpp_mutex.lock();
return 0;
} catch (std::exception & ex) {
SDL_SetError("unable to lock C++ mutex: %s", ex.what());
return -1;
} catch (...) {
SDL_SetError("unable to lock C++ mutex due to an unknown exception");
return -1;
}
return 0;
#endif /* SDL_THREADS_DISABLED */
}
/* Unlock the mutex */
@ -107,33 +93,21 @@ extern "C"
int
SDL_mutexV(SDL_mutex * mutex)
{
#if SDL_THREADS_DISABLED
return 0;
#else
SDL_threadID threadID = SDL_ThreadID();
DWORD realThreadID = GetCurrentThreadId();
if (mutex == NULL) {
SDL_SetError("Passed a NULL mutex");
return -1;
}
/* If we don't own the mutex, we can't unlock it */
if (SDL_ThreadID() != mutex->owner) {
SDL_SetError("mutex not owned by this thread");
try {
mutex->cpp_mutex.unlock();
return 0;
} catch (...) {
// catch any and all exceptions, just in case something happens.
SDL_SetError("unable to unlock C++ mutex due to an unknown exception");
return -1;
}
if (mutex->recursive) {
--mutex->recursive;
} else {
/* The order of operations is important.
First reset the owner so another thread doesn't lock
the mutex and set the ownership before we reset it,
then release the lock semaphore.
*/
mutex->owner = 0;
SDL_SemPost(mutex->sem);
}
return 0;
#endif /* SDL_THREADS_DISABLED */
}
/* vi: set ts=4 sw=4 expandtab: */

View file

@ -19,4 +19,12 @@
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_config.h"
#include <mutex>
struct SDL_mutex
{
std::recursive_mutex cpp_mutex;
};
/* vi: set ts=4 sw=4 expandtab: */

View file

@ -25,20 +25,51 @@
extern "C" {
#include "SDL_thread.h"
#include "../SDL_systhread.h"
#include "../SDL_thread_c.h"
#include "SDL_log.h"
}
#include <mutex>
#include <thread>
// HACK: Mimic C++11's thread_local keyword on Visual C++ 2012 (aka. VC++ 11)
// TODO: make sure this hack doesn't get used if and when Visual C++ supports
// the official, 'thread_local' keyword.
#ifdef _MSC_VER
#define thread_local __declspec(thread)
// Documentation for __declspec(thread) can be found online at:
// http://msdn.microsoft.com/en-us/library/2s9wt68x.aspx
#endif
static void
RunThread(void *args)
{
SDL_RunThread(args);
}
extern "C"
int
SDL_SYS_CreateThread(SDL_Thread * thread, void *args)
{
SDL_SetError("Threads are not supported on this platform");
return (-1);
try {
std::thread cpp_thread(RunThread, args);
thread->handle = (void *) new std::thread(std::move(cpp_thread));
return 0;
} catch (std::exception & ex) {
SDL_SetError("unable to create a C++ thread: %s", ex.what());
return -1;
} catch (...) {
SDL_SetError("unable to create a C++ thread due to an unknown exception");
return -1;
}
}
extern "C"
void
SDL_SYS_SetupThread(const char *name)
{
// Make sure a thread ID gets assigned ASAP, for debugging purposes:
SDL_ThreadID();
return;
}
@ -46,13 +77,27 @@ extern "C"
SDL_threadID
SDL_ThreadID(void)
{
return (0);
static thread_local SDL_threadID current_thread_id = 0;
static SDL_threadID next_thread_id = 1;
static std::mutex next_thread_id_mutex;
if (current_thread_id == 0) {
std::lock_guard<std::mutex> lock(next_thread_id_mutex);
current_thread_id = next_thread_id;
++next_thread_id;
}
return current_thread_id;
}
extern "C"
int
SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
{
// Thread priorities do not look to be settable via C++11's thread
// interface, at least as of this writing (Nov 2012). std::thread does
// provide access to the OS' native handle, however, and some form of
// priority-setting could, in theory, be done through this interface.
return (0);
}
@ -60,7 +105,20 @@ extern "C"
void
SDL_SYS_WaitThread(SDL_Thread * thread)
{
return;
if ( ! thread) {
return;
}
try {
std::thread * cpp_thread = (std::thread *) thread->handle;
if (cpp_thread->joinable()) {
cpp_thread->join();
}
} catch (...) {
// Catch any exceptions, just in case.
// Report nothing, as SDL_WaitThread does not seem to offer a means
// to report errors to its callers.
}
}
/* vi: set ts=4 sw=4 expandtab: */