Page 1 of 1

How to ensure an action is performed 60 times a second?

Posted: Wed Jan 10, 2018 12:00 am
by Substance12
So, I'm working on a networked game and I need some data to be stored 60 times a second. Right now I'm only calling a function every love.update(), but I know that's not gonna work because framerate can vary a lot.

I've thought of doing it so a counter adds the delta time to a variable, and every time it's equal or higher than 0.01666666 it performs the function and takes 0.01666666 from the counter, leaving it usually a little higher than zero. However, I realized that maybe the delta time can be a lot higher than 0.01666666 sometimes, so I thought of checking how many times 0.01666666 can fit in whatever the number in the counter might be and performing the action the same number of times, as well as taking (0.01666666 * number of times) from the counter. Then when a second passes, the function will have been called 60 times. Is this correct?

Related to this, I was looking through the mari0 source code, and I found this odd snippet at the top of love.update(). Is this related to my problem? What does it do?

Code: Select all

	dt = math.min(0.01666667, dt)
	
	--speed
	if speed ~= speedtarget then
		if speed > speedtarget then
			speed = math.max(speedtarget, speed+(speedtarget-speed)*dt*5)
		elseif speed < speedtarget then
			speed = math.min(speedtarget, speed+(speedtarget-speed)*dt*5)
		end
		
		if math.abs(speed-speedtarget) < 0.02 then
			speed = speedtarget
		end
		
		if speed > 0 then
			for i, v in pairs(soundlist) do
				v:setPitch( speed )
			end
			music.pitch = speed
			love.audio.setVolume(volume)
		else
			love.audio.setVolume(0)
		end
	end
	
	dt = dt * speed
	gdt = dt
	
	if frameadvance == 1 then
		return
	elseif frameadvance == 2 then
		frameadvance = 1
	end

	if skipupdate then
		skipupdate = false
		return
	end

Re: How to ensure an action is performed 60 times a second?

Posted: Wed Jan 10, 2018 1:25 am
by zorg
From the looks of it, most of the code deals with syncing the audio playback rate to the framerate; not sure if that's a good idea or if that's something you want to implement for yourself.

There are a few ways on tackling things.

On windows at least, when you drag a window, it will suspend processing on the main thread; when you release the window, it'll give back an elapsed (delta) time between the start and end timestamps of the drag.

This means you can do one of three things:
- Limit the deltatime so that spikes like that will not cause the game to try and catch up however many seconds or even minutes of work all at once, which would cause jumps in both logic and rendering in your game. This way, it'll be like the game was paused when you started dragging it.
- Not limit it, which would make it catch up near-instantaneously... i'm sure this too could have its uses.
- Use a thread for logic... sadly, in löve, you cannot separate (all) of the graphics from the main thread, so you cannot sidestep this issue.



Onto your original issue; you could implement a simple timer inside your love.update, as you said yourself; add the current delta time to the accumulator, and either:
- use a branch (if): each update cycle would only do one "tick", even if the game would start to get slow and it would be unable to catch up
- use a loop (while): in the case of the accumulator containing more than one full period, this will process all backed up "ticks" and only then proceed to love.draw.

In both cases, you just put the data assignment inside that block, and it should work.

Re: How to ensure an action is performed 60 times a second?

Posted: Wed Jan 10, 2018 8:42 am
by Maurice
Just to elaborate on the code you copied from Mari0, it actually does 6 things:

- Limit the delta time to 1/60 of a second (zorg mentioned this)
- Modify the pitch of all sounds according to speed, which is modified by the bullet time cheat
- Modify the dt based on the speed (again, bullet time)
- Set a global dt variable because I can't code
- Debug code for frame advancing (there should be more somewhere)
- Skip the current update if so desired. This is mostly for after loading the level for example where I know that dt will be unusually high