Discussion:
sleeping better for lower latencies (and other things)
(too old to reply)
Pierre Phaneuf
2008-05-05 04:33:34 UTC
Permalink
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/
Eddy Cullen
2008-05-09 09:01:13 UTC
Permalink
Hi,

I'm not entirely sure why I know this... I think it's from
discussion/documentation about threading, so don't take it is as gospel...

From what I understand, one of the design decisions made, was to
provide consistent behaviour across platforms. Due to restrictions on OS
locking/threading semantics on some operating systems, SDL implements
busy-waiting, even on platforms that support interaction with the scheduler.

Possibly, the key issue is whether this decision should stand - perhaps
additional APIs are required?

As I write this, it all feels horribly familiar, like it's already been
discussed on the group...

*Dons Flame-Retardant Suit*

;)

Jitsu Love,

Eddy
Post by Pierre Phaneuf
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
- 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!
------------------------------------------------------------------------
_______________________________________________
SDL mailing list
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org
Pierre Phaneuf
2008-05-11 18:20:15 UTC
Permalink
From what I understand, one of the design decisions made, was to provide
consistent behaviour across platforms. Due to restrictions on OS
locking/threading semantics on some operating systems, SDL implements
busy-waiting, even on platforms that support interaction with the scheduler.
There's some stuff that is *really* hard to get consistent semantics
right across multiple platforms. But this particular API
(SDL_WaitEvent) is quite simple: wait until the next event, as quietly
as possible. You could achieve that with an extra method in the video
driver dispatch table, and if it is NULL, fall back to the current
method of looping with the delay, it would only be better.

Note that this involves no interaction with the scheduler, just using
the platform API the most common way. On most platforms, there is a
blocking "wait for the next event" call (XNextEvent, GetMessage,
PhNextEvent, etc), which fits in quite naturally, and in most cases,
they also allow fitting that with waiting for other things
(ConnectionNumber with select, WaitForMultipleObject, PhEventArm and
PhEventRead). It isn't surprising and is quite natural, since that's
how most hardware in the last 20 years works (interrupt-driven).

