Love2d Timestep and keypress (with repeat) not updating correctly?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
love2d
Prole
Posts: 12
Joined: Mon Apr 09, 2018 5:58 am

Love2d Timestep and keypress (with repeat) not updating correctly?

Post by love2d »

Following this post: https://gafferongames.com/post/fix_your_timestep/ I wanted to provide a non-fixed timestep. Essentially, a game tick rate independent of frame rate (due to users being able to run game at non-60fps, vsync on / off...)

I came across this gist which gets the job done:

https://gist.github.com/Leandros/98624b9b9d9d26df18c4

The issue is that if we set TICKRATE to 1/1000 love.update will run every 1/1000th a second right? Well using a mix of the latest 11.X https://love2d.org/wiki/love.run and the gist above, pumping events like above with `love.keyboard.setKeyRepeat(true)` (such as a user moving their character on the screen, holding down an arrow key) does not produce a love.keypress every 1/1000th a second as the update loop.

Even if I add the event loop within the tickrate loop, it seems that the keypress (e.g. left arrow key) happens in a fixed rate, regardless of if TICKRATE is 1/1, 1/60, 1/1000...

Is my only option to sync the holding down of keypresses and a non-fixed timestep to use keyboard.isDown within the love.update function?

EDIT:

I've noticed https://love2d.org/wiki/love.keyboard.setKeyRepeat mentions that it depends on the users system. I've tried changing my system repeat rate and that definitely affects how love.keypressed is called. Even adding a dt timer in love.keypressed seems to not matter, as realistically a slower repeat rate from system will slow a users actions down for quick actions. I think the only solution is keyboard.keydown in update

EDIT2:

I've created a Fork of the gist here: https://gist.github.com/jakebesworth/ac ... 77105a41f8 that works with 11.X, licensed, and will hopefully be continually updated as necessary
Last edited by love2d on Mon Apr 30, 2018 5:53 am, edited 2 times in total.
User avatar
zorg
Party member
Posts: 3444
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by zorg »

First i'm going to assume you did notice that the gist was from 2016, and you modified the infinite loop to be a returned function, like how the wiki page gives you the current 11.x default love.run function.

Theoretically yes, depending on your cpu speed, os scheduler and stuff, you might get 1000 ticks per second with it.

Now, the important bit, setKeyRepeat is cancer because it uses the OS' own repeat detection rates, which is basically useless for any and all purposes, even though people like to say otherwise.

What you want to do is forget ever setting that to true, and use a combination of love.keyPressed and love.keyboard.isScancodeDown (or love.keyboard.isDown if you want to mess with people who have non-US key layouts selected.) to detect a press, and detect if it's still held down or not; that way, it will be accurate down to your tickrate. (You can also use keyReleased if you want to)

In short, yes, you need to use one of those two functions inside love.update or whereever to get what you want.



That said, what you call "non-fixed timestep" is a bit of a misnomer, since you do want the timestep (the tickrate) to be fixed to 1/1000, you just don't want it to be tied to the graphical framerate.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
love2d
Prole
Posts: 12
Joined: Mon Apr 09, 2018 5:58 am

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by love2d »

Zorg wrote: First i'm going to assume you did notice that the gist was from 2016, and you modified the infinite loop to be a returned function, like how the wiki page gives you the current 11.x default love.run function.
Yes I did. After this research is done, I intend to submit my latest version to the gist.
Zorg wrote: Now, the important bit, setKeyRepeat is cancer because it uses the OS' own repeat detection rates, which is basically useless for any and all purposes, even though people like to say otherwise.
That's the conclusion I've come to, so it's good to get reassurance.
Zorg wrote: In short, yes, you need to use one of those two functions inside love.update or whereever to get what you want.
Ok, solves my question!
Zorg wrote: That said, what you call "non-fixed timestep" is a bit of a misnomer, since you do want the timestep (the tickrate) to be fixed to 1/1000, you just don't want it to be tied to the graphical framerate.
Good point!

In reality I'll probably use some fancy number like 60, 100, 128, tick rate, I need to decide on that (it can't be too low if x + 1 movement of objects is too slow).

Thanks Zorg for answering my question!
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by ivan »

love2d wrote: Sun Apr 29, 2018 6:04 am The issue is that if we set TICKRATE to 1/1000 love.update will run every 1/1000th a second right?
NO! You might be able to clamp the lower bound of your "tickrate" to 1/1000 but there is no way to ensure that love.update is called every 1/1000th of a second. There is no way to guarantee this when you are running in a multi-tasking operating system.
The following piece of code may lock indefinitely on a slower CPU because it doesn't cap the "frameskip" or rather the "tickskip":

Code: Select all

        while lag >= TICKRATE do
            if love.update then love.update(TICKRATE) end
            lag = lag - TICKRATE
        end
You see, the problem is that your "love.update" callback might take longer than 1/1000th of a second to execute.
So you need to include something like "maxframeskip" or "maxtickskip".
User avatar
Nixola
Inner party member
Posts: 1949
Joined: Tue Dec 06, 2011 7:11 pm
Location: Italy

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by Nixola »

zorg wrote: Sun Apr 29, 2018 6:24 amNow, the important bit, setKeyRepeat is cancer because it uses the OS' own repeat detection rates, which is basically useless for any and all purposes, even though people like to say otherwise.
As far as I can tell, it's only useful in GUIs or other situations where the user is typing.
lf = love.filesystem
ls = love.sound
la = love.audio
lp = love.physics
lt = love.thread
li = love.image
lg = love.graphics
User avatar
pgimeno
Party member
Posts: 3549
Joined: Sun Oct 18, 2015 2:58 pm

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by pgimeno »

Nixola wrote: Sun Apr 29, 2018 8:02 am As far as I can tell, it's only useful in GUIs or other situations where the user is typing.
Right. Even though love.textinput repeats always, you still need setKeyRepeat to auto-repeat arrow keys for faster movement.
love2d
Prole
Posts: 12
Joined: Mon Apr 09, 2018 5:58 am

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by love2d »

ivan wrote: Sun Apr 29, 2018 7:04 am
love2d wrote: Sun Apr 29, 2018 6:04 am The issue is that if we set TICKRATE to 1/1000 love.update will run every 1/1000th a second right?
NO! You might be able to clamp the lower bound of your "tickrate" to 1/1000 but there is no way to ensure that love.update is called every 1/1000th of a second. There is no way to guarantee this when you are running in a multi-tasking operating system.
The following piece of code may lock indefinitely on a slower CPU because it doesn't cap the "frameskip" or rather the "tickskip":

Code: Select all

        while lag >= TICKRATE do
            if love.update then love.update(TICKRATE) end
            lag = lag - TICKRATE
        end
You see, the problem is that your "love.update" callback might take longer than 1/1000th of a second to execute.
So you need to include something like "maxframeskip" or "maxtickskip".
Thanks for the reply ivan. I understand that in practice it won't run every 1/1000th of a second as you mentioned. Also, ideally I'd set TICKRATE to like 60, or 256 or some smaller number, not that it fixes your concern (PCs can slow down for a multitude of reasons).

I'm a little confused with your concern though, can you elaborate?

Even if love.update takes say 1/2th of a second to execute (more than 1/1000th), (meaning the game will appear slower for slow computers). But even if we don't, eventually that loop should end, I'm not sure where the indefinite lock comes into play.

What do you mean by "tickskip" or "frameskip" in this context (if you can provide a small pseudo-code snippet that might help me wrap my head around the problem).

Other than that, assuming I've updated the code to return a function and based it off of 11.X https://love2d.org/wiki/love.run do you see any other issues with the code snippet?
User avatar
ivan
Party member
Posts: 1911
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by ivan »

love2d wrote: Sun Apr 29, 2018 3:00 pm Even if love.update takes say 1/2th of a second to execute (more than 1/1000th), (meaning the game will appear slower for slow computers). But even if we don't, eventually that loop should end, I'm not sure where the indefinite lock comes into play.
Well, I'll put it this way, let's say that love.update takes 10 ms every time.
After you execute your loop once you already have produced 10 ms lag.
Then you execute the loop a second time, with 10 ms of lag.
Since your "target update rate" is 1ms you have to call love.update 10 times.
Therefore after the second run you have to call love.update 10 times to compensate for the lag, but that adds an additional 100 ms of lag...
My suggestion would be to break the loop or limit the maximum amount of lag that can accumulate.

Code: Select all

  local interval = 1/targetFrameRate
  lag = math.min(lag + dt, interval*maxFrameSkip)
  while lag >= interval do
    lag = lag - interval
    update(interval)
  end
This way you may skip some frames if any lag occurs.
If the lag becomes too much, the game will slow down temporarily (but it won't hang).
love2d
Prole
Posts: 12
Joined: Mon Apr 09, 2018 5:58 am

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by love2d »

ivan wrote: Sun Apr 29, 2018 3:31 pm
love2d wrote: Sun Apr 29, 2018 3:00 pm Even if love.update takes say 1/2th of a second to execute (more than 1/1000th), (meaning the game will appear slower for slow computers). But even if we don't, eventually that loop should end, I'm not sure where the indefinite lock comes into play.
Well, I'll put it this way, let's say that love.update takes 10 ms every time.
After you execute your loop once you already have produced 10 ms lag.
Then you execute the loop a second time, with 10 ms of lag.
Since your "target update rate" is 1ms you have to call love.update 10 times.
Therefore after the second run you have to call love.update 10 times to compensate for the lag, but that adds an additional 100 ms of lag...
My suggestion would be to break the loop or limit the maximum amount of lag that can accumulate.

Code: Select all

  local interval = 1/targetFrameRate
  lag = math.min(lag + dt, interval*maxFrameSkip)
  while lag >= interval do
    lag = lag - interval
    update(interval)
  end
This way you may skip some frames if any lag occurs.
If the lag becomes too much, the game will slow down temporarily (but it won't hang).
Awesome, thanks ivan! That makes sense, It's very much the "spiral of death" mentioned in the "Fix your Timestep" article, which I'm surprised wasn't added in the gist, I'll have to add it.
love2d
Prole
Posts: 12
Joined: Mon Apr 09, 2018 5:58 am

Re: Love2d Timestep and keypress (with repeat) not updating correctly?

Post by love2d »

ivan wrote: This way you may skip some frames if any lag occurs.
If the lag becomes too much, the game will slow down temporarily (but it won't hang).
Hey Ivan (and others) here's my updated code. I've tested it, and it seems to work (I can't think of a way to check the frameskip portion) where game tick rate is independent of frame rate, with following the guidelines to disallow spiral of death issues:

Code: Select all

--[[
    Based off of: https://gafferongames.com/post/fix_your_timestep/
        https://github.com/SSYGEN/blog/issues/11
    Code from: https://gist.github.com/Leandros/98624b9b9d9d26df18c4
---]]

-- 1 / Ticks Per Second 
local TICK_RATE = 1 / 1000

-- How many Frames are allowed to be skipped at once due to lag (no spiral of death)
local MAX_FRAME_SKIP = 250

-- No Framerate cap currently, either max frames CPU can handle (up to 1000), or vsync

function love.run()
    if love.load then love.load(love.arg.parseGameArguments(arg), arg) end
 
    -- We don't want the first frame's dt to include time taken by love.load.
    if love.timer then love.timer.step() end

    local lag = 0.0

    -- Main loop time.
    return function()
        -- Process events.
        if love.event then
            love.event.pump()
            for name, a,b,c,d,e,f in love.event.poll() do
                if name == "quit" then
                    if not love.quit or not love.quit() then
                        return a or 0
                    end
                end
                love.handlers[name](a,b,c,d,e,f)
            end
        end

        -- Cap number of Frames that can be skipped so lag doesn't accumulate
        if love.timer then lag = math.min(lag + love.timer.step(), TICK_RATE * MAX_FRAME_SKIP) end

        while lag >= TICK_RATE do
            if love.update then love.update(TICK_RATE) end
            lag = lag - TICK_RATE
        end

        if love.graphics and love.graphics.isActive() then
            love.graphics.origin()
            love.graphics.clear(love.graphics.getBackgroundColor())
 
            if love.draw then love.draw() end
            love.graphics.present()
        end

        -- Even though we limit tick rate and not frame rate, we might want to cap framerate at 1000 frame rate as mentioned https://love2d.org/forums/viewtopic.php?f=4&t=76998&p=198629&hilit=love.timer.sleep#p160881
        if love.timer then love.timer.sleep(0.001) end
    end
end
How does it look?

One thing I will probably add later is a Framerate cap availability if the user wants it...
Post Reply

Who is online

Users browsing this forum: No registered users and 97 guests