// SONIC ROBO BLAST 2 //----------------------------------------------------------------------------- // 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 #include "../doomdef.h" #include "../i_threads.h" #include typedef void * (*Create_fn)(void); struct Link; struct Thread; typedef struct Link * Link; typedef struct Thread * Thread; struct Link { void * data; Link next; Link prev; }; struct Thread { I_thread_fn entry; void * userdata; SDL_Thread * thread; }; static Link i_thread_pool; static Link i_mutex_pool; static Link i_cond_pool; static I_mutex i_thread_pool_mutex; static I_mutex i_mutex_pool_mutex; static I_mutex i_cond_pool_mutex; static SDL_atomic_t i_threads_running = {1}; static Link Insert_link ( Link * head, Link link ){ link->prev = NULL; link->next = (*head); if ((*head)) (*head)->prev = link; (*head) = link; return link; } static void Free_link ( Link * head, Link link ){ if (link->prev) link->prev->next = link->next; else (*head) = link->next; if (link->next) link->next->prev = link->prev; free(link->data); free(link); } static Link New_link (void *data) { Link link; link = malloc(sizeof *link); if (! link) abort(); link->data = data; return link; } static void * Identity ( Link * pool_anchor, I_mutex pool_mutex, void ** anchor, Create_fn create_fn ){ void * id; id = SDL_AtomicGetPtr(anchor); if (! id) { I_lock_mutex(&pool_mutex); { id = SDL_AtomicGetPtr(anchor); if (! id) { id = (*create_fn)(); if (! id) abort(); Insert_link(pool_anchor, New_link(id)); SDL_AtomicSetPtr(anchor, id); } } I_unlock_mutex(pool_mutex); } return id; } static int Worker ( Link link ){ Thread th; th = link->data; (*th->entry)(th->userdata); if (SDL_AtomicGet(&i_threads_running)) { I_lock_mutex(&i_thread_pool_mutex); { if (SDL_AtomicGet(&i_threads_running)) { SDL_DetachThread(th->thread); Free_link(&i_thread_pool, link); } } I_unlock_mutex(i_thread_pool_mutex); } return 0; } void I_spawn_thread ( const char * name, I_thread_fn entry, void * userdata ){ Link link; Thread th; th = malloc(sizeof *th); if (! th) abort();/* this is pretty GNU of me */ th->entry = entry; th->userdata = userdata; I_lock_mutex(&i_thread_pool_mutex); { link = Insert_link(&i_thread_pool, New_link(th)); if (SDL_AtomicGet(&i_threads_running)) { th->thread = SDL_CreateThread( (SDL_ThreadFunction)Worker, name, link ); if (! th->thread) abort(); } } I_unlock_mutex(i_thread_pool_mutex); } int I_thread_is_stopped (void) { return ( ! SDL_AtomicGet(&i_threads_running) ); } void I_start_threads (void) { i_thread_pool_mutex = SDL_CreateMutex(); i_mutex_pool_mutex = SDL_CreateMutex(); i_cond_pool_mutex = SDL_CreateMutex(); if (!( i_thread_pool_mutex && i_mutex_pool_mutex && i_cond_pool_mutex )){ abort(); } } void I_stop_threads (void) { Link link; Link next; Thread th; SDL_mutex * mutex; SDL_cond * cond; if (i_threads_running.value) { /* rely on the good will of thread-san */ SDL_AtomicSet(&i_threads_running, 0); I_lock_mutex(&i_thread_pool_mutex); { for ( link = i_thread_pool; link; link = next ){ next = link->next; th = link->data; SDL_WaitThread(th->thread, NULL); free(th); free(link); } } I_unlock_mutex(i_thread_pool_mutex); for ( link = i_mutex_pool; link; link = next ){ next = link->next; mutex = link->data; SDL_DestroyMutex(mutex); free(link); } for ( link = i_cond_pool; link; link = next ){ next = link->next; cond = link->data; SDL_DestroyCond(cond); free(link); } SDL_DestroyMutex(i_thread_pool_mutex); SDL_DestroyMutex(i_mutex_pool_mutex); SDL_DestroyMutex(i_cond_pool_mutex); } } void I_lock_mutex ( I_mutex * anchor ){ SDL_mutex * mutex; mutex = Identity( &i_mutex_pool, i_mutex_pool_mutex, anchor, (Create_fn)SDL_CreateMutex ); if (SDL_LockMutex(mutex) == -1) abort(); } void I_unlock_mutex ( I_mutex id ){ if (SDL_UnlockMutex(id) == -1) abort(); } void I_hold_cond ( I_cond * cond_anchor, I_mutex mutex_id ){ SDL_cond * cond; cond = Identity( &i_cond_pool, i_cond_pool_mutex, cond_anchor, (Create_fn)SDL_CreateCond ); if (SDL_CondWait(cond, mutex_id) == -1) abort(); } void I_wake_one_cond ( I_cond * anchor ){ SDL_cond * cond; cond = Identity( &i_cond_pool, i_cond_pool_mutex, anchor, (Create_fn)SDL_CreateCond ); if (SDL_CondSignal(cond) == -1) abort(); } void I_wake_all_cond ( I_cond * anchor ){ SDL_cond * cond; cond = Identity( &i_cond_pool, i_cond_pool_mutex, anchor, (Create_fn)SDL_CreateCond ); if (SDL_CondBroadcast(cond) == -1) abort(); }