Also, note that SDL_WaitEvent didn't really do real busy waiting, but
rather does "arbitrary waiting". This is better than busy waiting for
CPU usage (although worse than my approach), but mainly has terrible
latency effects. As for interactions with locking/threading semantics,
hardly any platforms have threads as a first class citizen, BeOS and
Java being exceptions, and from what I can see, that wasn't the best
idea ever (the best thing is to use a mix of threads and state
machines, with a low number of threads, too many threads leading to
excessive context switching, which Java finally found out and
addressed with NIO, at some point, and BeOS died before they could).
Possibly, the key issue is whether this decision should stand - perhaps
additional APIs are required?
I'd just propose improving SDL_WaitEvent internally, with the exact
same semantics that it has currently, without big changes in
architecture (if I could do most of it from outside of SDL in my test
program, it's obviously not too far).

I'm also not too thrilled by the excessive reliance on the event
queue, which has some poor characteristics. On most platform, SDL
could get events directly from the platform, without any queueing, and
it would reduce the "losing events" effect (either platforms will be
sensible and not lose events, or they will lose them, and we'll be in
the same place as today), while letting proper event flow control work
(SDL_PumpEvents can lead an underlying event system to believe that an
SDL application is processing events quickly, when in fact SDL are
throwing them out as fast as possible, which is a loss of information,
leading to bad decisions on the part of the platform, in this case,
sending even more events!). We still need the event queue for
internally generated events (SDL_PushEvent), and when getting an
event, SDL would first look at the queue, and if it is empty, ask the
platform (possibly in a blocking way, if it is in SDL_WaitEvent).
As I write this, it all feels horribly familiar, like it's already been
discussed on the group...
I'd be very interested in pointers as to that, actually. Often, there
is a reason for why things are in some way. I actually have rather
specific rules for whining and flaming (documented here:
http://www.advogato.org/person/pphaneuf/diary/118.html). In this
particular case, there is a tricky bit: I have personally done it (or
at least helped out doing it) better on Svgalib, X11 and Win32 (I
suspect it's possible on Mac OS "classic", since they're not exactly
thread-friendly in general). The documentation for Photon seems to
point that it is in fact designed for exactly this (QNX is often close
to the hardware, so it's not surprising).

But there's other platforms supported by SDL that might have different
restrictions that I do not know about. As I said, I'm still going with
at least category B ("whining is allowed if you can figure out a
better way"), and it'd be surprising if I was wrong, considering the
state of hardware in general, but there are some weird things out
there (*eyes WinCE suspiciously*)...

So I'm quite keen on finding out more about those other odd platforms. :-)
--
http://pphaneuf.livejournal.com/
Edward Cullen
2008-05-12 23:17:45 UTC
Permalink
Hi,

Must confess, I'm not in a position to comment on the detail of what
you've written; I was simply trying to give you what I know as an
overview...

I think you'll need to take this up with Sam and/or Ryan directly, as
they're MUCH more familiar with the internals and design decisions with
regard to various platforms than I probably ever will :).

Sam is normally very open to ideas (especially those accompanied by
sample implementations...), but he has also spent a very long time
refining SDL, so don't be surprised if he has 'answers for everything'.
It may be that some original assumptions no longer apply - SDL started
back in 1998ish... :D

Eddy
Post by Pierre Phaneuf
From what I understand, one of the design decisions made, was to provide
consistent behaviour across platforms. Due to restrictions on OS
locking/threading semantics on some operating systems, SDL implements
busy-waiting, even on platforms that support interaction with the scheduler.
There's some stuff that is *really* hard to get consistent semantics
right across multiple platforms. But this particular API
(SDL_WaitEvent) is quite simple: wait until the next event, as quietly
as possible. You could achieve that with an extra method in the video
driver dispatch table, and if it is NULL, fall back to the current
method of looping with the delay, it would only be better.
Note that this involves no interaction with the scheduler, just using
the platform API the most common way. On most platforms, there is a
blocking "wait for the next event" call (XNextEvent, GetMessage,
PhNextEvent, etc), which fits in quite naturally, and in most cases,
they also allow fitting that with waiting for other things
(ConnectionNumber with select, WaitForMultipleObject, PhEventArm and
PhEventRead). It isn't surprising and is quite natural, since that's
how most hardware in the last 20 years works (interrupt-driven).
Also, note that SDL_WaitEvent didn't really do real busy waiting, but
rather does "arbitrary waiting". This is better than busy waiting for
CPU usage (although worse than my approach), but mainly has terrible
latency effects. As for interactions with locking/threading semantics,
hardly any platforms have threads as a first class citizen, BeOS and
Java being exceptions, and from what I can see, that wasn't the best
idea ever (the best thing is to use a mix of threads and state
machines, with a low number of threads, too many threads leading to
excessive context switching, which Java finally found out and
addressed with NIO, at some point, and BeOS died before they could).
Possibly, the key issue is whether this decision should stand - perhaps
additional APIs are required?
I'd just propose improving SDL_WaitEvent internally, with the exact
same semantics that it has currently, without big changes in
architecture (if I could do most of it from outside of SDL in my test
program, it's obviously not too far).
I'm also not too thrilled by the excessive reliance on the event
queue, which has some poor characteristics. On most platform, SDL
could get events directly from the platform, without any queueing, and
it would reduce the "losing events" effect (either platforms will be
sensible and not lose events, or they will lose them, and we'll be in
the same place as today), while letting proper event flow control work
(SDL_PumpEvents can lead an underlying event system to believe that an
SDL application is processing events quickly, when in fact SDL are
throwing them out as fast as possible, which is a loss of information,
leading to bad decisions on the part of the platform, in this case,
sending even more events!). We still need the event queue for
internally generated events (SDL_PushEvent), and when getting an
event, SDL would first look at the queue, and if it is empty, ask the
platform (possibly in a blocking way, if it is in SDL_WaitEvent).
As I write this, it all feels horribly familiar, like it's already been
discussed on the group...
I'd be very interested in pointers as to that, actually. Often, there
is a reason for why things are in some way. I actually have rather
http://www.advogato.org/person/pphaneuf/diary/118.html). In this
particular case, there is a tricky bit: I have personally done it (or
at least helped out doing it) better on Svgalib, X11 and Win32 (I
suspect it's possible on Mac OS "classic", since they're not exactly
thread-friendly in general). The documentation for Photon seems to
point that it is in fact designed for exactly this (QNX is often close
to the hardware, so it's not surprising).
But there's other platforms supported by SDL that might have different
restrictions that I do not know about. As I said, I'm still going with
at least category B ("whining is allowed if you can figure out a
better way"), and it'd be surprising if I was wrong, considering the
state of hardware in general, but there are some weird things out
there (*eyes WinCE suspiciously*)...
So I'm quite keen on finding out more about those other odd platforms. :-)
KHMan
2008-05-13 02:09:49 UTC
Permalink
Post by Edward Cullen
Hi,
Must confess, I'm not in a position to comment on the detail of what
you've written; I was simply trying to give you what I know as an
overview...
I think you'll need to take this up with Sam and/or Ryan directly, as
they're MUCH more familiar with the internals and design decisions with
regard to various platforms than I probably ever will :).
Sam is normally very open to ideas (especially those accompanied by
sample implementations...), but he has also spent a very long time
refining SDL, so don't be surprised if he has 'answers for everything'.
It may be that some original assumptions no longer apply - SDL started
back in 1998ish... :D
Eddy, you don't have to reply to every query that goes by. A
mailing list allows the community to share expertise, and
sometimes it's better to wait a while for other, possibly more
comprehensive or expert replies, before jumping in. Some restraint
is sometimes better, and it's not necessary to be a spokesperson
for Sam or Ryan. Less noise will give higher quality conversations.
Post by Edward Cullen
Eddy
[snip]
HTH,
--
Cheers,
Kein-Hong Man (esq.)
Kuala Lumpur, Malaysia
Edward Cullen
2008-05-18 10:20:54 UTC
Permalink
I was just *trying* to be polite. Sheesh!
Post by KHMan
Post by Edward Cullen
Hi,
Must confess, I'm not in a position to comment on the detail of what
you've written; I was simply trying to give you what I know as an
overview...
I think you'll need to take this up with Sam and/or Ryan directly, as
they're MUCH more familiar with the internals and design decisions with
regard to various platforms than I probably ever will :).
Sam is normally very open to ideas (especially those accompanied by
sample implementations...), but he has also spent a very long time
refining SDL, so don't be surprised if he has 'answers for everything'.
It may be that some original assumptions no longer apply - SDL started
back in 1998ish... :D
Eddy, you don't have to reply to every query that goes by. A
mailing list allows the community to share expertise, and
sometimes it's better to wait a while for other, possibly more
comprehensive or expert replies, before jumping in. Some restraint
is sometimes better, and it's not necessary to be a spokesperson
for Sam or Ryan. Less noise will give higher quality conversations.
Post by Edward Cullen
Eddy
[snip]
HTH,
Bob Pendleton
2008-05-13 14:23:25 UTC
Permalink
This has all been discussed before, and before, and before... :-) It is the
way it is because it is plenty good enough and it can be made to work
everywhere while "better" solutions only work on some operating system. See
my FastEvents library at gameprogrammer.com to see my reaction (and
solution) to the problem on the platforms I care about. Wow, I just checked,
that stuff is six years old now.

Bob Pendleton
Hi,
Must confess, I'm not in a position to comment on the detail of what you've
written; I was simply trying to give you what I know as an overview...
I think you'll need to take this up with Sam and/or Ryan directly, as
they're MUCH more familiar with the internals and design decisions with
regard to various platforms than I probably ever will :).
Sam is normally very open to ideas (especially those accompanied by sample
implementations...), but he has also spent a very long time refining SDL, so
don't be surprised if he has 'answers for everything'. It may be that some
original assumptions no longer apply - SDL started back in 1998ish... :D
Eddy
From what I understand, one of the design decisions made, was to provide
Post by Eddy Cullen
consistent behaviour across platforms. Due to restrictions on OS
locking/threading semantics on some operating systems, SDL implements
busy-waiting, even on platforms that support interaction with the scheduler.
There's some stuff that is *really* hard to get consistent semantics
right across multiple platforms. But this particular API
(SDL_WaitEvent) is quite simple: wait until the next event, as quietly
as possible. You could achieve that with an extra method in the video
driver dispatch table, and if it is NULL, fall back to the current
method of looping with the delay, it would only be better.
Note that this involves no interaction with the scheduler, just using
the platform API the most common way. On most platforms, there is a
blocking "wait for the next event" call (XNextEvent, GetMessage,
PhNextEvent, etc), which fits in quite naturally, and in most cases,
they also allow fitting that with waiting for other things
(ConnectionNumber with select, WaitForMultipleObject, PhEventArm and
PhEventRead). It isn't surprising and is quite natural, since that's
how most hardware in the last 20 years works (interrupt-driven).
Also, note that SDL_WaitEvent didn't really do real busy waiting, but
rather does "arbitrary waiting". This is better than busy waiting for
CPU usage (although worse than my approach), but mainly has terrible
latency effects. As for interactions with locking/threading semantics,
hardly any platforms have threads as a first class citizen, BeOS and
Java being exceptions, and from what I can see, that wasn't the best
idea ever (the best thing is to use a mix of threads and state
machines, with a low number of threads, too many threads leading to
excessive context switching, which Java finally found out and
addressed with NIO, at some point, and BeOS died before they could).
Possibly, the key issue is whether this decision should stand - perhaps
Post by Eddy Cullen
additional APIs are required?
I'd just propose improving SDL_WaitEvent internally, with the exact
same semantics that it has currently, without big changes in
architecture (if I could do most of it from outside of SDL in my test
program, it's obviously not too far).
I'm also not too thrilled by the excessive reliance on the event
queue, which has some poor characteristics. On most platform, SDL
could get events directly from the platform, without any queueing, and
it would reduce the "losing events" effect (either platforms will be
sensible and not lose events, or they will lose them, and we'll be in
the same place as today), while letting proper event flow control work
(SDL_PumpEvents can lead an underlying event system to believe that an
SDL application is processing events quickly, when in fact SDL are
throwing them out as fast as possible, which is a loss of information,
leading to bad decisions on the part of the platform, in this case,
sending even more events!). We still need the event queue for
internally generated events (SDL_PushEvent), and when getting an
event, SDL would first look at the queue, and if it is empty, ask the
platform (possibly in a blocking way, if it is in SDL_WaitEvent).
As I write this, it all feels horribly familiar, like it's already been
Post by Eddy Cullen
discussed on the group...
I'd be very interested in pointers as to that, actually. Often, there
is a reason for why things are in some way. I actually have rather
http://www.advogato.org/person/pphaneuf/diary/118.html). In this
particular case, there is a tricky bit: I have personally done it (or
at least helped out doing it) better on Svgalib, X11 and Win32 (I
suspect it's possible on Mac OS "classic", since they're not exactly
thread-friendly in general). The documentation for Photon seems to
point that it is in fact designed for exactly this (QNX is often close
to the hardware, so it's not surprising).
But there's other platforms supported by SDL that might have different
restrictions that I do not know about. As I said, I'm still going with
at least category B ("whining is allowed if you can figure out a
better way"), and it'd be surprising if I was wrong, considering the
state of hardware in general, but there are some weird things out
there (*eyes WinCE suspiciously*)...
So I'm quite keen on finding out more about those other odd platforms. :-)
_______________________________________________
SDL mailing list
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org
--
+ Bob Pendleton: writer and programmer
+ email: ***@Pendleton.com
+ web: www.GameProgrammer.com
+ www.Wise2Food.com

+--------------------------------------+
Pierre Phaneuf
2008-05-13 14:40:06 UTC
Permalink
Post by Bob Pendleton
This has all been discussed before, and before, and before... :-) It is the
way it is because it is plenty good enough and it can be made to work
everywhere while "better" solutions only work on some operating system. See
my FastEvents library at gameprogrammer.com to see my reaction (and
solution) to the problem on the platforms I care about. Wow, I just checked,
that stuff is six years old now.
That's one of the things I'd be interested in, a pointer to previous
discussions where this is explained (no sense in explaining it
again!).

Although while I can think of many radical improvements (which could
possibly work only on some platforms), I think we could fix up
SDL_WaitEvent to be a bit more reasonable on a few platforms, where it
could fall back to the current methods on those platforms where it
wouldn't work.

I'm a bit disappointed at the "lowest common denominator" that we're
getting sometimes, for example how events can be lost with SDL even if
we are on a platform that never loses events (which is most of them).
I guess that's the price of portability...
--
http://pphaneuf.livejournal.com/
Bob Pendleton
2008-05-13 15:28:27 UTC
Permalink
Post by Bob Pendleton
Post by Bob Pendleton
This has all been discussed before, and before, and before... :-) It is
the
Post by Bob Pendleton
way it is because it is plenty good enough and it can be made to work
everywhere while "better" solutions only work on some operating system.
See
Post by Bob Pendleton
my FastEvents library at gameprogrammer.com to see my reaction (and
solution) to the problem on the platforms I care about. Wow, I just
checked,
Post by Bob Pendleton
that stuff is six years old now.
That's one of the things I'd be interested in, a pointer to previous
discussions where this is explained (no sense in explaining it
again!).
Although while I can think of many radical improvements (which could
possibly work only on some platforms), I think we could fix up
SDL_WaitEvent to be a bit more reasonable on a few platforms, where it
could fall back to the current methods on those platforms where it
wouldn't work.
I'm a bit disappointed at the "lowest common denominator" that we're
getting sometimes, for example how events can be lost with SDL even if
we are on a platform that never loses events (which is most of them).
I guess that's the price of portability...
Here's the thing; you can lose events in SDL, but you don't. It is very
difficult to actually lose events because it can only happen when your code
is running to slow to be useful. One other point, it is possible to lose
events on all platforms. Yes, the probability of losing events is very low,
but any system with finite memory can lose events. Many device handlers for
verbose devices such as mice and tablets are written to merge events (i.e.
throw them away) just to prevent the software from having to handle so many
events.

Look at my stuff, I think you will like it. A lot of people use it to reduce
latency and increase reliability on Linux and Windows. I have reports of it
working on the Mac as well.

Bob Pendleton
Post by Bob Pendleton
--
http://pphaneuf.livejournal.com/
_______________________________________________
SDL mailing list
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org
--
+ Bob Pendleton: writer and programmer
+ email: ***@Pendleton.com
+ web: www.GameProgrammer.com
+ www.Wise2Food.com

+--------------------------------------+
Pierre Phaneuf
2008-05-13 18:53:46 UTC
Permalink
Post by Bob Pendleton
Here's the thing; you can lose events in SDL, but you don't. It is very
difficult to actually lose events because it can only happen when your code
is running to slow to be useful. One other point, it is possible to lose
events on all platforms. Yes, the probability of losing events is very low,
but any system with finite memory can lose events. Many device handlers for
verbose devices such as mice and tablets are written to merge events (i.e.
throw them away) just to prevent the software from having to handle so many
events.
I've been the type to do sound processing (on Linux) without using a
separate thread, so coming back to the main loop in time is something
I've more or less HAD TO master, so I haven't been losing messages too
much (I was just using this as an example). :-)

I used that particular thing because I saw a lot of stuff referring to
it, like comments in the form of "now we won't lose events,
hopefully!", or the whole event thread thing (so that you can have a
thread picking up events and overflowing the SDL queue as fast as
possible?!?). It's not entirely correct to say that you can always
lose events, because some are "level" rather than "edge" triggered.
select() is a good example of this: it's an event delivery mechanism
that cannot possibly overrun (of course, the socket themselves could,
but that's another subject). But in any case, that's okay if some
(most?) events *could* be lost, what I'm saying is that we shouldn't
be introducing a mechanism where ALL events are now put to the lowest
common denominator: ANY event can now be lost! Equality for all, or
something? ;-)

Also, there is flow control issues to fully pumping the event queue
whenever we have the chance, like SDL does: it gives false signals to
event sources, saying that they can send more, which doesn't help if
we start drowning in events.
Post by Bob Pendleton
Look at my stuff, I think you will like it. A lot of people use it to reduce
latency and increase reliability on Linux and Windows. I have reports of it
working on the Mac as well.
I've read its code, actually. Since it uses just about only SDL
functions, it should indeed work just fine on a Mac (and other
plaforms with SDL threading support). It cannot push events from the
same thread that gets them, obviously, but that is kind of a normal
thing to deadlock on (and that's why separating a "there's something
to look at" event from the thing to look at can be useful, for
removing that deadlock). I'm very annoyed at things like the
timerCallback, because that's just a band-aid to work around missing
functionality, and I was here just wishing for that functionality.
Hiding the hack inside a library doesn't make me any happier,
unfortunately.

Sometimes, I wish SDL/X11 was based on libXt rather than libX11 (see
XtAppAddTimeOut and XtAppAddInput, which are *exactly* what I want,
straight from the mid-80's). ;-)
--
http://pphaneuf.livejournal.com/
Ryan C. Gordon
2008-05-15 21:37:39 UTC
Permalink
Post by Pierre Phaneuf
be introducing a mechanism where ALL events are now put to the lowest
common denominator: ANY event can now be lost! Equality for all, or
something? ;-)
For what it's worth, in almost a decade of using SDL for high-end games,
I've never had a problem with losing events, and if I did, it was
probably a game bug, not an SDL bug.

For what it's worth, you _could_ implement a system that makes SDL work
more like what Windows is doing behind the scenes...it doesn't generate
an event for every mouse motion, but rather sets a "the mouse has moved"
flag when the hardware interrupt triggers, and generates the mouse
motion event if this flag is set when you pump the queue, with a delta
between the last mouse event and the current one...which guarantees the
one thing that will definitely cause a queue overflow never can, at a
probably imperceptible loss in precision.

But frankly, I don't think this is a realistic problem in SDL, even on
OSes that aren't making that tradeoff behind the scenes. The amount of
lag between event queue pumps that you'd have to introduce means that
lost events are the least of your problems.

Now the poll-sleep-poll-sleep problem we were discussing with
SDL_WaitEvent() is worth fixing, since it'll eat battery life on
embedded devices, where blocking forever in select() works better.

--ryan.
Pierre Phaneuf
2008-05-15 22:16:23 UTC
Permalink
Post by Ryan C. Gordon
Post by Pierre Phaneuf
be introducing a mechanism where ALL events are now put to the lowest
common denominator: ANY event can now be lost! Equality for all, or
something? ;-)
For what it's worth, in almost a decade of using SDL for high-end games,
I've never had a problem with losing events, and if I did, it was probably a
game bug, not an SDL bug.
The problem with high-end games is that they're usually coded at least
half-way properly (all relatively speaking, I've seen some awful
things too!), and they're usually not the type of games that clamp the
framerate, but go full tilt and just SDL_PollEvent() between each
frame. A high-end 3D game might be missing events if things fell down
on Mesa software rendering, but you'll have other problems beside
missing events anyway, you're not likely to notice. ;-)

