I think we may have spoken about your project in the past. It’s good to see that you’re aiming lower with your send rate being 10 messages per second, although again I would recommend using this as an absolute maximum (try and aim for lower than this, and use client-side correction such as interpolation to account for visual discrepancies).
I wonder if @TheUKDude can add some input here. He has done some testing on larger scales than I have, so hopefully he can give you some more tips than the ones i’ve already given in other posts for the numbers you are going for.
To answer some of your other concerns though:
You talk a little about channels, and I fear you may be using the wrong ones, so lets go over the basic concepts surrounding the QOS that unet provies:
-
by default, you are using UDP as your transfer protocol (unless you are building for WebGL, which I would highly recommend that you don’t with the numbers that you are talking about, in which case you would be using TCP).
-
UDP, by design, is an unreliable transfer protocol. It is referred to as “unreliable”, because it is designed to fire-and-forget. In short, you send a message and don’t care about whether it gets there or not. It was specifically implemented for high-frequency transfer, such as what we end up doing in multiplayer games where we send updates about objects on a relatively high frequency.
-
it may seem like “unreliable” is a bad thing, but in actual fact it’s excellent in scenarios where you are sending regular updates about something. So for example, if you are sending position updates about objects every 10th of a second, it’s okay if one of those messages fails to reach the other end because another one will be sent shortly. This is known as “natural package loss” and it’s one of the things that has made high-frequency transfer possible.
-
now, with QOS, any time you see “unreliable”, you are simply using bog-standard UDP, where unet will send the message, and allow for natural package loss. In short, use an unreliable channel for anything that you are sending frequently and/or where you can account for package loss. And also, use this as much as you possibly can!
-
with any QOS related to “reliable”, a layer exists in unet on top of the usual sending of messages. When you send a message on a reliable channel, unet adds a byte to the message to inform the other side that it is a reliable message, in which case the other side knows to “acknowledge” receipt of the message. It must then respond to the sender to notify them that the message was received. This should NEVER be used in high or even moderately frequent messages because if a message fails, unet will keep trying up to a certain number of times, causing blockage and pile-up before it finally disconnects the connection. So in short: use reliable for things like RPC, or infrequent messages where you need to garuntee delivery
-
state update channels are a little weird in my opinion. I’ve struggled to find a good use for them but ultimately if you end up in a situation where messages pile up, the last message will be the one that is sent and the first message will be the one that is received. All others are disregarded… it may just be that i’ve yet to build something that needs a state update channel but personally I can’t think of a situation where you would want to use one.
-
although you don’t talk about them (yet), I figured it worth mentioning:
-
the “fragmented” variants help when sending HUGE amounts of data in one single message. I would only use this if you’re planning on sending something like a encoded media file, such as an image. Otherwise, you should avoid using fragmented channels and just make sure that your message sizes are within acceptable boundaries
-
the “sequenced” channels help to ensure that messages are sent and received in the correct order. This causes a little bit of overhead (due to the added byte(s) that are send to state the ordering of the messages, and also to ensure that messages are processed in the correct order) but helps to account for the natural disordering of messages. Since you will find that the time that it takes to send a message can vary from message to message, messages will naturally end up disordered from time to time. This should rarely cause you any problems though so in theory you should always be able to avoid sequenced channels, but in some cases they are useful
-
an allCostDelivery channel is just a high-priority reliable channel. If your networking involves system or internal processes, such as connection/disconnection or other important notifications, then use this channel. Otherwise, never use this channel.
As mentioned above, this is because you’re using a state update channel. Only the last message is being sent (from the server), and the first message is being received (on the client).
We spoke breifly about this in another post, but this ultimately comes from a lack of internal resources (rather than system resources such as CPU). From my testing, it appears to indicate that you are running out of pools or buffers. Some ConnectionConfig properties have been said to help here, such as raising the maximum message count/size, but these appear to only relate to reliable messages so i’m not entirely sure why you would see this for unreliable messages. My gut feeling though is just too many messages going though the network…
For you, the absolute key is getting your message count as low as possible. As mentioned before, this will largely be done by lowering your send rate (and accounting for that on the client), but will also be done using other techniques such as interest management, and making sure that you combine many small messages into one larger one. You will find that your networking is far more efficient if you combine many small messages into one larger one. You just have to be clever about how you set that up and handle it.
We also spoke briefly about multi-threading. If you are getting to a point where you just cannot get what you want out of unet, it’s very likely that your ultimate bottleneck is that unity is designed to be used to build primarily single-threaded applications, with limited support to multi-threading. I am fairly sure that unet already offloads some work to worker threads but even if it does, it wont be doing it as efficiently as if you were to do it yourself.
Just don’t. Although the NetworkServerSimple class is a stripped back version of the NetworkServer HLAPI class, it still expects you to use HLAPI functionality. At minimum, you will use registered handlers (check NetworkServerSimple.RegisterHandler) to handle messages, rather than the much leaner NetworkTransport.Receive. I’ve been forced to use NetworkServerSimple in my project because I need websocket support. Unless you need the same, stick to the LLAPI as you absolutely need all the performance you can get.
For your other concerns, particularly related to the upper limits of unet, i’ll leave that for @TheUKDude to discuss (if he is willing) as he has done some bench-marking on that.
Hopefully this helps. Good luck!