love icon indicating copy to clipboard operation
love copied to clipboard

`love.timer.sleep(0.001)` locks game to 500fps rather than 1000fps

Open expikr opened this issue 1 year ago • 16 comments

The default love.run yields via love.timer.sleep(0.001) to reduce CPU usage.

I'm just curious why the result is a stable 500fps rather than 1000fps?

expikr avatar Sep 13 '23 19:09 expikr

The duration of sleep is ultimately up to what the OS scheduler decides. Recent Windows updates changed its standard OS sleep function to have a minimum possible duration of 2ms apparently, instead of 1ms.

Microsoft also introduced a new sleep function recently which can theoretically have shorter durations (although I'm not sure how short exactly, in practice). love calls into SDL2's sleep function which doesn't use that new API, although SDL3 exposes a new function which does.

slime73 avatar Sep 13 '23 19:09 slime73

Here's the relevant SDL3 change. love will use the new API eventually, once SDL3 is released and stable. https://github.com/libsdl-org/SDL/pull/6232

slime73 avatar Sep 13 '23 19:09 slime73

Had the same issue a few days ago actually, I wanted a 8000hz thread to poll input at 8000hz (those new 8khz mouses) but love.timer.sleep couldn't wait less than 1ms.

My solution was love2d uses LuaJIT, so you have access to C functions too through ffi.cdef, and nanosleep worked like a charm.

I'm guessing this is also a solution love2d could make use of natively.

xchiptune avatar Sep 18 '23 23:09 xchiptune

Just a heads up, love.event.pump is not safe to call on non-main threads. Multiple events within a single frame won't be missed if it's called once per frame though.

slime73 avatar Sep 18 '23 23:09 slime73

Unless you are explicitly capturing timestamps at 8000hz, there is precisely zero benefit to do anything other than pumping events at the start of frame. Subtick input processing can easily be implemented by processing the pumped events individually and emit events when a change happens (as opposed to lumping them into a "current frame state")

If you need to capture precise timestamps at 8000Hz, you should be rolling your own message loop with user32.GetMessage in a blocking thread dedicated to listening for WM_INPUT and call kernel32.QueryPerformaneCounter as the first thing after GetMessage unblocks.

SDL (and by extension Love) is completely unsuitable for precise message timestamping because they uses PeekMessage -- which polls rather than blocks -- for both SDL_PollEvent and SDL_WaitEvent

expikr avatar Sep 20 '23 02:09 expikr

Well I'd love to talk more about my project but the specifics of that aren't relevant here. It was just anecdotal and the main point was there is an easy way to perform more granular sleeps on Love2D.

And I'm on Linux, Windows doesn't have nanosleep.

I believe on Windows your options are extremely limited. The only way to perform sub 1ms sleeps without using busy-waits is waitable timer objects. Any modules you use on C or C++ is either using waitable timer objects or a busy loop with QueryPerformaneCounter under the hood. Waitable timer objects poses it's own issues since they aren't as fast as busy-waits or regular sleeps despite the higher precision.

So you're left with choosing between the cpu intensive busy wait, or the slower waitable timer object.

xchiptune avatar Sep 20 '23 04:09 xchiptune

On windows, GetMessage() blocks the thread until there is a message that satisfies the specified filter. In other words, it is the lowest possible latency method to timestamp when a message is received by the process, far more accurate than any busy-wait loops could possible get you.

expikr avatar Sep 21 '23 21:09 expikr

Did you forget the topic of this issue? You started this issue raising question about the resolution of love.timer.sleep()

Inputs and GetMessage() are not relevant to this issue.

xchiptune avatar Sep 23 '23 01:09 xchiptune

Ultimately there's not much we can do here I guess. You can always redefine your own love.run with the love.timer.sleep call removed.

MikuAuahDark avatar Sep 23 '23 02:09 MikuAuahDark

Did you forget the topic of this issue? You started this issue raising question about the resolution of love.timer.sleep()

Inputs and GetMessage() are not relevant to this issue.

It's in response to your use case of wanting to sample 8000Hz inputs. I'm saying that using Love2D for that premise is wrong to begin with, you should be rolling your own input thread with GetMessage.

expikr avatar Sep 23 '23 12:09 expikr

just in case , i made a fork of love2d that remove love.timer.sleep(0.001) : https://github.com/quentin452/love-experiments

quentin452 avatar Feb 23 '24 00:02 quentin452

You don't need to fork love to do that, you can customize love.run already.

However, an extra 1-2ms per frame is not a drastic improvement in most situations. Don't be fooled by the big FPS numbers, they aren't linear. :)

slime73 avatar Feb 23 '24 00:02 slime73

does love.timer.sleep(0) yield at all?

expikr avatar Mar 03 '24 05:03 expikr

It can, however the behaviour is pretty dependent on outside factors. Here's an article talking about it on Windows: https://randomascii.wordpress.com/2012/06/05/in-praise-of-idleness/

slime73 avatar Mar 04 '24 01:03 slime73

:)

like i want to optimize my game , when i see more FPS i am happy :D

quentin452 avatar Mar 08 '24 04:03 quentin452

Then look up how many watts your PC is consuming running at full speed, and see the costs of electricity to be less happy :D

Keyslam avatar Mar 08 '24 08:03 Keyslam