Update rate desynchronization on client side prediction and server reconciliation

Started by
5 comments, last by hplus0603 6 years, 4 months ago

Here is some background info:

I have shared PhysicsEngine which is fully deterministic (if you have the same exact state feeding the same exact inputs and tick the same exact number of times, the end result will be the same). The simulation “ticks” at a constant frequency 20Hz. RenderEngine is working separately at its own refresh rate, so I will no describe it here.

Client-side step:

  1. Creates Input package based on the current keyboard/mouse state.
  2. Assigns unique id (basically frame number) to the Input and send it to the server.
  3. Saves  Input in the array for future reconciliation.
  4. Passes the Input to the PhysicsEngine and calculates predicted WorldState.
  5. Receives WorldState from the server, search for the relevant Input in the array and make server reconciliation (re-applies inputs that are not confirmed yet and clears all confirmed inputs based on the id)
  6. Makes a smooth transition from predicted to the authoritative position (if needed).

So, on every step of the simulation, we receive two snapshots:

  • Input - a packaged information about the current input device state (mouse position, keys pressed and attached unique id)
  • WorldState - calculated with PhysiscsEngine information about all entities (position, velocity, etc.). Also includes unique  id.

Server-side step:

  1. Generates a WorldState based on received Input packages.
  2. Sends a current WorldState to the clients.

QUESTION:

Basically, client-side prediction and server reconciliation works well except one thing. As I said before, both client and server sides running with the same PhysicsEngine code (thanks to js) at the refresh rate 20 times per second. BUT on the client side it runs a little bit faster (here is why).

So WorldState and Input snapshots are generating faster on the client side that server side, e.g. while server generates 484 tick, a client is at 502 or even more. After some time, a difference so big that the client starts to be littered with non-accepted inputs because they are produced more frequency than on the server side.

In ideal will be cool if client and server simulation rates will be independent.

How to deal with that? Any ideas?

Advertisement

1. Fix your system so that it runs 20 times a second on average. It doesn't matter that you don't have 100% control over exactly when each tick happens. It's not reasonable to expect to have full control over exactly when the updates occur, so you should just ensure that you're running the correct number over a longer period of time, by looking at the time elapsed and running sufficient updates to 'catch up'.

2. Drop the expectation that the client physics should be deterministic. It's impractical to have 2 deterministic simulations running in sync because you don't have control over the latency between the 2 simulations, unless you drop the update rate down to a point where you know you will always have time to synchronise them, which usually rules out real-time physics.

You need to send the latest time step from the server to each client somewhat frequently.

If the client finds that it is about to step ahead of the server, it should delay for a small amount of time, and then resume the regular ticking.

Also:

both client and server sides running with the same PhysicsEngine code (thanks to js)

You can not, in general, assume that JavaScript will be fully deterministic, if you use floating point numbers.

And, it turns out, in JavaScript, all numbers are floating point.

So it's only deterministic if you don't use numbers.

 

enum Bool { True, False, FileNotFound };
On 01.12.2017 at 3:04 PM, Kylotan said:

1. Fix your system so that it runs 20 times a second on average.

Thanks, will do.

 

On 01.12.2017 at 3:04 PM, Kylotan said:

Drop the expectation that the client physics should be deterministic.

Yes, I know that it is almost impossible to make physics absolutely deterministic on different machines, but anyway I think it is better to have as deterministic system as possible to reduce differences between client&server.

It is possible to run client physics on the different rate from server physics? For example client update internal physics (and predict movement) at 40Hz, but at the server physics is running on 20Hz to save CPU time. Also, how to deal with Input packages in this case? How often do I need to send them on the server?

Yes, you can run whatever simulations you want at whatever step rates you want. They will not be deterministic if they differ, though.

And, a player running at 40 hz may be able to just jump onto a ledge, where the 20 hz version of the simulation barely misses, causing a fairly noticeable gameplay glitch.

If server runs at 20 Hz, there's no benefit in sending packets more often than that.

You're saying you want to "save on server CPU" -- what measurements do you have that you are in any way limited on your server CPU throughput right now? If your game works better with 40 Hz simulation on both sides, perhaps it would be useful to just run at that rate, and only worry about lower rate if you ever measure and find that it's actually a problem?

 

enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement