Stuttering & Fixed Timesteps

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
User avatar
Bryant
Prole
Posts: 8
Joined: Wed Sep 28, 2011 12:12 am
Location: Vancouver, BC, Canada
Contact:

Re: Stuttering & Fixed Timesteps

Post by Bryant » Thu Sep 29, 2011 11:45 pm

Ha, good points Taehl. I've only ever worked with variable timesteps before, so I'm learning about this topic as I go along :)

One of the XNA developers summed up the "Fix Your Timestep" article in a way I found helpful:
A few games use a hybrid approach (as in the link that was posted above). They use a variable timestep for Draw, calling it as fast as the GPU retrace will allow, but apply fixed timestep CPU logic to control the frequency of Update. To smooth out the visual results (since Draw may now be called half way in between two Update time locations, or there could even be more than one Draw per Update), they interpolate the game state some fraction between two different Update results when drawing at times that do not exactly align to a fixed timestep tick point. This allows them to maintain as good as possible monitor sync, while still being able to write their physics assuming a nice simple fixed timestep. The downside is the extra code for lining up the two timelines and doing the state interpolation, which can be a serious PITA. There's nothing built into the XNA Framework to help you if you want to use this mode: you'd have to start with our variable timestep and then implement your own logic on top of that.
The accumulator method takes care of the fixed update and variable draw timesteps. The missing link is the state interpolation to smooth out the end results. I have no idea what an implementation of that would look like, but I guess I just need to take a stab at it :)

(Unless someone here has already done it? :oops:)

User avatar
Taehl
Dreaming in associative arrays
Posts: 1024
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: Stuttering & Fixed Timesteps

Post by Taehl » Fri Sep 30, 2011 1:05 am