I don't think I've ever lost events either, since my code was already
geared up to drive Linux/OSS sound from a single thread, but I suspect
those that do miss events are those using SDL_PushEvent() a lot,
possibly from other threads, say. The queue is what, 128 events, 256?
Something like that...

The fact is, the way it's designed, it has a limited capacity, and
once *something* fills it up, it's filled up for everything, including
things that weren't buggy otherwise.
Post by Ryan C. Gordon
For what it's worth, you _could_ implement a system that makes SDL work more
like what Windows is doing behind the scenes...it doesn't generate an event
for every mouse motion, but rather sets a "the mouse has moved" flag when
the hardware interrupt triggers, and generates the mouse motion event if
this flag is set when you pump the queue, with a delta between the last
mouse event and the current one...which guarantees the one thing that will
definitely cause a queue overflow never can, at a probably imperceptible
loss in precision.
But frankly, I don't think this is a realistic problem in SDL, even on OSes
that aren't making that tradeoff behind the scenes. The amount of lag
between event queue pumps that you'd have to introduce means that lost
events are the least of your problems.
Artificially pumping events from the system into SDL makes this worse
on those platforms, actually, because it turns what could be just one
event into a number of discrete events.

What is generally best is to keep the queueing you do to a minimum, if
you can. For example, you can keep an event queue, like we do now, but
only for SDL_PushEvent()-generated events, and if it's empty (or
alternatively, implementation details to be worked out), do a
PeekMessage or a XPending/XNextEvent, translate it, and hand it out,
as we get them in SDL itself, instead queueing them in, then out
immediately. There's a few details to avoid starvation in some
situations, but nothing too bad.

