[Solved] Three Solutions to UDP's Critical Packet Loss

Introduction
So I’ve recently just quit Minecraft due to everyone trying to make money out of it and ruining the game for me. In fact, it’s been so recent that I haven’t had a chance to get a decent username and change my avatar… I don’t know whether people write introduction posts on here, so I am going to include mine in this post. Hi.

The Problem
The game, which I am working on currently has ~20 types of packets, ~6 of which are critical.
I don’t really want to use TCP for only a quarter of packets sent, especially since critical packets aren’t sent very frequently in the game and wasting resources isn’t a great idea.
Here are some solutions that I have thought of. If you could let me know what you think, that would be great. Thank you.

The Chosen Solution
As suggested by princec, the best solution seems to be to store an array of unconfirmed critical packets for each Client and resend them every game tick, until the Client sends a valid confirmation packet for each critical packet that is being resent. Thank you :slight_smile:

[s]Solution One
The loss rate is 1% to 5% depending on network latency, so assuming it’s 5%, that’s 1/20 packets lost.
However, if critical packets are resent once, that 0.05 chance becomes 0.0025, and if they are resent twice, the chance decreases to 0.000125, so only 1 in 8000 critical packets are lost. I don’t actually like this solution, since 1 in 8000 games could be messed up for someone, somewhere.

Solution Two
When a critical packet is received by the client, they send a confirmation packet to the server. This contains the critical packet’s identification byte (first byte that defines what the packet does) and the length or checksum of the packet. If the receiver does not get this confirmation packet within , the critical packet is resent. This cycle is repeated until a valid confirmation packet arrives.

Solution Three
Use client prediction. Basically, the client decides when it should be receiving critical packets. E.g. when an LivingEntityMovement packet is received for a LivingEntity that doesn’t exist (for the client), they request that LivingEntity’s join packet to be resent. This should be hardly noticeable by the player.

Solution Four
Switch to TCP. I do not like this option at all because I’d have to rewrite my rather simple UDP library to make it into a TCP library but more importantly because 3/4 of the packets sent by my game are not in any way critical. The 1/4 that are critical, are hardly ever sent, compared to the other packets. TCP is only an option if the other two solutions that I’ve thought of absolutely suck.[/s]

I think it is better:
This cycle is repeated until a valid confirmation packet arrives, or connection timed out.

Maintain a list of critical packets sent to clients. Keep sending every packet in that list until you receive acknowledgements for each one (remove each on on receipt). Client must sent acknowledgement for every critical packet it receives (whether it’s received it already or not).

Cas :slight_smile:

You could try Aeron. Presentation here.

I will certainly watch this, but for such a small game, I’d rather use my own library, which is really small and only contains essential code.
Edit: that video had a lot of true and valuable information in it, especially concerning useless features in commercial and open source projects. Thanks :slight_smile:

That’s a really nice idea. What sort of interval do you recommend though? I was thinking of 250 or 500 milliseconds.

Switching to TCP ought to be simple, and it solves your problem definitively.

The other solutions put a band-aid on UDP, but only probabilistically, and at the same time introduce
other problems. A retransmitted UDP packed is out of order, and adding consideration of the state
of communications to higher levels of the program complicates things greatly.

If you think about it, the main reason not to switch is the less-predictable latentcy; and recovery
of critical packets adds this same unpredictability.

BTW, solution #6 would be to redesign your game to not have critical packets.

However often you are currently sending data. You must be sending lots of small amounts frequently and expecting it to turn up quicky or you’d be using TCP, right? :slight_smile: Basically every time you want to send a non-critical message, you should append all the unack’ed message too, and if you don’t have anything to send… fall back to as fast as you feel is necessary. The response time is down to your application’s needs.

Cas :slight_smile:

Switching to TCP isn’t really preferable because the game doesn’t need all of it’s features: it doesn’t matter what order most packets arrive in and packets that are meant to be received after a critical packet are quite easy to handle. For example, if the first movement (location update) packet for an entity arrives before the spawning packet for that entity, the client can just ignore it; another one will be sent in a second anyway.

Redesigning the game not to have critical packets would be rather difficult, primarily because of it having small maps that change each round and don’t require chunking. For games that use chunking, this is certainly the best option (in a lot of cases) though.

Unneeded “features” of TCP shouldn’t be a consideration, they cost you nothing.

One thing I’ve tried in a networked demo I created was to use both a TCP connection and UDP to send packets. I feared that the usage of TCP would cause more of my UDP packets to drop but since I only used TCP for a very few critical packets like you, it was a nonissue and worked fine.

One of the issues of having 2 connections (regardless of lowlevel protocol) is that you have to maintain these two connections… make sure they both get through the firewall, handle I/O trouble of 1 or the other. The internet is a hostile place (for packets) and maintaining 1 connection is hard enough as it is.