1 // SONIC ROBO BLAST 2
2 //-----------------------------------------------------------------------------
3 // Copyright (C) 2020 by James R.
4 //
5 // This program is free software distributed under the
6 // terms of the GNU General Public License, version 2.
7 // See the 'LICENSE' file for more details.
8 //-----------------------------------------------------------------------------
9 /// \file  i_threads.c
10 /// \brief Multithreading abstraction
11 
12 #include "../doomdef.h"
13 #include "../i_threads.h"
14 
15 #include <SDL.h>
16 
17 typedef void * (*Create_fn)(void);
18 
19 struct Link;
20 struct Thread;
21 
22 typedef struct Link   * Link;
23 typedef struct Thread * Thread;
24 
25 struct Link
26 {
27 	void * data;
28 	Link   next;
29 	Link   prev;
30 };
31 
32 struct Thread
33 {
34 	I_thread_fn   entry;
35 	void        * userdata;
36 
37 	SDL_Thread  * thread;
38 };
39 
40 static Link    i_thread_pool;
41 static Link    i_mutex_pool;
42 static Link    i_cond_pool;
43 
44 static I_mutex        i_thread_pool_mutex;
45 static I_mutex        i_mutex_pool_mutex;
46 static I_mutex        i_cond_pool_mutex;
47 
48 static SDL_atomic_t   i_threads_running = {1};
49 
50 static Link
Insert_link(Link * head,Link link)51 Insert_link (
52 		Link * head,
53 		Link   link
54 ){
55 	link->prev = NULL;
56 	link->next = (*head);
57 	if ((*head))
58 		(*head)->prev = link;
59 	(*head)    = link;
60 	return link;
61 }
62 
63 static void
Free_link(Link * head,Link link)64 Free_link (
65 		Link * head,
66 		Link   link
67 ){
68 	if (link->prev)
69 		link->prev->next = link->next;
70 	else
71 		(*head) = link->next;
72 
73 	if (link->next)
74 		link->next->prev = link->prev;
75 
76 	free(link->data);
77 	free(link);
78 }
79 
80 static Link
New_link(void * data)81 New_link (void *data)
82 {
83 	Link link;
84 
85 	link = malloc(sizeof *link);
86 
87 	if (! link)
88 		abort();
89 
90 	link->data = data;
91 
92 	return link;
93 }
94 
95 static void *
Identity(Link * pool_anchor,I_mutex pool_mutex,void ** anchor,Create_fn create_fn)96 Identity (
97 		Link      *  pool_anchor,
98 		I_mutex      pool_mutex,
99 
100 		void      ** anchor,
101 
102 		Create_fn    create_fn
103 ){
104 	void * id;
105 
106 	id = SDL_AtomicGetPtr(anchor);
107 
108 	if (! id)
109 	{
110 		I_lock_mutex(&pool_mutex);
111 		{
112 			id = SDL_AtomicGetPtr(anchor);
113 
114 			if (! id)
115 			{
116 				id = (*create_fn)();
117 
118 				if (! id)
119 					abort();
120 
121 				Insert_link(pool_anchor, New_link(id));
122 
123 				SDL_AtomicSetPtr(anchor, id);
124 			}
125 		}
126 		I_unlock_mutex(pool_mutex);
127 	}
128 
129 	return id;
130 }
131 
132 static int
Worker(Link link)133 Worker (
134 		Link link
135 ){
136 	Thread th;
137 
138 	th = link->data;
139 
140 	(*th->entry)(th->userdata);
141 
142 	if (SDL_AtomicGet(&i_threads_running))
143 	{
144 		I_lock_mutex(&i_thread_pool_mutex);
145 		{
146 			if (SDL_AtomicGet(&i_threads_running))
147 			{
148 				SDL_DetachThread(th->thread);
149 				Free_link(&i_thread_pool, link);
150 			}
151 		}
152 		I_unlock_mutex(i_thread_pool_mutex);
153 	}
154 
155 	return 0;
156 }
157 
158 void
I_spawn_thread(const char * name,I_thread_fn entry,void * userdata)159 I_spawn_thread (
160 		const char  * name,
161 		I_thread_fn   entry,
162 		void        * userdata
163 ){
164 	Link   link;
165 	Thread th;
166 
167 	th = malloc(sizeof *th);
168 
169 	if (! th)
170 		abort();/* this is pretty GNU of me */
171 
172 	th->entry    = entry;
173 	th->userdata = userdata;
174 
175 	I_lock_mutex(&i_thread_pool_mutex);
176 	{
177 		link = Insert_link(&i_thread_pool, New_link(th));
178 
179 		if (SDL_AtomicGet(&i_threads_running))
180 		{
181 			th->thread = SDL_CreateThread(
182 					(SDL_ThreadFunction)Worker,
183 					name,
184 					link
185 			);
186 
187 			if (! th->thread)
188 				abort();
189 		}
190 	}
191 	I_unlock_mutex(i_thread_pool_mutex);
192 }
193 
194 int
I_thread_is_stopped(void)195 I_thread_is_stopped (void)
196 {
197 	return ( ! SDL_AtomicGet(&i_threads_running) );
198 }
199 
200 void
I_start_threads(void)201 I_start_threads (void)
202 {
203 	i_thread_pool_mutex = SDL_CreateMutex();
204 	i_mutex_pool_mutex  = SDL_CreateMutex();
205 	i_cond_pool_mutex   = SDL_CreateMutex();
206 
207 	if (!(
208 				i_thread_pool_mutex &&
209 				i_mutex_pool_mutex  &&
210 				i_cond_pool_mutex
211 	)){
212 		abort();
213 	}
214 }
215 
216 void
I_stop_threads(void)217 I_stop_threads (void)
218 {
219 	Link        link;
220 	Link        next;
221 
222 	Thread      th;
223 	SDL_mutex * mutex;
224 	SDL_cond  * cond;
225 
226 	if (i_threads_running.value)
227 	{
228 		/* rely on the good will of thread-san */
229 		SDL_AtomicSet(&i_threads_running, 0);
230 
231 		I_lock_mutex(&i_thread_pool_mutex);
232 		{
233 			for (
234 					link = i_thread_pool;
235 					link;
236 					link = next
237 			){
238 				next = link->next;
239 				th   = link->data;
240 
241 				SDL_WaitThread(th->thread, NULL);
242 
243 				free(th);
244 				free(link);
245 			}
246 		}
247 		I_unlock_mutex(i_thread_pool_mutex);
248 
249 		for (
250 				link = i_mutex_pool;
251 				link;
252 				link = next
253 		){
254 			next  = link->next;
255 			mutex = link->data;
256 
257 			SDL_DestroyMutex(mutex);
258 
259 			free(link);
260 		}
261 
262 		for (
263 				link = i_cond_pool;
264 				link;
265 				link = next
266 		){
267 			next = link->next;
268 			cond = link->data;
269 
270 			SDL_DestroyCond(cond);
271 
272 			free(link);
273 		}
274 
275 		SDL_DestroyMutex(i_thread_pool_mutex);
276 		SDL_DestroyMutex(i_mutex_pool_mutex);
277 		SDL_DestroyMutex(i_cond_pool_mutex);
278 	}
279 }
280 
281 void
I_lock_mutex(I_mutex * anchor)282 I_lock_mutex (
283 		I_mutex * anchor
284 ){
285 	SDL_mutex * mutex;
286 
287 	mutex = Identity(
288 			&i_mutex_pool,
289 			i_mutex_pool_mutex,
290 			anchor,
291 			(Create_fn)SDL_CreateMutex
292 	);
293 
294 	if (SDL_LockMutex(mutex) == -1)
295 		abort();
296 }
297 
298 void
I_unlock_mutex(I_mutex id)299 I_unlock_mutex (
300 		I_mutex id
301 ){
302 	if (SDL_UnlockMutex(id) == -1)
303 		abort();
304 }
305 
306 void
I_hold_cond(I_cond * cond_anchor,I_mutex mutex_id)307 I_hold_cond (
308 		I_cond  * cond_anchor,
309 		I_mutex   mutex_id
310 ){
311 	SDL_cond * cond;
312 
313 	cond = Identity(
314 			&i_cond_pool,
315 			i_cond_pool_mutex,
316 			cond_anchor,
317 			(Create_fn)SDL_CreateCond
318 	);
319 
320 	if (SDL_CondWait(cond, mutex_id) == -1)
321 		abort();
322 }
323 
324 void
I_wake_one_cond(I_cond * anchor)325 I_wake_one_cond (
326 		I_cond * anchor
327 ){
328 	SDL_cond * cond;
329 
330 	cond = Identity(
331 			&i_cond_pool,
332 			i_cond_pool_mutex,
333 			anchor,
334 			(Create_fn)SDL_CreateCond
335 	);
336 
337 	if (SDL_CondSignal(cond) == -1)
338 		abort();
339 }
340 
341 void
I_wake_all_cond(I_cond * anchor)342 I_wake_all_cond (
343 		I_cond * anchor
344 ){
345 	SDL_cond * cond;
346 
347 	cond = Identity(
348 			&i_cond_pool,
349 			i_cond_pool_mutex,
350 			anchor,
351 			(Create_fn)SDL_CreateCond
352 	);
353 
354 	if (SDL_CondBroadcast(cond) == -1)
355 		abort();
356 }
357