My idea is that this can leave the events-dropping to the system, and
just do it things straightforwardly in SDL itself (no queueing for
everything). I'm understand I might be a bit excessive, though, coming
from high-performance network server stuff, but I'd like to have as
much common code in my server as in my client, so that would be nice
to get right.
Post by Ryan C. Gordon
Now the poll-sleep-poll-sleep problem we were discussing with
SDL_WaitEvent() is worth fixing, since it'll eat battery life on embedded
devices, where blocking forever in select() works better.
Yeah, that's one of the most important problems, I'd say (plus, I
think there's a tiny detail wrong in X11_Pending that can get it
starving in some rare circumstances, a one-liner fix kind of thing,
nothing too serious). The previous stuff sounds more like 1.3 fodder
to me, and I'd be interested in figuring it out properly, since that's
a rare occasion where the API compatibility is being broken.
--
http://pphaneuf.livejournal.com/
Forrest Voight
2008-05-15 23:59:21 UTC
Permalink
I agree that having something like select() would be more useful.

Here's a PyGame snippet that does this (only on x11):

import pygame
import ctypes
import select

def get_fd():
w = pygame.display.get_wm_info()
w = w['display']
n = int(str(w)[23:-1], 16)
n = ctypes.cast(n+8, ctypes.POINTER(ctypes.c_int)).contents.value
n = ctypes.cast(n+8, ctypes.POINTER(ctypes.c_int)).contents.value
return n

