diff options
Diffstat (limited to 'src/libs/threads')
-rw-r--r-- | src/libs/threads/Makeinfo | 11 | ||||
-rw-r--r-- | src/libs/threads/pthread/Makeinfo | 2 | ||||
-rw-r--r-- | src/libs/threads/pthread/posixthreads.c | 672 | ||||
-rw-r--r-- | src/libs/threads/pthread/posixthreads.h | 103 | ||||
-rw-r--r-- | src/libs/threads/sdl/Makeinfo | 2 | ||||
-rw-r--r-- | src/libs/threads/sdl/sdlthreads.c | 706 | ||||
-rw-r--r-- | src/libs/threads/sdl/sdlthreads.h | 106 | ||||
-rw-r--r-- | src/libs/threads/thrcommon.c | 451 | ||||
-rw-r--r-- | src/libs/threads/thrcommon.h | 28 |
9 files changed, 2081 insertions, 0 deletions
diff --git a/src/libs/threads/Makeinfo b/src/libs/threads/Makeinfo new file mode 100644 index 0000000..57f56a8 --- /dev/null +++ b/src/libs/threads/Makeinfo @@ -0,0 +1,11 @@ +case "$uqm_THREADLIB" in + SDL) + uqm_SUBDIRS="sdl" + ;; + PTHREAD) + uqm_SUBDIRS="pthread" + ;; +esac + +uqm_CFILES="thrcommon.c" +uqm_HFILES="thrcommon.h" diff --git a/src/libs/threads/pthread/Makeinfo b/src/libs/threads/pthread/Makeinfo new file mode 100644 index 0000000..251db7b --- /dev/null +++ b/src/libs/threads/pthread/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="posixthreads.c" +uqm_HFILES="posixthreads.h" diff --git a/src/libs/threads/pthread/posixthreads.c b/src/libs/threads/pthread/posixthreads.c new file mode 100644 index 0000000..4f0d2e8 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.c @@ -0,0 +1,672 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include "libs/misc.h" +#include "libs/memlib.h" +#include "posixthreads.h" +#include <pthread.h> +#include <unistd.h> + +#include <semaphore.h> + +#include "libs/log/uqmlog.h" + +typedef struct _thread { + pthread_t native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static pthread_mutex_t threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + sem_t sem; + TrueThread thread; +}; + +void +InitThreadSystem_PT (void) +{ + pthread_mutex_init (&threadQueueMutex, NULL); +} + +void +UnInitThreadSystem_PT (void) +{ + pthread_mutex_destroy (&threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + pthread_mutex_lock (&threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + pthread_mutex_unlock (&threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + pthread_mutex_unlock (&threadQueueMutex); +} + +static TrueThread +FindThreadInfo (pthread_t threadID) +{ + TrueThread ptr; + + pthread_mutex_lock (&threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (ptr->native == threadID) + { + pthread_mutex_unlock (&threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + pthread_mutex_unlock (&threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static void * +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + sem_t *sem; + TrueThread thread; + int result; + + //log_add (log_Debug, "ThreadHelper()"); + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = &((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + if (sem_wait (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_wait fail"); + exit(EXIT_FAILURE); + } + if (sem_destroy (sem)) + { + log_add(log_Fatal, "ThreadHelper sem_destroy fail"); + exit(EXIT_FAILURE); + } + + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return (void*)result; +} + +void +DestroyThread_PT (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + pthread_attr_t attr; + + + //log_add (log_Debug, "CreateThread_PT '%s'", name); + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + if (sem_init(&startInfo->sem, 0, 0) < 0) + { + log_add (log_Fatal, "createthread seminit fail"); + exit(EXIT_FAILURE); + } + startInfo->thread = thread; + + pthread_attr_init(&attr); + if (pthread_attr_setstacksize(&attr, 75000)) + { + log_add (log_Debug, "pthread stacksize fail"); + } + if (pthread_create(&thread->native, &attr, ThreadHelper, (void *)startInfo)) + { + log_add (log_Debug, "pthread create fail"); + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +//#if 0 + log_add (log_Debug, "Thread '%s' created.", thread->name); + fflush (stderr); +//#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + if (sem_post (&startInfo->sem)) + { + log_add(log_Fatal, "CreateThread sem_post fail"); + exit(EXIT_FAILURE); + } + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_PT (TimeCount sleepTime) +{ + usleep (sleepTime * 1000000 / ONE_SECOND); +} + +void +SleepThreadUntil_PT (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_PT (); + else + usleep ((wakeTime - now) * 1000000 / ONE_SECOND); +} + +void +TaskSwitch_PT (void) { + usleep (1000); +} + +void +WaitThread_PT (Thread thread, int *status) { + //log_add(log_Debug, "WaitThread_PT '%s', status %x", ((TrueThread)thread)->name, status); + //pthread_join(((TrueThread)thread)->native, status); + pthread_join(((TrueThread)thread)->native, NULL); + //log_add(log_Debug, "WaitThread_PT '%s' complete", ((TrueThread)thread)->name); +} + +ThreadLocal * +GetMyThreadLocal_PT (void) +{ + TrueThread t = FindThreadInfo (pthread_self()); + return t ? t->localData : NULL; +} + +/* These are the pthread implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + pthread_mutex_t mutex; +#ifdef TRACK_CONTENTION + pthread_t owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_PT (const char *name, DWORD syncClass) +#else +CreateMutex_PT (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + + if (mutex != NULL) + { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + if (pthread_mutex_init(&mutex->mutex, &attr)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + pthread_mutexattr_destroy(&attr); + +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + return mutex; +} + +void +DestroyMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; + //log_add_nothread(log_Debug, "Destroying mutex '%s'", mutex->name); + pthread_mutex_destroy (&mutex->mutex); + free (mutex); +} + +void +LockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + + while (pthread_mutex_lock (&mutex->mutex) != 0) + { + //log_add_nothread (log_Debug, "Attempt to acquire mutex '%s' failretry", mutex->name); + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + mutex->owner = pthread_self(); +#endif +} + +void +UnlockMutex_PT (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (pthread_mutex_unlock (&mutex->mutex) != 0) + { + TaskSwitch_PT (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + sem_t sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_PT (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + + //log_add (log_Debug, "Creating semaphore '%s'", sem->name); + + if (sem_init(&sem->sem, 0, initial) < 0) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + //log_add (log_Debug, "Creating semaphore '%s' success", sem->name); + return sem; +} + +void +DestroySemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Destroying semaphore '%s'", sem->name); + if (sem_destroy (&sem->sem)) + { + log_add (log_Debug, "Destroying semaphore '%s' failed", sem->name); + } + HFree (sem); +} + +void +SetSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + int contention = 0; + sem_getvalue(&sem->sem, &contention); + contention = !contention; + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + //log_add (log_Debug, "Attempt to set semaphore '%s'", sem->name); + while (sem_wait (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to set semaphore '%s' failretry", sem->name); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to set semaphore '%s' success", sem->name); +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_PT (Semaphore s) +{ + Sem *sem = (Sem *)s; + //log_add (log_Debug, "Attempt to clear semaphore '%s' %x", sem->name, sem); + while (sem_post (&sem->sem) == -1) + { + //log_add (log_Debug, "Attempt to clear semaphore %x failretry", sem); + TaskSwitch_PT (); + } + //log_add (log_Debug, "Attempt to clear semaphore %x success", sem); +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + pthread_mutex_t mutex; + pthread_t thread_id; + unsigned int locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_PT (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_PT (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + if (pthread_mutex_init(&mtx->mutex, NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_mutex_destroy(&mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (pthread_mutex_lock (&mtx->mutex)) + TaskSwitch_PT (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + pthread_t thread_id = pthread_self(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + pthread_mutex_unlock (&mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_PT (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + pthread_cond_t cond; + pthread_mutex_t mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_PT (const char *name, DWORD syncClass) +#else +CreateCondVar_PT (void) +#endif +{ + int err1, err2; + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + err1 = pthread_cond_init(&cv->cond, NULL); + err2 = pthread_mutex_init(&cv->mutex, NULL); + if (err1 || err2) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_destroy(&cv->cond); + pthread_mutex_destroy(&cv->mutex); + HFree (cv); +} + +void +WaitCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_mutex_lock (&cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (pthread_cond_wait (&cv->cond, &cv->mutex) != 0) + { + TaskSwitch_PT (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + pthread_mutex_unlock (&cv->mutex); +} + +void +SignalCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_signal(&cv->cond); +} + +void +BroadcastCondVar_PT (CondVar c) +{ + cvar *cv = (cvar *) c; + pthread_cond_broadcast(&cv->cond); +} diff --git a/src/libs/threads/pthread/posixthreads.h b/src/libs/threads/pthread/posixthreads.h new file mode 100644 index 0000000..9945b53 --- /dev/null +++ b/src/libs/threads/pthread/posixthreads.h @@ -0,0 +1,103 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_THREADS_PTHREAD_POSIXTHREADS_H_ +#define LIBS_THREADS_PTHREAD_POSIXTHREADS_H_ + +#include "port.h" +#include "libs/threadlib.h" + +void InitThreadSystem_PT (void); +void UnInitThreadSystem_PT (void); + +#ifdef NAMED_SYNCHRO +/* Prototypes with the "name" field */ +Thread CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Mutex CreateMutex_PT (const char *name, DWORD syncClass); +Semaphore CreateSemaphore_PT (DWORD initial, const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_PT (const char *name, DWORD syncClass); +CondVar CreateCondVar_PT (const char *name, DWORD syncClass); +#else +/* Prototypes without the "name" field. */ +Thread CreateThread_PT (ThreadFunction func, void *data, SDWORD stackSize); +Mutex CreateMutex_PT (void); +Semaphore CreateSemaphore_PT (DWORD initial); +RecursiveMutex CreateRecursiveMutex_PT (void); +CondVar CreateCondVar_PT (void); +#endif + +ThreadLocal *GetMyThreadLocal_PT (void); + +void SleepThread_PT (TimeCount sleepTime); +void SleepThreadUntil_PT (TimeCount wakeTime); +void TaskSwitch_PT (void); +void WaitThread_PT (Thread thread, int *status); +void DestroyThread_PT (Thread thread); + +void DestroyMutex_PT (Mutex m); +void LockMutex_PT (Mutex m); +void UnlockMutex_PT (Mutex m); + +void DestroySemaphore_PT (Semaphore sem); +void SetSemaphore_PT (Semaphore sem); +void ClearSemaphore_PT (Semaphore sem); + +void DestroyCondVar_PT (CondVar c); +void WaitCondVar_PT (CondVar c); +void SignalCondVar_PT (CondVar c); +void BroadcastCondVar_PT (CondVar c); + +void DestroyRecursiveMutex_PT (RecursiveMutex m); +void LockRecursiveMutex_PT (RecursiveMutex m); +void UnlockRecursiveMutex_PT (RecursiveMutex m); +int GetRecursiveMutexDepth_PT (RecursiveMutex m); + +#define NativeInitThreadSystem InitThreadSystem_PT +#define NativeUnInitThreadSystem UnInitThreadSystem_PT + +#define NativeGetMyThreadLocal GetMyThreadLocal_PT + +#define NativeCreateThread CreateThread_PT +#define NativeSleepThread SleepThread_PT +#define NativeSleepThreadUntil SleepThreadUntil_PT +#define NativeTaskSwitch TaskSwitch_PT +#define NativeWaitThread WaitThread_PT +#define NativeDestroyThread DestroyThread_PT + +#define NativeCreateMutex CreateMutex_PT +#define NativeDestroyMutex DestroyMutex_PT +#define NativeLockMutex LockMutex_PT +#define NativeUnlockMutex UnlockMutex_PT + +#define NativeCreateSemaphore CreateSemaphore_PT +#define NativeDestroySemaphore DestroySemaphore_PT +#define NativeSetSemaphore SetSemaphore_PT +#define NativeClearSemaphore ClearSemaphore_PT + +#define NativeCreateCondVar CreateCondVar_PT +#define NativeDestroyCondVar DestroyCondVar_PT +#define NativeWaitCondVar WaitCondVar_PT +#define NativeSignalCondVar SignalCondVar_PT +#define NativeBroadcastCondVar BroadcastCondVar_PT + +#define NativeCreateRecursiveMutex CreateRecursiveMutex_PT +#define NativeDestroyRecursiveMutex DestroyRecursiveMutex_PT +#define NativeLockRecursiveMutex LockRecursiveMutex_PT +#define NativeUnlockRecursiveMutex UnlockRecursiveMutex_PT +#define NativeGetRecursiveMutexDepth GetRecursiveMutexDepth_PT + +#endif /* _PTTHREAD_H */ + diff --git a/src/libs/threads/sdl/Makeinfo b/src/libs/threads/sdl/Makeinfo new file mode 100644 index 0000000..7a7ad76 --- /dev/null +++ b/src/libs/threads/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="sdlthreads.c" +uqm_HFILES="sdlthreads.h" diff --git a/src/libs/threads/sdl/sdlthreads.c b/src/libs/threads/sdl/sdlthreads.c new file mode 100644 index 0000000..118951d --- /dev/null +++ b/src/libs/threads/sdl/sdlthreads.c @@ -0,0 +1,706 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdlib.h> +#include "libs/misc.h" +#include "libs/memlib.h" +#include "sdlthreads.h" +#ifdef PROFILE_THREADS +#include <signal.h> +#include <unistd.h> +#endif +#include "libs/log.h" + +#if defined(PROFILE_THREADS) && !defined(WIN32) +#include <sys/time.h> +#include <sys/resource.h> +#endif + +#if SDL_MAJOR_VERSION == 1 +typedef Uint32 SDL_threadID; +#endif + +typedef struct _thread { + void *native; +#ifdef NAMED_SYNCHRO + const char *name; +#endif +#ifdef PROFILE_THREADS + int startTime; +#endif /* PROFILE_THREADS */ + ThreadLocal *localData; + struct _thread *next; +} *TrueThread; + +static volatile TrueThread threadQueue = NULL; +static SDL_mutex *threadQueueMutex; + +struct ThreadStartInfo +{ + ThreadFunction func; + void *data; + SDL_sem *sem; + TrueThread thread; +}; + +#ifdef PROFILE_THREADS +static void +SigUSR1Handler (int signr) { + if (getpgrp () != getpid ()) + { + // Only act for the main process + return; + } + PrintThreadsStats (); + // It's not a good idea in general to do many things in a signal + // handler, (and especially the locking) but I guess it will + // have to do for now (and it's only for debugging). + (void) signr; /* Satisfying compiler (unused parameter) */ +} + +static void +LocalStats (SDL_Thread *thread) { +#if defined (WIN32) || !defined(SDL_PTHREADS) + fprintf (stderr, "Thread ID %08lx\n", (Uint64)SDL_GetThreadID (thread)); +#else /* !defined (WIN32) && defined(SDL_PTHREADS) */ + // This only works if SDL implements threads as processes + pid_t pid; + struct rusage ru; + long seconds; + + pid = (pid_t) SDL_GetThreadID (thread); + fprintf (stderr, "Pid %d\n", (int) pid); + getrusage(RUSAGE_SELF, &ru); + seconds = ru.ru_utime.tv_sec + ru.ru_utime.tv_sec; + fprintf (stderr, "Used %ld.%ld minutes of processor time.\n", + seconds / 60, seconds % 60); +#endif /* defined (WIN32) && defined(SDL_PTHREADS) */ +} + +void +PrintThreadsStats_SDL (void) +{ + TrueThread ptr; + int now; + + now = GetTimeCounter (); + SDL_mutexP (threadQueueMutex); + fprintf(stderr, "--- Active threads ---\n"); + for (ptr = threadQueue; ptr != NULL; ptr = ptr->next) { + fprintf (stderr, "Thread named '%s'.\n", ptr->name); + fprintf (stderr, "Started %d.%d minutes ago.\n", + (now - ptr->startTime) / 60000, + ((now - ptr->startTime) / 1000) % 60); + LocalStats (ptr->native); + if (ptr->next != NULL) + fprintf(stderr, "\n"); + } + SDL_mutexV (threadQueueMutex); + fprintf(stderr, "----------------------\n"); + fflush (stderr); +} +#endif /* PROFILE_THREADS */ + +void +InitThreadSystem_SDL (void) +{ + threadQueueMutex = SDL_CreateMutex (); +#ifdef PROFILE_THREADS + signal(SIGUSR1, SigUSR1Handler); +#endif +} + +void +UnInitThreadSystem_SDL (void) +{ +#ifdef PROFILE_THREADS + signal(SIGUSR1, SIG_DFL); +#endif + SDL_DestroyMutex (threadQueueMutex); +} + +static void +QueueThread (TrueThread thread) +{ + SDL_mutexP (threadQueueMutex); + thread->next = threadQueue; + threadQueue = thread; + SDL_mutexV (threadQueueMutex); +} + +static void +UnQueueThread (TrueThread thread) +{ + volatile TrueThread *ptr; + + SDL_mutexP (threadQueueMutex); + ptr = &threadQueue; + while (*ptr != thread) + { +#ifdef DEBUG_THREADS + if (*ptr == NULL) + { + // Should not happen. + log_add (log_Debug, "Error: Trying to remove non-present thread " + "from thread queue."); + fflush (stderr); + explode (); + } +#endif /* DEBUG_THREADS */ + ptr = &(*ptr)->next; + } + *ptr = (*ptr)->next; + SDL_mutexV (threadQueueMutex); +} + +static TrueThread +FindThreadInfo (SDL_threadID threadID) +{ + TrueThread ptr; + + SDL_mutexP (threadQueueMutex); + ptr = threadQueue; + while (ptr) + { + if (SDL_GetThreadID (ptr->native) == threadID) + { + SDL_mutexV (threadQueueMutex); + return ptr; + } + ptr = ptr->next; + } + SDL_mutexV (threadQueueMutex); + return NULL; +} + +#ifdef NAMED_SYNCHRO +static const char * +MyThreadName (void) +{ + TrueThread t = FindThreadInfo (SDL_ThreadID ()); + return t ? t->name : "Unknown (probably renderer)"; +} +#endif + +static int +ThreadHelper (void *startInfo) { + ThreadFunction func; + void *data; + SDL_sem *sem; + TrueThread thread; + int result; + + func = ((struct ThreadStartInfo *) startInfo)->func; + data = ((struct ThreadStartInfo *) startInfo)->data; + sem = ((struct ThreadStartInfo *) startInfo)->sem; + + // Wait until the Thread structure is available. + SDL_SemWait (sem); + SDL_DestroySemaphore (sem); + thread = ((struct ThreadStartInfo *) startInfo)->thread; + HFree (startInfo); + + result = (*func) (data); + +#ifdef DEBUG_THREADS + log_add (log_Debug, "Thread '%s' done (returned %d).", + thread->name, result); + fflush (stderr); +#endif + + UnQueueThread (thread); + DestroyThreadLocal (thread->localData); + FinishThread (thread); + /* Destroying the thread is the responsibility of ProcessThreadLifecycles() */ + return result; +} + +void +DestroyThread_SDL (Thread t) +{ + HFree (t); +} + +Thread +CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize +#ifdef NAMED_SYNCHRO + , const char *name +#endif + ) +{ + TrueThread thread; + struct ThreadStartInfo *startInfo; + + thread = (struct _thread *) HMalloc (sizeof *thread); +#ifdef NAMED_SYNCHRO + thread->name = name; +#endif +#ifdef PROFILE_THREADS + thread->startTime = GetTimeCounter (); +#endif + + thread->localData = CreateThreadLocal (); + + startInfo = (struct ThreadStartInfo *) HMalloc (sizeof (*startInfo)); + startInfo->func = func; + startInfo->data = data; + startInfo->sem = SDL_CreateSemaphore (0); + startInfo->thread = thread; + +#if SDL_MAJOR_VERSION == 1 + // SDL1 case + thread->native = SDL_CreateThread (ThreadHelper, (void *) startInfo); +#elif defined(NAMED_SYNCHRO) + // SDL2 with UQM-aware named threads case + thread->native = SDL_CreateThread (ThreadHelper, thread->name, (void *) startInfo); +#else + // SDL2 without UQM-aware named threads; use a placeholder for debuggers + thread->native = SDL_CreateThread (ThreadHelper, "UQM worker thread", (void *)startInfo); +#endif + if (!(thread->native)) + { + DestroyThreadLocal (thread->localData); + HFree (startInfo); + HFree (thread); + return NULL; + } + // The responsibility to free 'startInfo' and 'thread' is now by the new + // thread. + + QueueThread (thread); + +#ifdef DEBUG_THREADS +#if 0 + log_add (log_Debug, "Thread '%s' created.", ThreadName (thread)); + fflush (stderr); +#endif +#endif + + // Signal to the new thread that the thread structure is ready + // and it can begin to use it. + SDL_SemPost (startInfo->sem); + + (void) stackSize; /* Satisfying compiler (unused parameter) */ + return thread; +} + +void +SleepThread_SDL (TimeCount sleepTime) +{ + SDL_Delay (sleepTime * 1000 / ONE_SECOND); +} + +void +SleepThreadUntil_SDL (TimeCount wakeTime) { + TimeCount now; + + now = GetTimeCounter (); + if (wakeTime <= now) + TaskSwitch_SDL (); + else + SDL_Delay ((wakeTime - now) * 1000 / ONE_SECOND); +} + +void +TaskSwitch_SDL (void) { + SDL_Delay (1); +} + +void +WaitThread_SDL (Thread thread, int *status) { + SDL_WaitThread (((TrueThread)thread)->native, status); +} + +ThreadLocal * +GetMyThreadLocal_SDL (void) +{ + TrueThread t = FindThreadInfo (SDL_ThreadID ()); + return t ? t->localData : NULL; +} + +/* These are the SDL implementations of the UQM synchronization objects. */ + +/* Mutexes. */ +/* TODO. The w_memlib uses Mutexes right now, so we can't use HMalloc + * or HFree. Once that goes, this needs to change. */ + +typedef struct _mutex { + SDL_mutex *mutex; +#ifdef TRACK_CONTENTION + SDL_threadID owner; +#endif +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Mut; + + +Mutex +#ifdef NAMED_SYNCHRO +CreateMutex_SDL (const char *name, DWORD syncClass) +#else +CreateMutex_SDL (void) +#endif +{ + Mut *mutex = malloc (sizeof (Mut)); + if (mutex != NULL) + { + mutex->mutex = SDL_CreateMutex(); +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif +#ifdef NAMED_SYNCHRO + mutex->name = name; + mutex->syncClass = syncClass; +#endif + } + + if ((mutex == NULL) || (mutex->mutex == NULL)) + { +#ifdef NAMED_SYNCHRO + /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Fatal, "Could not initialize mutex '%s':" + "aborting.", name); +#else + log_add_nothread (log_Fatal, "Could not initialize mutex:" + "aborting."); +#endif + exit (EXIT_FAILURE); + } + + return mutex; +} + +void +DestroyMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; + SDL_DestroyMutex (mutex->mutex); + free (mutex); +} + +void +LockMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + /* This code isn't really quite right; race conditions between + * check and lock remain and can produce reports of contention + * where the thread never sleeps, or fail to report in + * situations where it does. If tracking with perfect + * accuracy becomes important, the TRACK_CONTENTION mutex will + * need to handle its own wake/sleep cycles with condition + * variables (check the history of this file for the + * CrossThreadMutex code). This almost-measure is being added + * because for the most part it should suffice. */ + if (mutex->owner && (mutex->syncClass & TRACK_CONTENTION_CLASSES)) + { /* logging depends on Mutexes, so we have to use the + * non-threaded version instead */ + log_add_nothread (log_Debug, "Thread '%s' blocking on mutex '%s'", + MyThreadName (), mutex->name); + } +#endif + while (SDL_mutexP (mutex->mutex) != 0) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + mutex->owner = SDL_ThreadID (); +#endif +} + +void +UnlockMutex_SDL (Mutex m) +{ + Mut *mutex = (Mut *)m; +#ifdef TRACK_CONTENTION + mutex->owner = 0; +#endif + while (SDL_mutexV (mutex->mutex) != 0) + { + TaskSwitch_SDL (); + } +} + +/* Semaphores. */ + +typedef struct _sem { + SDL_sem *sem; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} Sem; + +Semaphore +CreateSemaphore_SDL (DWORD initial +#ifdef NAMED_SYNCHRO + , const char *name, DWORD syncClass +#endif + ) +{ + Sem *sem = (Sem *) HMalloc (sizeof (struct _sem)); +#ifdef NAMED_SYNCHRO + sem->name = name; + sem->syncClass = syncClass; +#endif + sem->sem = SDL_CreateSemaphore (initial); + if (sem->sem == NULL) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize semaphore '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize semaphore:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } + return sem; +} + +void +DestroySemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; + SDL_DestroySemaphore (sem->sem); + HFree (sem); +} + +void +SetSemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; +#ifdef TRACK_CONTENTION + BOOLEAN contention = !(SDL_SemValue (sem->sem)); + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on semaphore '%s'", + MyThreadName (), sem->name); + } +#endif + while (SDL_SemWait (sem->sem) == -1) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + if (contention && (sem->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' awakens," + " released from semaphore '%s'", MyThreadName (), sem->name); + } +#endif +} + +void +ClearSemaphore_SDL (Semaphore s) +{ + Sem *sem = (Sem *)s; + while (SDL_SemPost (sem->sem) == -1) + { + TaskSwitch_SDL (); + } +} + +/* Recursive mutexes. Adapted from mixSDL code, which was adapted from + the original DCQ code. */ + +typedef struct _recm { + SDL_mutex *mutex; + SDL_threadID thread_id; + Uint32 locks; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} RecM; + +RecursiveMutex +#ifdef NAMED_SYNCHRO +CreateRecursiveMutex_SDL (const char *name, DWORD syncClass) +#else +CreateRecursiveMutex_SDL (void) +#endif +{ + RecM *mtx = (RecM *) HMalloc (sizeof (struct _recm)); + + mtx->thread_id = 0; + mtx->mutex = SDL_CreateMutex (); + if (mtx->mutex == NULL) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize recursive " + "mutex '%s': aborting.", name); +#else + log_add (log_Fatal, "Could not initialize recursive " + "mutex: aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + mtx->name = name; + mtx->syncClass = syncClass; +#endif + mtx->locks = 0; + return (RecursiveMutex) mtx; +} + +void +DestroyRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_DestroyMutex (mtx->mutex); + HFree (mtx); +} + +void +LockRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_threadID thread_id = SDL_ThreadID(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef TRACK_CONTENTION + if (mtx->thread_id && (mtx->syncClass & TRACK_CONTENTION_CLASSES)) + { + log_add (log_Debug, "Thread '%s' blocking on '%s'", + MyThreadName (), mtx->name); + } +#endif + while (SDL_mutexP (mtx->mutex)) + TaskSwitch_SDL (); + mtx->thread_id = thread_id; + } + mtx->locks++; +} + +void +UnlockRecursiveMutex_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + SDL_threadID thread_id = SDL_ThreadID(); + if (!mtx->locks || mtx->thread_id != thread_id) + { +#ifdef NAMED_SYNCHRO + log_add (log_Debug, "'%s' attempted to unlock %s when it " + "didn't hold it", MyThreadName (), mtx->name); +#endif + } + else + { + mtx->locks--; + if (!mtx->locks) + { + mtx->thread_id = 0; + SDL_mutexV (mtx->mutex); + } + } +} + +int +GetRecursiveMutexDepth_SDL (RecursiveMutex val) +{ + RecM *mtx = (RecM *)val; + return mtx->locks; +} + +typedef struct _cond { + SDL_cond *cond; + SDL_mutex *mutex; +#ifdef NAMED_SYNCHRO + const char *name; + DWORD syncClass; +#endif +} cvar; + +CondVar +#ifdef NAMED_SYNCHRO +CreateCondVar_SDL (const char *name, DWORD syncClass) +#else +CreateCondVar_SDL (void) +#endif +{ + cvar *cv = (cvar *) HMalloc (sizeof (cvar)); + cv->cond = SDL_CreateCond (); + cv->mutex = SDL_CreateMutex (); + if ((cv->cond == NULL) || (cv->mutex == NULL)) + { +#ifdef NAMED_SYNCHRO + log_add (log_Fatal, "Could not initialize condition variable '%s':" + " aborting.", name); +#else + log_add (log_Fatal, "Could not initialize condition variable:" + " aborting."); +#endif + exit (EXIT_FAILURE); + } +#ifdef NAMED_SYNCHRO + cv->name = name; + cv->syncClass = syncClass; +#endif + return cv; +} + +void +DestroyCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_DestroyCond (cv->cond); + SDL_DestroyMutex (cv->mutex); + HFree (cv); +} + +void +WaitCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_mutexP (cv->mutex); +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' waiting for signal from '%s'", + MyThreadName (), cv->name); + } +#endif + while (SDL_CondWait (cv->cond, cv->mutex) != 0) + { + TaskSwitch_SDL (); + } +#ifdef TRACK_CONTENTION + if (cv->syncClass & TRACK_CONTENTION_CLASSES) + { + log_add (log_Debug, "Thread '%s' received signal from '%s'," + " awakening.", MyThreadName (), cv->name); + } +#endif + SDL_mutexV (cv->mutex); +} + +void +SignalCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_CondSignal (cv->cond); +} + +void +BroadcastCondVar_SDL (CondVar c) +{ + cvar *cv = (cvar *) c; + SDL_CondBroadcast (cv->cond); +} diff --git a/src/libs/threads/sdl/sdlthreads.h b/src/libs/threads/sdl/sdlthreads.h new file mode 100644 index 0000000..c93c947 --- /dev/null +++ b/src/libs/threads/sdl/sdlthreads.h @@ -0,0 +1,106 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_THREADS_SDL_SDLTHREADS_H_ +#define LIBS_THREADS_SDL_SDLTHREADS_H_ + +#include "port.h" +#include SDL_INCLUDE(SDL.h) +#include SDL_INCLUDE(SDL_thread.h) +#include "libs/threadlib.h" +#include "libs/timelib.h" + +void InitThreadSystem_SDL (void); +void UnInitThreadSystem_SDL (void); + +#ifdef NAMED_SYNCHRO +/* Prototypes with the "name" field */ +Thread CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize, const char *name); +Mutex CreateMutex_SDL (const char *name, DWORD syncClass); +Semaphore CreateSemaphore_SDL (DWORD initial, const char *name, DWORD syncClass); +RecursiveMutex CreateRecursiveMutex_SDL (const char *name, DWORD syncClass); +CondVar CreateCondVar_SDL (const char *name, DWORD syncClass); +#else +/* Prototypes without the "name" field. */ +Thread CreateThread_SDL (ThreadFunction func, void *data, SDWORD stackSize); +Mutex CreateMutex_SDL (void); +Semaphore CreateSemaphore_SDL (DWORD initial); +RecursiveMutex CreateRecursiveMutex_SDL (void); +CondVar CreateCondVar_SDL (void); +#endif + +ThreadLocal *GetMyThreadLocal_SDL (void); + +void SleepThread_SDL (TimeCount sleepTime); +void SleepThreadUntil_SDL (TimeCount wakeTime); +void TaskSwitch_SDL (void); +void WaitThread_SDL (Thread thread, int *status); +void DestroyThread_SDL (Thread thread); + +void DestroyMutex_SDL (Mutex m); +void LockMutex_SDL (Mutex m); +void UnlockMutex_SDL (Mutex m); + +void DestroySemaphore_SDL (Semaphore sem); +void SetSemaphore_SDL (Semaphore sem); +void ClearSemaphore_SDL (Semaphore sem); + +void DestroyCondVar_SDL (CondVar c); +void WaitCondVar_SDL (CondVar c); +void SignalCondVar_SDL (CondVar c); +void BroadcastCondVar_SDL (CondVar c); + +void DestroyRecursiveMutex_SDL (RecursiveMutex m); +void LockRecursiveMutex_SDL (RecursiveMutex m); +void UnlockRecursiveMutex_SDL (RecursiveMutex m); +int GetRecursiveMutexDepth_SDL (RecursiveMutex m); + +#define NativeInitThreadSystem InitThreadSystem_SDL +#define NativeUnInitThreadSystem UnInitThreadSystem_SDL + +#define NativeGetMyThreadLocal GetMyThreadLocal_SDL + +#define NativeCreateThread CreateThread_SDL +#define NativeSleepThread SleepThread_SDL +#define NativeSleepThreadUntil SleepThreadUntil_SDL +#define NativeTaskSwitch TaskSwitch_SDL +#define NativeWaitThread WaitThread_SDL +#define NativeDestroyThread DestroyThread_SDL + +#define NativeCreateMutex CreateMutex_SDL +#define NativeDestroyMutex DestroyMutex_SDL +#define NativeLockMutex LockMutex_SDL +#define NativeUnlockMutex UnlockMutex_SDL + +#define NativeCreateSemaphore CreateSemaphore_SDL +#define NativeDestroySemaphore DestroySemaphore_SDL +#define NativeSetSemaphore SetSemaphore_SDL +#define NativeClearSemaphore ClearSemaphore_SDL + +#define NativeCreateCondVar CreateCondVar_SDL +#define NativeDestroyCondVar DestroyCondVar_SDL +#define NativeWaitCondVar WaitCondVar_SDL +#define NativeSignalCondVar SignalCondVar_SDL +#define NativeBroadcastCondVar BroadcastCondVar_SDL + +#define NativeCreateRecursiveMutex CreateRecursiveMutex_SDL +#define NativeDestroyRecursiveMutex DestroyRecursiveMutex_SDL +#define NativeLockRecursiveMutex LockRecursiveMutex_SDL +#define NativeUnlockRecursiveMutex UnlockRecursiveMutex_SDL +#define NativeGetRecursiveMutexDepth GetRecursiveMutexDepth_SDL + +#endif /* LIBS_THREADS_SDL_SDLTHREADS_H_ */ + diff --git a/src/libs/threads/thrcommon.c b/src/libs/threads/thrcommon.c new file mode 100644 index 0000000..bc54751 --- /dev/null +++ b/src/libs/threads/thrcommon.c @@ -0,0 +1,451 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include "libs/threadlib.h" +#include "libs/timelib.h" +#include "libs/log.h" +#include "libs/async.h" +#include "libs/memlib.h" +#include "thrcommon.h" + +#define LIFECYCLE_SIZE 8 +typedef struct { + ThreadFunction func; + void *data; + SDWORD stackSize; + Semaphore sem; + Thread value; +#ifdef NAMED_SYNCHRO + const char *name; +#endif +} SpawnRequest_struct; + +typedef SpawnRequest_struct *SpawnRequest; + +static Mutex lifecycleMutex; +static SpawnRequest pendingBirth[LIFECYCLE_SIZE]; +static Thread pendingDeath[LIFECYCLE_SIZE]; + +void +InitThreadSystem (void) +{ + int i; + NativeInitThreadSystem (); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + pendingBirth[i] = NULL; + pendingDeath[i] = NULL; + } + lifecycleMutex = CreateMutex ("Thread Lifecycle Mutex", SYNC_CLASS_RESOURCE); +} + +void +UnInitThreadSystem (void) +{ + NativeUnInitThreadSystem (); + DestroyMutex (lifecycleMutex); +} + +static Thread +FlagStartThread (SpawnRequest s) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + if (pendingBirth[i] == NULL) + { + pendingBirth[i] = s; + UnlockMutex (lifecycleMutex); + if (s->sem) + { + Thread result; + SetSemaphore (s->sem); + DestroySemaphore (s->sem); + result = s->value; + HFree (s); + return result; + } + return NULL; + } + } + log_add (log_Fatal, "Thread Lifecycle array filled. This is a fatal error! Make LIFECYCLE_SIZE something larger than %d.", LIFECYCLE_SIZE); + exit (EXIT_FAILURE); +} + +void +FinishThread (Thread thread) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + if (pendingDeath[i] == NULL) + { + pendingDeath[i] = thread; + UnlockMutex (lifecycleMutex); + return; + } + } + log_add (log_Fatal, "Thread Lifecycle array filled. This is a fatal error! Make LIFECYCLE_SIZE something larger than %d.", LIFECYCLE_SIZE); + exit (EXIT_FAILURE); +} + +/* Only call from main thread! */ +void +ProcessThreadLifecycles (void) +{ + int i; + LockMutex (lifecycleMutex); + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + SpawnRequest s = pendingBirth[i]; + if (s != NULL) + { +#ifdef NAMED_SYNCHRO + s->value = NativeCreateThread (s->func, s->data, s->stackSize, s->name); +#else + s->value = NativeCreateThread (s->func, s->data, s->stackSize); +#endif + if (s->sem) + { + ClearSemaphore (s->sem); + /* The spawning thread's FlagStartThread will clean up s */ + } + else + { + /* The thread value has been lost to the game logic. We must + clean up s ourself. */ + HFree (s); + } + pendingBirth[i] = NULL; + } + } + + for (i = 0; i < LIFECYCLE_SIZE; i++) + { + Thread t = pendingDeath[i]; + if (t != NULL) + { + WaitThread (t, NULL); + pendingDeath[i] = NULL; + DestroyThread (t); + } + } + UnlockMutex (lifecycleMutex); +} + + +/* The Create routines look different based on whether NAMED_SYNCHRO + is defined or not. */ + +#ifdef NAMED_SYNCHRO +Thread +CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->name = name; + s->sem = CreateSemaphore (0, "SpawnRequest semaphore", SYNC_CLASS_RESOURCE); + return FlagStartThread (s); +} + +void +StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize, const char *name) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->name = name; + s->sem = NULL; + FlagStartThread (s); +} + +Mutex +CreateMutex_Core (const char *name, DWORD syncClass) +{ + return NativeCreateMutex (name, syncClass); +} + +Semaphore +CreateSemaphore_Core (DWORD initial, const char *name, DWORD syncClass) +{ + return NativeCreateSemaphore (initial, name, syncClass); +} + +RecursiveMutex +CreateRecursiveMutex_Core (const char *name, DWORD syncClass) +{ + return NativeCreateRecursiveMutex (name, syncClass); +} + +CondVar +CreateCondVar_Core (const char *name, DWORD syncClass) +{ + return NativeCreateCondVar (name, syncClass); +} + +#else +/* These are the versions of Create* without the names. */ +Thread +CreateThread_Core (ThreadFunction func, void *data, SDWORD stackSize) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->sem = CreateSemaphore (0, "SpawnRequest semaphore", SYNC_CLASS_RESOURCE); + return FlagStartThread (s); +} + +void +StartThread_Core (ThreadFunction func, void *data, SDWORD stackSize) +{ + SpawnRequest s = HMalloc(sizeof (SpawnRequest_struct)); + s->func = func; + s->data = data; + s->stackSize = stackSize; + s->sem = NULL; + FlagStartThread (s); +} + +Mutex +CreateMutex_Core (void) +{ + return NativeCreateMutex (); +} + +Semaphore +CreateSemaphore_Core (DWORD initial) +{ + return NativeCreateSemaphore (initial); +} + +RecursiveMutex +CreateRecursiveMutex_Core (void) +{ + return NativeCreateRecursiveMutex (); +} + +CondVar +CreateCondVar_Core (void) +{ + return NativeCreateCondVar (); +} +#endif + +void +DestroyThread (Thread t) +{ + NativeDestroyThread (t); +} + +ThreadLocal * +CreateThreadLocal (void) +{ + ThreadLocal *tl = HMalloc (sizeof (ThreadLocal)); + tl->flushSem = CreateSemaphore (0, "FlushGraphics", SYNC_CLASS_VIDEO); + return tl; +} + +void +DestroyThreadLocal (ThreadLocal *tl) +{ + DestroySemaphore (tl->flushSem); + HFree (tl); +} + +ThreadLocal * +GetMyThreadLocal (void) +{ + return NativeGetMyThreadLocal (); +} + +void +WaitThread (Thread thread, int *status) +{ + NativeWaitThread (thread, status); +} + +#ifdef DEBUG_SLEEP +extern uint32 mainThreadId; +extern uint32 SDL_ThreadID(void); +#endif /* DEBUG_SLEEP */ + +void +HibernateThread (TimePeriod timePeriod) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() == mainThreadId) + log_add (log_Debug, "HibernateThread called from main thread.\n"); +#endif /* DEBUG_SLEEP */ + + NativeSleepThread (timePeriod); +} + +void +HibernateThreadUntil (TimeCount wakeTime) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() == mainThreadId) + log_add (log_Debug, "HibernateThreadUntil called from main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + NativeSleepThreadUntil (wakeTime); +} + +void +SleepThread (TimePeriod timePeriod) +{ + TimeCount now; + +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() != mainThreadId) + log_add (log_Debug, "SleepThread called from non-main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + now = GetTimeCounter (); + SleepThreadUntil (now + timePeriod); +} + +// Sleep until wakeTime, but call asynchrounous operations until then. +void +SleepThreadUntil (TimeCount wakeTime) +{ +#ifdef DEBUG_SLEEP + if (SDL_ThreadID() != mainThreadId) + log_add (log_Debug, "SleepThreadUntil called from non-main " + "thread.\n"); +#endif /* DEBUG_SLEEP */ + + for (;;) { + uint32 nextTimeMs; + TimeCount nextTime; + TimeCount now; + + Async_process (); + + now = GetTimeCounter (); + if (wakeTime <= now) + return; + + nextTimeMs = Async_timeBeforeNextMs (); + nextTime = (nextTimeMs / 1000) * ONE_SECOND + + ((nextTimeMs % 1000) * ONE_SECOND / 1000); + // Overflow-safe conversion. + if (wakeTime < nextTime) + nextTime = wakeTime; + + NativeSleepThreadUntil (nextTime); + } +} + +void +TaskSwitch (void) +{ + NativeTaskSwitch (); +} + +void +DestroyMutex (Mutex sem) +{ + NativeDestroyMutex (sem); +} + +void +LockMutex (Mutex sem) +{ + NativeLockMutex (sem); +} + +void +UnlockMutex (Mutex sem) +{ + NativeUnlockMutex (sem); +} + +void +DestroySemaphore (Semaphore sem) +{ + NativeDestroySemaphore (sem); +} + +void +SetSemaphore (Semaphore sem) +{ + NativeSetSemaphore (sem); +} + +void +ClearSemaphore (Semaphore sem) +{ + NativeClearSemaphore (sem); +} + +void +DestroyCondVar (CondVar cv) +{ + NativeDestroyCondVar (cv); +} + +void +WaitCondVar (CondVar cv) +{ + NativeWaitCondVar (cv); +} + +void +SignalCondVar (CondVar cv) +{ + NativeSignalCondVar (cv); +} + +void +BroadcastCondVar (CondVar cv) +{ + NativeBroadcastCondVar (cv); +} + +void +DestroyRecursiveMutex (RecursiveMutex mutex) +{ + NativeDestroyRecursiveMutex (mutex); +} + +void +LockRecursiveMutex (RecursiveMutex mutex) +{ + NativeLockRecursiveMutex (mutex); +} + +void +UnlockRecursiveMutex (RecursiveMutex mutex) +{ + NativeUnlockRecursiveMutex (mutex); +} + +int +GetRecursiveMutexDepth (RecursiveMutex mutex) +{ + return NativeGetRecursiveMutexDepth (mutex); +} diff --git a/src/libs/threads/thrcommon.h b/src/libs/threads/thrcommon.h new file mode 100644 index 0000000..d6d6d41 --- /dev/null +++ b/src/libs/threads/thrcommon.h @@ -0,0 +1,28 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef LIBS_THREADS_THRCOMMON_H_ +#define LIBS_THREADS_THRCOMMON_H_ + + +#if defined(THREADLIB_SDL) +# include "sdl/sdlthreads.h" +#elif defined(THREADLIB_PTHREAD) +# include "pthread/posixthreads.h" +#endif /* defined(THREADLIB_PTHREAD) */ + + +#endif /* _THR_COMMON_H */ |