Advertisement

Server state synchronization and input handling

Started by July 13, 2017 11:12 PM
6 comments, last by Nantuk 7 years, 4 months ago

Hello Game Dev! Sorry if I'm beating a dead horse with this thread, but I'm making a fast paced action networked game, and I'm having trouble wrapping my head around some things. Namely, how should the server handle ticking player input packets that arrive at the server at an incorrect simulation steps, as a result of network lag, lag jitter, and inevitably imperfect time synchronization algorithms. I'll start by outlining my understandings, and I would be eternally grateful to anyone who can shed some light on what I'm missing.

For the sake of simplicity, I'm assuming the client has synchronized to the server's clock in some respect, and bundles a "suggested simulation step" that the input ought to be executed on by the server.

I've read a few threads here, and a lot of the documentation that exists out there. As far as I can tell, there are 2 main methods to rectify inputs arriving at the server at incorrect times:

method 1. allow the server to rewind time and re simulate the game for late arriving input packets. This would allow late inputs to still have a chance of being simulated. The server would only allow rewinding to some maximum amount of time, to help curb cheating, and undesirably large jumps in the simulation for other players.

method 2. synchronize the client's clock forward of the server's clock by half of round trip time (RTT/2), plus some constant (c) to account for jitter in lag. So in total, (RTT/2) + c. Then, when the input arrives at the server, it arrives on time most of the time. If the input arrives earlier than it needs to, it can be buffered at the server until the appropriate simulation step to execute the input.

As far as I can tell, method 1 has the benefit of minimizing any sort of input buffering for players with good pings, but is a very heavy handed solution, and might cause frequent jumps in a given client's simulation. Method 2 seems to be a nice simple solution, would provide nice smooth simulation, but introduces a sizable lag for a given player's input to be seen by other players.

I suppose I have 3 main questions on this topic, and I would be a very happy boy if I could get some help on them:

1. Is method 1 a widely used approach? It seems like it introduces a massive amount of work, as a result of the server and clients having to correct their simulation state, all the while introducing potentially jittery movement.

2. I get that method 2 can buffer early input packets, but how does it account for late arriving input packets? I get that the server should acknowledge the late input packet in some way to let the client know it should adjust its simulation time, but would it drop that late arrived input? would it execute it, but at the CURRENT frame? What if there are 2 or more late inputs?

3. Am I missing any other solution? Are these 2 methods the widely used methods for handling state and input on the server? I can imagine a combination solution of method 1 and method 2, is this advisable?

 

Thanks in advance for all of your time! I feel very stuck on this, and will send all the karma your way for any help. In the mean time, I'll keep reading forum posts, to see if this has come up before.

 

*edit* wording.

 

The methods are not mutually exclusive.

In fact, the "source networking model" is a little bit of both.

For an approach that uses entirely method 1, look at "GGPO" which is a networking library for fighting games. Because fighting simulation is typically cheap, rewinding and replaying simulation is not a big deal in that context.

In method 2, you would add slightly more than just RTT/2 to the buffer time, so that packets generally don't arrive late. When packets arrive late, you simply discard them. If you assume that the player will do whatever they did last tick (if moving forward, keep moving forward, and so forth,) then you will guess right-ish enough.

When packets are lost you will of course have to correct the player whose packets were lost. This may cause snapping on their screen, the magnitude of which depends on the time duration between loss and correction. If your simulation is not 100% deterministic, you will likely want to correct all clients on some regular schedule anyway, because otherwise they will drift out of sync over time.

 

 

enum Bool { True, False, FileNotFound };
Advertisement

Thanks for the reply, this helps a lot. I think the simulation is pretty close to 100% deterministic, but I will for sure regularly correct player movement. I think to do otherwise would be asking for de-syncing trouble.

Discarding old packets in method 2 makes sense, and assuming the last input is sustained for the future also makes sense.

I was not aware that Valve's Source Engine did rewinding for late arriving input on the server (method 1). I have read that it does a sort of rewind in order to see if a player's ray-cast bullet hit anything (outlined in link1, and link 2), but nothing on the topic of the server rewinding the entire simulation for something like a player's movement input. I know the client basically does this, for locally predicting the input, but should the server? I really want to clarify this, because before I go implementing a globally rewindable server, I want to ensure it is a good strategy to do so. I see that GGPO does this, but it would typically be between 2 players. I plan on supporting upwards of 16-32 player. In your opinion, is allowing the server to rewind to account for late movement input packets generally recommendable?

You are correct, Source does not rewind the entire simulation. It only rewinds for hit tests. The point of my answer was that you can mix and match the different approaches, until you get a solution that matches your particular game. And, because each game has different gameplay requirements, there can't be a one-size-fits-all solution to this problem.

For 32 players, I would not use rewinding on the server. In general, I prefer to never rewind on the server, and instead discard late inputs, but it does depend on what kind of feel (and what kind of simulation cost) your game has.

enum Bool { True, False, FileNotFound };

Thats fair, I guess it really depends on the game. Its probably time for me to jump feet first in and see what is best for me. I'll start with the simulating the client ahead of the server method, and build off of that. Thanks for all your help!

Let me elaborate on how Quake 3 and Source networking do this which I think will clear up a few things for you:

 

The server sends snapshots of the world state to clients at 20 Hz. These snapshots are timestamped with the server time.

 

Once the client receives two of these snapshots, they initialize their time clock to the timestamp of the first snapshot and then run this clock forward each frame. The client then renders the world each frame as an interpolation between the first snapshot and the second snapshot. In an ideal scenario, the client will receive a third snapshot at the exact moment they've reached the timestamp of snapshot 2 and so on. Because snapshots are sent at 20 Hz, it means that the client time is typically 50ms + RTT/2 behind the server at any given time.

 

To keep the clock synchronized, the client clock is adjusted each time a new snapshot is received from the server. If the client hadn't yet finished interpolating to the next snapshot, the clock is bumped forward because the client is probably behind. If the client had already finished interpolating to the next snapshot and had begun extrapolating, the clock is nudged back because the client is probably ahead. If you're looking at the Quake 3 source code, this is the CL_AdjustTimeDelta function.

 

When clients send input to the server, they timestamp them with their clock so that the server can tell where they were in their snapshot interpolation and therefore what the state of the world was, as they saw it, when their input was entered. If the server stores a history of the snapshots they send out, then this timestamp can be used to rewind the state of the server for checking things like bullet traces. However, for general movement and gameplay the server usually does not rewind time. In these cases, the timestamp of the input is subtracted from the last input that was received on the server and this delta time is used to advance the player.

 

highly suggest you download the Quake 3 source code and get it running. It's an invaluable learning tool and is really quite an elegant implementation of the networking model.

Advertisement

Thanks for all the info on Quake 3. That sounds like good advice, I should take a look at Quake 3 to see how they did what they did.

This topic is closed to new replies.

Advertisement