1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2006 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21*/
22#include "SDL_config.h"
23
24/* System independent thread management routines for SDL */
25
26#include "SDL_mutex.h"
27#include "SDL_thread.h"
28#include "../SDL_thread_c.h"
29#include "../SDL_systhread.h"
30
31#define ARRAY_CHUNKSIZE	32
32/* The array of threads currently active in the application
33   (except the main thread)
34   The manipulation of an array here is safer than using a linked list.
35*/
36static int SDL_maxthreads = 0;
37static int SDL_numthreads = 0;
38static SDL_Thread **SDL_Threads = NULL;
39static struct SignalSemaphore thread_lock;
40int thread_lock_created = 0;
41
42int SDL_ThreadsInit(void)
43{
44	InitSemaphore(&thread_lock);
45	thread_lock_created=1;
46	return 0;
47}
48
49/* This should never be called...
50   If this is called by SDL_Quit(), we don't know whether or not we should
51   clean up threads here.  If any threads are still running after this call,
52   they will no longer have access to any per-thread data.
53 */
54void SDL_ThreadsQuit()
55{
56	thread_lock_created=0;
57}
58
59/* Routines for manipulating the thread list */
60static void SDL_AddThread(SDL_Thread *thread)
61{
62	SDL_Thread **threads;
63
64	/* WARNING:
65	   If the very first threads are created simultaneously, then
66	   there could be a race condition causing memory corruption.
67	   In practice, this isn't a problem because by definition there
68	   is only one thread running the first time this is called.
69	*/
70	if ( !thread_lock_created ) {
71		if ( SDL_ThreadsInit() < 0 ) {
72			return;
73		}
74	}
75	ObtainSemaphore(&thread_lock);
76
77	/* Expand the list of threads, if necessary */
78#ifdef DEBUG_THREADS
79	printf("Adding thread (%d already - %d max)\n",
80			SDL_numthreads, SDL_maxthreads);
81#endif
82	if ( SDL_numthreads == SDL_maxthreads ) {
83		threads=(SDL_Thread **)SDL_malloc((SDL_maxthreads+ARRAY_CHUNKSIZE)*
84		                              (sizeof *threads));
85		if ( threads == NULL ) {
86			SDL_OutOfMemory();
87			goto done;
88		}
89		SDL_memcpy(threads, SDL_Threads, SDL_numthreads*(sizeof *threads));
90		SDL_maxthreads += ARRAY_CHUNKSIZE;
91		if ( SDL_Threads ) {
92			SDL_free(SDL_Threads);
93		}
94		SDL_Threads = threads;
95	}
96	SDL_Threads[SDL_numthreads++] = thread;
97done:
98	ReleaseSemaphore(&thread_lock);
99}
100
101static void SDL_DelThread(SDL_Thread *thread)
102{
103	int i;
104
105	if ( thread_lock_created ) {
106		ObtainSemaphore(&thread_lock);
107		for ( i=0; i<SDL_numthreads; ++i ) {
108			if ( thread == SDL_Threads[i] ) {
109				break;
110			}
111		}
112		if ( i < SDL_numthreads ) {
113			--SDL_numthreads;
114			while ( i < SDL_numthreads ) {
115				SDL_Threads[i] = SDL_Threads[i+1];
116				++i;
117			}
118#ifdef DEBUG_THREADS
119			printf("Deleting thread (%d left - %d max)\n",
120					SDL_numthreads, SDL_maxthreads);
121#endif
122		}
123		ReleaseSemaphore(&thread_lock);
124	}
125}
126
127/* The default (non-thread-safe) global error variable */
128static SDL_error SDL_global_error;
129
130/* Routine to get the thread-specific error variable */
131SDL_error *SDL_GetErrBuf(void)
132{
133	SDL_error *errbuf;
134
135	errbuf = &SDL_global_error;
136	if ( SDL_Threads ) {
137		int i;
138		Uint32 this_thread;
139
140		this_thread = SDL_ThreadID();
141		ObtainSemaphore(&thread_lock);
142		for ( i=0; i<SDL_numthreads; ++i ) {
143			if ( this_thread == SDL_Threads[i]->threadid ) {
144				errbuf = &SDL_Threads[i]->errbuf;
145				break;
146			}
147		}
148		ReleaseSemaphore(&thread_lock);
149	}
150	return(errbuf);
151}
152
153
154/* Arguments and callback to setup and run the user thread function */
155typedef struct {
156	int (*func)(void *);
157	void *data;
158	SDL_Thread *info;
159	struct Task *wait;
160} thread_args;
161
162void SDL_RunThread(void *data)
163{
164	thread_args *args;
165	int (*userfunc)(void *);
166	void *userdata;
167	int *statusloc;
168
169	/* Perform any system-dependent setup
170	   - this function cannot fail, and cannot use SDL_SetError()
171	 */
172	SDL_SYS_SetupThread();
173
174	/* Get the thread id */
175	args = (thread_args *)data;
176	args->info->threadid = SDL_ThreadID();
177
178	/* Figure out what function to run */
179	userfunc = args->func;
180	userdata = args->data;
181	statusloc = &args->info->status;
182
183	/* Wake up the parent thread */
184	Signal(args->wait,SIGBREAKF_CTRL_E);
185
186	/* Run the function */
187	*statusloc = userfunc(userdata);
188}
189
190SDL_Thread *SDL_CreateThread(int (*fn)(void *), void *data)
191{
192	SDL_Thread *thread;
193	thread_args *args;
194	int ret;
195
196	/* Allocate memory for the thread info structure */
197	thread = (SDL_Thread *)SDL_malloc(sizeof(*thread));
198	if ( thread == NULL ) {
199		SDL_OutOfMemory();
200		return(NULL);
201	}
202	SDL_memset(thread, 0, (sizeof *thread));
203	thread->status = -1;
204
205	/* Set up the arguments for the thread */
206	args = (thread_args *)SDL_malloc(sizeof(*args));
207	if ( args == NULL ) {
208		SDL_OutOfMemory();
209		SDL_free(thread);
210		return(NULL);
211	}
212	args->func = fn;
213	args->data = data;
214	args->info = thread;
215	args->wait = FindTask(NULL);
216	if ( args->wait == NULL ) {
217		SDL_free(thread);
218		SDL_free(args);
219		SDL_OutOfMemory();
220		return(NULL);
221	}
222
223	/* Add the thread to the list of available threads */
224	SDL_AddThread(thread);
225
226	D(bug("Starting thread...\n"));
227
228	/* Create the thread and go! */
229	ret = SDL_SYS_CreateThread(thread, args);
230	if ( ret >= 0 ) {
231		D(bug("Waiting for thread CTRL_E...\n"));
232		/* Wait for the thread function to use arguments */
233		Wait(SIGBREAKF_CTRL_E);
234		D(bug("  Arrived."));
235	} else {
236		/* Oops, failed.  Gotta free everything */
237		SDL_DelThread(thread);
238		SDL_free(thread);
239		thread = NULL;
240	}
241	SDL_free(args);
242
243	/* Everything is running now */
244	return(thread);
245}
246
247void SDL_WaitThread(SDL_Thread *thread, int *status)
248{
249	if ( thread ) {
250		SDL_SYS_WaitThread(thread);
251		if ( status ) {
252			*status = thread->status;
253		}
254		SDL_DelThread(thread);
255		SDL_free(thread);
256	}
257}
258
259Uint32 SDL_GetThreadID(SDL_Thread *thread)
260{
261	Uint32 id;
262
263	if ( thread ) {
264		id = thread->threadid;
265	} else {
266		id = SDL_ThreadID();
267	}
268	return(id);
269}
270
271void SDL_KillThread(SDL_Thread *thread)
272{
273	if ( thread ) {
274		SDL_SYS_KillThread(thread);
275		SDL_WaitThread(thread, NULL);
276	}
277}
278
279