Recommendations for Authoritative Network Model

Started by
39 comments, last by Angus Hollands 11 years, 11 months ago
Unless your goal is to specifically solve the "wall hack" problem at the expense of everything else (like, finishing the game, and tuning the networking so it feels good and plays well,) then I suggest you go with option 1.
Only when you find that something is a real problem should you actually do work to solve that problem :-)
enum Bool { True, False, FileNotFound };
Advertisement
I've nearly finished the basic delta implementation. I still have one issue (probably negative feedback) that results in a bottleneck with > 2 clients.
But it is likely this is easily fixeable. However, i seem to be dropping input packets every so often.
In addition, I have a question regarding gamestates.

Questions of the day!:

  1. How do the modern shooters deal with dropped input packets? Do they "mind" if the odd batch is dropped, or do they carry on regardless. Also, how often / when is a state update sent? Because of the dropped inputs the two game states can fall out of sync fast.
  2. When the server updates a gamestate using inputs from a client, does it apply those inputs to the latest gamestate, or the tick that they were intended for? (E.g if they were synchronised, and the inputs were sent at T100, would the server update the gamestate which may be T102 or find and update T100?
1. Typically, you send a RLE compressed set of input states for the last N packets in each packet. This allows the server to recover from lost inputs. There are other options as well, such as only actually simulating the client on ticks where data is received, or correcting the client, telling it that the input it sent wasn't received and correcting it back in time.

2. Typically, the client runs the local agent "ahead" of the server, so that input arrives "in time" for simulation on the server, and is then forwarded to other clients that display remote agents "behind" the server. This means no re-simulation or time-jumps are needed. However, other architectures exist as well -- there are many, many ways to skin this cat, and they all have slightly different trade-offs in how latency is hidden and compensated for.

enum Bool { True, False, FileNotFound };
That's been a useful post hplus! I've never encountered run-length encoding before. In addition, it forced me to look at my input packets and i can probably compress them further by using an index array rather than key based array (dictionary) and also i was sending key states for keys that were just released, which is useless to the server.

Purely out of interest, why not RLE the entire packet, if you're sending batched packets anyway? CPU time?

Purely out of interest, why not RLE the entire packet, if you're sending batched packets anyway? CPU time?


RLE on the actual input packet is unlikely to save any space. For most games, your input for a given step really is just three values:
- A bitmask of the input keys (movement, action, ...)
- The mouse left/right value (and/or the heading value)
- The mouse up/down value (and/or the pitch value)

Typically you pack this into 4 bytes -- 1 byte for 8 different input buttons, and 12 bits each for heading and pitch.
Why RLE works, is because those values typically don't change every frame, so the packet can say "here's the current state, and that was the current state for the previous X ticks, and before that there was this other state, that was current for Y ticks before that."
You can easily say that you just pack two separate states, plus the number of ticks of validity for each, to limit the amount of data used (10 bytes total in this case.) This gives you between 1 and 511 ticks' worth of dropped packet resilience, depending on how active the player is in changing the input.
Another option is to just mash the last 5 states into each packet, for the last 5 ticks, and if you drop more than 4 packets, you're toast and get corrected by the server.
enum Bool { True, False, FileNotFound };
Thanks Hplus!
I've taken a slightly different approach here. Firstly, I have used RLE on the entire "batch" of inputs, typically a batch consists of 6 ticks worth of input. The RLE is used on the previous six packets before the current six packets (collected between network ticks). In the event that an input is dropped, it can be retreived during the net packet, or then corrected by the server.
After effectively doubling the packet size in terms of content, the use of run length encoding compresses the packet to 80 bytes, from 300! It's rather useful.
Question n-1:

At the moment, I have the basics working. But, It doesn't account for delay from server to client, so when i receive a gamestate (delta or snapshot) It applies it as the current gamestate.
I'm now trying to remember our discussion earlier..
Leaving input prediction until later, is this correct:

  1. Store all received gamestates from the server. This is part of the networking just to set the last received gamestate
  2. Calling this every frame; Extrapolate using two or three game states to determine what the server's gamestate should look like at the current tick. And store this as simulated every tick.
  3. When you receive a gamestate, get the delta between the simulated gamestate for that tick and the server's gamestate, and then apply the delta.

If this is correct, should input prediction also modify the extrpolated state? I presume so..
There are three ways to deal with corrections from the server.

1) Take the "sledgehammer" approach. If you get a correction, which should be very rare, then you snap the user back to the corrected position at the current step on the client. The server needs to remember this correction, too, and apply the same correction on the same step. This is simple, robust, and feels terrible to the user, so only do this if corrections really are rare.

2) Take a "playback log" approach. When you send input to the server, save the state you think the client has for each step. When you get a correction, correct the state at that step, and then re-play the input locally up to the current step number. This uses more RAM and more synchronization, but is less impactful to the user, and will correctly deal with simple "one packet was dropped" problems.

3) Take a "smooth into destination" approach. When you get a correction to the server, don't immediately change the client, but instead keep a value for how much the difference in position is. Then slowly apply this delta in position over some time -- say, half a second, or a second. This requires de-coupling the server-decided state, the client-decided state, and the displayed state, and is a lot more complex. It _can_ smooth out corrections more than the other options, but it's in my opinion too complex to be worth it, and the user will see "non-physical" behavior ("gliding") over more than just a single step.

enum Bool { True, False, FileNotFound };
Im starting on client prediction, but i have to consider a few things. Firstly, are there any good resources for extrapolation of positions and orientations in a fast manner?

Im starting on client prediction, but i have to consider a few things. Firstly, are there any good resources for extrapolation of positions and orientations in a fast manner?


A demo of forward-extrapolation, with code.
enum Bool { True, False, FileNotFound };

This topic is closed to new replies.

Advertisement