1 /*         ______   ___    ___
2  *        /\  _  \ /\_ \  /\_ \
3  *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___
4  *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
5  *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
6  *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
7  *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
8  *                                           /\____/
9  *                                           \_/__/
10  *
11  *	Internal cross-platform threading API for Windows.
12  *
13  *	By Peter Wang.
14  *
15  *      See readme.txt for copyright information.
16  */
17 
18 
19 #include "allegro5/allegro.h"
20 #include "allegro5/internal/aintern.h"
21 #include "allegro5/internal/aintern_thread.h"
22 
23 #include <mmsystem.h>
24 #include <process.h>
25 
26 
27 
28 /* threads */
29 
thread_proc_trampoline(void * data)30 static unsigned __stdcall thread_proc_trampoline(void *data)
31 {
32    _AL_THREAD *thread = data;
33    (*thread->proc)(thread, thread->arg);
34 
35    /* _endthreadex does not automatically close the thread handle,
36     * unlike _endthread.  We rely on this in al_join_thread().
37     */
38    _endthreadex(0);
39    return 0;
40 }
41 
42 
_al_thread_create(_AL_THREAD * thread,void (* proc)(_AL_THREAD *,void *),void * arg)43 void _al_thread_create(_AL_THREAD *thread, void (*proc)(_AL_THREAD*, void*), void *arg)
44 {
45    ASSERT(thread);
46    ASSERT(proc);
47    {
48       InitializeCriticalSection(&thread->cs);
49 
50       thread->should_stop = false;
51       thread->proc = proc;
52       thread->arg = arg;
53 
54       thread->thread = (void *)_beginthreadex(NULL, 0,
55          thread_proc_trampoline, thread, 0, NULL);
56    }
57 }
58 
59 
_al_thread_create_with_stacksize(_AL_THREAD * thread,void (* proc)(_AL_THREAD *,void *),void * arg,size_t stacksize)60 void _al_thread_create_with_stacksize(_AL_THREAD* thread, void (*proc)(_AL_THREAD*, void*), void *arg, size_t stacksize)
61 {
62    ASSERT(thread);
63    ASSERT(proc);
64    {
65       InitializeCriticalSection(&thread->cs);
66 
67       thread->should_stop = false;
68       thread->proc = proc;
69       thread->arg = arg;
70 
71       thread->thread = (void *)_beginthreadex(NULL, stacksize,
72          thread_proc_trampoline, thread, 0, NULL);
73    }
74 }
75 
76 
_al_thread_set_should_stop(_AL_THREAD * thread)77 void _al_thread_set_should_stop(_AL_THREAD *thread)
78 {
79    ASSERT(thread);
80 
81    EnterCriticalSection(&thread->cs);
82    {
83       thread->should_stop = true;
84    }
85    LeaveCriticalSection(&thread->cs);
86 }
87 
88 
89 
_al_thread_join(_AL_THREAD * thread)90 void _al_thread_join(_AL_THREAD *thread)
91 {
92    ASSERT(thread);
93 
94    _al_thread_set_should_stop(thread);
95    WaitForSingleObject(thread->thread, INFINITE);
96 
97    CloseHandle(thread->thread);
98    DeleteCriticalSection(&thread->cs);
99 }
100 
101 
_al_thread_detach(_AL_THREAD * thread)102 void _al_thread_detach(_AL_THREAD *thread)
103 {
104    ASSERT(thread);
105 
106    CloseHandle(thread->thread);
107    DeleteCriticalSection(&thread->cs);
108 }
109 
110 
111 
112 /* mutexes */
113 
_al_mutex_init(_AL_MUTEX * mutex)114 void _al_mutex_init(_AL_MUTEX *mutex)
115 {
116    ASSERT(mutex);
117 
118    if (!mutex->cs)
119       mutex->cs = al_malloc(sizeof *mutex->cs);
120    ASSERT(mutex->cs);
121    if (mutex->cs)
122       InitializeCriticalSection(mutex->cs);
123    else
124       abort();
125 }
126 
127 
_al_mutex_init_recursive(_AL_MUTEX * mutex)128 void _al_mutex_init_recursive(_AL_MUTEX *mutex)
129 {
130    /* These are the same on Windows. */
131    _al_mutex_init(mutex);
132 }
133 
134 
_al_mutex_destroy(_AL_MUTEX * mutex)135 void _al_mutex_destroy(_AL_MUTEX *mutex)
136 {
137    ASSERT(mutex);
138 
139    if (mutex->cs) {
140       DeleteCriticalSection(mutex->cs);
141       al_free(mutex->cs);
142       mutex->cs = NULL;
143    }
144 }
145 
146 
147 /* condition variables */
148 
149 /*
150  * The algorithm used was originally designed for the pthread-win32
151  * package.  I translated the pseudo-code below line for line.
152  *
153  * --- From pthread-win32: ---
154  * Algorithm:
155  * The algorithm used in this implementation is that developed by
156  * Alexander Terekhov in colaboration with Louis Thomas. The bulk
157  * of the discussion is recorded in the file README.CV, which contains
158  * several generations of both colaborators original algorithms. The final
159  * algorithm used here is the one referred to as
160  *
161  *     Algorithm 8a / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ALL
162  *
163  * presented below in pseudo-code as it appeared:
164  *
165  * [snip]
166  * -------------------------------------------------------------
167  *
168  *     Algorithm 9 / IMPL_SEM,UNBLOCK_STRATEGY == UNBLOCK_ALL
169  *
170  * presented below in pseudo-code; basically 8a...
171  *                                      ...BUT W/O "spurious wakes" prevention:
172  *
173  *
174  * given:
175  * semBlockLock - bin.semaphore
176  * semBlockQueue - semaphore
177  * mtxExternal - mutex or CS
178  * mtxUnblockLock - mutex or CS
179  * nWaitersGone - int
180  * nWaitersBlocked - int
181  * nWaitersToUnblock - int
182  *
183  * wait( timeout ) {
184  *
185  *   [auto: register int result          ]     // error checking omitted
186  *   [auto: register int nSignalsWasLeft ]
187  *
188  *   sem_wait( semBlockLock );
189  *   ++nWaitersBlocked;
190  *   sem_post( semBlockLock );
191  *
192  *   unlock( mtxExternal );
193  *   bTimedOut = sem_wait( semBlockQueue,timeout );
194  *
195  *   lock( mtxUnblockLock );
196  *   if ( 0 != (nSignalsWasLeft = nWaitersToUnblock) ) {
197  *     --nWaitersToUnblock;
198  *   }
199  *   else if ( INT_MAX/2 == ++nWaitersGone ) { // timeout/canceled or
200  *                                             // spurious semaphore :-)
201  *     sem_wait( semBlockLock );
202  *     nWaitersBlocked -= nWaitersGone;        // something is going on here
203  *                                             //  - test of timeouts? :-)
204  *     sem_post( semBlockLock );
205  *     nWaitersGone = 0;
206  *   }
207  *   unlock( mtxUnblockLock );
208  *
209  *   if ( 1 == nSignalsWasLeft ) {
210  *     sem_post( semBlockLock );               // open the gate
211  *   }
212  *
213  *   lock( mtxExternal );
214  *
215  *   return ( bTimedOut ) ? ETIMEOUT : 0;
216  * }
217  *
218  * signal(bAll) {
219  *
220  *   [auto: register int result         ]
221  *   [auto: register int nSignalsToIssue]
222  *
223  *   lock( mtxUnblockLock );
224  *
225  *   if ( 0 != nWaitersToUnblock ) {        // the gate is closed!!!
226  *     if ( 0 == nWaitersBlocked ) {        // NO-OP
227  *       return unlock( mtxUnblockLock );
228  *     }
229  *     if (bAll) {
230  *       nWaitersToUnblock += nSignalsToIssue=nWaitersBlocked;
231  *       nWaitersBlocked = 0;
232  *     }
233  *     else {
234  *       nSignalsToIssue = 1;
235  *       ++nWaitersToUnblock;
236  *       --nWaitersBlocked;
237  *     }
238  *   }
239  *   else if ( nWaitersBlocked > nWaitersGone ) { // HARMLESS RACE CONDITION!
240  *     sem_wait( semBlockLock );                  // close the gate
241  *     if ( 0 != nWaitersGone ) {
242  *       nWaitersBlocked -= nWaitersGone;
243  *       nWaitersGone = 0;
244  *     }
245  *     if (bAll) {
246  *       nSignalsToIssue = nWaitersToUnblock = nWaitersBlocked;
247  *       nWaitersBlocked = 0;
248  *     }
249  *     else {
250  *       nSignalsToIssue = nWaitersToUnblock = 1;
251  *       --nWaitersBlocked;
252  *     }
253  *   }
254  *   else { // NO-OP
255  *     return unlock( mtxUnblockLock );
256  *   }
257  *
258  *   unlock( mtxUnblockLock );
259  *   sem_post( semBlockQueue,nSignalsToIssue );
260  *   return result;
261  * }
262  * -------------------------------------------------------------
263  */
264 
265 
_al_cond_init(_AL_COND * cond)266 void _al_cond_init(_AL_COND *cond)
267 {
268    cond->nWaitersBlocked = 0;
269    cond->nWaitersGone = 0;
270    cond->nWaitersToUnblock = 0;
271    cond->semBlockQueue = CreateSemaphore(NULL, 0, INT_MAX, NULL);
272    InitializeCriticalSection(&cond->semBlockLock);
273    InitializeCriticalSection(&cond->mtxUnblockLock);
274 }
275 
276 
_al_cond_destroy(_AL_COND * cond)277 void _al_cond_destroy(_AL_COND *cond)
278 {
279    DeleteCriticalSection(&cond->mtxUnblockLock);
280    DeleteCriticalSection(&cond->semBlockLock);
281    CloseHandle(cond->semBlockQueue);
282 }
283 
284 
285 /* returns -1 on timeout */
cond_wait(_AL_COND * cond,_AL_MUTEX * mtxExternal,DWORD timeout)286 static int cond_wait(_AL_COND *cond, _AL_MUTEX *mtxExternal, DWORD timeout)
287 {
288    int nSignalsWasLeft;
289    bool bTimedOut;
290    DWORD dwWaitResult;
291 
292    EnterCriticalSection(&cond->semBlockLock);
293    ++cond->nWaitersBlocked;
294    LeaveCriticalSection(&cond->semBlockLock);
295 
296    _al_mutex_unlock(mtxExternal);
297 
298    dwWaitResult = WaitForSingleObject(cond->semBlockQueue, timeout);
299    if (dwWaitResult == WAIT_TIMEOUT)
300       bTimedOut = true;
301    else if (dwWaitResult == WAIT_OBJECT_0)
302       bTimedOut = false;
303    else {
304       /* bad! what to do? */
305       _al_mutex_lock(mtxExternal);
306       ASSERT(false);
307       return 0;
308    }
309 
310    EnterCriticalSection(&cond->mtxUnblockLock);
311    if (0 != (nSignalsWasLeft = cond->nWaitersToUnblock)) {
312       --(cond->nWaitersToUnblock);
313    }
314    else if (INT_MAX/2 == ++(cond->nWaitersGone)) { /* timeout/canceled or spurious semaphore :-) */
315       EnterCriticalSection(&cond->semBlockLock);
316       cond->nWaitersBlocked -= cond->nWaitersGone; /* something is going on here
317                                                       - test of timeouts? :-) */
318       LeaveCriticalSection(&cond->semBlockLock);
319       cond->nWaitersGone = 0;
320    }
321    LeaveCriticalSection(&cond->mtxUnblockLock);
322 
323    if (1 == nSignalsWasLeft) {
324       LeaveCriticalSection(&cond->semBlockLock); /* open the gate */
325    }
326 
327    _al_mutex_lock(mtxExternal);
328 
329    return bTimedOut ? -1 : 0;
330 }
331 
332 
_al_cond_wait(_AL_COND * cond,_AL_MUTEX * mtxExternal)333 void _al_cond_wait(_AL_COND *cond, _AL_MUTEX *mtxExternal)
334 {
335    int result;
336 
337    ASSERT(cond);
338    ASSERT(mtxExternal);
339 
340    result = cond_wait(cond, mtxExternal, INFINITE);
341    ASSERT(result != -1);
342    (void)result;
343 }
344 
345 
_al_cond_timedwait(_AL_COND * cond,_AL_MUTEX * mtxExternal,const ALLEGRO_TIMEOUT * timeout)346 int _al_cond_timedwait(_AL_COND *cond, _AL_MUTEX *mtxExternal,
347    const ALLEGRO_TIMEOUT *timeout)
348 {
349    ALLEGRO_TIMEOUT_WIN *win_timeout = (ALLEGRO_TIMEOUT_WIN *) timeout;
350    DWORD now;
351    DWORD rel_msecs;
352 
353    ASSERT(cond);
354    ASSERT(mtxExternal);
355 
356    now = timeGetTime();
357    rel_msecs = win_timeout->abstime - now;
358    if (rel_msecs > INT_MAX) {
359       rel_msecs = 0;
360    }
361 
362    return cond_wait(cond, mtxExternal, rel_msecs);
363 }
364 
365 
cond_signal(_AL_COND * cond,bool bAll)366 static void cond_signal(_AL_COND *cond, bool bAll)
367 {
368    int nSignalsToIssue;
369 
370    EnterCriticalSection(&cond->mtxUnblockLock);
371 
372    if (0 != cond->nWaitersToUnblock) {   /* the gate is closed!!! */
373       if (0 == cond->nWaitersBlocked) {  /* NO-OP */
374          LeaveCriticalSection(&cond->mtxUnblockLock);
375          return;
376       }
377       if (bAll) {
378          cond->nWaitersToUnblock += (nSignalsToIssue = cond->nWaitersBlocked);
379          cond->nWaitersBlocked = 0;
380       }
381       else {
382          nSignalsToIssue = 1;
383          ++cond->nWaitersToUnblock;
384          --cond->nWaitersBlocked;
385       }
386    }
387    else if (cond->nWaitersBlocked > cond->nWaitersGone) { /* HARMLESS RACE CONDITION! */
388       EnterCriticalSection(&cond->semBlockLock); /* close the gate */
389       if (0 != cond->nWaitersGone) {
390          cond->nWaitersBlocked -= cond->nWaitersGone;
391          cond->nWaitersGone = 0;
392       }
393       if (bAll) {
394          nSignalsToIssue = (cond->nWaitersToUnblock = cond->nWaitersBlocked);
395          cond->nWaitersBlocked = 0;
396       }
397       else {
398          nSignalsToIssue = cond->nWaitersToUnblock = 1;
399          --cond->nWaitersBlocked;
400       }
401    }
402    else { /* NO-OP */
403       LeaveCriticalSection(&cond->mtxUnblockLock);
404       return;
405    }
406 
407    LeaveCriticalSection(&cond->mtxUnblockLock);
408    ReleaseSemaphore(cond->semBlockQueue, nSignalsToIssue, NULL);
409    return;
410 }
411 
412 
_al_cond_broadcast(_AL_COND * cond)413 void _al_cond_broadcast(_AL_COND *cond)
414 {
415    cond_signal(cond, true);
416 }
417 
418 
_al_cond_signal(_AL_COND * cond)419 void _al_cond_signal(_AL_COND *cond)
420 {
421    cond_signal(cond, false);
422 }
423 
424 
425 /* vim: set sts=3 sw=3 et: */
426