1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 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/* An implementation of condition variables using semaphores and mutexes */
25/*
26   This implementation borrows heavily from the BeOS condition variable
27   implementation, written by Christopher Tate and Owen Smith.  Thanks!
28 */
29
30#include "SDL_thread.h"
31
32struct SDL_cond
33{
34	SDL_mutex *lock;
35	int waiting;
36	int signals;
37	SDL_sem *wait_sem;
38	SDL_sem *wait_done;
39};
40
41/* Create a condition variable */
42SDL_cond * SDL_CreateCond(void)
43{
44	SDL_cond *cond;
45
46	cond = (SDL_cond *) SDL_malloc(sizeof(SDL_cond));
47	if ( cond ) {
48		cond->lock = SDL_CreateMutex();
49		cond->wait_sem = SDL_CreateSemaphore(0);
50		cond->wait_done = SDL_CreateSemaphore(0);
51		cond->waiting = cond->signals = 0;
52		if ( ! cond->lock || ! cond->wait_sem || ! cond->wait_done ) {
53			SDL_DestroyCond(cond);
54			cond = NULL;
55		}
56	} else {
57		SDL_OutOfMemory();
58	}
59	return(cond);
60}
61
62/* Destroy a condition variable */
63void SDL_DestroyCond(SDL_cond *cond)
64{
65	if ( cond ) {
66		if ( cond->wait_sem ) {
67			SDL_DestroySemaphore(cond->wait_sem);
68		}
69		if ( cond->wait_done ) {
70			SDL_DestroySemaphore(cond->wait_done);
71		}
72		if ( cond->lock ) {
73			SDL_DestroyMutex(cond->lock);
74		}
75		SDL_free(cond);
76	}
77}
78
79/* Restart one of the threads that are waiting on the condition variable */
80int SDL_CondSignal(SDL_cond *cond)
81{
82	if ( ! cond ) {
83		SDL_SetError("Passed a NULL condition variable");
84		return -1;
85	}
86
87	/* If there are waiting threads not already signalled, then
88	   signal the condition and wait for the thread to respond.
89	*/
90	SDL_LockMutex(cond->lock);
91	if ( cond->waiting > cond->signals ) {
92		++cond->signals;
93		SDL_SemPost(cond->wait_sem);
94		SDL_UnlockMutex(cond->lock);
95		SDL_SemWait(cond->wait_done);
96	} else {
97		SDL_UnlockMutex(cond->lock);
98	}
99
100	return 0;
101}
102
103/* Restart all threads that are waiting on the condition variable */
104int SDL_CondBroadcast(SDL_cond *cond)
105{
106	if ( ! cond ) {
107		SDL_SetError("Passed a NULL condition variable");
108		return -1;
109	}
110
111	/* If there are waiting threads not already signalled, then
112	   signal the condition and wait for the thread to respond.
113	*/
114	SDL_LockMutex(cond->lock);
115	if ( cond->waiting > cond->signals ) {
116		int i, num_waiting;
117
118		num_waiting = (cond->waiting - cond->signals);
119		cond->signals = cond->waiting;
120		for ( i=0; i<num_waiting; ++i ) {
121			SDL_SemPost(cond->wait_sem);
122		}
123		/* Now all released threads are blocked here, waiting for us.
124		   Collect them all (and win fabulous prizes!) :-)
125		 */
126		SDL_UnlockMutex(cond->lock);
127		for ( i=0; i<num_waiting; ++i ) {
128			SDL_SemWait(cond->wait_done);
129		}
130	} else {
131		SDL_UnlockMutex(cond->lock);
132	}
133
134	return 0;
135}
136
137/* Wait on the condition variable for at most 'ms' milliseconds.
138   The mutex must be locked before entering this function!
139   The mutex is unlocked during the wait, and locked again after the wait.
140
141Typical use:
142
143Thread A:
144	SDL_LockMutex(lock);
145	while ( ! condition ) {
146		SDL_CondWait(cond);
147	}
148	SDL_UnlockMutex(lock);
149
150Thread B:
151	SDL_LockMutex(lock);
152	...
153	condition = true;
154	...
155	SDL_UnlockMutex(lock);
156 */
157int SDL_CondWaitTimeout(SDL_cond *cond, SDL_mutex *mutex, Uint32 ms)
158{
159	int retval;
160
161	if ( ! cond ) {
162		SDL_SetError("Passed a NULL condition variable");
163		return -1;
164	}
165
166	/* Obtain the protection mutex, and increment the number of waiters.
167	   This allows the signal mechanism to only perform a signal if there
168	   are waiting threads.
169	 */
170	SDL_LockMutex(cond->lock);
171	++cond->waiting;
172	SDL_UnlockMutex(cond->lock);
173
174	/* Unlock the mutex, as is required by condition variable semantics */
175	SDL_UnlockMutex(mutex);
176
177	/* Wait for a signal */
178	if ( ms == SDL_MUTEX_MAXWAIT ) {
179		retval = SDL_SemWait(cond->wait_sem);
180	} else {
181		retval = SDL_SemWaitTimeout(cond->wait_sem, ms);
182	}
183
184	/* Let the signaler know we have completed the wait, otherwise
185           the signaler can race ahead and get the condition semaphore
186           if we are stopped between the mutex unlock and semaphore wait,
187           giving a deadlock.  See the following URL for details:
188        http://www-classic.be.com/aboutbe/benewsletter/volume_III/Issue40.html
189	*/
190	SDL_LockMutex(cond->lock);
191	if ( cond->signals > 0 ) {
192		/* If we timed out, we need to eat a condition signal */
193		if ( retval > 0 ) {
194			SDL_SemWait(cond->wait_sem);
195		}
196		/* We always notify the signal thread that we are done */
197		SDL_SemPost(cond->wait_done);
198
199		/* Signal handshake complete */
200		--cond->signals;
201	}
202	--cond->waiting;
203	SDL_UnlockMutex(cond->lock);
204
205	/* Lock the mutex, as is required by condition variable semantics */
206	SDL_LockMutex(mutex);
207
208	return retval;
209}
210
211/* Wait on the condition variable forever */
212int SDL_CondWait(SDL_cond *cond, SDL_mutex *mutex)
213{
214	return SDL_CondWaitTimeout(cond, mutex, SDL_MUTEX_MAXWAIT);
215}
216