From 551b55866ba590f6ce45f7cc2a26eab10d123e57 Mon Sep 17 00:00:00 2001 From: David Ludwig Date: Sat, 24 Nov 2012 11:17:23 -0500 Subject: [PATCH] WinRT: added a functional threading backend using C++11 apis --- src/thread/stdcpp/SDL_syscond.cpp | 178 +++++++++++----------------- src/thread/stdcpp/SDL_sysmutex.cpp | 106 +++++++---------- src/thread/stdcpp/SDL_sysmutex_c.h | 8 ++ src/thread/stdcpp/SDL_systhread.cpp | 66 ++++++++++- 4 files changed, 176 insertions(+), 182 deletions(-) diff --git a/src/thread/stdcpp/SDL_syscond.cpp b/src/thread/stdcpp/SDL_syscond.cpp index a603a1ba3..4ca1d96f7 100644 --- a/src/thread/stdcpp/SDL_syscond.cpp +++ b/src/thread/stdcpp/SDL_syscond.cpp @@ -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 +#include +#include +#include + +#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 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(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 */ diff --git a/src/thread/stdcpp/SDL_sysmutex.cpp b/src/thread/stdcpp/SDL_sysmutex.cpp index e220bc5df..e43b255e8 100644 --- a/src/thread/stdcpp/SDL_sysmutex.cpp +++ b/src/thread/stdcpp/SDL_sysmutex.cpp @@ -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 + +#include "SDL_sysmutex_c.h" +#include -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: */ diff --git a/src/thread/stdcpp/SDL_sysmutex_c.h b/src/thread/stdcpp/SDL_sysmutex_c.h index ed546b689..500e57d40 100644 --- a/src/thread/stdcpp/SDL_sysmutex_c.h +++ b/src/thread/stdcpp/SDL_sysmutex_c.h @@ -19,4 +19,12 @@ 3. This notice may not be removed or altered from any source distribution. */ #include "SDL_config.h" + +#include + +struct SDL_mutex +{ + std::recursive_mutex cpp_mutex; +}; + /* vi: set ts=4 sw=4 expandtab: */ diff --git a/src/thread/stdcpp/SDL_systhread.cpp b/src/thread/stdcpp/SDL_systhread.cpp index e40d8f277..fd4cdafb3 100644 --- a/src/thread/stdcpp/SDL_systhread.cpp +++ b/src/thread/stdcpp/SDL_systhread.cpp @@ -25,20 +25,51 @@ extern "C" { #include "SDL_thread.h" #include "../SDL_systhread.h" +#include "../SDL_thread_c.h" +#include "SDL_log.h" +} + +#include +#include + +// 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 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: */