|
@@ -20,44 +20,38 @@ |
|
|
*/ |
|
|
#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 */ |
|
|
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 */ |
|
|