Using SDL_PushEvent() to talk between threads

Using SDL_PushEvent() to talk between threads.

I’ve been spending a lot of time testing and playing with SDL. To say
the least, SDL has made me a very happy camper. Most of the time it
simply works. If I had one complaint about SDL it would be with the
documentation. But, since I have the source code I can always UTSL (Use
The Source Luke) and find out exactly what it really does under the covers.

When I test a library like SDL I like to find a project that will let me
test the library and maybe get something useful as a result of the work.
I was looking for a project that would test the SDL thread and network
APIs so I decided to build a threaded network IO handler that would just
sit off to the side of a game and send events when a TCP connection was
made, when a TCP connection closed, when data was available on a TCP
connection or when a UDP message arrived. That would let me handle the
network the same way I handle the keyboard or mouse and it seems to me
like a very natural way to handle network IO in an interactive program
of any kind, and especially good for games where players are connected
over a network.

As usual, all the problems I ran into in building this library were the
result of my own assumptions and misunderstanding the documentation. The
first thing I noticed when I did an initial stress test was that it ran
fast! Then I noticed that it ran fast because my events were being lost.
I soon found out (UTSL) that SDL_PushEvent() returns 0 (zero) if the
event queue is full and doesn’t push the event. The documentation says
it returns 0 when the event is pushed and -1 when it isn’t. Cool. After
debugging that and complaining about it I went on to write some tests on
the SDL event processing system.

The first question I had is how do you wait for the event queue to not
be full? I looked through the documentation, since SDL_PushEvent() is
documented as being thread safe it would seem like there should be a way
for a thread to wait for the queue to empty out. If it is there I didn’t
find it in the documentation or the code. I looked at SDL_WaitEvent() to
see how it waits for events to show up. It seemed reasonable to me to
use a similar method to wait for the queue to empty out.

Digging through the SDL_WaitEvent() code is when got my nose rubbed in
the fact that SDL is designed to work on operating systems that do NOT
support threads. SDL_WaitEvent waits for events to arrive in the queue
the simplest way possible, it uses SDL_Delay() to wait for 10
milliseconds. Checking the queue 100 times per second and possibly
polling the input events 100 times per second is great for single
threaded games and it gives you the same results if you use it on a
multi-threaded system. So, I decide to see what would happen if I used
the same technique to wait for the queue to empty. If the queue is full,
just wait a while and try again. This approach to the problem is not the
one you’ll find in text books. But, you don’t learn anything by
believing everything you read in text books.

I wrote a simple test program that has two threads. One thread is the
SDL main thread and it waits for events to be put in the queue and when
it gets an event, it just ignores it. The other thread does nothing but
try to stuff events into the queue and it waits when the queue is full.
The code that waits for the event queue to empty out looks like this:

 while (0 >= (val = SDL_PushEvent(&ev)))
 {
   SDL_Delay(10);
 }

I wanted to see how fast this was so I tested it, but a test results
don’t mean much unless you have some idea of how fast it could be. To
figure out how fast it should be I looked at the SDL event code and saw
that the queue can hold at most 127 items and since SDL_WaitEvent()
looks at the queue 100 times per second we know that I can’t push more
than 12700 events through the queue in a second.

When I did 1 to 10 second long tests with this code I got events/second
rates ranging from 6000 to just less than 12700. When I ran it for 1000
seconds I saw a consistent 6350 events/second. This is exactly what you
would expect to get if the 10 millisecond wait in my code and in the
SDL_WaitEvent() code were added together to give a 20 millisecond wait
(6350 = 127 * 100 / 2.) This is perfectly acceptable rate for many
applications. From the point of view of my event driven network library,
this is a great rate for the client side of a game, but it isn’t so good
for a server. And, I would like to use the same library for both the
client and the server. So, I wanted to see if I could make this code run
a little faster.

The next step was to write my own version of SDL_WaitEvent() and use a
semaphore and a condition variable to control access to the event queue.
I wanted to make as few changes as possible so I wrote a simple
replacement routine that does the same things that SDL_WaitEvent() does,
but it also signals the condition variable when it removes an event from
the queue. The event sending code pushes events into the queue until it
is full and then waits on the condition. This way the queue still gets
checked 100 times/second, and it ensures that regular SDL events aren’t
lost. The new event sending code looks like:

 SDL_LockMutex(eventLock);
 while (0 >= (val = SDL_PushEvent(&ev)))
 {
   SDL_CondWait(eventWait, eventLock);
 }
 SDL_UnlockMutex(eventLock);

And my WaitEvent code looks like:

int WaitEvent(SDL_Event *ev)
{
int val = 0;
while (0 == (val = SDL_PollEvent(ev)))
{
SDL_Delay(10);
}
SDL_CondSignal(eventWait);
return val;
}
When I tested this code I found that it consistently handled 12700
events/second. That result makes sense because the sending thread wakes
up soon after an event is removed from the queue. Since the sending code
always wakes up shortly after an event is removed from the queue the
WaitEvent code always finds a full queue when it wakes up and it wakes
up 100 times/second. This is more than fast enough for client code and
fast enough for a lot of server applications.

