Monday, September 10, 2018

New Networking API Complete! - JCGNetwork

The new in house networking API has now been completed and tested, and I've started with the task of ripping out Unet and replacing it with the new API - JCGNetwork.  I took some inspiration from the Unet HLAPI but no code, and really do everything a whole lot differently.  I'm considering releasing it as its own product to help other developers.

JCGNetwork uses a layered transport on top of UDP, supporting multiple channels, channel settings, and flexible message sizes.  All channels have packet ordering enforced, remove duplicates, and channels can be reliable or unreliable.  JCGNetwork is multithreaded, where the actual sending/receiving occurs on a separate thread, while the processing of the message contents occurs on the main thread.

With Unet I was implementing encryption on a message by message basis, but I've integrated a fast and weak encryption as a channel option of JCGNetwork.  The encryption is by no means hack proof, but will make casual reading or spoofing of packets more difficult.  Additionally I've integrated both hardware and IP ban lists.

With Unet messages sizes, rate of message send, and message buffer size were all limited in their configuration.  In fact, I suspect a bug in the fragmented message system in the Unet LLAPI was my primary problem I was hitting in the end.  Even without that possible issue, Unet's fragmented message system supported only a small number of fragments, meaning that message size was always a concern outside of performance.  Standard channels couldn't support more than a single packet of data (up to 1500 bytes), and fragmented channels supported only up to 64 fragments, but chopped them down to 500 bytes by default.  Messages would also be held to wait for additional messages to combine with by default of 0.1 seconds, and was only globally configurable.  Message buffers could overfill without any way of seeing what their status was, without any notification other than in the console log, and without any remedy other than possibly slowing down sends even though your code couldn't know to do so.

With JCGNetwork message fragmentation supports up to max int number of fragments for a single message (2 billion+), and occurs automatically without having to configure the channel for fragmentation.  Not that you should try to send a 2 billion fragmented message, but it basically just gets the networking API out of the way as a blocker to sending large messages if needed.  JCGNetwork supports small message combining with again a default of holding for up to 0.1 seconds, but this is per channel configurable.  You can have a separate high performance channel with a 0 wait time that sends immediately.  This is nice because when you need performance for things like close by objects updating their positions, the performance is there, and when the object is distant you can use a channel with a holding time for more efficient use of the network when speed isn't that important.  JCGNetwork also places no arbitrary limits on outgoing or incoming buffer sizes either, using basic lists and queues that can again support up to max int number of items, so you'll run out of memory rather than run out of buffer size, but it should never get to that.

The high level networking uses a scheme similar in concept to the Unet HLAPI but functioning entirely differently.  Rather than using a not very performance friendly automatic serialization/deserialization system, with JCGNetwork the developer manually writes all serialize/deserialize functions for all messages, RPCs, and sync variables.  This takes a little longer to develop the game, but results in a more clear understanding of what is going on, more control, and should be higher performance.

The high level API of JCGNetwork still uses the concept of a Player Object, and uses a unified RPC like system in place of Command, ClientRpc, and TargetRpc of Unet.  In JCGNetwork the server can always send RPCs to clients, but the client can only send an RPC to the server on an object owned by that client's connection.  This though can be a weakness of Unet, so in JCGNetwork I've added the option on an object by object basis to allow unsafe client RPCs, where any client can send an RPC on that object even without being the owner.  Similar to Unet, sync variables only go from server to client, but differently the server will send all variables instead of just what changed, and the server needs to manually set a bool to true when it wants to send those variables to the clients.

Unet had a network visibility system that would only instantiate network objects within a certain range.  This had many limitations, such as basing on the physics system so needed a collider, and it may not be ideal to wait to instantiate an object until it is close.  There were also a good number of reported issues with this system that never were resolved.  JCGNetwork uses a completely different system called object subscriptions.  Every few seconds all networked objects will check distance against all Player Objects and add or remove those players' connections from their subscribed lists.  All connected clients will instantiate all networked objects, but unsubscribed connections will get fewer or even no updates on those objects, depending on the scripts run on those objects.  This allows for things like frequent position updates for objects close by, but objects far in the distance are still seen but their position isn't updated as frequently because it doesn't really matter at that distance.

So the work is on integrating this new system.  I expect the work to be completed within a few weeks.  I'm really excited about it, so I'll probably put in a lot of extra time so I can see the results.

No comments:

Post a Comment