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 »

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: 1025
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: Stuttering & Fixed Timesteps

Post by Taehl »

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: 3166
Joined: Mon Aug 23, 2010 6:45 am
Location: Nova Scotia, Canada
Contact:

Re: Stuttering & Fixed Timesteps

Post by slime »

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 »

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: 1915
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: Stuttering & Fixed Timesteps

Post by ivan »

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 »

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: 1025
Joined: Mon Jan 11, 2010 5:07 am
Location: CA, USA
Contact:

Re: Stuttering & Fixed Timesteps

Post by Taehl »

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 »

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 292 times
User avatar
kraftman
Party member
Posts: 277
Joined: Sat May 14, 2011 10:18 am

Re: Stuttering & Fixed Timesteps

Post by kraftman »

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

Re: Stuttering & Fixed Timesteps

Post by ivan »

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: Ahrefs [Bot], Amazon [Bot], Google [Bot] and 11 guests