State interpolation sounds like an awful idea to me. Not only does it mean you need to keep the current state, but also the last state (meaning, twice as much memory usage); you need to do interpolation on all that (meaning eating more CPU); and the controls would always lag at greater than or equal to [the time it takes to update the state] + [the time you wait between state updates] (since the time the player touches a control is in the "future" of the game's state, since you have to wait for the next update before the control will have any affect).

So, you're using more RAM and CPU, and intentionally lagging the controls (a sin, in my mind!), all for what, exactly? I fail to see how this scheme would offer a smoother experience than without it (to say nothing of performance in general).
Earliest Love2D supporter who can't Love anymore. Let me disable pixel shaders if I don't use them, dammit!
Lenovo Thinkpad X60 Tablet, built like a tank. But not fancy enough for Love2D 0.10.0+.

User avatar
slime
Solid Snayke
Posts: 2853
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Stuttering & Fixed Timesteps

Post by slime » Fri Sep 30, 2011 1:58 am

Taehl wrote:State interpolation sounds like an awful idea to me. Not only does it mean you need to keep the current state, but also the last state (meaning, twice as much memory usage); you need to do interpolation on all that (meaning eating more CPU); and the controls would always lag at greater than or equal to [the time it takes to update the state] + [the time you wait between state updates] (since the time the player touches a control is in the "future" of the game's state, since you have to wait for the next update before the control will have any affect).

So, you're using more RAM and CPU, and intentionally lagging the controls (a sin, in my mind!), all for what, exactly? I fail to see how this scheme would offer a smoother experience than without it (to say nothing of performance in general).
The linked article (titled "Fix your timestep!") explains it very nicely. The extra amount of CPU and RAM usage is negligible, and a fixed physics timestep is necessary in some situations - because of the way physics engines work, a deterministic simulation isn't really possible with a variable timestep.

A more optimal solution would be to separate rendering from updating entirely by putting everything except rendering into its own thread, but that's another topic. :P

User avatar
Xgoff
Party member
Posts: 211
Joined: Fri Nov 19, 2010 4:20 am

Re: Stuttering & Fixed Timesteps

Post by Xgoff » Fri Sep 30, 2011 3:20 am

Bryant wrote:Xgoff -- Floating point precision is exactly the problem I have with a variable timestep :( Have you tried implementing your own fixed timestep in LÖVE yet?
no i've just been using the default setup for now. actually whether or not i even make this game in love depends on whether or not i decide on 3d graphics (technically it'd still have 2d gameplay, but adding all the relevant 3d code would probably be annoying)

although atm i'm working on other things

User avatar
ivan
Party member
Posts: 1530
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Stuttering & Fixed Timesteps

Post by ivan » Sat Oct 01, 2011 10:26 am

First of, the box2d documentation says that b2worlds should be updated with a constant timestep.
If your timestep varies you may get unstable behavior with certain joints, etc.

Code: Select all

function love.update(dt)
	local accum = dt
	while accum > 0 do		-- accumulator for physics! no more penetration!
		local dt = math.min( 1/50, accum )	-- use whatever max dt value works best for you
		accum = accum - dt
		
		-- now, do whatever it is you need to do with dt
	end
end
That's not exactly how accumulators are supposed to work.
Your dt varies if accum is < 1/50.
Also, your accumulator doesn't really 'accumulate' anything since you set it to "dt" at each update.
If you are getting 'penetration' (usually called 'tunneling') with a particular body you might want to set its "bullet" flag to true.

The following code is from a short tutorial that I wrote on box2D:

Code: Select all

accumulator = 0

function Update ( delta )
  accumulator = accumulator + delta

  while accumulator > 0.016 do
    world:Step ( 0.016, 10 )
    accumulator = accumulator - 0.016
  end
end

pancakepalace
Prole
Posts: 40
Joined: Wed Aug 03, 2011 3:13 pm

Re: Stuttering & Fixed Timesteps

Post by pancakepalace » Sat Oct 01, 2011 11:12 am

Doesn't the stuttering occur because you are not moving the player the same pixel distance on every instance of love.draw(). Your pixel distance depends on many variables including some that are floating point like dt so that after being floored to the nearest x and y, your distance changes making a stutter.

Couldn't you simply set the player speed to an integer that doesn't change. This would mean that at every dt interval your player moves this speed (or distance) in pixels. If you then want to calculate the speed per second so that it doesn't depend on framerate your could then get the number of FPS from dt and adjust the speed accordingly.

I very well might be missing something.

Code: Select all

player = {}

function love.load()
	player.image = love.graphics.newImage( 'content/textures/player.png' )
	player.x = 0
	player.y = 0
	player.speed = 3
	love.graphics.setBackgroundColor( 245, 245, 245 )
end

function love.update(dt)
	if love.keyboard.isDown( "up" ) then
		player.y = player.y - player.speed
	end
	
	if love.keyboard.isDown( "down" ) then
		player.y = player.y + player.speed
	end
	
	if love.keyboard.isDown( "left" ) then
		player.x = player.x - player.speed
	end
	
	if love.keyboard.isDown( "right" ) then
		player.x = player.x + player.speed
	end		
end

function love.draw()
	love.graphics.draw( player.image, player.x, player.y )
end

User avatar
Taehl
Dreaming in associative arrays
Posts: 1024
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: Stuttering & Fixed Timesteps

Post by Taehl » Sat Oct 01, 2011 7:17 pm

ivan wrote: That's not exactly how accumulators are supposed to work.
Your dt varies if accum is < 1/50.
Also, your accumulator doesn't really 'accumulate' anything since you set it to "dt" at each update.
If you are getting 'penetration' (usually called 'tunneling') with a particular body you might want to set its "bullet" flag to true.
All well and good. But I'm not using Box2D, so I don't have a "bullet" flag to set to true. My game works fine with variable timesteps, it just needs to make sure that things don't move more than half a meter in one update (hence, a (quite high) speed limit and the code I posted). Anyway, it doesn't matter if it doesn't work "exactly" like some article says it should - it works flawlessly. Underlife can be running at 2 FPS and still have no problems.

And who said anything about Box2D anyway?
Earliest Love2D supporter who can't Love anymore. Let me disable pixel shaders if I don't use them, dammit!
Lenovo Thinkpad X60 Tablet, built like a tank. But not fancy enough for Love2D 0.10.0+.

User avatar
Bryant
Prole
Posts: 8
Joined: Wed Sep 28, 2011 12:12 am
Location: Vancouver, BC, Canada
Contact:

Re: Stuttering & Fixed Timesteps

Post by Bryant » Mon Oct 03, 2011 10:42 pm

A friend of mine gave me another technique to try out. It's not a true fixed timestep update loop, so it might not work for my situation. But it produces relatively smooth, jitter-free movement. It's still not perfect, but it seems to produce, at least for me, smoother results than previous update loops we've discussed.

Here's the meat of it:

Code: Select all

function love.run()
	
	if love.load then love.load(arg) end

	local totalTime = 0.0
	local deltaTime = 1 / 200
	
	local currentTime = love.timer.getMicroTime()
	local accumulator = 0.0

    -- Main loop time.
    while true do
    	
    	local newTime = love.timer.getMicroTime()
    	local frameTime = newTime - currentTime
    	
    	if frameTime > 0.25 then
    		frameTime = 0.25
    	elseif frameTime < 0.0 then -- Handle the case where getMicroTime() rolls over back to 0
    		frameTime = deltaTime
   		end
    	
    	currentTime = newTime
    	
    	accumulator = accumulator + frameTime
    	
    	while( accumulator >= deltaTime ) do
    		
    		if love.update then love.update(1.0) end

    		totalTime = totalTime + deltaTime
    		accumulator = accumulator - deltaTime
    	
    	end
    	
    	if love.update then love.update(accumulator / deltaTime) end
    	accumulator = 0.0
    	
        if love.graphics then
            love.graphics.clear()
            
            if love.draw then love.draw() end
            love.graphics.present()
        end

        -- See the attached .love file for more
There are 2 major things to note. I'm running the update loop at 200 frames per second, passing a delta time of 1 to each update. If, at the end of those 200 updates, I still have some unaccounted for time, I'll run update() again with a fraction of my previous delta time (this step is what prevents it from being a perfect fixed timestep update loop).

How does this run on your machines? Jittery? Smooth?
Attachments
JitterTest.love
(7.88 KiB) Downloaded 163 times

User avatar
kraftman
Party member
Posts: 277
Joined: Sat May 14, 2011 10:18 am

Re: Stuttering & Fixed Timesteps

Post by kraftman » Mon Oct 03, 2011 11:03 pm

smoooooth

User avatar
ivan
Party member
Posts: 1530
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Stuttering & Fixed Timesteps

Post by ivan » Tue Oct 04, 2011 5:10 am

My game works fine with variable timesteps, it just needs to make sure that things don't move more than half a meter in one update
Bryant wrote:at the end of those 200 updates, I still have some unaccounted for time, I'll run update() again with a fraction of my previous delta time (this step is what prevents it from being a perfect fixed timestep update loop)
I don't really understand why you would want to use accumulators if you delta is allowed to vary.
A simpler solution would be to simply clamp the delta value.

Post Reply

Who is online

Users browsing this forum: No registered users and 9 guests