diff --git a/desmume/src/libretro-common/rthreads/rthreads.c b/desmume/src/libretro-common/rthreads/rthreads.c index 794b09909..edf3829c4 100644 --- a/desmume/src/libretro-common/rthreads/rthreads.c +++ b/desmume/src/libretro-common/rthreads/rthreads.c @@ -1,556 +1,592 @@ -/* Copyright (C) 2010-2016 The RetroArch team - * - * --------------------------------------------------------------------------------------- - * The following license statement only applies to this file (rthreads.c). - * --------------------------------------------------------------------------------------- - * - * Permission is hereby granted, free of charge, - * to any person obtaining a copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation the rights to - * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifdef __unix__ -#define _POSIX_C_SOURCE 199309 -#endif - -#include - -#include -#include - -/* with RETRO_WIN32_USE_PTHREADS, pthreads can be used even on win32. Maybe only supported in MSVC>=2005 */ - -#if defined(_WIN32) && !defined(RETRO_WIN32_USE_PTHREADS) -#define USE_WIN32_THREADS -#ifdef _XBOX -#include -#else -#define WIN32_LEAN_AND_MEAN -#include -#endif -#elif defined(GEKKO) -#include "gx_pthread.h" -#elif defined(PSP) -#include "psp_pthread.h" -#else -#include -#include -#endif - -#ifdef __MACH__ -#include -#include -#endif - -struct thread_data -{ - void (*func)(void*); - void *userdata; -}; - -struct sthread -{ -#ifdef USE_WIN32_THREADS - HANDLE thread; -#else - pthread_t id; -#endif -}; - -struct slock -{ -#ifdef USE_WIN32_THREADS - HANDLE lock; -#else - pthread_mutex_t lock; -#endif -}; - -#ifdef USE_WIN32_THREADS -//TODO - there's actually no need for this struct. we could do it all with ugly pointer syntax. save that for later. -struct ConceptualBlock -{ - struct ConceptualBlock* next; -}; -#endif - -struct scond -{ -#ifdef USE_WIN32_THREADS - HANDLE event, hot_potato; - volatile struct ConceptualBlock* volatile root; //the root of the queue; NULL if queue is empty - volatile int waiters; //equivalent to the queue length - volatile int wakens; -#else - pthread_cond_t cond; -#endif -}; - -#ifdef USE_WIN32_THREADS -static DWORD CALLBACK thread_wrap(void *data_) -#else -static void *thread_wrap(void *data_) -#endif -{ - struct thread_data *data = (struct thread_data*)data_; - if (!data) - return 0; - data->func(data->userdata); - free(data); - return 0; -} - -/** - * sthread_create: - * @start_routine : thread entry callback function - * @userdata : pointer to userdata that will be made - * available in thread entry callback function - * - * Create a new thread. - * - * Returns: pointer to new thread if successful, otherwise NULL. - */ -sthread_t *sthread_create(void (*thread_func)(void*), void *userdata) -{ - bool thread_created = false; - struct thread_data *data = NULL; - sthread_t *thread = (sthread_t*)calloc(1, sizeof(*thread)); - - if (!thread) - return NULL; - - data = (struct thread_data*)calloc(1, sizeof(*data)); - if (!data) - goto error; - - data->func = thread_func; - data->userdata = userdata; - -#ifdef USE_WIN32_THREADS - thread->thread = CreateThread(NULL, 0, thread_wrap, data, 0, NULL); - thread_created = !!thread->thread; -#else - thread_created = pthread_create(&thread->id, NULL, thread_wrap, data) == 0; -#endif - - if (!thread_created) - goto error; - - return thread; - -error: - if (data) - free(data); - free(thread); - return NULL; -} - -/** - * sthread_detach: - * @thread : pointer to thread object - * - * Detach a thread. When a detached thread terminates, its - * resource sare automatically released back to the system - * without the need for another thread to join with the - * terminated thread. - * - * Returns: 0 on success, otherwise it returns a non-zero error number. - */ -int sthread_detach(sthread_t *thread) -{ -#ifdef USE_WIN32_THREADS - CloseHandle(thread->thread); - free(thread); - return 0; -#else - return pthread_detach(thread->id); -#endif -} - -/** - * sthread_join: - * @thread : pointer to thread object - * - * Join with a terminated thread. Waits for the thread specified by - * @thread to terminate. If that thread has already terminated, then - * it will return immediately. The thread specified by @thread must - * be joinable. - * - * Returns: 0 on success, otherwise it returns a non-zero error number. - */ -void sthread_join(sthread_t *thread) -{ -#ifdef USE_WIN32_THREADS - WaitForSingleObject(thread->thread, INFINITE); - CloseHandle(thread->thread); -#else - pthread_join(thread->id, NULL); -#endif - free(thread); -} - -/** - * sthread_isself: - * @thread : pointer to thread object - * - * Join with a terminated thread. Waits for the thread specified by - * @thread to terminate. If that thread has already terminated, then - * it will return immediately. The thread specified by @thread must - * be joinable. - * - * Returns: true (1) if calling thread is the specified thread - */ -bool sthread_isself(sthread_t *thread) -{ -#ifdef USE_WIN32_THREADS - return GetCurrentThread() == thread->thread; -#else - return pthread_equal(pthread_self(),thread->id); -#endif -} - -/** - * slock_new: - * - * Create and initialize a new mutex. Must be manually - * freed. - * - * Returns: pointer to a new mutex if successful, otherwise NULL. - **/ -slock_t *slock_new(void) -{ - bool mutex_created = false; - slock_t *lock = (slock_t*)calloc(1, sizeof(*lock)); - if (!lock) - return NULL; - -#ifdef USE_WIN32_THREADS - lock->lock = CreateMutex(NULL, FALSE, NULL); - mutex_created = !!lock->lock; -#else - mutex_created = (pthread_mutex_init(&lock->lock, NULL) == 0); -#endif - - if (!mutex_created) - goto error; - - return lock; - -error: - free(lock); - return NULL; -} - -/** - * slock_free: - * @lock : pointer to mutex object - * - * Frees a mutex. - **/ -void slock_free(slock_t *lock) -{ - if (!lock) - return; - -#ifdef USE_WIN32_THREADS - CloseHandle(lock->lock); -#else - pthread_mutex_destroy(&lock->lock); -#endif - free(lock); -} - -/** - * slock_lock: - * @lock : pointer to mutex object - * - * Locks a mutex. If a mutex is already locked by - * another thread, the calling thread shall block until - * the mutex becomes available. -**/ -void slock_lock(slock_t *lock) -{ -#ifdef USE_WIN32_THREADS - WaitForSingleObject(lock->lock, INFINITE); -#else - pthread_mutex_lock(&lock->lock); -#endif -} - -/** - * slock_unlock: - * @lock : pointer to mutex object - * - * Unlocks a mutex. - **/ -void slock_unlock(slock_t *lock) -{ -#ifdef USE_WIN32_THREADS - ReleaseMutex(lock->lock); -#else - pthread_mutex_unlock(&lock->lock); -#endif -} - -/** - * scond_new: - * - * Creates and initializes a condition variable. Must - * be manually freed. - * - * Returns: pointer to new condition variable on success, - * otherwise NULL. - **/ -scond_t *scond_new(void) -{ - bool event_created = false; - scond_t *cond = (scond_t*)calloc(1, sizeof(*cond)); - - if (!cond) - return NULL; - -#ifdef USE_WIN32_THREADS - /* this is very complex because recreating condition variable semantics with win32 parts is not easy (or maybe it is and I just havent seen how) */ - /* the main problem is that a condition variable can be used to wake up a thread, but only if the thread is already waiting. */ - /* whereas a win32 event will 'wake up' a thread in advance (the event will be set in advance, so a 'waiter' wont even have to wait on it) */ - cond->event = CreateEvent(NULL, FALSE, FALSE, NULL); - cond->hot_potato = CreateEvent(NULL, FALSE, FALSE, NULL); - cond->waiters = 0; - cond->wakens = 0; - cond->root = NULL; - event_created = !!cond->event; -#else - event_created = (pthread_cond_init(&cond->cond, NULL) == 0); -#endif - - if (!event_created) - goto error; - - return cond; - -error: - free(cond); - return NULL; -} - -/** - * scond_free: - * @cond : pointer to condition variable object - * - * Frees a condition variable. -**/ -void scond_free(scond_t *cond) -{ - if (!cond) - return; - -#ifdef USE_WIN32_THREADS - CloseHandle(cond->event); - CloseHandle(cond->hot_potato); -#else - pthread_cond_destroy(&cond->cond); -#endif - free(cond); -} - -/** - * scond_wait: - * @cond : pointer to condition variable object - * @lock : pointer to mutex object - * - * Block on a condition variable (i.e. wait on a condition). - **/ -void scond_wait(scond_t *cond, slock_t *lock) -{ -#ifdef USE_WIN32_THREADS - - //setup a queue (linked list) of blocked threads - volatile struct ConceptualBlock myblock; - myblock.next = NULL; - volatile struct ConceptualBlock* volatile * ptr = &cond->root; - while(*ptr != NULL) - ptr = &((*ptr)->next); - *ptr = &myblock; - - //now the conceptual lock release and condition block are supposed to be atomic. - //we can't do that in windows, but we can simulate the effects by using the queue, by the following analysis: - //What happens if they aren't atomic? - //1. a signaller can rush in and signal, expecting a waiter to get it; but the waiter wouldn't, because he isn't blocked yet - //solution: win32 events make this easy. the event will sit there enabled - //2. a signaller can rush in and signal, and then turn right around and wait - //solution: the signaller will get queued behind the waiter, who's enqueued before he releases the mutex - - for(;;) - { - bool myturn = (cond->root == &myblock); - - if(!myturn) - { - //well, someone else needs to get to go, maybe it's their turn - //NOTE: this depends on good fair behavour of thread blocking in the OS. I think it's OK - SetEvent(cond->hot_potato); - } - - ReleaseMutex(lock->lock); - - if(myturn) - { - WaitForSingleObject(cond->event, INFINITE); - break; - } - else - { - WaitForSingleObject(cond->hot_potato, INFINITE); - - //re-acquire mutex just for interrogating the queue - WaitForSingleObject(lock->lock, INFINITE); - } - } - - //re-acquire mutex - WaitForSingleObject(lock->lock, INFINITE); - - //remove ourselves from the queue - cond->root = myblock.next; - - //if we have any more wakening to do, chain it here - cond->wakens--; - if(cond->wakens>0) - SetEvent(cond->event); - - cond->waiters--; - - //always leave this set. because--TBD: explain later - SetEvent(cond->hot_potato); - -#else - pthread_cond_wait(&cond->cond, &lock->lock); -#endif -} - -/** - * scond_broadcast: - * @cond : pointer to condition variable object - * - * Broadcast a condition. Unblocks all threads currently blocked - * on the specified condition variable @cond. - **/ -int scond_broadcast(scond_t *cond) -{ -#ifdef USE_WIN32_THREADS - - /* remember: we currently have mutex */ - if(cond->root == NULL) return 0; - - //awaken everything which is currently queued up - if(cond->wakens == 0) SetEvent(cond->event); - cond->wakens = cond->waiters; - - return 0; -#else - return pthread_cond_broadcast(&cond->cond); -#endif -} - -/** - * scond_signal: - * @cond : pointer to condition variable object - * - * Signal a condition. Unblocks at least one of the threads currently blocked - * on the specified condition variable @cond. - **/ -void scond_signal(scond_t *cond) -{ -#ifdef USE_WIN32_THREADS - - /* remember: we currently have mutex */ - if(cond->root == NULL) return; - - //wake up the next thing in the queue - if(cond->wakens == 0) SetEvent(cond->event); - cond->wakens++; - -#else - pthread_cond_signal(&cond->cond); -#endif -} - -/** - * scond_wait_timeout: - * @cond : pointer to condition variable object - * @lock : pointer to mutex object - * @timeout_us : timeout (in microseconds) - * - * Try to block on a condition variable (i.e. wait on a condition) until - * @timeout_us elapses. - * - * Returns: false (0) if timeout elapses before condition variable is - * signaled or broadcast, otherwise true (1). - **/ -bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us) -{ -#ifdef USE_WIN32_THREADS - DWORD ret; - - WaitForSingleObject(cond->event, 0); - ret = SignalObjectAndWait(lock->lock, cond->event, - (DWORD)(timeout_us) / 1000, FALSE); - - slock_lock(lock); - return ret == WAIT_OBJECT_0; -#else - int ret; - int64_t seconds, remainder; - struct timespec now = {0}; - -#ifdef __MACH__ - /* OSX doesn't have clock_gettime. */ - clock_serv_t cclock; - mach_timespec_t mts; - - host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); - clock_get_time(cclock, &mts); - mach_port_deallocate(mach_task_self(), cclock); - now.tv_sec = mts.tv_sec; - now.tv_nsec = mts.tv_nsec; -#elif defined(__CELLOS_LV2__) - sys_time_sec_t s; - sys_time_nsec_t n; - - sys_time_get_current_time(&s, &n); - now.tv_sec = s; - now.tv_nsec = n; -#elif defined(__mips__) - struct timeval tm; - - gettimeofday(&tm, NULL); - now.tv_sec = tm.tv_sec; - now.tv_nsec = tm.tv_usec * 1000; -#elif defined(RETRO_WIN32_USE_PTHREADS) - _ftime64_s(&now); -#elif !defined(GEKKO) - /* timeout on libogc is duration, not end time. */ - clock_gettime(CLOCK_REALTIME, &now); -#endif - - seconds = timeout_us / INT64_C(1000000); - remainder = timeout_us % INT64_C(1000000); - - now.tv_sec += seconds; - now.tv_nsec += remainder * INT64_C(1000); - - ret = pthread_cond_timedwait(&cond->cond, &lock->lock, &now); - return (ret == 0); -#endif +/* Copyright (C) 2010-2016 The RetroArch team + * + * --------------------------------------------------------------------------------------- + * The following license statement only applies to this file (rthreads.c). + * --------------------------------------------------------------------------------------- + * + * Permission is hereby granted, free of charge, + * to any person obtaining a copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifdef __unix__ +#define _POSIX_C_SOURCE 199309 +#endif + +#include + +#include +#include + +/* with RETRO_WIN32_USE_PTHREADS, pthreads can be used even on win32. Maybe only supported in MSVC>=2005 */ + +#if defined(_WIN32) && !defined(RETRO_WIN32_USE_PTHREADS) +#define USE_WIN32_THREADS +#ifdef _XBOX +#include +#else +#define WIN32_LEAN_AND_MEAN +#include +#endif +#elif defined(GEKKO) +#include "gx_pthread.h" +#elif defined(PSP) +#include "psp_pthread.h" +#else +#include +#include +#endif + +#ifdef __MACH__ +#include +#include +#endif + +struct thread_data +{ + void (*func)(void*); + void *userdata; +}; + +struct sthread +{ +#ifdef USE_WIN32_THREADS + HANDLE thread; +#else + pthread_t id; +#endif +}; + +struct slock +{ +#ifdef USE_WIN32_THREADS + HANDLE lock; +#else + pthread_mutex_t lock; +#endif +}; + +struct scond +{ +#ifdef USE_WIN32_THREADS + + /* The syntax we'll use is mind-bending unless we use a struct. Plus, we might want to store more info later */ + /* This will be used as a linked list immplementing a queue of waiting threads */ + struct QueueEntry + { + struct QueueEntry* next; + }; + + /* With this implementation of scond, we don't have any way of waking (or even identifying) specific threads */ + /* But we need to wake them in the order indicated by the queue. */ + /* This potato token will get get passed around every waiter. The bearer can test whether he's next, and hold onto the potato if he is. */ + /* When he's done he can then put it back into play to progress the queue further */ + HANDLE hot_potato; + + /* The primary signalled event. Hot potatoes are passed until this is set. */ + HANDLE event; + + /* the head of the queue; NULL if queue is empty */ + struct QueueEntry* head; + + /* equivalent to the queue length */ + int waiters; + + /* how many waiters in the queue have been conceptually wakened by signals (even if we haven't managed to actually wake them yet */ + int wakens; + +#else + pthread_cond_t cond; +#endif +}; + +#ifdef USE_WIN32_THREADS +static DWORD CALLBACK thread_wrap(void *data_) +#else +static void *thread_wrap(void *data_) +#endif +{ + struct thread_data *data = (struct thread_data*)data_; + if (!data) + return 0; + data->func(data->userdata); + free(data); + return 0; +} + +/** + * sthread_create: + * @start_routine : thread entry callback function + * @userdata : pointer to userdata that will be made + * available in thread entry callback function + * + * Create a new thread. + * + * Returns: pointer to new thread if successful, otherwise NULL. + */ +sthread_t *sthread_create(void (*thread_func)(void*), void *userdata) +{ + bool thread_created = false; + struct thread_data *data = NULL; + sthread_t *thread = (sthread_t*)calloc(1, sizeof(*thread)); + + if (!thread) + return NULL; + + data = (struct thread_data*)calloc(1, sizeof(*data)); + if (!data) + goto error; + + data->func = thread_func; + data->userdata = userdata; + +#ifdef USE_WIN32_THREADS + thread->thread = CreateThread(NULL, 0, thread_wrap, data, 0, NULL); + thread_created = !!thread->thread; +#else + thread_created = pthread_create(&thread->id, NULL, thread_wrap, data) == 0; +#endif + + if (!thread_created) + goto error; + + return thread; + +error: + if (data) + free(data); + free(thread); + return NULL; +} + +/** + * sthread_detach: + * @thread : pointer to thread object + * + * Detach a thread. When a detached thread terminates, its + * resource sare automatically released back to the system + * without the need for another thread to join with the + * terminated thread. + * + * Returns: 0 on success, otherwise it returns a non-zero error number. + */ +int sthread_detach(sthread_t *thread) +{ +#ifdef USE_WIN32_THREADS + CloseHandle(thread->thread); + free(thread); + return 0; +#else + return pthread_detach(thread->id); +#endif +} + +/** + * sthread_join: + * @thread : pointer to thread object + * + * Join with a terminated thread. Waits for the thread specified by + * @thread to terminate. If that thread has already terminated, then + * it will return immediately. The thread specified by @thread must + * be joinable. + * + * Returns: 0 on success, otherwise it returns a non-zero error number. + */ +void sthread_join(sthread_t *thread) +{ +#ifdef USE_WIN32_THREADS + WaitForSingleObject(thread->thread, INFINITE); + CloseHandle(thread->thread); +#else + pthread_join(thread->id, NULL); +#endif + free(thread); +} + +/** + * sthread_isself: + * @thread : pointer to thread object + * + * Join with a terminated thread. Waits for the thread specified by + * @thread to terminate. If that thread has already terminated, then + * it will return immediately. The thread specified by @thread must + * be joinable. + * + * Returns: true (1) if calling thread is the specified thread + */ +bool sthread_isself(sthread_t *thread) +{ +#ifdef USE_WIN32_THREADS + return GetCurrentThread() == thread->thread; +#else + return pthread_equal(pthread_self(),thread->id); +#endif +} + +/** + * slock_new: + * + * Create and initialize a new mutex. Must be manually + * freed. + * + * Returns: pointer to a new mutex if successful, otherwise NULL. + **/ +slock_t *slock_new(void) +{ + bool mutex_created = false; + slock_t *lock = (slock_t*)calloc(1, sizeof(*lock)); + if (!lock) + return NULL; + +#ifdef USE_WIN32_THREADS + lock->lock = CreateMutex(NULL, FALSE, NULL); + mutex_created = !!lock->lock; +#else + mutex_created = (pthread_mutex_init(&lock->lock, NULL) == 0); +#endif + + if (!mutex_created) + goto error; + + return lock; + +error: + free(lock); + return NULL; +} + +/** + * slock_free: + * @lock : pointer to mutex object + * + * Frees a mutex. + **/ +void slock_free(slock_t *lock) +{ + if (!lock) + return; + +#ifdef USE_WIN32_THREADS + CloseHandle(lock->lock); +#else + pthread_mutex_destroy(&lock->lock); +#endif + free(lock); +} + +/** + * slock_lock: + * @lock : pointer to mutex object + * + * Locks a mutex. If a mutex is already locked by + * another thread, the calling thread shall block until + * the mutex becomes available. +**/ +void slock_lock(slock_t *lock) +{ +#ifdef USE_WIN32_THREADS + WaitForSingleObject(lock->lock, INFINITE); +#else + pthread_mutex_lock(&lock->lock); +#endif +} + +/** + * slock_unlock: + * @lock : pointer to mutex object + * + * Unlocks a mutex. + **/ +void slock_unlock(slock_t *lock) +{ +#ifdef USE_WIN32_THREADS + ReleaseMutex(lock->lock); +#else + pthread_mutex_unlock(&lock->lock); +#endif +} + +/** + * scond_new: + * + * Creates and initializes a condition variable. Must + * be manually freed. + * + * Returns: pointer to new condition variable on success, + * otherwise NULL. + **/ +scond_t *scond_new(void) +{ + scond_t *cond = (scond_t*)calloc(1, sizeof(*cond)); + + if (!cond) + return NULL; + +#ifdef USE_WIN32_THREADS + /* This is very complex because recreating condition variable semantics with win32 parts is not easy */ + /* The main problem is that a condition variable can be used to wake up a thread, but only if the thread is already waiting; */ + /* whereas a win32 event will 'wake up' a thread in advance (the event will be set in advance, so a 'waiter' wont even have to wait on it) */ + /* So at the very least, we need to do something clever. But there's bigger problems. */ + /* We don't even have a straightforward way in win32 to satisfy pthread_cond_wait's atomicity requirement. The bulk of this algorithm is solving that. */ + /* Note: We might could simplify this using vista+ condition variables, but we wanted an XP compatible solution. */ + cond->event = CreateEvent(NULL, FALSE, FALSE, NULL); + if(!cond->event) goto error; + cond->hot_potato = CreateEvent(NULL, FALSE, FALSE, NULL); + if(!cond->hot_potato) + { + CloseHandle(cond->event); + goto error; + } + cond->waiters = cond->wakens = 0; + cond->head = NULL; + +#else + if(pthread_cond_init(&cond->cond, NULL) != 0) + goto error; +#endif + + return cond; + +error: + free(cond); + return NULL; +} + +/** + * scond_free: + * @cond : pointer to condition variable object + * + * Frees a condition variable. +**/ +void scond_free(scond_t *cond) +{ + if (!cond) + return; + +#ifdef USE_WIN32_THREADS + CloseHandle(cond->event); + CloseHandle(cond->hot_potato); +#else + pthread_cond_destroy(&cond->cond); +#endif + free(cond); +} + +/** + * scond_wait: + * @cond : pointer to condition variable object + * @lock : pointer to mutex object + * + * Block on a condition variable (i.e. wait on a condition). + **/ +void scond_wait(scond_t *cond, slock_t *lock) +{ +#ifdef USE_WIN32_THREADS + + /* add ourselves to a queue of waiting threads */ + struct QueueEntry myentry; + myentry.next = NULL; + struct QueueEntry** ptr = &cond->head; + while(*ptr) /* walk to the end of the linked list */ + ptr = &((*ptr)->next); + *ptr = &myentry; + + cond->waiters++; + + /* now the conceptual lock release and condition block are supposed to be atomic. */ + /* we can't do that in windows, but we can simulate the effects by using the queue, by the following analysis: */ + /* What happens if they aren't atomic? */ + /* 1. a signaller can rush in and signal, expecting a waiter to get it; but the waiter wouldn't, because he isn't blocked yet */ + /* solution: win32 events make this easy. the event will sit there enabled */ + /* 2. a signaller can rush in and signal, and then turn right around and wait */ + /* solution: the signaller will get queued behind the waiter, who's enqueued before he releases the mutex */ + + for(;;) + { + /* We're always in the mutex here*/ + + /* It's my turn if I'm the head of the queue */ + bool myturn = (cond->head == &myentry); + + if(!myturn) + { + /* As long as someone is even going to be able to wake up when they receive the potato, keep it going round */ + if(cond->wakens>0) + SetEvent(cond->hot_potato); + } + + /* If it's my turn, I hold the potato */ + + ReleaseMutex(lock->lock); + + if(myturn) + { + WaitForSingleObject(cond->event, INFINITE); + break; + } + else + { + /* Wait to catch the hot potato before checking for my turn again */ + WaitForSingleObject(cond->hot_potato, INFINITE); + + /* Re-acquire the mutex just for interrogating the queue */ + WaitForSingleObject(lock->lock, INFINITE); + } + } + + /* Reacquire the main mutex */ + WaitForSingleObject(lock->lock, INFINITE); + + /* Remove ourselves from the queue */ + cond->head = myentry.next; + cond->waiters--; + + /* If any other wakenings are pending, go ahead and set it up */ + /* There may actually be no waiters. That's OK. The first waiter will come in, find it's his turn, and immediately get the signaled event */ + cond->wakens--; + if(cond->wakens>0) + { + SetEvent(cond->event); + + /* Progress the queue: Put the hot potato back into play. It'll be tossed around until next in line gets it */ + SetEvent(cond->hot_potato); + } + +#else + pthread_cond_wait(&cond->cond, &lock->lock); +#endif +} + +/** + * scond_broadcast: + * @cond : pointer to condition variable object + * + * Broadcast a condition. Unblocks all threads currently blocked + * on the specified condition variable @cond. + **/ +int scond_broadcast(scond_t *cond) +{ +#ifdef USE_WIN32_THREADS + + /* remember: we currently have mutex */ + if(cond->waiters == 0) return 0; + + /* awaken everything which is currently queued up */ + if(cond->wakens == 0) SetEvent(cond->event); + cond->wakens = cond->waiters; + + /* Since there is now at least one pending waken, the potato must be in play */ + SetEvent(cond->hot_potato); + + return 0; +#else + return pthread_cond_broadcast(&cond->cond); +#endif +} + +/** + * scond_signal: + * @cond : pointer to condition variable object + * + * Signal a condition. Unblocks at least one of the threads currently blocked + * on the specified condition variable @cond. + **/ +void scond_signal(scond_t *cond) +{ +#ifdef USE_WIN32_THREADS + + /* remember: we currently have mutex */ + if(cond->waiters == 0) return; + + /* wake up the next thing in the queue */ + if(cond->wakens == 0) SetEvent(cond->event); + cond->wakens++; + + /* Since there is now at least one pending waken, the potato must be in play */ + SetEvent(cond->hot_potato); + +#else + pthread_cond_signal(&cond->cond); +#endif +} + +/** + * scond_wait_timeout: + * @cond : pointer to condition variable object + * @lock : pointer to mutex object + * @timeout_us : timeout (in microseconds) + * + * Try to block on a condition variable (i.e. wait on a condition) until + * @timeout_us elapses. + * + * Returns: false (0) if timeout elapses before condition variable is + * signaled or broadcast, otherwise true (1). + **/ +bool scond_wait_timeout(scond_t *cond, slock_t *lock, int64_t timeout_us) +{ +#ifdef USE_WIN32_THREADS + DWORD ret; + + /* TODO: this is woefully inadequate. It needs to be solved with the newer approach used above */ + WaitForSingleObject(cond->event, 0); + ret = SignalObjectAndWait(lock->lock, cond->event, + (DWORD)(timeout_us) / 1000, FALSE); + + slock_lock(lock); + return ret == WAIT_OBJECT_0; +#else + int ret; + int64_t seconds, remainder; + struct timespec now = {0}; + +#ifdef __MACH__ + /* OSX doesn't have clock_gettime. */ + clock_serv_t cclock; + mach_timespec_t mts; + + host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock); + clock_get_time(cclock, &mts); + mach_port_deallocate(mach_task_self(), cclock); + now.tv_sec = mts.tv_sec; + now.tv_nsec = mts.tv_nsec; +#elif defined(__CELLOS_LV2__) + sys_time_sec_t s; + sys_time_nsec_t n; + + sys_time_get_current_time(&s, &n); + now.tv_sec = s; + now.tv_nsec = n; +#elif defined(__mips__) + struct timeval tm; + + gettimeofday(&tm, NULL); + now.tv_sec = tm.tv_sec; + now.tv_nsec = tm.tv_usec * 1000; +#elif defined(RETRO_WIN32_USE_PTHREADS) + _ftime64_s(&now); +#elif !defined(GEKKO) + /* timeout on libogc is duration, not end time. */ + clock_gettime(CLOCK_REALTIME, &now); +#endif + + seconds = timeout_us / INT64_C(1000000); + remainder = timeout_us % INT64_C(1000000); + + now.tv_sec += seconds; + now.tv_nsec += remainder * INT64_C(1000); + + ret = pthread_cond_timedwait(&cond->cond, &lock->lock, &now); + return (ret == 0); +#endif } \ No newline at end of file