WinRT: added a functional threading backend using C++11 apis
authorDavid Ludwig <dludwig@pobox.com>
Sat, 24 Nov 2012 11:17:23 -0500
changeset 83607f1bc00e59fc
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
     1.1 --- a/src/thread/stdcpp/SDL_syscond.cpp	Sat Nov 24 11:16:45 2012 -0500
     1.2 +++ b/src/thread/stdcpp/SDL_syscond.cpp	Sat Nov 24 11:17:23 2012 -0500
     1.3 @@ -20,21 +20,20 @@
     1.4  */
     1.5  #include "SDL_config.h"
     1.6  
     1.7 -/* An implementation of condition variables using semaphores and mutexes */
     1.8 -/*
     1.9 -   This implementation borrows heavily from the BeOS condition variable
    1.10 -   implementation, written by Christopher Tate and Owen Smith.  Thanks!
    1.11 - */
    1.12 +extern "C" {
    1.13 +#include "SDL_thread.h"
    1.14 +}
    1.15  
    1.16 -#include "SDL_thread.h"
    1.17 +#include <chrono>
    1.18 +#include <condition_variable>
    1.19 +#include <exception>
    1.20 +#include <ratio>
    1.21 +
    1.22 +#include "SDL_sysmutex_c.h"
    1.23  
    1.24  struct SDL_cond
    1.25  {
    1.26 -    SDL_mutex *lock;
    1.27 -    int waiting;
    1.28 -    int signals;
    1.29 -    SDL_sem *wait_sem;
    1.30 -    SDL_sem *wait_done;
    1.31 +    std::condition_variable_any cpp_cond;
    1.32  };
    1.33  
    1.34  /* Create a condition variable */
    1.35 @@ -42,22 +41,17 @@
    1.36  SDL_cond *
    1.37  SDL_CreateCond(void)
    1.38  {
    1.39 -    SDL_cond *cond;
    1.40 -
    1.41 -    cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond));
    1.42 -    if (cond) {
    1.43 -        cond->lock = SDL_CreateMutex();
    1.44 -        cond->wait_sem = SDL_CreateSemaphore(0);
    1.45 -        cond->wait_done = SDL_CreateSemaphore(0);
    1.46 -        cond->waiting = cond->signals = 0;
    1.47 -        if (!cond->lock || !cond->wait_sem || !cond->wait_done) {
    1.48 -            SDL_DestroyCond(cond);
    1.49 -            cond = NULL;
    1.50 -        }
    1.51 -    } else {
    1.52 -        SDL_OutOfMemory();
    1.53 +    /* Allocate and initialize the condition variable */
    1.54 +    try {
    1.55 +        SDL_cond * cond = new SDL_cond;
    1.56 +        return cond;
    1.57 +    } catch (std::exception & ex) {
    1.58 +        SDL_SetError("unable to create C++ condition variable: %s", ex.what());
    1.59 +        return NULL;
    1.60 +    } catch (...) {
    1.61 +        SDL_SetError("unable to create C++ condition variable due to an unknown exception");
    1.62 +        return NULL;
    1.63      }
    1.64 -    return (cond);
    1.65  }
    1.66  
    1.67  /* Destroy a condition variable */
    1.68 @@ -66,16 +60,11 @@
    1.69  SDL_DestroyCond(SDL_cond * cond)
    1.70  {
    1.71      if (cond) {
    1.72 -        if (cond->wait_sem) {
    1.73 -            SDL_DestroySemaphore(cond->wait_sem);
    1.74 +        try {
    1.75 +            delete cond;
    1.76 +        } catch (...) {
    1.77 +            // catch any and all exceptions, just in case something happens
    1.78          }
    1.79 -        if (cond->wait_done) {
    1.80 -            SDL_DestroySemaphore(cond->wait_done);
    1.81 -        }
    1.82 -        if (cond->lock) {
    1.83 -            SDL_DestroyMutex(cond->lock);
    1.84 -        }
    1.85 -        SDL_free(cond);
    1.86      }
    1.87  }
    1.88  
    1.89 @@ -89,20 +78,14 @@
    1.90          return -1;
    1.91      }
    1.92  
    1.93 -    /* If there are waiting threads not already signalled, then
    1.94 -       signal the condition and wait for the thread to respond.
    1.95 -     */
    1.96 -    SDL_LockMutex(cond->lock);
    1.97 -    if (cond->waiting > cond->signals) {
    1.98 -        ++cond->signals;
    1.99 -        SDL_SemPost(cond->wait_sem);
   1.100 -        SDL_UnlockMutex(cond->lock);
   1.101 -        SDL_SemWait(cond->wait_done);
   1.102 -    } else {
   1.103 -        SDL_UnlockMutex(cond->lock);
   1.104 +    try {
   1.105 +        cond->cpp_cond.notify_one();
   1.106 +        return 0;
   1.107 +    } catch (...) {
   1.108 +        // catch any and all exceptions, just in case something happens
   1.109 +        SDL_SetError("unable to signal C++ condition variable due to an unknown exception");
   1.110 +        return -1;
   1.111      }
   1.112 -
   1.113 -    return 0;
   1.114  }
   1.115  
   1.116  /* Restart all threads that are waiting on the condition variable */
   1.117 @@ -115,30 +98,14 @@
   1.118          return -1;
   1.119      }
   1.120  
   1.121 -    /* If there are waiting threads not already signalled, then
   1.122 -       signal the condition and wait for the thread to respond.
   1.123 -     */
   1.124 -    SDL_LockMutex(cond->lock);
   1.125 -    if (cond->waiting > cond->signals) {
   1.126 -        int i, num_waiting;
   1.127 -
   1.128 -        num_waiting = (cond->waiting - cond->signals);
   1.129 -        cond->signals = cond->waiting;
   1.130 -        for (i = 0; i < num_waiting; ++i) {
   1.131 -            SDL_SemPost(cond->wait_sem);
   1.132 -        }
   1.133 -        /* Now all released threads are blocked here, waiting for us.
   1.134 -           Collect them all (and win fabulous prizes!) :-)
   1.135 -         */
   1.136 -        SDL_UnlockMutex(cond->lock);
   1.137 -        for (i = 0; i < num_waiting; ++i) {
   1.138 -            SDL_SemWait(cond->wait_done);
   1.139 -        }
   1.140 -    } else {
   1.141 -        SDL_UnlockMutex(cond->lock);
   1.142 +    try {
   1.143 +        cond->cpp_cond.notify_all();
   1.144 +        return 0;
   1.145 +    } catch (...) {
   1.146 +        // catch any and all exceptions, just in case something happens
   1.147 +        SDL_SetError("unable to broadcast C++ condition variable due to an unknown exception");
   1.148 +        return -1;
   1.149      }
   1.150 -
   1.151 -    return 0;
   1.152  }
   1.153  
   1.154  /* Wait on the condition variable for at most 'ms' milliseconds.
   1.155 @@ -166,56 +133,43 @@
   1.156  int
   1.157  SDL_CondWaitTimeout(SDL_cond * cond, SDL_mutex * mutex, Uint32 ms)
   1.158  {
   1.159 -    int retval;
   1.160 -
   1.161      if (!cond) {
   1.162          SDL_SetError("Passed a NULL condition variable");
   1.163          return -1;
   1.164      }
   1.165  
   1.166 -    /* Obtain the protection mutex, and increment the number of waiters.
   1.167 -       This allows the signal mechanism to only perform a signal if there
   1.168 -       are waiting threads.
   1.169 -     */
   1.170 -    SDL_LockMutex(cond->lock);
   1.171 -    ++cond->waiting;
   1.172 -    SDL_UnlockMutex(cond->lock);
   1.173 -
   1.174 -    /* Unlock the mutex, as is required by condition variable semantics */
   1.175 -    SDL_UnlockMutex(mutex);
   1.176 -
   1.177 -    /* Wait for a signal */
   1.178 -    if (ms == SDL_MUTEX_MAXWAIT) {
   1.179 -        retval = SDL_SemWait(cond->wait_sem);
   1.180 -    } else {
   1.181 -        retval = SDL_SemWaitTimeout(cond->wait_sem, ms);
   1.182 +    if (!mutex) {
   1.183 +        SDL_SetError("Passed a NULL mutex variable");
   1.184 +        return -1;
   1.185      }
   1.186  
   1.187 -    /* Let the signaler know we have completed the wait, otherwise
   1.188 -       the signaler can race ahead and get the condition semaphore
   1.189 -       if we are stopped between the mutex unlock and semaphore wait,
   1.190 -       giving a deadlock.  See the following URL for details:
   1.191 -       http://www-classic.be.com/aboutbe/benewsletter/volume_III/Issue40.html
   1.192 -     */
   1.193 -    SDL_LockMutex(cond->lock);
   1.194 -    if (cond->signals > 0) {
   1.195 -        /* If we timed out, we need to eat a condition signal */
   1.196 -        if (retval > 0) {
   1.197 -            SDL_SemWait(cond->wait_sem);
   1.198 +    try {
   1.199 +        std::unique_lock<std::recursive_mutex> cpp_lock(mutex->cpp_mutex, std::defer_lock_t());
   1.200 +        if (ms == SDL_MUTEX_MAXWAIT) {
   1.201 +            cond->cpp_cond.wait(
   1.202 +                cpp_lock
   1.203 +                );
   1.204 +            cpp_lock.release();
   1.205 +            return 0;
   1.206 +        } else {
   1.207 +            auto wait_result = cond->cpp_cond.wait_for(
   1.208 +                cpp_lock,
   1.209 +                std::chrono::duration<Uint32, std::milli>(ms)
   1.210 +                );
   1.211 +            cpp_lock.release();
   1.212 +            if (wait_result == std::cv_status::timeout) {
   1.213 +                return SDL_MUTEX_TIMEDOUT;
   1.214 +            } else {
   1.215 +                return 0;
   1.216 +            }
   1.217          }
   1.218 -        /* We always notify the signal thread that we are done */
   1.219 -        SDL_SemPost(cond->wait_done);
   1.220 -
   1.221 -        /* Signal handshake complete */
   1.222 -        --cond->signals;
   1.223 +    } catch (std::exception & ex) {
   1.224 +        SDL_SetError("unable to wait on C++ condition variable: %s", ex.what());
   1.225 +        return -1;
   1.226 +    } catch (...) {
   1.227 +        SDL_SetError("unable to lock wait on C++ condition variable due to an unknown exception");
   1.228 +        return -1;
   1.229      }
   1.230 -    --cond->waiting;
   1.231 -    SDL_UnlockMutex(cond->lock);
   1.232 -
   1.233 -    /* Lock the mutex, as is required by condition variable semantics */
   1.234 -    SDL_LockMutex(mutex);
   1.235 -
   1.236 -    return retval;
   1.237  }
   1.238  
   1.239  /* Wait on the condition variable forever */
     2.1 --- a/src/thread/stdcpp/SDL_sysmutex.cpp	Sat Nov 24 11:16:45 2012 -0500
     2.2 +++ b/src/thread/stdcpp/SDL_sysmutex.cpp	Sat Nov 24 11:17:23 2012 -0500
     2.3 @@ -20,41 +20,34 @@
     2.4  */
     2.5  #include "SDL_config.h"
     2.6  
     2.7 -/* An implementation of mutexes using semaphores */
     2.8 -
     2.9 +extern "C" {
    2.10  #include "SDL_thread.h"
    2.11  #include "SDL_systhread_c.h"
    2.12 +#include "SDL_log.h"
    2.13 +}
    2.14  
    2.15 +#include <exception>
    2.16  
    2.17 -struct SDL_mutex
    2.18 -{
    2.19 -    int recursive;
    2.20 -    SDL_threadID owner;
    2.21 -    SDL_sem *sem;
    2.22 -};
    2.23 +#include "SDL_sysmutex_c.h"
    2.24 +#include <Windows.h>
    2.25 +
    2.26  
    2.27  /* Create a mutex */
    2.28  extern "C"
    2.29  SDL_mutex *
    2.30  SDL_CreateMutex(void)
    2.31  {
    2.32 -    SDL_mutex *mutex;
    2.33 -
    2.34 -    /* Allocate mutex memory */
    2.35 -    mutex = (SDL_mutex *) SDL_malloc(sizeof(*mutex));
    2.36 -    if (mutex) {
    2.37 -        /* Create the mutex semaphore, with initial value 1 */
    2.38 -        mutex->sem = SDL_CreateSemaphore(1);
    2.39 -        mutex->recursive = 0;
    2.40 -        mutex->owner = 0;
    2.41 -        if (!mutex->sem) {
    2.42 -            SDL_free(mutex);
    2.43 -            mutex = NULL;
    2.44 -        }
    2.45 -    } else {
    2.46 -        SDL_OutOfMemory();
    2.47 +    /* Allocate and initialize the mutex */
    2.48 +    try {
    2.49 +        SDL_mutex * mutex = new SDL_mutex;
    2.50 +        return mutex;
    2.51 +    } catch (std::exception & ex) {
    2.52 +        SDL_SetError("unable to create C++ mutex: %s", ex.what());
    2.53 +        return NULL;
    2.54 +    } catch (...) {
    2.55 +        SDL_SetError("unable to create C++ mutex due to an unknown exception");
    2.56 +        return NULL;
    2.57      }
    2.58 -    return mutex;
    2.59  }
    2.60  
    2.61  /* Free the mutex */
    2.62 @@ -63,10 +56,11 @@
    2.63  SDL_DestroyMutex(SDL_mutex * mutex)
    2.64  {
    2.65      if (mutex) {
    2.66 -        if (mutex->sem) {
    2.67 -            SDL_DestroySemaphore(mutex->sem);
    2.68 +        try {
    2.69 +            delete mutex;
    2.70 +        } catch (...) {
    2.71 +            // catch any and all exceptions, just in case something happens
    2.72          }
    2.73 -        SDL_free(mutex);
    2.74      }
    2.75  }
    2.76  
    2.77 @@ -75,31 +69,23 @@
    2.78  int
    2.79  SDL_mutexP(SDL_mutex * mutex)
    2.80  {
    2.81 -#if SDL_THREADS_DISABLED
    2.82 -    return 0;
    2.83 -#else
    2.84 -    SDL_threadID this_thread;
    2.85 -
    2.86 +    SDL_threadID threadID = SDL_ThreadID();
    2.87 +    DWORD realThreadID = GetCurrentThreadId();
    2.88      if (mutex == NULL) {
    2.89          SDL_SetError("Passed a NULL mutex");
    2.90          return -1;
    2.91      }
    2.92  
    2.93 -    this_thread = SDL_ThreadID();
    2.94 -    if (mutex->owner == this_thread) {
    2.95 -        ++mutex->recursive;
    2.96 -    } else {
    2.97 -        /* The order of operations is important.
    2.98 -           We set the locking thread id after we obtain the lock
    2.99 -           so unlocks from other threads will fail.
   2.100 -         */
   2.101 -        SDL_SemWait(mutex->sem);
   2.102 -        mutex->owner = this_thread;
   2.103 -        mutex->recursive = 0;
   2.104 +    try {
   2.105 +        mutex->cpp_mutex.lock();
   2.106 +        return 0;
   2.107 +    } catch (std::exception & ex) {
   2.108 +        SDL_SetError("unable to lock C++ mutex: %s", ex.what());
   2.109 +        return -1;
   2.110 +    } catch (...) {
   2.111 +        SDL_SetError("unable to lock C++ mutex due to an unknown exception");
   2.112 +        return -1;
   2.113      }
   2.114 -
   2.115 -    return 0;
   2.116 -#endif /* SDL_THREADS_DISABLED */
   2.117  }
   2.118  
   2.119  /* Unlock the mutex */
   2.120 @@ -107,33 +93,21 @@
   2.121  int
   2.122  SDL_mutexV(SDL_mutex * mutex)
   2.123  {
   2.124 -#if SDL_THREADS_DISABLED
   2.125 -    return 0;
   2.126 -#else
   2.127 +    SDL_threadID threadID = SDL_ThreadID();
   2.128 +    DWORD realThreadID = GetCurrentThreadId();
   2.129      if (mutex == NULL) {
   2.130          SDL_SetError("Passed a NULL mutex");
   2.131          return -1;
   2.132      }
   2.133  
   2.134 -    /* If we don't own the mutex, we can't unlock it */
   2.135 -    if (SDL_ThreadID() != mutex->owner) {
   2.136 -        SDL_SetError("mutex not owned by this thread");
   2.137 +    try {
   2.138 +        mutex->cpp_mutex.unlock();
   2.139 +        return 0;
   2.140 +    } catch (...) {
   2.141 +        // catch any and all exceptions, just in case something happens.
   2.142 +        SDL_SetError("unable to unlock C++ mutex due to an unknown exception");
   2.143          return -1;
   2.144      }
   2.145 -
   2.146 -    if (mutex->recursive) {
   2.147 -        --mutex->recursive;
   2.148 -    } else {
   2.149 -        /* The order of operations is important.
   2.150 -           First reset the owner so another thread doesn't lock
   2.151 -           the mutex and set the ownership before we reset it,
   2.152 -           then release the lock semaphore.
   2.153 -         */
   2.154 -        mutex->owner = 0;
   2.155 -        SDL_SemPost(mutex->sem);
   2.156 -    }
   2.157 -    return 0;
   2.158 -#endif /* SDL_THREADS_DISABLED */
   2.159  }
   2.160  
   2.161  /* vi: set ts=4 sw=4 expandtab: */
     3.1 --- a/src/thread/stdcpp/SDL_sysmutex_c.h	Sat Nov 24 11:16:45 2012 -0500
     3.2 +++ b/src/thread/stdcpp/SDL_sysmutex_c.h	Sat Nov 24 11:17:23 2012 -0500
     3.3 @@ -19,4 +19,12 @@
     3.4    3. This notice may not be removed or altered from any source distribution.
     3.5  */
     3.6  #include "SDL_config.h"
     3.7 +
     3.8 +#include <mutex>
     3.9 +
    3.10 +struct SDL_mutex
    3.11 +{
    3.12 +    std::recursive_mutex cpp_mutex;
    3.13 +};
    3.14 +
    3.15  /* vi: set ts=4 sw=4 expandtab: */
     4.1 --- a/src/thread/stdcpp/SDL_systhread.cpp	Sat Nov 24 11:16:45 2012 -0500
     4.2 +++ b/src/thread/stdcpp/SDL_systhread.cpp	Sat Nov 24 11:17:23 2012 -0500
     4.3 @@ -25,20 +25,51 @@
     4.4  extern "C" {
     4.5  #include "SDL_thread.h"
     4.6  #include "../SDL_systhread.h"
     4.7 +#include "../SDL_thread_c.h"
     4.8 +#include "SDL_log.h"
     4.9 +}
    4.10 +
    4.11 +#include <mutex>
    4.12 +#include <thread>
    4.13 +
    4.14 +// HACK: Mimic C++11's thread_local keyword on Visual C++ 2012 (aka. VC++ 11)
    4.15 +// TODO: make sure this hack doesn't get used if and when Visual C++ supports
    4.16 +// the official, 'thread_local' keyword.
    4.17 +#ifdef _MSC_VER
    4.18 +#define thread_local __declspec(thread)
    4.19 +// Documentation for __declspec(thread) can be found online at:
    4.20 +// http://msdn.microsoft.com/en-us/library/2s9wt68x.aspx
    4.21 +#endif
    4.22 +
    4.23 +static void
    4.24 +RunThread(void *args)
    4.25 +{
    4.26 +    SDL_RunThread(args);
    4.27  }
    4.28  
    4.29  extern "C"
    4.30  int
    4.31  SDL_SYS_CreateThread(SDL_Thread * thread, void *args)
    4.32  {
    4.33 -    SDL_SetError("Threads are not supported on this platform");
    4.34 -    return (-1);
    4.35 +    try {
    4.36 +        std::thread cpp_thread(RunThread, args);
    4.37 +        thread->handle = (void *) new std::thread(std::move(cpp_thread));
    4.38 +        return 0;
    4.39 +    } catch (std::exception & ex) {
    4.40 +        SDL_SetError("unable to create a C++ thread: %s", ex.what());
    4.41 +        return -1;
    4.42 +    } catch (...) {
    4.43 +        SDL_SetError("unable to create a C++ thread due to an unknown exception");
    4.44 +        return -1;
    4.45 +    }
    4.46  }
    4.47  
    4.48  extern "C"
    4.49  void
    4.50  SDL_SYS_SetupThread(const char *name)
    4.51  {
    4.52 +    // Make sure a thread ID gets assigned ASAP, for debugging purposes:
    4.53 +    SDL_ThreadID();
    4.54      return;
    4.55  }
    4.56  
    4.57 @@ -46,13 +77,27 @@
    4.58  SDL_threadID
    4.59  SDL_ThreadID(void)
    4.60  {
    4.61 -    return (0);
    4.62 +    static thread_local SDL_threadID current_thread_id = 0;
    4.63 +    static SDL_threadID next_thread_id = 1;
    4.64 +    static std::mutex next_thread_id_mutex;
    4.65 +
    4.66 +    if (current_thread_id == 0) {
    4.67 +        std::lock_guard<std::mutex> lock(next_thread_id_mutex);
    4.68 +        current_thread_id = next_thread_id;
    4.69 +        ++next_thread_id;
    4.70 +    }
    4.71 +
    4.72 +    return current_thread_id;
    4.73  }
    4.74  
    4.75  extern "C"
    4.76  int
    4.77  SDL_SYS_SetThreadPriority(SDL_ThreadPriority priority)
    4.78  {
    4.79 +    // Thread priorities do not look to be settable via C++11's thread
    4.80 +    // interface, at least as of this writing (Nov 2012).  std::thread does
    4.81 +    // provide access to the OS' native handle, however, and some form of
    4.82 +    // priority-setting could, in theory, be done through this interface.
    4.83      return (0);
    4.84  }
    4.85  
    4.86 @@ -60,7 +105,20 @@
    4.87  void
    4.88  SDL_SYS_WaitThread(SDL_Thread * thread)
    4.89  {
    4.90 -    return;
    4.91 +    if ( ! thread) {
    4.92 +        return;
    4.93 +    }
    4.94 +
    4.95 +    try {
    4.96 +        std::thread * cpp_thread = (std::thread *) thread->handle;
    4.97 +        if (cpp_thread->joinable()) {
    4.98 +            cpp_thread->join();
    4.99 +        }
   4.100 +    } catch (...) {
   4.101 +        // Catch any exceptions, just in case.
   4.102 +        // Report nothing, as SDL_WaitThread does not seem to offer a means
   4.103 +        // to report errors to its callers.
   4.104 +    }
   4.105  }
   4.106  
   4.107  /* vi: set ts=4 sw=4 expandtab: */