Files
SRB2Kart-Saturn/src/i_threads.c
T
Alug 0985229077 refactor i_thread stuff
based on https://codeberg.org/srb2classic/srb2classic/commit/3670a7c1c193185cf4921de8955c96e63fbecc62
and other things from srb2classic
this also now allows to check if a thread failed to spawn and reduces duped stuff and such
2026-02-02 16:54:40 +01:00

393 lines
7.5 KiB
C

// SONIC ROBO BLAST 2 KART
//-----------------------------------------------------------------------------
// Copyright (C) 2020-2023 by James R.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file i_threads.c
/// \brief Multithreading abstraction
#ifdef HAVE_THREADS
#if defined (__unix__) || defined(UNIXCOMMON)
#include <pthread.h>
#include "i_threads.h"
#include "doomdef.h"
#include "doomtype.h"
typedef struct thread_s thread_t;
struct thread_s
{
thread_t *next;
void *userdata;
I_ThreadFn func;
pthread_t thread;
};
// we use a linked list to avoid moving memory blocks when allocating new threads.
static thread_t *thread_list;
static pthread_mutex_t thread_lock = PTHREAD_MUTEX_INITIALIZER;
static void *HandleThread(void *data)
{
thread_t *thread = data;
thread->func(thread->userdata);
pthread_mutex_lock(&thread_lock);
thread->func = NULL;
pthread_mutex_unlock(&thread_lock);
return NULL;
}
FUNCWARNRV
int I_SpawnThread(const char *name, I_ThreadFn entry, void *userdata)
{
thread_t *thread;
(void)name;
pthread_mutex_lock(&thread_lock);
thread = thread_list;
while (thread != NULL)
{
if (thread->func == NULL)
{
// join with the exited thread to release it's resources.
pthread_join(thread->thread, NULL);
break;
}
thread = thread->next;
}
if (thread == NULL)
{
thread = malloc(sizeof(thread_t));
if (thread == NULL)
return false;
thread->next = thread_list;
thread_list = thread;
}
thread->func = entry;
thread->userdata = userdata;
int err = pthread_create(&thread->thread, NULL, HandleThread, thread);
pthread_mutex_unlock(&thread_lock);
return err == 0;
}
int I_ThreadIsStopped(void)
{
thread_t *thread;
pthread_mutex_lock(&thread_lock);
thread = thread_list;
while (thread != NULL)
{
if (thread->func != NULL)
{
pthread_mutex_unlock(&thread_lock);
return false;
}
thread = thread->next;
}
pthread_mutex_unlock(&thread_lock);
return true;
}
void I_StartThreads(void)
{
}
void I_StopThreads(void)
{
thread_t *thread = thread_list;
while (thread != NULL)
{
// join with all threads here, since finished threads haven't been awaited yet.
pthread_join(thread->thread, NULL);
thread = thread->next;
}
}
void I_LockMutex(I_Mutex *anchor)
{
pthread_mutex_lock(&thread_lock);
if (*anchor == NULL)
{
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
// SRB2 relies on lock recursion, so we need a mutex configured for that.
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
*anchor = malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(*anchor, &attr);
pthread_mutexattr_destroy(&attr);
}
pthread_mutex_unlock(&thread_lock);
pthread_mutex_lock(*anchor);
}
void I_UnlockMutex(I_Mutex id)
{
pthread_mutex_unlock(id);
}
void I_HoldCond(I_Cond *condAnchor, I_Mutex mutexId)
{
I_Assert(mutexId != NULL);
pthread_mutex_lock(&thread_lock);
if (*condAnchor == NULL)
{
*condAnchor = malloc(sizeof(pthread_cond_t));
pthread_cond_init(*condAnchor, NULL);
}
pthread_mutex_unlock(&thread_lock);
pthread_cond_wait(*condAnchor, mutexId);
}
void I_WakeOneCond(I_Cond *anchor)
{
pthread_mutex_lock(&thread_lock);
if (*anchor == NULL)
{
*anchor = malloc(sizeof(pthread_cond_t));
pthread_cond_init(*anchor, NULL);
}
pthread_mutex_unlock(&thread_lock);
pthread_cond_signal(*anchor);
}
void I_WakeAllCond(I_Cond *anchor)
{
pthread_mutex_lock(&thread_lock);
if (*anchor == NULL)
{
*anchor = malloc(sizeof(pthread_cond_t));
pthread_cond_init(*anchor, NULL);
}
pthread_mutex_unlock(&thread_lock);
pthread_cond_broadcast(*anchor);
}
thread_handle_t I_GetCurrentThread(void)
{
return pthread_self();
}
#elif defined (_WIN32)
#include <windows.h>
#include "i_threads.h"
#include "doomdef.h"
#include "doomtype.h"
typedef struct thread_s thread_t;
struct thread_s
{
thread_t *next;
void *userdata;
I_ThreadFn func;
HANDLE thread;
DWORD threadId;
};
// we use a linked list to avoid moving memory blocks when allocating new threads.
static thread_t *thread_list;
static CRITICAL_SECTION thread_lock;
static DWORD __stdcall HandleThread(void *data)
{
thread_t *thread = data;
thread->func(thread->userdata);
EnterCriticalSection(&thread_lock);
thread->func = NULL;
LeaveCriticalSection(&thread_lock);
return 0;
}
FUNCWARNRV
int I_SpawnThread(const char *name, I_ThreadFn entry, void *userdata)
{
thread_t *thread;
(void)name;
EnterCriticalSection(&thread_lock);
thread = thread_list;
while (thread != NULL)
{
if (thread->func == NULL)
{
CloseHandle(thread->thread);
break;
}
thread = thread->next;
}
if (thread == NULL)
{
thread = malloc(sizeof(thread_t));
if (thread == NULL)
return false;
thread->next = thread_list;
thread_list = thread;
}
thread->func = entry;
thread->userdata = userdata;
thread->thread = CreateThread(NULL, 0, HandleThread, thread, 0, &thread->threadId);
LeaveCriticalSection(&thread_lock);
return thread->thread != NULL;
}
int I_ThreadIsStopped(void)
{
thread_t *thread;
EnterCriticalSection(&thread_lock);
thread = thread_list;
while (thread != NULL)
{
if (thread->func != NULL)
{
LeaveCriticalSection(&thread_lock);
return false;
}
thread = thread->next;
}
LeaveCriticalSection(&thread_lock);
return true;
}
void I_StartThreads(void)
{
InitializeCriticalSection(&thread_lock);
}
void I_StopThreads(void)
{
thread_t *thread = thread_list;
while (thread != NULL)
{
WaitForSingleObject(thread->thread, INFINITE);
CloseHandle(thread->thread);
thread = thread->next;
}
DeleteCriticalSection(&thread_lock);
}
void I_LockMutex(I_Mutex *anchor)
{
EnterCriticalSection(&thread_lock);
if (*anchor == NULL)
{
*anchor = malloc(sizeof(CRITICAL_SECTION));
InitializeCriticalSection(*anchor);
}
LeaveCriticalSection(&thread_lock);
EnterCriticalSection(*anchor);
}
void I_UnlockMutex(I_Mutex id)
{
LeaveCriticalSection(id);
}
void I_HoldCond(I_Cond *condAnchor, I_Mutex mutexId)
{
I_Assert(mutexId != NULL);
EnterCriticalSection(&thread_lock);
if (*condAnchor == NULL)
{
*condAnchor = malloc(sizeof(CONDITION_VARIABLE));
InitializeConditionVariable(*condAnchor);
}
LeaveCriticalSection(&thread_lock);
SleepConditionVariableCS(*condAnchor, mutexId, INFINITE);
}
void I_WakeOneCond(I_Cond *anchor)
{
EnterCriticalSection(&thread_lock);
if (*anchor == NULL)
{
*anchor = malloc(sizeof(CONDITION_VARIABLE));
InitializeConditionVariable(*anchor);
}
LeaveCriticalSection(&thread_lock);
WakeConditionVariable(*anchor);
}
void I_WakeAllCond(I_Cond *anchor)
{
EnterCriticalSection(&thread_lock);
if (*anchor == NULL)
{
*anchor = malloc(sizeof(CONDITION_VARIABLE));
InitializeConditionVariable(*anchor);
}
LeaveCriticalSection(&thread_lock);
WakeAllConditionVariable(*anchor);
}
void *I_GetCurrentThread(void)
{
return GetCurrentThread();
}
#else
#include "i_threads.h"
FUNCWARNRV
int I_SpawnThread(const char *name, I_ThreadFn entry, void *userdata)
{
(void)name;
entry(userdata);
return true;
}
int I_ThreadIsStopped(void)
{
return true;
}
void I_StartThreads(void)
{
}
void I_StopThreads(void)
{
}
void I_LockMutex(I_Mutex *anchor)
{
(void)anchor;
}
void I_UnlockMutex(I_Mutex id)
{
(void)id;
}
void I_HoldCond(I_Cond *condAnchor, I_Mutex mutexId)
{
(void)condAnchor;
(void)mutexId;
}
void I_WakeOneCond(I_Cond *anchor)
{
(void)anchor;
}
void I_WakeAllCond(I_Cond *anchor)
{
(void)anchor;
}
void *I_GetCurrentThread(void)
{
return NULL;
}
#endif
#endif // HAVE_THREADS