2
\$\begingroup\$

I understand that in a snapshot-based network replication system the server holds onto N full snapshots and uses incoming ACK'd data from the clients to construct delta snapshots. This makes sense. What I have not seen a lot of information about is how the client uses that delta compressed snapshot to apply the changes.

Does the client store N snapshots so that it can reconstitute the delta compression against the reference it claims to have, or does it simply have one snapshot state of accumulated deltas? I imagine the former being correct, but the latter being less memory.

I've read How would a game-state snapshot system be implemented for networked real-time games? and and other answers, looked at Fabien's post, and read a couple of articles by Valve, but I am still unclear.

Any thoughts on this?

\$\endgroup\$
1
\$\begingroup\$

Overview

The server's state updates with each game tick and a new state is calculated. This new state is enqueued until it is ready to be sent.

When the networking loop is ready to send the state it checks to see what the last acknowledged state is. If there is none then it sends the full state. If there is one, it calculates the delta between that state, and the new one, and sends the resulting delta.

This delta is then sent to the client. The client enqueues the delta into its buffer. The client code then interpolates between deltas to calculate the current state.

Old states need to be stored to buffer network traffic

Clients usually have a queue of game states or deltas. This buffer is used to reduce jitter (new deltas may arrive at semi-random times), provide insulation against packet loss/lag (if a packet fails to come through or is slow, there is still time to wait for another) and increase the rate of visual updates (deltas may be sent 100 times per second, but the client wants to run at 200fps and has a 144hz monitor).

Keep in mind, even though I called these "old" states, the client is yet to see the data. These states are only old from the server's perspective.

Beyond that, it depends on how deltas are being used, and what your architecture is. Deltas can either be absolute (A = 1) or relative (A += 1).

There's no need to store past states for absolute deltas

If deltas are absolute, there is no need for clients to store past states. The server can calculate the delta from the last acknowledged state, and the system will run correctly. There is no chance that you will mess something up by setting A = 1 and then setting A = 1 again.

For example, imagine the client has the following states held in its cache:

  • {tick_number = 1, A = 0}
  • {tick_number = 3, A = 2}

The client then receives a delta {tick_number = 2, A = 1}. We can trust that the server has sent us the delta relative to the last state it knows we have received, so we can insert it without any problems.

You need a consistent reference point for relative deltas

If the system is using relative deltas, it is important to make sure that the client and server understand that the delta is relative to the same thing. If the server things that A = 1, while the client thinks that A = 10, then a delta of A += 1 is going to yield different results.

With this architecture, the client will need to store the youngest state that the server has used in a delta and every state younger than that.

For example, imagine the client has the following states held in its cache:

  • {tick_number = 1, A = 0}
  • {tick_number = 2, A = 1}
  • {tick_number = 3, A = 2}

The client receives the following delta from the server delta: {tick_number = 5, relative_to = 2, A = 2}. The client now knows that the server is always trying to use the newest state that the client has sent an acknowledgement for. Since the server is happy to send packets relative to tick number 2, that means there is no reason why the server would then go back to sending relative to tick number 1, so we can discard that state (unless it is still being used for interpolation).

For applications with sparse tick rates and/or unreliable networks, there may be benefits to holding on to packets

Continuing from the scenario above, the client has received the packet for tick number 4, and discarded its own tick number 1 since it is obsolete. This is the state of packets in the client:

  • {tick_number = 2, A = 1}
  • {tick_number = 3, A = 2}
  • {tick_number = 5, A = 2}

Now the client receives a delta that has arrived out of order: delta {tick_number = 4, relative_to = 1, A = 3}. Well, if we had kept the tick number 1 state, we would be able to use this packet to interpolate and calculate the tick number 4 state. In the previous example we threw it out, so now we can't properly calculate the tick number 4 state.

Luckily, ticks are usually very small, so we can interpolate between tick number 3 and 5. If this would harm your application, then it could be worth storing states a little longer.

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.