Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inconsistent physics delta #31016

Closed
Tracked by #45334 ...
raymoo opened this issue Aug 1, 2019 · 6 comments
Closed
Tracked by #45334 ...

Inconsistent physics delta #31016

raymoo opened this issue Aug 1, 2019 · 6 comments

Comments

@raymoo
Copy link
Contributor

raymoo commented Aug 1, 2019

Duriel has shown here that the physics delta is not constant, contrary to the documentation: https://discordapp.com/channels/212250894228652034/342056330523049988/606477496589877250

The documentation specifies that the delta is constant: https://docs.godotengine.org/en/3.1/classes/class_node.html?highlight=Node#class-node-method-physics-process

I'm unable to reproduce the behavior, but this has implications for the consistency of physics simulations. Is the documentation or the implementation wrong?

@Calinou
Copy link
Member

Calinou commented Aug 1, 2019

Maybe this is related to #26887? As far as I know, @TheDuriel is on Windows 🙂

@TheDuriel
Copy link
Contributor

Correct I am on windows 7.

https://docs.godotengine.org/en/3.1/classes/class_node.html?highlight=Node#class-node-method-physics-process

"the delta variable should be constant."

Should. The docs never claim that it is.

https://docs.nvidia.com/gameworks/content/gameworkslibrary/physx/guide/Manual/BestPractices.html#the-well-of-despair

This has a good explanation of why it is not constant. If it were, godot would speed up / slow down when the physics server enters this state and can not recover. However, it recovers.

I did produce a MRP in discord that can exhibit this behavior as expected, by increasing the load on the physics server through physics calculations to such a degree that the main thread is affected as well. This then will cause fluctuations in the total delta per second to compensate. By doing more/less calls of the physics server per second. Moreso, this compensation starts with a slight overcompensation of about 3 extra frames.

Honestly I don't see why there needs to be a bug report about a correct implementation of frameskip.

@raymoo
Copy link
Contributor Author

raymoo commented Aug 1, 2019

As far as I can tell, Godot is supposed to be using the second option in that article you linked.

About the MRP you posted, it doesn't show the same issue as your screenshots I linked to. Your MRP shows a varying tick rate, but not a varying delta (the total delta is always the number of ticks multiplied by 1/60). This is normal catch-up behavior achieved by performing more physics ticks to make up for the earlier deficit, and doesn't require any well-of-despair avoidance since it's able to run the simulation quickly enough. Do you have a project that can help reproduce the varying delta from the earlier screenshots?

I've modified your project from discord (modified project attached)
ModifiedGame.zip
to print the minimum and maximum delta over each second, confirming that the delta does not change in that project:

actors:	572	ticks:	59	min delta:	0.016667	max delta:	0.016667
actors:	601	ticks:	60	min delta:	0.016667	max delta:	0.016667
actors:	630	ticks:	60	min delta:	0.016667	max delta:	0.016667
actors:	659	ticks:	61	min delta:	0.016667	max delta:	0.016667
actors:	684	ticks:	60	min delta:	0.016667	max delta:	0.016667
actors:	703	ticks:	62	min delta:	0.016667	max delta:	0.016667
actors:	718	ticks:	58	min delta:	0.016667	max delta:	0.016667
actors:	730	ticks:	64	min delta:	0.016667	max delta:	0.016667
actors:	737	ticks:	56	min delta:	0.016667	max delta:	0.016667
actors:	745	ticks:	62	min delta:	0.016667	max delta:	0.016667
actors:	753	ticks:	61	min delta:	0.016667	max delta:	0.016667
actors:	761	ticks:	63	min delta:	0.016667	max delta:	0.016667
actors:	768	ticks:	56	min delta:	0.016667	max delta:	0.016667
actors:	776	ticks:	64	min delta:	0.016667	max delta:	0.016667
actors:	783	ticks:	56	min delta:	0.016667	max delta:	0.016667
actors:	791	ticks:	64	min delta:	0.016667	max delta:	0.016667
actors:	798	ticks:	56	min delta:	0.016667	max delta:	0.016667
actors:	806	ticks:	64	min delta:	0.016667	max delta:	0.016667
actors:	813	ticks:	56	min delta:	0.016667	max delta:	0.016667
actors:	821	ticks:	64	min delta:	0.016667	max delta:	0.016667

