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 timing results (possibly delta related) #74961

Closed
jar-wtf opened this issue Mar 15, 2023 · 7 comments
Closed

Inconsistent timing results (possibly delta related) #74961

jar-wtf opened this issue Mar 15, 2023 · 7 comments

Comments

@jar-wtf
Copy link

jar-wtf commented Mar 15, 2023

Godot version

4.0.dev

System information

WINDOWS 10, RTX 2060 ,VULCAN

Issue description

I've been testing a firearm scene prototype and messing with the fire rates. I've been noticing that the fire rate is not very consistent. I've been testing the time it takes to empty a mag and it varies a bit. If a mag of 30rds at 600RPM gets emptied within the first couple of seconds of starting the scene, it will results in ~3.2s to empty. After that, it will take about ~2.889s consistently. The problem is that while most of the runs after the first mag are going to be ~2.889s, it will randomly result in ~3.2s again and in my main project I can audibly hear the difference as well.

Sometimes the 3.2s run happens only once every now and then, but other times it stays for a couple of runs before it goes back to 2.889s.

I've recreated this prototype in Godot 3.5 where the problem is non-existent. I've ran the Godot 3.5 project for a long time and not even randomly did the deviation happen.

Steps to reproduce

CONTROLS:

  • FIRE (LEFT MOUSE)
  • RELOAD (R)

NOTE: Weapon may or may not have full-auto enabled. This can be checked in the inspector as a boolean variable. Enable to run tests.

  1. I would run the Godot 4 version first. Run minimum 3 trials where the result will look something like: [~3.22, 2.889, 2.889]
  2. Leave that running in the background
  3. Test Godot 3.5 version of the prototype and run that a few times as well. Results should be consistent: ~[2.883, 2.883, 2.883]
  4. Go back to the Godot 4 project and now that it's had some time to sit, run them again. Issue might happen straight away or it might need more time to bring the issue to light again.
  5. Can go back to Godot 3.5 and test that project again, but should be consistent

Minimal reproduction project

firearm-testing-recreation.zip
Updated: https://github.com/jar-wtf/firearm-testing-recreation

@Calinou
Copy link
Member

Calinou commented Mar 15, 2023

Are both 4.x and 3.x projects running at the exact same framerate? Different framerates will cause fluctuations here, especially if using _process() instead of _physics_process().

The initial firing sequence being longer in 4.x may be due to shader compilation stutter. By default, _physics_process() may only run 8 times per rendered frame at most, so a large enough stutter can actually slow down the game. (You can adjust this with the Max Physics Steps Per Frame advanced project setting.)

Also, the Timer node isn't designed to handle very low wait times, as it can only emit timeout once per frame at most.

@jar-wtf
Copy link
Author

jar-wtf commented Mar 15, 2023

Yes, but this has nothing to do with the framerate necessarily. The firearm code is not framerate dependent. I mention both because the problem happens in Godot 4, but not in Godot 3. Godot 3 provides consistent results. Godot 4 on the other hand has inconsistencies. I'm not really 100% sure what the problem is here, but I would assuming having it recreated in both and one having the correct results would help narrow things down a bit.

I'm also not using the timer node. I've increase the physics max fps previously as well and no change.

@RedworkDE
Copy link
Member

The firearm code is not framerate dependent

This is not true. For example increase the firerate to 1'000'000 and observe how it take half a second to empty the mag, instead of the expected 2 ms.

  1. The code cannot fire more than one bullet per frame.
  2. The code looses the time between when the bullet should have been fired and the next frame, when it actually gets fired.
  3. The way you keep track of of time by accumulating deltas in a floating point variable is extremely susceptible to floating-point precision issues, esp given that the fire delay is an integer multiple of the expected frame time.

If you increase or decrease the rpm a bit (+- <10) the actual fire rate should pretty consistently land in either bucket for both the 3.x and 4.x version.

@jar-wtf
Copy link
Author

jar-wtf commented Mar 16, 2023

This is not true. For example increase the firerate to 1'000'000 and observe how it take half a second to empty the mag, instead of the expected 2

I get your point in the extreme case, and you're right it is somewhat frame dependent, but unrealistic. The limit is around 2000 before it stops "accurately" emptying the mag in about the right time. This covers most firearms and the RPM will be consistent across different frame rates, which is important, but not the point of the post.

The difference in completion times isn't really the issue I'm getting at. I'm trying to shine some light on how you can consistently get the same result on Godot 3, while on Godot 4 it can randomly result in ~3.2s instead of ~2.889 (@600rpm). During game play I would consider that a big deal since the player is randomly emptying a mag at different rates randomly. The small difference between the results in Godot 3/4 are not the issue.

The Godot 4 version will get ~3.2s if you empty the mag within a few seconds of starting the scene and will produce ~2.889 for a consistent amount of time. The problem is that it will begin to generate ~3.2s at random during runtime and will go back to 2.889 at some point. You will never get ~3.2s, let alone anything over 3s at 600rpm, in the Godot 3 project.

The way you keep track of of time by accumulating deltas in a floating point variable is extremely susceptible to floating-point precision issues, esp given that the fire delay is an integer multiple of the expected frame time.

Agreed and could this possibly be why I'm randomly getting these inconsistencies? iirc GD4 uses 64bit and GD3 uses 32bit floats?

I can upload a video later of this issue if it will help clear things up. I've got some recordings of this happening.

@RedworkDE
Copy link
Member

RedworkDE commented Mar 16, 2023

I have seen the issue. My point was that your code is extremely susceptible to amplify tiny timing and rounding issue, that cannot be expected to stay consistent across a major version upgrade which include a rewrite of many major systems.

In particular, given perfect math and frame times, when you code runs at exactly 60FPS a bullet is fired every 6 frames for a total time of 2.9s (exactly). Now if the framerate would increase to 60.01FPS the sixth frame of a few microseconds to early to fire the bullet so it waits until the seventh frame and now the whole thing takes 3.38 seconds.

Now in reality these differences are cause more by floating-point than by timing issues, but the point still stands: Your code is not numerically stable and thus small changes in the input result in large output changes.

The point I tries to make with varying the input RPM was, that (at 60 FPS) your code cannot achieve an effective RPM of anything other than 3600, 1800, 1200, 900, 720, 600, 514.2857, 450, 400, 360, 327.2727, 300, 276.9231, 257.1429, 240, ... and it always snaps to the next lower one.

The question really isn't why do you see these things after the upgrade to gd4, but why did it work in gd3. (Probably because the frame times where a bit slow there, or the different precision has rounded these issues away)

@jar-wtf
Copy link
Author

jar-wtf commented Mar 16, 2023

Gotcha, that makes sense.

Can close. This was very informative.

@Calinou
Copy link
Member

Calinou commented Mar 17, 2023

Agreed and could this possibly be why I'm randomly getting these inconsistencies? iirc GD4 uses 64bit and GD3 uses 32bit floats?

Both use 64-bit scalar float in GDScript, but floats in Vectors and packed arrays are 32-bit by default in Godot 4 (they always are in Godot 3). See Large world coordinates in the documentation for more information.

@Calinou Calinou closed this as not planned Won't fix, can't repro, duplicate, stale Mar 17, 2023
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

3 participants