WinRT: added a functional threading backend using C++11 apis
authorDavid Ludwig <dludwig@pobox.com>
Sat, 24 Nov 2012 11:17:23 -0500
changeset 8360 7f1bc00e59fc
parent 8359 80700b50c17a
child 8361 eefad2ec4f76
WinRT: added a functional threading backend using C++11 apis
src/thread/stdcpp/SDL_syscond.cpp
src/thread/stdcpp/SDL_sysmutex.cpp
src/thread/stdcpp/SDL_sysmutex_c.h
src/thread/stdcpp/SDL_systhread.cpp
--- a/src/thread/stdcpp/SDL_syscond.cpp	Sat Nov 24 11:16:45 2012 -0500
+++ b/src/thread/stdcpp/SDL_syscond.cpp	Sat Nov 24 11:17:23 2012 -0500
@@ -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 "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 @@
 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 @@
 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 @@
         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 @@
         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 @@
 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 */
--- a/src/thread/stdcpp/SDL_sysmutex.cpp	Sat Nov 24 11:16:45 2012 -0500
+++ b/src/thread/stdcpp/SDL_sysmutex.cpp	Sat Nov 24 11:17:23 2012 -0500
@@ -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"
+}
 
-struct SDL_mutex
-{
-    int recursive;
-    SDL_threadID owner;
-    SDL_sem *sem;
-};
+#include <exception>
+
+#include "SDL_sysmutex_c.h"
+#include <Windows.h>
+
 
 /* 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 @@
 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 @@
 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 @@
 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: */
--- a/src/thread/stdcpp/SDL_sysmutex_c.h	Sat Nov 24 11:16:45 2012 -0500
+++ b/src/thread/stdcpp/SDL_sysmutex_c.h	Sat Nov 24 11:17:23 2012 -0500
@@ -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: */
--- a/src/thread/stdcpp/SDL_systhread.cpp	Sat Nov 24 11:16:45 2012 -0500
+++ b/src/thread/stdcpp/SDL_systhread.cpp	Sat Nov 24 11:17:23 2012 -0500
@@ -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 @@
 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 @@
 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: */