Advertisement

Input generated in-between snapshots gets lost, best approach?

Started by November 27, 2024 05:01 PM
8 comments, last by raz0328 1 month ago

Hi All,

When working in a client-authoritative server model, it is common for the server to send world state updates (snapshots) to the clients at certain rate.

We all now about this and how well it works, but I'm facing the following problem and would like to now hot this is solved on other games/what is the best approach.

My simulation runs at 60hz, and the server sends snapshots at a minimum of 20hz. This means that every 3 ticks, a snapshot leaves the server, the problem I face is what to do with inputs (specially shooting a weapon) that get generated in between those snapshots, that information will never reach any clients since there was no shooting at the time the snapshot was generated.

My solution was to accumulate the inputs generated between snapshots and append them before sending, so for each Player in the snapshot there is also an array of N inputs that were generated in-between snapshots.

This works fine but I'm not sure this is the best approach, I don't like having this extra complexity on the snapshot system. Because it means that on the client side, when I interpolate between snapshots I have to extract this information, and it also adds some overhead to the snapshot packet etc etc etc.

As a second problem, which I would also like to have an opinion on; if a snapshot gets lost on its way to a client, and that snapshot carried a “fire” input. The client would never represent that weapon firing… To more in-game terms: imagine you are playing with a friend and he is shooting a weapon every 1 second, you suddenly loose a snapshot package and now you see your friend skipping the firing when in reality he DID fire the weapon. I have NEVER experienced this in any games so the facts that this could easily happen in my implementation raises some questions.

Thanks in advance!

None

Hi @raz0328 .

One way to solve the problem with missing snapshot with shot data (not the only one, just a first idea) is to have shot timestamp (shotTs) in the shooting character data. And maybe some data about shot: position, lookDirection, etc.

And in case there is a missing shot - client can “draw” it if he wants. For example, you can introduce some time window in the past from current moment (one second for example). If the missing shotTs is in this window - the client can visualize it and play a sound.

And what about problem with complexity on the snapshot system, where you need to accumulate multiple shots in one snapshot. I don't know of any other way to replicate shots on the client, while several shots are possible in one replication tick

Advertisement

Why 20 hz for the update? Why 3 frames to retransmit the world, does it really change that much to be more efficient than events?

The snapshot method you described is not typical for action games, which usually transmit events immediately.

While hobby games all do their own thing, it is far more common to only send events as they happen. For the small games I have been in contracts for that's how all of them did it, sending a steady stream of movement start/stop, fire buttons, jump events, and with each also that player's position (and often also velocity). The minimum packet size is 20 bytes so with efficient encoding and few players it can be hard to even reach the minimum packet size, let alone completely fill a single packet.

As for the lost update problem, it is pretty typical to establish subchannels for both reliable and unreliable data. Reliable channels use a sliding window protocol to repeat data across the wire if it wasn't acknowledged, unreliable channels don't bother and only send once which works fine virtually every time. With a simple sequence number the two sides can acknowledge transfer and move the data windows rather quickly. Some follow TCP's approach of buffering and only retransmitting on failure, others keep repeating it every time until acknowledged, details will depend on what you are sending and how often. Sequence numbers also solve the problem of packet order for late arrival and duplicates, although they are also pretty rare in modern networks.

I think I didn't explain myself correctly (or I'm not understanding something)… My original post is from the server's perspective, yes, the Client itself will send inputs every single frame to the Server.

  1. Client sends inputs every frame
  2. Server simulates the game state (lets say at 60hz)
  3. Server sends world state (snapshot) every 20hz

This is how it is done in the Source engine, Battlefield, and Overwatch.

As far as I understand, these snapshots contain all the information needed for all client to replicate the server state, in my case things like player position, health, velocity, and if it was pressing fire or not. The problem is that sometimes, this snapshot will not carry the “pressing fire” data, because at the time the server packed the snapshot, the player wasn't shooting, altough he was just a few ticks before.

For the small games I have been in contracts for that's how all of them did it, sending a steady stream of movement start/stop, fire buttons, jump events, and with each also that player's position (and often also velocity).

I am sending a steady stream of inputs, from the client to the server. If you are referring to broadcasting an “event” from the server to all clients every time a client fires its weapon it would be the first time I heard about it.

None

Servers can send similar updates to the client, especially if data changes are small or infrequent. It is quite common for servers to do exactly that. Once the server has validated all events in a tick there is no reason not to bundle them up and send them out immediately on modern networks.

What you described was more typical in the days of dial up and early DSL when networks were better measured in bits per second or kilobits per second, and for RTS games rather than shooters. Even 20 years ago when high end home networks were getting megabit connections it was important. These days some thoughtful efficient packing is more than enough for anything a hobby game will throw at it.

Thanks, I will put some thought into the snapshot packet and send it immediately after processing… this would mean that no event would lost because it would be always contained in the snapshot for that tick. I didn't want to do this because client are predicting the game state all the time and there is no real need to send them a snapshot every single tick, but I guess I'll go with that for now.

I'm not making a hobby game, I'm working on a networking library for a game engine and I've been able to handle hundreds of rigidbodies at the same time with server-authoritative movement, it is efficient and robust.

I still don't understand how games like Counter Strike do it, it has been documented many times that their model only sends snapshots “sporadically” (20hz), so you are saying that Counter Strike is in reality sending

20 snapshot messages a second for movement/positions + 1 independent message for every fire event ?

None

Advertisement

You should probably also learn how Unreal Engine does it as well, as it is the biggest in the industry. Your survey would be lacking if you omit the largest entry. If you are looking at Source and what they did 20 years ago, that's informative but two decades behind the times.

The tools let you record the sessions and reply all the packets, looking at each object and each bit encoded over the wire.

I have found it can handle 64 players out of the box without much issue, and 100 pretty easily. When inexperienced devs start relying too much on physics it breaks things, but usually it's not the engine but the developer not thinking about latency.

I think you're close to something that will work fine.

The most important bit that all good systems share is a common sense of time, especially if you have a fixed time step size (which Unreal Engine doesn't do, btw, but at least it has a shared game time!)

Once you have a common sense of which time step is which, and your clients typically run their player ahead of the server, and every remote entity “behind” the server (at the same timestep number as the server, but after one transmission delay in physical time,) you can then record all inputs with a timestep.

For minimum latency, clients should forward inputs each timestep (60 Hz in this case,) and the server should forward all gathered inputs each step! This will be a pretty small packet 60 times a second per player, and is totally worth it for lower latency.

Then, every so often (20 times a second might be more often than you need) the server sends snapshots. Of course, those will arrive “late” for players; here's where the log of input events helps out: After applying the snapshot state, re-play the remembered inputs that apply after the snapshot timestamp.

That's really “it.” As long as everyone agrees on what the sequence of events is, and as long as everyone receives all events, everything will work out great. The snapshots are there to compensate in cases where either simulation is non-deterministic, or when particular input event packets may have been lost in transit.

enum Bool { True, False, FileNotFound };

Thanks for your help, it does make sense that I'm not constrained in any way by the snapshot rate, I can send snapshots every 20hz AND also send other small stuff between them (like weapon events).

Thank you all.

None

This topic is closed to new replies.

Advertisement