def wait(fd, timeout=None):
select.select([fd], [], [], timeout)

Also, I think removing SDL_PushEvent() would solve a lot of
problems... including simplifying event blocking when it is
implemented. It doesn't make sense to use an outside library to manage
a game's event queue to me...

Cheers,
Forrest Voight
Post by Pierre Phaneuf
Post by Ryan C. Gordon
Post by Pierre Phaneuf
be introducing a mechanism where ALL events are now put to the lowest
common denominator: ANY event can now be lost! Equality for all, or
something? ;-)
For what it's worth, in almost a decade of using SDL for high-end games,
I've never had a problem with losing events, and if I did, it was probably a
game bug, not an SDL bug.
The problem with high-end games is that they're usually coded at least
half-way properly (all relatively speaking, I've seen some awful
things too!), and they're usually not the type of games that clamp the
framerate, but go full tilt and just SDL_PollEvent() between each
frame. A high-end 3D game might be missing events if things fell down
on Mesa software rendering, but you'll have other problems beside
missing events anyway, you're not likely to notice. ;-)
I don't think I've ever lost events either, since my code was already
geared up to drive Linux/OSS sound from a single thread, but I suspect
those that do miss events are those using SDL_PushEvent() a lot,
possibly from other threads, say. The queue is what, 128 events, 256?
Something like that...
The fact is, the way it's designed, it has a limited capacity, and
once *something* fills it up, it's filled up for everything, including
things that weren't buggy otherwise.
Post by Ryan C. Gordon
For what it's worth, you _could_ implement a system that makes SDL work more
like what Windows is doing behind the scenes...it doesn't generate an event
for every mouse motion, but rather sets a "the mouse has moved" flag when
the hardware interrupt triggers, and generates the mouse motion event if
this flag is set when you pump the queue, with a delta between the last
mouse event and the current one...which guarantees the one thing that will
definitely cause a queue overflow never can, at a probably imperceptible
loss in precision.
But frankly, I don't think this is a realistic problem in SDL, even on OSes
that aren't making that tradeoff behind the scenes. The amount of lag
between event queue pumps that you'd have to introduce means that lost
events are the least of your problems.
Artificially pumping events from the system into SDL makes this worse
on those platforms, actually, because it turns what could be just one
event into a number of discrete events.
What is generally best is to keep the queueing you do to a minimum, if
you can. For example, you can keep an event queue, like we do now, but
only for SDL_PushEvent()-generated events, and if it's empty (or
alternatively, implementation details to be worked out), do a
PeekMessage or a XPending/XNextEvent, translate it, and hand it out,
as we get them in SDL itself, instead queueing them in, then out
immediately. There's a few details to avoid starvation in some
situations, but nothing too bad.
My idea is that this can leave the events-dropping to the system, and
just do it things straightforwardly in SDL itself (no queueing for
everything). I'm understand I might be a bit excessive, though, coming
from high-performance network server stuff, but I'd like to have as
much common code in my server as in my client, so that would be nice
to get right.
Post by Ryan C. Gordon
Now the poll-sleep-poll-sleep problem we were discussing with
SDL_WaitEvent() is worth fixing, since it'll eat battery life on embedded
devices, where blocking forever in select() works better.
Yeah, that's one of the most important problems, I'd say (plus, I
think there's a tiny detail wrong in X11_Pending that can get it
starving in some rare circumstances, a one-liner fix kind of thing,
nothing too serious). The previous stuff sounds more like 1.3 fodder
to me, and I'd be interested in figuring it out properly, since that's
a rare occasion where the API compatibility is being broken.
--
http://pphaneuf.livejournal.com/
_______________________________________________
SDL mailing list
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org
René Dudfield
2008-05-16 07:54:29 UTC
Permalink
ah nice one.