As for why you wouldn't want delta to vary, the reason is that it can cause the simulation to be inconsistent, beyond just the (very small) effects of floating point errors. For example, consider the following kinematics code:

v = 0
func _physics_process(delta):
  v += 10 * delta
  position += v * delta

How does this behave over one second? It varies based on timestep:

10 FPS: position = 5.5
20 FPS: position = 5.25
30 FPS: position = 5.1667
60 FPS: 5.0833

Note I didn't simulate this in Godot (If delta varies over different frames, then it may give incorrect results). I used this python code: https://pastebin.com/ELTQSBz0. This just demonstrates the effects of changing the timestep, and isn't specific to any particular implementation.

What are the implications of this kind of consistency? For linear movement, such as most bullets, not much. The path should stay the same, though collisions might happen or a different location, or objects may phase through each other if the timestep is too large.

For movement with acceleration, such as jumping, it means that the capabilities of players, such as jump height, may vary significantly based on lag. This is unacceptable in games that have non-linear kinematics (such as platformers with gravity and jumping) and require precise movement.

@girng
Copy link

girng commented Aug 3, 2019

I can't reproduce this issue with raymoo's example. I'm on Windows 10

@lawnjelly
Copy link
Member

Duriel has shown here that the physics delta is not constant, contrary to the documentation:

I couldn't make complete sense of the thread on discord (my IQ seemed to be dropping as I read), but given that I've currently been improving the timestepping (been offline for a few days so I would have answered earlier)..

Note that this is subject to change as I have a PR in for semi fixed timestep, which also includes a simplified fixed timestep which might be easier for testing this kind of thing:

  • The delta passed to _physics_process is indeed constant afaik

  • What may be causing confusion is that strange things can happen once you start pushing to the performance limits

  • Aside from the jitter fix thing (I would turn this to 0 for any testing, or wait and use the simplified fixed timestep), the main timer just adds up the delta since the last call to main::iteration, and calculates how many physics ticks are due at the requested tick rate (somewhat confusingly labelled physics fps, this confuses frames with physics ticks, which are completely different things).

  • Once it has calculated how many ticks to do on that main::iteration (equates to a frame in most cases) it applies a hard limit of maximum 8 physics ticks, presumably to prevent runaway physics spiral of doom, and to deal with large gaps (maybe the OS taking over, or even going to menu screens, I haven't checked how pausing the scene tree affects the timer, it should really be reset when unpausing).

So this hard limit of 8 ticks may result in some strange behaviour in some cases. The game might 'lose time' which might be something to take account of depending on how you structure your AI / logic (i.e. you might want to base a game timer on the number of physics ticks passed, rather than the 'time passed').

If something stalls the game and there is a big gap for a second, the main_timer will think there is a boatload of ticks to catch up on and might do too many for a time, although in practice I think with the current logic it will try to catch them all up at once (e.g. 50 ticks) then cap this to 8 and only run 8 extra. I don't think it gets into the spiral of needing more over a prolonged period to catch up.

The thing about reports of time going backwards is interesting. As far as I remember it just calls an OS specific 'gettime' function at the start of main::iteration, something like e.g. QueryPerformanceCounter on windows.

Windows does some jiggery pokery to fake some decent values for this afaik, because of speedstepping in CPUs, multicore processors etc, but barring OS bugs you would hope that time was moving only forward (I have read reports of QueryPerformanceCounter going backwards though, so perhaps there should be a clamp to positive values in there, we can maybe add this if not present).

Another suspect for timing bugs could be thread issues like main::iteration being called again while it is already running, I've always assumed this doesn't happen as it would cause lots of things to go pear shaped (what calls main::iteration might be different on different platforms).

If anyone is interested how it works, I would encourage reading the source. All the magic is done in main::iteration and main_timer_sync, and it is fairly simple to understand (aside from the jitter fix, no one knows how that works 😄 ).

@raymoo
Copy link
Contributor Author

raymoo commented Apr 9, 2021

I made this issue out of passive aggression so it can be closed. I don't actually believe this issue exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants