In trying to make my main loop more efficient/less latent, I noticed than within the
SDL_WaitEventTimeout() function, there is a
SDL_Delay(10) in a loop. A dead-sleep() call like this in the middle of the main loop seems problematic, for at least two reasons:
- 10ms is a long time, almost a whole 60fps frame. This means frames will be dropped.
- If an input event comes, we’ll ignore it (for no good reason) for 1-10ms, resulting in input latency.
Am I understanding this function correctly? Or is there another function that I should be using to do a wait-for-input-with-timeout?
As a hack, I reduced my
SDL_Delay to 1. However, the ‘proper’ solution solution seems to be to pass down the timeout to the inner
SDL_PeepEvents() function so that it can use a SDL_Cond to sleep+wait for immediate wake up. Thoughts?
Yeah, 10ms sleep is a lot.
But, to be honest, I don’t think this function (or a similar one) should be used in the main-loop of a game that somehow aims to render at a high framerate.
if there are no events, however you implement it, it will sleep for at least 1ms (because the timeout is an integer in milliseconds) and you usually don’t want to waste a millisecond of a frame waiting for events.
This might be useful for a texteditor or something like that, where you don’t need to update the screen (as often) as long as there are no events (and want to add some sleep if there’s nothing to do to keep CPU/GPU usage low).
For a game, just do the usual
// handle event
It will handle all events that happened since you last did this (last frame) and events that happen afterwards can be handled next frame. (You could even do this multiple times per frame, in case you want to do any last-millisecond adjustments due to new input late in the frame - but I’m not sure that really makes sense)
The way it’s implemented now, even if you pass it a timeout of 1ms, it’ll sleep for at least 10ms (see the source,
src/events/SDL_event.c). In order to actually get the 1ms sleep, you have to change the function itself (or a copy of it).
I can’t really do a fully busy loop like you describe though - I’m on a mobile app in an environment where battery life is critical. Sometimes I have very fast/simple frames, so sleeping 16ms (or more) is desirable.
can’t you leave the sleeping to vsync?
(assuming mobile generally supports vsync and the drivers don’t do some kind of busy-wait while waiting for the next frame)
That’s bad. As well as waiting for longer than expected when no event occurs, if an event does occur within the specified timeout period there may be a latency of up to 10ms before the function returns, which is undesirable. Because SDL_WaitEvent() calls SDL_WaitEventTimeout() it may suffer the same latency.
I don’t know about the OP but my application is heavily event-driven (all drawing operations are triggered by events received from another thread, for example) so latency is important. Sleeping in SDL_RenderPresent (waiting for vsync) is just as bad as sleeping in SDL_WaitEventTimeout from that point of view, both are wasting time when events could have been processed.
Ideally what I want to do is wait in SDL_WaitEventTimeout until just before the next vsync is due and then call SDL_RenderPresent, but obviously that needs a fine-grained timeout. Fortunately SDL_WaitEventTimeout calls only public functions so the entire routine can be copied into user code, with the SDL_Delay changed to 1 ms, but it shouldn’t be necessary.
That long, hidden delay seems like a significant bug to me.
You should still be able to do the standard SDL_PollEvent loop just with
an explicit SDL_Sleep based on the delta time through the loop. That’s
a pretty standard way to cap the frame rate.
This has come up from time to time, and a fully-waitable SDL event loop has long been on my wishlist. There was a patch for 1.2 that implemented this with caveats, and I’d like to try to implement this for 2.0 at some point.
Part of the problem is there are parts of SDL that can’t fully wait, so you end up having to poll in some cases anyhow. But I can envision a design where various subsystems hand over a list of platform-specific handles that we can select (or WaitForMultipleObjects) on, putting the process fully to sleep until there’s new data or a timeout. In practice, there are problems with this (various APIs we talk to don’t offer a select()able handle, or need polling, etc), but we can probably get mostly there.
The SDL_Delay(10) is there because this code has been collecting dust since the 1990’s, when you couldn’t wait less than 10ms on Linux or Windows 95, and trying to do so would likely be a busy-loop internally, having the opposite of the desired effect on battery life. That can probably drop to 1 in modern times, though, but the real solution is not to sleep in a loop, but get a bunch of handles to wait on.
The SDL_Delay(10) is there because this code has been collecting dust since the 1990’s
One more thought: the reason this is collecting dust is because while there are totally legitimate reasons to use this interface, most things shouldn’t.
I think it’s still probably worth it to make it do a
SDL_Delay(1). There’s no real downside (I don’t think?) and it would make it functionality equivalent to the best non-busy loop solution offered here (ie
SDL_Delay). I could submit a patch if desired
Since it’s not deprecated and is even called by
SDL_WaitEvent, I’m not sure why anyone would think to avoid it.
I can’t speak for Linux, but I’m pretty sure Windows 95 simply slept for the shortest period (which may have been 10ms or more) if a smaller value was specified. I still have a working Windows 95 PC so I can test it - but there’s not a lot of point in demonstrating that SDL was poorly coded from the start, even if it was!
You’re probably right; on Mac OS X in those times, it would definitely busy-loop, and I assumed Win95 did too.