I guess you could make a post event with this wait function that first
posts something to the sdl event queue, then post a noop X event... so
that your program wakes up to process the event.

cu,
Post by Forrest Voight
I agree that having something like select() would be more useful.
import pygame
import ctypes
import select
w = pygame.display.get_wm_info()
w = w['display']
n = int(str(w)[23:-1], 16)
n = ctypes.cast(n+8, ctypes.POINTER(ctypes.c_int)).contents.value
n = ctypes.cast(n+8, ctypes.POINTER(ctypes.c_int)).contents.value
return n
select.select([fd], [], [], timeout)
Also, I think removing SDL_PushEvent() would solve a lot of
problems... including simplifying event blocking when it is
implemented. It doesn't make sense to use an outside library to manage
a game's event queue to me...
Cheers,
Forrest Voight
Post by Pierre Phaneuf
Post by Ryan C. Gordon
Post by Pierre Phaneuf
be introducing a mechanism where ALL events are now put to the lowest
common denominator: ANY event can now be lost! Equality for all, or
something? ;-)
For what it's worth, in almost a decade of using SDL for high-end games,
I've never had a problem with losing events, and if I did, it was probably a
game bug, not an SDL bug.
The problem with high-end games is that they're usually coded at least
half-way properly (all relatively speaking, I've seen some awful
things too!), and they're usually not the type of games that clamp the
framerate, but go full tilt and just SDL_PollEvent() between each
frame. A high-end 3D game might be missing events if things fell down
on Mesa software rendering, but you'll have other problems beside
missing events anyway, you're not likely to notice. ;-)
I don't think I've ever lost events either, since my code was already
geared up to drive Linux/OSS sound from a single thread, but I suspect
those that do miss events are those using SDL_PushEvent() a lot,
possibly from other threads, say. The queue is what, 128 events, 256?
Something like that...
The fact is, the way it's designed, it has a limited capacity, and
once *something* fills it up, it's filled up for everything, including
things that weren't buggy otherwise.
Post by Ryan C. Gordon
For what it's worth, you _could_ implement a system that makes SDL work more
like what Windows is doing behind the scenes...it doesn't generate an event
for every mouse motion, but rather sets a "the mouse has moved" flag when
the hardware interrupt triggers, and generates the mouse motion event if
this flag is set when you pump the queue, with a delta between the last
mouse event and the current one...which guarantees the one thing that will
definitely cause a queue overflow never can, at a probably imperceptible
loss in precision.
But frankly, I don't think this is a realistic problem in SDL, even on OSes
that aren't making that tradeoff behind the scenes. The amount of lag
between event queue pumps that you'd have to introduce means that lost
events are the least of your problems.
Artificially pumping events from the system into SDL makes this worse
on those platforms, actually, because it turns what could be just one
event into a number of discrete events.
What is generally best is to keep the queueing you do to a minimum, if
you can. For example, you can keep an event queue, like we do now, but
only for SDL_PushEvent()-generated events, and if it's empty (or
alternatively, implementation details to be worked out), do a
PeekMessage or a XPending/XNextEvent, translate it, and hand it out,
as we get them in SDL itself, instead queueing them in, then out
immediately. There's a few details to avoid starvation in some
situations, but nothing too bad.
My idea is that this can leave the events-dropping to the system, and
just do it things straightforwardly in SDL itself (no queueing for
everything). I'm understand I might be a bit excessive, though, coming
from high-performance network server stuff, but I'd like to have as
much common code in my server as in my client, so that would be nice
to get right.
Post by Ryan C. Gordon
Now the poll-sleep-poll-sleep problem we were discussing with
SDL_WaitEvent() is worth fixing, since it'll eat battery life on embedded
devices, where blocking forever in select() works better.
Yeah, that's one of the most important problems, I'd say (plus, I
think there's a tiny detail wrong in X11_Pending that can get it
starving in some rare circumstances, a one-liner fix kind of thing,
nothing too serious). The previous stuff sounds more like 1.3 fodder
to me, and I'd be interested in figuring it out properly, since that's
a rare occasion where the API compatibility is being broken.
--
http://pphaneuf.livejournal.com/
_______________________________________________
SDL mailing list
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org
_______________________________________________
SDL mailing list
http://lists.libsdl.org/listinfo.cgi/sdl-libsdl.org
Pierre Phaneuf
2008-05-16 18:55:10 UTC
Permalink
Post by Forrest Voight
I agree that having something like select() would be more useful.
Me too, but that's one of the trickiest part, because the equivalent
APIs are rather different from one platform to the other.
Of note that this is more or less what my demo program at the top of
this thread does, but that there is some problem with it and switching
to fullscreen, where SDL seems to rely on SDL_PollEvent() to be called
when there is nothing to do, at certain (more or less random) times. I
find this particular bit of code rather strange, my Xlib code that
does switching to/from fullscreen didn't need any of this and seems to
work fine?

