Here’s a small test program that will set one on the path toward
waiting for events in the most efficient way for an X11 program (I say
"on the path", because there’s a few details missing, which I’ll get
to later, just don’t use that code right off!). Depending on the
#define at the top, it will use one of the three following methods to
wait for events:
-
busy-looping with SDL_PollEvent(), which will give you the lowest
latency possible, but will also use 100% CPU -
using SDL_WaitEvent(), which is slightly mis-named, because it’s
more “busy-looping with a 10 millisecond backoff between tries”, which
is not good for latency, because it pretty much rounds events to the
10 milliseconds -
waiting on the X11 socket with select(), which means that it will
sleep as much as possible, letting the kernel wakes us up at exactly
the moment an event arrives (or at least, as soon as possible),
getting a pretty good latency, but also giving an opportunity to get
notified of socket readiness, as well as having a fairly good timer
mechanism (using the select() timeout for the next timer to expire)
One of the interesting things is that with some process schedulers,
sleeping for a while will favour your process later when you do need
to run for a while (call it good karma, if you will).
Now, for some of the issues…
With that kind of setup where you’re watching for multiple kinds of
events at once, you have to be careful not to starve one of them. In
this particular test program, I did just one SDL_PeepEvents() for each
turn of the loop, going through select() and SDL_PumpEvents() for each
iteration. The structure of this particular program made it not so
convenient (I usually have a callback-based design, so I can process
the events from within a loop of SDL_PeepEvents() calls), but in a
real program, this would starve the processing of SDL events. The idea
is that you should do one call to select(), then process each fd
you’ve put into it, and the processing needed for the X11 fd is to
call SDL_PumpEvents() once and then call SDL_PeepEvents() to drain
the SDL event queue. If you call SDL_PumpEvents() more than once, you
could put yourself in a situation where you starve everything else (as
you process events, more can arrive, and you end up in that loop
forever, or at least, spending a lot of time in it).
As it happens, while scouring the SDL code, I noticed that
SDL_PumpEvents() calls X11_Pending() repeatedly, which is the
equivalent of the starving I just described (if X11 events kept
arriving, X11_Pending() will keep reading them off and adding them to
the Xlib queue as they arrive). What needs to be done to avoid
starving is to do one X11_Pending(), keep the number it returned and
do that many X11_DispatchEvent(), no more.
Also, I noticed that X11_Pending() is much more complicated than it
needs to be. First, it starts with XFlush() followed by
XEventsQueued(QueuedAlready), which is the same as
XEventsQueued(QueuedAfterFlush), which is the same as… XPending().
This whole function seems to be needed because XPending() is supposed
to block when there is no events, but that’s not true, from what I can
see. It calls ioctl(FIONREAD) on the fd to know how many bytes are
readable, which is more or less the equivalent of doing a select()
call first, and/or using non-blocking I/O. So, in summary, the whole
thing could be replaced by, uh, XPending().
Another problem with my trick is that the switch_waiting trick done in
X11_PumpEvents() will never kick in (yow!). I’m not entirely sure why
this is needed at all (having written my own fullscreen mode handling
for Xlib, only now migrating to SDL from Xlib), I didn’t have it and
things were fine, as far as I could see? Something interesting I could
learn from you guys? Maybe an informative comment there would be
appropriate?
Thanks!–
http://pphaneuf.livejournal.com/
-------------- next part --------------
An embedded and charset-unspecified text was scrubbed…
Name: sdlwait.c
URL: http://lists.libsdl.org/pipermail/sdl-libsdl.org/attachments/20080505/cc891964/attachment.asc