1 | /* |
2 | Copyright (C) 1997-2021 Sam Lantinga <slouken@libsdl.org> |
3 | |
4 | This software is provided 'as-is', without any express or implied |
5 | warranty. In no event will the authors be held liable for any damages |
6 | arising from the use of this software. |
7 | |
8 | Permission is granted to anyone to use this software for any purpose, |
9 | including commercial applications, and to alter it and redistribute it |
10 | freely. |
11 | */ |
12 | #include <stdio.h> |
13 | |
14 | #include "SDL.h" |
15 | |
16 | /* |
17 | Absolutely basic tests just to see if we get the expected value |
18 | after calling each function. |
19 | */ |
20 | |
21 | static |
22 | char * |
23 | tf(SDL_bool tf) |
24 | { |
25 | static char *t = "TRUE" ; |
26 | static char *f = "FALSE" ; |
27 | |
28 | if (tf) |
29 | { |
30 | return t; |
31 | } |
32 | |
33 | return f; |
34 | } |
35 | |
36 | static |
37 | void RunBasicTest() |
38 | { |
39 | int value; |
40 | SDL_SpinLock lock = 0; |
41 | |
42 | SDL_atomic_t v; |
43 | SDL_bool tfret = SDL_FALSE; |
44 | |
45 | SDL_Log("\nspin lock---------------------------------------\n\n" ); |
46 | |
47 | SDL_AtomicLock(&lock); |
48 | SDL_Log("AtomicLock lock=%d\n" , lock); |
49 | SDL_AtomicUnlock(&lock); |
50 | SDL_Log("AtomicUnlock lock=%d\n" , lock); |
51 | |
52 | SDL_Log("\natomic -----------------------------------------\n\n" ); |
53 | |
54 | SDL_AtomicSet(&v, 0); |
55 | tfret = SDL_AtomicSet(&v, 10) == 0 ? SDL_TRUE : SDL_FALSE; |
56 | SDL_Log("AtomicSet(10) tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
57 | tfret = SDL_AtomicAdd(&v, 10) == 10 ? SDL_TRUE : SDL_FALSE; |
58 | SDL_Log("AtomicAdd(10) tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
59 | |
60 | SDL_AtomicSet(&v, 0); |
61 | SDL_AtomicIncRef(&v); |
62 | tfret = (SDL_AtomicGet(&v) == 1) ? SDL_TRUE : SDL_FALSE; |
63 | SDL_Log("AtomicIncRef() tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
64 | SDL_AtomicIncRef(&v); |
65 | tfret = (SDL_AtomicGet(&v) == 2) ? SDL_TRUE : SDL_FALSE; |
66 | SDL_Log("AtomicIncRef() tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
67 | tfret = (SDL_AtomicDecRef(&v) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE; |
68 | SDL_Log("AtomicDecRef() tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
69 | tfret = (SDL_AtomicDecRef(&v) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE; |
70 | SDL_Log("AtomicDecRef() tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
71 | |
72 | SDL_AtomicSet(&v, 10); |
73 | tfret = (SDL_AtomicCAS(&v, 0, 20) == SDL_FALSE) ? SDL_TRUE : SDL_FALSE; |
74 | SDL_Log("AtomicCAS() tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
75 | value = SDL_AtomicGet(&v); |
76 | tfret = (SDL_AtomicCAS(&v, value, 20) == SDL_TRUE) ? SDL_TRUE : SDL_FALSE; |
77 | SDL_Log("AtomicCAS() tfret=%s val=%d\n" , tf(tfret), SDL_AtomicGet(&v)); |
78 | } |
79 | |
80 | /**************************************************************************/ |
81 | /* Atomic operation test |
82 | * Adapted with permission from code by Michael Davidsaver at: |
83 | * http://bazaar.launchpad.net/~mdavidsaver/epics-base/atomic/revision/12105#src/libCom/test/epicsAtomicTest.c |
84 | * Original copyright 2010 Brookhaven Science Associates as operator of Brookhaven National Lab |
85 | * http://www.aps.anl.gov/epics/license/open.php |
86 | */ |
87 | |
88 | /* Tests semantics of atomic operations. Also a stress test |
89 | * to see if they are really atomic. |
90 | * |
91 | * Several threads adding to the same variable. |
92 | * at the end the value is compared with the expected |
93 | * and with a non-atomic counter. |
94 | */ |
95 | |
96 | /* Number of concurrent incrementers */ |
97 | #define NThreads 2 |
98 | #define CountInc 100 |
99 | #define VALBITS (sizeof(atomicValue)*8) |
100 | |
101 | #define atomicValue int |
102 | #define CountTo ((atomicValue)((unsigned int)(1<<(VALBITS-1))-1)) |
103 | #define NInter (CountTo/CountInc/NThreads) |
104 | #define Expect (CountTo-NInter*CountInc*NThreads) |
105 | |
106 | enum { |
107 | CountTo_GreaterThanZero = CountTo > 0, |
108 | }; |
109 | SDL_COMPILE_TIME_ASSERT(size, CountTo_GreaterThanZero); /* check for rollover */ |
110 | |
111 | static SDL_atomic_t good = { 42 }; |
112 | |
113 | static atomicValue bad = 42; |
114 | |
115 | static SDL_atomic_t threadsRunning; |
116 | |
117 | static SDL_sem *threadDone; |
118 | |
119 | static |
120 | int SDLCALL adder(void* junk) |
121 | { |
122 | unsigned long N=NInter; |
123 | SDL_Log("Thread subtracting %d %lu times\n" ,CountInc,N); |
124 | while (N--) { |
125 | SDL_AtomicAdd(&good, -CountInc); |
126 | bad-=CountInc; |
127 | } |
128 | SDL_AtomicAdd(&threadsRunning, -1); |
129 | SDL_SemPost(threadDone); |
130 | return 0; |
131 | } |
132 | |
133 | static |
134 | void runAdder(void) |
135 | { |
136 | Uint32 start, end; |
137 | int T=NThreads; |
138 | |
139 | start = SDL_GetTicks(); |
140 | |
141 | threadDone = SDL_CreateSemaphore(0); |
142 | |
143 | SDL_AtomicSet(&threadsRunning, NThreads); |
144 | |
145 | while (T--) |
146 | SDL_CreateThread(adder, "Adder" , NULL); |
147 | |
148 | while (SDL_AtomicGet(&threadsRunning) > 0) |
149 | SDL_SemWait(threadDone); |
150 | |
151 | SDL_DestroySemaphore(threadDone); |
152 | |
153 | end = SDL_GetTicks(); |
154 | |
155 | SDL_Log("Finished in %f sec\n" , (end - start) / 1000.f); |
156 | } |
157 | |
158 | static |
159 | void RunEpicTest() |
160 | { |
161 | int b; |
162 | atomicValue v; |
163 | |
164 | SDL_Log("\nepic test---------------------------------------\n\n" ); |
165 | |
166 | SDL_Log("Size asserted to be >= 32-bit\n" ); |
167 | SDL_assert(sizeof(atomicValue)>=4); |
168 | |
169 | SDL_Log("Check static initializer\n" ); |
170 | v=SDL_AtomicGet(&good); |
171 | SDL_assert(v==42); |
172 | |
173 | SDL_assert(bad==42); |
174 | |
175 | SDL_Log("Test negative values\n" ); |
176 | SDL_AtomicSet(&good, -5); |
177 | v=SDL_AtomicGet(&good); |
178 | SDL_assert(v==-5); |
179 | |
180 | SDL_Log("Verify maximum value\n" ); |
181 | SDL_AtomicSet(&good, CountTo); |
182 | v=SDL_AtomicGet(&good); |
183 | SDL_assert(v==CountTo); |
184 | |
185 | SDL_Log("Test compare and exchange\n" ); |
186 | |
187 | b=SDL_AtomicCAS(&good, 500, 43); |
188 | SDL_assert(!b); /* no swap since CountTo!=500 */ |
189 | v=SDL_AtomicGet(&good); |
190 | SDL_assert(v==CountTo); /* ensure no swap */ |
191 | |
192 | b=SDL_AtomicCAS(&good, CountTo, 44); |
193 | SDL_assert(!!b); /* will swap */ |
194 | v=SDL_AtomicGet(&good); |
195 | SDL_assert(v==44); |
196 | |
197 | SDL_Log("Test Add\n" ); |
198 | |
199 | v=SDL_AtomicAdd(&good, 1); |
200 | SDL_assert(v==44); |
201 | v=SDL_AtomicGet(&good); |
202 | SDL_assert(v==45); |
203 | |
204 | v=SDL_AtomicAdd(&good, 10); |
205 | SDL_assert(v==45); |
206 | v=SDL_AtomicGet(&good); |
207 | SDL_assert(v==55); |
208 | |
209 | SDL_Log("Test Add (Negative values)\n" ); |
210 | |
211 | v=SDL_AtomicAdd(&good, -20); |
212 | SDL_assert(v==55); |
213 | v=SDL_AtomicGet(&good); |
214 | SDL_assert(v==35); |
215 | |
216 | v=SDL_AtomicAdd(&good, -50); /* crossing zero down */ |
217 | SDL_assert(v==35); |
218 | v=SDL_AtomicGet(&good); |
219 | SDL_assert(v==-15); |
220 | |
221 | v=SDL_AtomicAdd(&good, 30); /* crossing zero up */ |
222 | SDL_assert(v==-15); |
223 | v=SDL_AtomicGet(&good); |
224 | SDL_assert(v==15); |
225 | |
226 | SDL_Log("Reset before count down test\n" ); |
227 | SDL_AtomicSet(&good, CountTo); |
228 | v=SDL_AtomicGet(&good); |
229 | SDL_assert(v==CountTo); |
230 | |
231 | bad=CountTo; |
232 | SDL_assert(bad==CountTo); |
233 | |
234 | SDL_Log("Counting down from %d, Expect %d remaining\n" ,CountTo,Expect); |
235 | runAdder(); |
236 | |
237 | v=SDL_AtomicGet(&good); |
238 | SDL_Log("Atomic %d Non-Atomic %d\n" ,v,bad); |
239 | SDL_assert(v==Expect); |
240 | SDL_assert(bad!=Expect); |
241 | } |
242 | |
243 | /* End atomic operation test */ |
244 | /**************************************************************************/ |
245 | |
246 | /**************************************************************************/ |
247 | /* Lock-free FIFO test */ |
248 | |
249 | /* This is useful to test the impact of another thread locking the queue |
250 | entirely for heavy-weight manipulation. |
251 | */ |
252 | #define TEST_SPINLOCK_FIFO |
253 | |
254 | #define NUM_READERS 4 |
255 | #define NUM_WRITERS 4 |
256 | #define EVENTS_PER_WRITER 1000000 |
257 | |
258 | /* The number of entries must be a power of 2 */ |
259 | #define MAX_ENTRIES 256 |
260 | #define WRAP_MASK (MAX_ENTRIES-1) |
261 | |
262 | typedef struct |
263 | { |
264 | SDL_atomic_t sequence; |
265 | SDL_Event event; |
266 | } SDL_EventQueueEntry; |
267 | |
268 | typedef struct |
269 | { |
270 | SDL_EventQueueEntry entries[MAX_ENTRIES]; |
271 | |
272 | char cache_pad1[SDL_CACHELINE_SIZE-((sizeof(SDL_EventQueueEntry)*MAX_ENTRIES)%SDL_CACHELINE_SIZE)]; |
273 | |
274 | SDL_atomic_t enqueue_pos; |
275 | |
276 | char cache_pad2[SDL_CACHELINE_SIZE-sizeof(SDL_atomic_t)]; |
277 | |
278 | SDL_atomic_t dequeue_pos; |
279 | |
280 | char cache_pad3[SDL_CACHELINE_SIZE-sizeof(SDL_atomic_t)]; |
281 | |
282 | #ifdef TEST_SPINLOCK_FIFO |
283 | SDL_SpinLock lock; |
284 | SDL_atomic_t rwcount; |
285 | SDL_atomic_t watcher; |
286 | |
287 | char cache_pad4[SDL_CACHELINE_SIZE-sizeof(SDL_SpinLock)-2*sizeof(SDL_atomic_t)]; |
288 | #endif |
289 | |
290 | SDL_atomic_t active; |
291 | |
292 | /* Only needed for the mutex test */ |
293 | SDL_mutex *mutex; |
294 | |
295 | } SDL_EventQueue; |
296 | |
297 | static void InitEventQueue(SDL_EventQueue *queue) |
298 | { |
299 | int i; |
300 | |
301 | for (i = 0; i < MAX_ENTRIES; ++i) { |
302 | SDL_AtomicSet(&queue->entries[i].sequence, i); |
303 | } |
304 | SDL_AtomicSet(&queue->enqueue_pos, 0); |
305 | SDL_AtomicSet(&queue->dequeue_pos, 0); |
306 | #ifdef TEST_SPINLOCK_FIFO |
307 | queue->lock = 0; |
308 | SDL_AtomicSet(&queue->rwcount, 0); |
309 | SDL_AtomicSet(&queue->watcher, 0); |
310 | #endif |
311 | SDL_AtomicSet(&queue->active, 1); |
312 | } |
313 | |
314 | static SDL_bool EnqueueEvent_LockFree(SDL_EventQueue *queue, const SDL_Event *event) |
315 | { |
316 | SDL_EventQueueEntry *entry; |
317 | unsigned queue_pos; |
318 | unsigned entry_seq; |
319 | int delta; |
320 | SDL_bool status; |
321 | |
322 | #ifdef TEST_SPINLOCK_FIFO |
323 | /* This is a gate so an external thread can lock the queue */ |
324 | SDL_AtomicLock(&queue->lock); |
325 | SDL_assert(SDL_AtomicGet(&queue->watcher) == 0); |
326 | SDL_AtomicIncRef(&queue->rwcount); |
327 | SDL_AtomicUnlock(&queue->lock); |
328 | #endif |
329 | |
330 | queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos); |
331 | for ( ; ; ) { |
332 | entry = &queue->entries[queue_pos & WRAP_MASK]; |
333 | entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence); |
334 | |
335 | delta = (int)(entry_seq - queue_pos); |
336 | if (delta == 0) { |
337 | /* The entry and the queue position match, try to increment the queue position */ |
338 | if (SDL_AtomicCAS(&queue->enqueue_pos, (int)queue_pos, (int)(queue_pos+1))) { |
339 | /* We own the object, fill it! */ |
340 | entry->event = *event; |
341 | SDL_AtomicSet(&entry->sequence, (int)(queue_pos + 1)); |
342 | status = SDL_TRUE; |
343 | break; |
344 | } |
345 | } else if (delta < 0) { |
346 | /* We ran into an old queue entry, which means it still needs to be dequeued */ |
347 | status = SDL_FALSE; |
348 | break; |
349 | } else { |
350 | /* We ran into a new queue entry, get the new queue position */ |
351 | queue_pos = (unsigned)SDL_AtomicGet(&queue->enqueue_pos); |
352 | } |
353 | } |
354 | |
355 | #ifdef TEST_SPINLOCK_FIFO |
356 | (void) SDL_AtomicDecRef(&queue->rwcount); |
357 | #endif |
358 | return status; |
359 | } |
360 | |
361 | static SDL_bool DequeueEvent_LockFree(SDL_EventQueue *queue, SDL_Event *event) |
362 | { |
363 | SDL_EventQueueEntry *entry; |
364 | unsigned queue_pos; |
365 | unsigned entry_seq; |
366 | int delta; |
367 | SDL_bool status; |
368 | |
369 | #ifdef TEST_SPINLOCK_FIFO |
370 | /* This is a gate so an external thread can lock the queue */ |
371 | SDL_AtomicLock(&queue->lock); |
372 | SDL_assert(SDL_AtomicGet(&queue->watcher) == 0); |
373 | SDL_AtomicIncRef(&queue->rwcount); |
374 | SDL_AtomicUnlock(&queue->lock); |
375 | #endif |
376 | |
377 | queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos); |
378 | for ( ; ; ) { |
379 | entry = &queue->entries[queue_pos & WRAP_MASK]; |
380 | entry_seq = (unsigned)SDL_AtomicGet(&entry->sequence); |
381 | |
382 | delta = (int)(entry_seq - (queue_pos + 1)); |
383 | if (delta == 0) { |
384 | /* The entry and the queue position match, try to increment the queue position */ |
385 | if (SDL_AtomicCAS(&queue->dequeue_pos, (int)queue_pos, (int)(queue_pos+1))) { |
386 | /* We own the object, fill it! */ |
387 | *event = entry->event; |
388 | SDL_AtomicSet(&entry->sequence, (int)(queue_pos+MAX_ENTRIES)); |
389 | status = SDL_TRUE; |
390 | break; |
391 | } |
392 | } else if (delta < 0) { |
393 | /* We ran into an old queue entry, which means we've hit empty */ |
394 | status = SDL_FALSE; |
395 | break; |
396 | } else { |
397 | /* We ran into a new queue entry, get the new queue position */ |
398 | queue_pos = (unsigned)SDL_AtomicGet(&queue->dequeue_pos); |
399 | } |
400 | } |
401 | |
402 | #ifdef TEST_SPINLOCK_FIFO |
403 | (void) SDL_AtomicDecRef(&queue->rwcount); |
404 | #endif |
405 | return status; |
406 | } |
407 | |
408 | static SDL_bool EnqueueEvent_Mutex(SDL_EventQueue *queue, const SDL_Event *event) |
409 | { |
410 | SDL_EventQueueEntry *entry; |
411 | unsigned queue_pos; |
412 | unsigned entry_seq; |
413 | int delta; |
414 | SDL_bool status = SDL_FALSE; |
415 | |
416 | SDL_LockMutex(queue->mutex); |
417 | |
418 | queue_pos = (unsigned)queue->enqueue_pos.value; |
419 | entry = &queue->entries[queue_pos & WRAP_MASK]; |
420 | entry_seq = (unsigned)entry->sequence.value; |
421 | |
422 | delta = (int)(entry_seq - queue_pos); |
423 | if (delta == 0) { |
424 | ++queue->enqueue_pos.value; |
425 | |
426 | /* We own the object, fill it! */ |
427 | entry->event = *event; |
428 | entry->sequence.value = (int)(queue_pos + 1); |
429 | status = SDL_TRUE; |
430 | } else if (delta < 0) { |
431 | /* We ran into an old queue entry, which means it still needs to be dequeued */ |
432 | } else { |
433 | SDL_Log("ERROR: mutex failed!\n" ); |
434 | } |
435 | |
436 | SDL_UnlockMutex(queue->mutex); |
437 | |
438 | return status; |
439 | } |
440 | |
441 | static SDL_bool DequeueEvent_Mutex(SDL_EventQueue *queue, SDL_Event *event) |
442 | { |
443 | SDL_EventQueueEntry *entry; |
444 | unsigned queue_pos; |
445 | unsigned entry_seq; |
446 | int delta; |
447 | SDL_bool status = SDL_FALSE; |
448 | |
449 | SDL_LockMutex(queue->mutex); |
450 | |
451 | queue_pos = (unsigned)queue->dequeue_pos.value; |
452 | entry = &queue->entries[queue_pos & WRAP_MASK]; |
453 | entry_seq = (unsigned)entry->sequence.value; |
454 | |
455 | delta = (int)(entry_seq - (queue_pos + 1)); |
456 | if (delta == 0) { |
457 | ++queue->dequeue_pos.value; |
458 | |
459 | /* We own the object, fill it! */ |
460 | *event = entry->event; |
461 | entry->sequence.value = (int)(queue_pos + MAX_ENTRIES); |
462 | status = SDL_TRUE; |
463 | } else if (delta < 0) { |
464 | /* We ran into an old queue entry, which means we've hit empty */ |
465 | } else { |
466 | SDL_Log("ERROR: mutex failed!\n" ); |
467 | } |
468 | |
469 | SDL_UnlockMutex(queue->mutex); |
470 | |
471 | return status; |
472 | } |
473 | |
474 | static SDL_sem *writersDone; |
475 | static SDL_sem *readersDone; |
476 | |
477 | typedef struct |
478 | { |
479 | SDL_EventQueue *queue; |
480 | int index; |
481 | char padding1[SDL_CACHELINE_SIZE-(sizeof(SDL_EventQueue*)+sizeof(int))%SDL_CACHELINE_SIZE]; |
482 | int waits; |
483 | SDL_bool lock_free; |
484 | char padding2[SDL_CACHELINE_SIZE-sizeof(int)-sizeof(SDL_bool)]; |
485 | } WriterData; |
486 | |
487 | typedef struct |
488 | { |
489 | SDL_EventQueue *queue; |
490 | int counters[NUM_WRITERS]; |
491 | int waits; |
492 | SDL_bool lock_free; |
493 | char padding[SDL_CACHELINE_SIZE-(sizeof(SDL_EventQueue*)+sizeof(int)*NUM_WRITERS+sizeof(int)+sizeof(SDL_bool))%SDL_CACHELINE_SIZE]; |
494 | } ReaderData; |
495 | |
496 | static int SDLCALL FIFO_Writer(void* _data) |
497 | { |
498 | WriterData *data = (WriterData *)_data; |
499 | SDL_EventQueue *queue = data->queue; |
500 | int i; |
501 | SDL_Event event; |
502 | |
503 | event.type = SDL_USEREVENT; |
504 | event.user.windowID = 0; |
505 | event.user.code = 0; |
506 | event.user.data1 = data; |
507 | event.user.data2 = NULL; |
508 | |
509 | if (data->lock_free) { |
510 | for (i = 0; i < EVENTS_PER_WRITER; ++i) { |
511 | event.user.code = i; |
512 | while (!EnqueueEvent_LockFree(queue, &event)) { |
513 | ++data->waits; |
514 | SDL_Delay(0); |
515 | } |
516 | } |
517 | } else { |
518 | for (i = 0; i < EVENTS_PER_WRITER; ++i) { |
519 | event.user.code = i; |
520 | while (!EnqueueEvent_Mutex(queue, &event)) { |
521 | ++data->waits; |
522 | SDL_Delay(0); |
523 | } |
524 | } |
525 | } |
526 | SDL_SemPost(writersDone); |
527 | return 0; |
528 | } |
529 | |
530 | static int SDLCALL FIFO_Reader(void* _data) |
531 | { |
532 | ReaderData *data = (ReaderData *)_data; |
533 | SDL_EventQueue *queue = data->queue; |
534 | SDL_Event event; |
535 | |
536 | if (data->lock_free) { |
537 | for ( ; ; ) { |
538 | if (DequeueEvent_LockFree(queue, &event)) { |
539 | WriterData *writer = (WriterData*)event.user.data1; |
540 | ++data->counters[writer->index]; |
541 | } else if (SDL_AtomicGet(&queue->active)) { |
542 | ++data->waits; |
543 | SDL_Delay(0); |
544 | } else { |
545 | /* We drained the queue, we're done! */ |
546 | break; |
547 | } |
548 | } |
549 | } else { |
550 | for ( ; ; ) { |
551 | if (DequeueEvent_Mutex(queue, &event)) { |
552 | WriterData *writer = (WriterData*)event.user.data1; |
553 | ++data->counters[writer->index]; |
554 | } else if (SDL_AtomicGet(&queue->active)) { |
555 | ++data->waits; |
556 | SDL_Delay(0); |
557 | } else { |
558 | /* We drained the queue, we're done! */ |
559 | break; |
560 | } |
561 | } |
562 | } |
563 | SDL_SemPost(readersDone); |
564 | return 0; |
565 | } |
566 | |
567 | #ifdef TEST_SPINLOCK_FIFO |
568 | /* This thread periodically locks the queue for no particular reason */ |
569 | static int SDLCALL FIFO_Watcher(void* _data) |
570 | { |
571 | SDL_EventQueue *queue = (SDL_EventQueue *)_data; |
572 | |
573 | while (SDL_AtomicGet(&queue->active)) { |
574 | SDL_AtomicLock(&queue->lock); |
575 | SDL_AtomicIncRef(&queue->watcher); |
576 | while (SDL_AtomicGet(&queue->rwcount) > 0) { |
577 | SDL_Delay(0); |
578 | } |
579 | /* Do queue manipulation here... */ |
580 | (void) SDL_AtomicDecRef(&queue->watcher); |
581 | SDL_AtomicUnlock(&queue->lock); |
582 | |
583 | /* Wait a bit... */ |
584 | SDL_Delay(1); |
585 | } |
586 | return 0; |
587 | } |
588 | #endif /* TEST_SPINLOCK_FIFO */ |
589 | |
590 | static void RunFIFOTest(SDL_bool lock_free) |
591 | { |
592 | SDL_EventQueue queue; |
593 | WriterData writerData[NUM_WRITERS]; |
594 | ReaderData readerData[NUM_READERS]; |
595 | Uint32 start, end; |
596 | int i, j; |
597 | int grand_total; |
598 | char textBuffer[1024]; |
599 | size_t len; |
600 | |
601 | SDL_Log("\nFIFO test---------------------------------------\n\n" ); |
602 | SDL_Log("Mode: %s\n" , lock_free ? "LockFree" : "Mutex" ); |
603 | |
604 | readersDone = SDL_CreateSemaphore(0); |
605 | writersDone = SDL_CreateSemaphore(0); |
606 | |
607 | SDL_memset(&queue, 0xff, sizeof(queue)); |
608 | |
609 | InitEventQueue(&queue); |
610 | if (!lock_free) { |
611 | queue.mutex = SDL_CreateMutex(); |
612 | } |
613 | |
614 | start = SDL_GetTicks(); |
615 | |
616 | #ifdef TEST_SPINLOCK_FIFO |
617 | /* Start a monitoring thread */ |
618 | if (lock_free) { |
619 | SDL_CreateThread(FIFO_Watcher, "FIFOWatcher" , &queue); |
620 | } |
621 | #endif |
622 | |
623 | /* Start the readers first */ |
624 | SDL_Log("Starting %d readers\n" , NUM_READERS); |
625 | SDL_zeroa(readerData); |
626 | for (i = 0; i < NUM_READERS; ++i) { |
627 | char name[64]; |
628 | SDL_snprintf(name, sizeof (name), "FIFOReader%d" , i); |
629 | readerData[i].queue = &queue; |
630 | readerData[i].lock_free = lock_free; |
631 | SDL_CreateThread(FIFO_Reader, name, &readerData[i]); |
632 | } |
633 | |
634 | /* Start up the writers */ |
635 | SDL_Log("Starting %d writers\n" , NUM_WRITERS); |
636 | SDL_zeroa(writerData); |
637 | for (i = 0; i < NUM_WRITERS; ++i) { |
638 | char name[64]; |
639 | SDL_snprintf(name, sizeof (name), "FIFOWriter%d" , i); |
640 | writerData[i].queue = &queue; |
641 | writerData[i].index = i; |
642 | writerData[i].lock_free = lock_free; |
643 | SDL_CreateThread(FIFO_Writer, name, &writerData[i]); |
644 | } |
645 | |
646 | /* Wait for the writers */ |
647 | for (i = 0; i < NUM_WRITERS; ++i) { |
648 | SDL_SemWait(writersDone); |
649 | } |
650 | |
651 | /* Shut down the queue so readers exit */ |
652 | SDL_AtomicSet(&queue.active, 0); |
653 | |
654 | /* Wait for the readers */ |
655 | for (i = 0; i < NUM_READERS; ++i) { |
656 | SDL_SemWait(readersDone); |
657 | } |
658 | |
659 | end = SDL_GetTicks(); |
660 | |
661 | SDL_DestroySemaphore(readersDone); |
662 | SDL_DestroySemaphore(writersDone); |
663 | |
664 | if (!lock_free) { |
665 | SDL_DestroyMutex(queue.mutex); |
666 | } |
667 | |
668 | SDL_Log("Finished in %f sec\n" , (end - start) / 1000.f); |
669 | |
670 | SDL_Log("\n" ); |
671 | for (i = 0; i < NUM_WRITERS; ++i) { |
672 | SDL_Log("Writer %d wrote %d events, had %d waits\n" , i, EVENTS_PER_WRITER, writerData[i].waits); |
673 | } |
674 | SDL_Log("Writers wrote %d total events\n" , NUM_WRITERS*EVENTS_PER_WRITER); |
675 | |
676 | /* Print a breakdown of which readers read messages from which writer */ |
677 | SDL_Log("\n" ); |
678 | grand_total = 0; |
679 | for (i = 0; i < NUM_READERS; ++i) { |
680 | int total = 0; |
681 | for (j = 0; j < NUM_WRITERS; ++j) { |
682 | total += readerData[i].counters[j]; |
683 | } |
684 | grand_total += total; |
685 | SDL_Log("Reader %d read %d events, had %d waits\n" , i, total, readerData[i].waits); |
686 | SDL_snprintf(textBuffer, sizeof(textBuffer), " { " ); |
687 | for (j = 0; j < NUM_WRITERS; ++j) { |
688 | if (j > 0) { |
689 | len = SDL_strlen(textBuffer); |
690 | SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, ", " ); |
691 | } |
692 | len = SDL_strlen(textBuffer); |
693 | SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, "%d" , readerData[i].counters[j]); |
694 | } |
695 | len = SDL_strlen(textBuffer); |
696 | SDL_snprintf(textBuffer + len, sizeof(textBuffer) - len, " }\n" ); |
697 | SDL_Log("%s" , textBuffer); |
698 | } |
699 | SDL_Log("Readers read %d total events\n" , grand_total); |
700 | } |
701 | |
702 | /* End FIFO test */ |
703 | /**************************************************************************/ |
704 | |
705 | int |
706 | main(int argc, char *argv[]) |
707 | { |
708 | /* Enable standard application logging */ |
709 | SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO); |
710 | |
711 | RunBasicTest(); |
712 | RunEpicTest(); |
713 | /* This test is really slow, so don't run it by default */ |
714 | #if 0 |
715 | RunFIFOTest(SDL_FALSE); |
716 | #endif |
717 | RunFIFOTest(SDL_TRUE); |
718 | return 0; |
719 | } |
720 | |
721 | /* vi: set ts=4 sw=4 expandtab: */ |
722 | |