Maybe some esoteric bit of knowledge about mysterious behaviour of
some X11 servers that I don't know? Somebody knows?
Post by Forrest Voight
Also, I think removing SDL_PushEvent() would solve a lot of
problems... including simplifying event blocking when it is
implemented. It doesn't make sense to use an outside library to manage
a game's event queue to me...
I think it's mostly used for communicating to the main thread from
other threads. I'm not *that* offended by the concept, but in my own
programs, I tend to reuse timers in that case, simply adding a timer
for *now*. I use a pipe set in non-blocking mode to wake up the
select() from other threads when signals come in (there's a classic
race conditional regarding select() and signal handling, again
XtAppAddSignal deals with that) or when timers are added (so that we
"notice" them if they're from another thread). Just write one byte,
ignore any error, and remember to read all the bytes from the pipe
after returning from select()! See
http://pphaneuf.livejournal.com/158971.html, and maybe
http://pphaneuf.livejournal.com/158346.html too (although SDL's need
in this matter are probably much simpler).
--
http://pphaneuf.livejournal.com/
Ryan C. Gordon
2008-05-18 00:52:59 UTC
Permalink
Post by Pierre Phaneuf
The problem with high-end games is that they're usually coded at least
half-way properly
Funniest thing I've read all day. :P

--ryan.
Sam Lantinga
2008-05-15 06:48:40 UTC
Permalink
Post by Pierre Phaneuf
I'd just propose improving SDL_WaitEvent internally, with the exact
same semantics that it has currently, without big changes in
architecture (if I could do most of it from outside of SDL in my test
program, it's obviously not too far).
I'm definitely open to this in SDL 1.3. SDL_WaitEvent() hasn't been
a big priority since most applications (games) have some sort of update
process that they run very frequently and SDL_PollEvent() often turns
out to be the most expedient method for handling this.

Perhaps a variant of SDL_WaitEvent() with a timeout would be more useful?

See ya,
-Sam Lantinga, Lead Software Engineer, Blizzard Entertainment
s***@bizna.name
2008-05-15 14:39:58 UTC
Permalink
Post by Sam Lantinga
Perhaps a variant of SDL_WaitEvent() with a timeout would be more useful?
YES. SWEET LIVING SMEG, YES!
Sorry, just had to let that out.
-:sigma.SB
Pierre Phaneuf
2008-05-15 14:50:42 UTC
Permalink
Post by s***@bizna.name
Post by Sam Lantinga
Perhaps a variant of SDL_WaitEvent() with a timeout would be more useful?
YES. SWEET LIVING SMEG, YES!
Yow! *Someone* wants his timer events... ;-)
--
http://pphaneuf.livejournal.com/
Pierre Phaneuf
2008-05-15 14:49:47 UTC
Permalink
Post by Sam Lantinga
I'm definitely open to this in SDL 1.3. SDL_WaitEvent() hasn't been
a big priority since most applications (games) have some sort of update
process that they run very frequently and SDL_PollEvent() often turns
out to be the most expedient method for handling this.
That was my guess. My interest is to be able to use SDL_WaitEvent()
for dedicated servers as well, making the overall code more general.
Post by Sam Lantinga
Perhaps a variant of SDL_WaitEvent() with a timeout would be more useful?
I proposed a quick fix for 1.2, but if this were to go in 1.3, I'd
make it a bit more sophisticated. The best thing would be lightweight
timers that do not use a thread, using a priority queue to get the
next expiring timer (as an aside, SDL_Delay(1) in the Unix-specific
timer thread could be replaced with a select() that sleeps for longer,
with a pipe to wake it up when the list of timers change).