(I have to say that the timer code, and all the time related code, in
SDL is just plain great. I’ve beaten that code as hard as I can and it
just works.)

Of course, at this point I was caught by the problem and had to see what
happened when I got rid of the delay in WaitEvent(). So I did. I wrote a
version of WaitEvent() that returns events until the queue is empty and
then waits on a condition variable. And I wrote a version of my event
sending thread that sends events until the queue is full and then waits
on a condition variable. My new WaitEvent() signals whenever it takes
and event out of the queue and my event pushing code signals whenever it
pushes and event on the queue. This approach to the problem gets rid of
all the delays but does add a lot of mutex control overhead.

This way of doing things has one very serious problem. It locks out all
of the SDL input events that are generated through SDL_PollEvent().
Event though the SDL_PollEvent() is called by my WaitEvent() code it
only gets called if my event pushing code pushes an event. No matter how
fast this new version runs it isn’t worth anything if it kills the SDL
input code. That problem stopped me for a little while until I realized
that an SDL timer could be used to broadcast a signal to wake up all
waiting threads. So, I set up a timer that fires 100 times per second
and wakes up all waiting threads. When I tested that code I found that
it got the SDL events and it was able to push over 30000 events per
second from my event pushing thread to the main SDL thread. I believe
that the speed I’m seeing is only limited by the speed of my test
machine, and not my anything in my code or in SDL. That version of the
test program attached to the bottom of this message.

I hope this information is useful to you.-----------------------------------------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include “SDL.h”
#include “SDL_thread.h”
#include “SDLUtils.h”
#include “trace.h”

static int doneYet = 0;
static SDL_cond *eventWait = NULL;
static SDL_mutex *eventLock = NULL;
static int num = 0;

static int runThread(void *nothing)
{
int i;
int val;
SDL_Event ev;

ev.type = SDL_USEREVENT;
ev.user.code = 0;
ev.user.data1 = 0;
ev.user.data2 = 0;

i = 0;
while (!doneYet)
{
ev.user.data1 = (void *)i;
SDL_LockMutex(eventLock);
while ((!doneYet) && (0 >= (val = SDL_PushEvent(&ev))))
{
SDL_CondWait(eventWait, eventLock);
}
SDL_UnlockMutex(eventLock);
SDL_CondSignal(eventWait);
num++;
//printf("%d\n", num);

 i++;

}

return 0;
}

Uint32 eventTimer(Uint32 interval, void *param)
{
SDL_CondBroadcast(eventWait);

return interval;
}

int WaitEvent(SDL_Event *ev)
{
int val = 0;

SDL_LockMutex(eventLock);
while (0 == (val = SDL_PollEvent(ev)))
{
SDL_CondWait(eventWait, eventLock);
}
SDL_UnlockMutex(eventLock);
SDL_CondSignal(eventWait);

num–;
//printf("%d\n", num);

return val;
}

int main(int argc, char **argv)
{
int count = 0;
double rate = 0.0;
Uint32 start = 0;
Uint32 end = 0;
Uint32 limit = (10 * 1000);
SDL_Thread *thread = NULL;
SDL_Event ev;

if (2 <= argc)
{
limit = 1000 * atoi(argv[1]);
}

SDL_Init(SDL_INIT_EVENTTHREAD |
SDL_INIT_VIDEO |
SDL_INIT_TIMER |
SDL_INIT_NOPARACHUTE);

eventWait = SDL_CreateCond();
eventLock = SDL_CreateMutex();

thread = SDL_CreateThread(runThread, NULL);
SDL_AddTimer(10, eventTimer, NULL);
//printf("%08x\n", (int)thread);

start = SDL_GetTicks();
while (WaitEvent(&ev) && (SDL_GetTicks() < (start + limit)))
{
//printf(“time=%u\n”, SDL_GetTicks());
switch (ev.type)
{
case SDL_USEREVENT:
//printf(“count=%d data1=%d\n”, count, (int)ev.user.data1);
count++;
break;

 case SDL_QUIT:
   printf("got here\n");
   exit(0); // this isn't supposed to happen
   break;

 default:
   printSDLEvent(&ev);
   break;
 }

}
doneYet = 1;
SDL_CondSignal(eventWait); // it might be waiting
SDL_WaitThread(thread, NULL); // wait for it to die

end = SDL_GetTicks();

rate = ((double)count) / (((double)(end - start)) / 1000.0);
printf("%f\n", rate);

SDL_Quit();
exit(0);
}

Using SDL_PushEvent() to talk between threads.

So Sam, maybe we should rewrote whole liblary for threads (1.3 ?). I can do
that for linux/X11 :slight_smile:
But i guess will use SDL threads/events/… for that so it should be
portable.

tell me just one thing, should i run my render stuff on another thread than
SDL_PoolEvent runs ?
will it speed up something ?
(i will try, but i would know if somebody has expierence with that).

GJ.