What this design allows is that you can then have the rendering hooked
up on a timer, possibly with a delay of zero (if the framerate is
uncapped), so a single main loop can be used in dedicated or in client
mode. When initializing the game client code, it registers the
renderer, and if you don't, SDL_WaitEvent() would sleep more, as
required. It also simplifies a little bit code that doesn't need the
low latency of the existing SDL timers (for example, I often use the
SDL timers to post an event to the main thread, so that I don't have
to deal with synchronization, this would simplify this relatively
common case).

As I pointed out in a previous email, it'd be nice if it was fully
fleshed out, like libXt's main loop API, for example (XtAppAddTimeOut,
XtAppAddInput, XtAppAddSignal). Other than the timeout, the other
functions could be platform-specific (XtAppAddInput uses an int for
the "input", assuming a Unix-style file descriptor, but there might be
other things). On Win32, using SDL_SysWMEvent already makes this
possible, for example.

Note that the current input and video subsystems could be based off of
that event API, and that a fully cross-platform API that would only
support timer events is actually sufficient to implement what SDL does
currently (support for more events would make things increasingly more
efficient).

I've got a patch on the way first for the X11_Pending() choking
problem first, though. :-)
--
http://pphaneuf.livejournal.com/
Pierre Phaneuf
2008-05-11 17:00:21 UTC
Permalink
Post by Pierre Phaneuf
- 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)
Matthew Garrett's chiming in on that subject, from a power management
point of view:

http://mjg59.livejournal.com/88608.html
--
http://pphaneuf.livejournal.com/
Ryan C. Gordon
2008-05-15 21:26:19 UTC
Permalink
Post by Pierre Phaneuf
- 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
You may be interested in this patch:

http://bugzilla.libsdl.org/show_bug.cgi?id=323#c2

We didn't put this into 1.2 because it's pretty invasive and awkward,
but reworking it for 1.3 could be a good idea.

--ryan.
Pierre Phaneuf
2008-05-15 21:54:49 UTC
Permalink
Post by Ryan C. Gordon
http://bugzilla.libsdl.org/show_bug.cgi?id=323#c2
We didn't put this into 1.2 because it's pretty invasive and awkward, but
reworking it for 1.3 could be a good idea.
Interesting, although it could probably be made to use a timeout
instead, which would make that other person quite happy, and might
also be used for the key repeat thing (haven't looked into that).

I've got a long weekend coming (yay Canada!), I'll look into this, thanks!
--
http://pphaneuf.livejournal.com/
Continue reading on narkive:
Loading...