Search Unity

  1. We would like to hear your feedback about Unity and our products. Click here for more information.
    Dismiss Notice

Ruffles - Another reliable UDP library

Discussion in 'Connected Games' started by TwoTen, May 14, 2019.

  1. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Hello!

    As the developer of MLAPI, the first library to introduce the now popular "transport" system where a high level API can switch the low level transport, and as UNETs deprecation is nearing, I have decided to create my own reliable UDP library which is being licenced under the BSD 3-Clause licence.

    GitHub Link

    Why another reliable UDP library?
    There are many alternatives out there, Enet, LiteNetLib, Lidgren and more.

    Enet is a lightweight library with minimalist features, it's fast and reliable.

    LiteNetLib and Lidgren (and more), both libraries (and their developers) I have great respect for as they fill the spot of being fully managed (written for .NET). However, for my personal taste they are simply too bloated or slow.

    I want something fully managed, lightweight and really fast. That brings us to some of Ruffles current features.

    Features
    Here are some of the main features of Ruffles. At the time of writing, I have only spent 3 days actually writing the library (with some code borrowed from previous projects of mine), but so far it's very stable and super fast.


    Security

    Ruffles doesn't take lightly on security, every single connection has to complete a HashCash like challenge and response to even get accepted. This prevents many layer 4 attacks. As documented in the readme, more security will be added in the future to completely ignore connections that dont complete the sliding window challenge. This will make slot filling attacks infeasible.

    Ruffles also ensures that the protocol is not used for DoS amplification by requiring large padded messages for the handshake process.

    Performance
    Ruffles is super high performance, it's 100% garbage free. This is accomplished with a custom memory allocator in GC space. This ensures no memory is leaked to the garbage collector unless for the purpose of resizing the memory manager itself. This makes Ruffles blazing fast. It also avoids memory copies as much as possible.

    Reliability and Sequencing

    There are currently a few types of channels in Ruffles. The types are:

    Reliable: All messages are guaranteed to be delivered, the order is not guaranteed, duplicates are dropped. Uses a fixed sliding window.
    ReliableSequenced: All messages are guaranteed to be delivered with the order also being guaranteed, duplicates are dropped. Uses a fixed sliding window.
    Unreliable: Delivery is not guaranteed, nor is the order. Duplicates are dropped.
    UnreliableSequenced: Delivery is not guaranteed but the order is. Older packets and duplicate packets are dropped.


    Threading
    Ruffles can run in many different threading environments, it can be run passively single threaded, actively single threaded, or in a threaded environment where everything is done via message queues while remaining garbage free.

    Dependency Free
    Ruffles is 100% dependency free, it's thus very portable and should run on most platforms.

    IPv6 Dual Mode
    More importantly than ever, as many platforms such as iOS are restricting what can be submitted to their ecosystems application stores. IPv6 support is built in dual mode. It does this by using two sockets bound to the same port, thus accomplishing full dual stack functionality that is invisible to the user.



    Final Words
    Ruffles is in early development, as of now, I have only spent 3 days writing it (with code borrowed from previous projects of mine). But it's really stable from my testing and it's blazing fast. More features are to come as modules, and also in the core library. Check out the GitHub repository for a full roadmap.

    I would love to hear what you guys think. Does anyone else like me see the usefulness of this? Or do you disagree with my stance and think that the available solutions are sufficient?

    Thanks, Albin
     
    Last edited: May 14, 2019
    Gyrase, zyzyx, wobes and 1 other person like this.
  2. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    5,518
    Impressive, especially for just a few days work.

    I'm curious how you prioritize speed while implementing an unreliable channel type which doesn't enforce ordering but does remove duplicates. When I was trying to implement this for my own network API I couldn't figure out how to do it quickly.

    What I came up with was storing all previous message ID's and comparing the incoming ID to all previous message ID's to check for duplicates. Not fast, and I eventually just went with an unreliable sequenced channel, where I drop out of order packets, and stays fast because I only need to compare against the last received message ID to tell if it is out of order or a duplicate.

    (I know I could have just looked through your github for the answer, but I thought this thread deserved a bump anyway)
     
    TwoTen likes this.
  3. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    First of all I just wanna say that we all appreciate you a ton. Thanks for making it fun to be a part of the Unity Networking community.

    As for performance related questions, the answer is usually SlidingWindow, SlidingWindow and SlidingWindow.

    The sliding window I use is basically a fixed array that has an API of an infinite one. If you create a SlidingWindow of type bool, make it size 10, and set indexes 0-11 to true. If you then check index 0 it will be false as it has fallen out of scope (my implementation just sets it to default(T), thus false). That allows you to (ab?)use this collection like a HashSet and just store true if you have received a sequence number.

    Here is the implementation:
    https://github.com/MidLevel/Ruffles/blob/master/Ruffles/Messaging/SlidingWindow.cs

    And in use:
    https://github.com/MidLevel/Ruffles/blob/master/Ruffles/Channeling/Channels/UnreliableChannel.cs

    As you can see, this means a lookup costs just a quick modulo and array index lookup, and the memory impact is fixed to the window.

    As long as you use structs for the sliding window type (or reuse the heap object), performance is going to be great as nothing will leak to the GC.


    These sliding window collections are super useful for writing high performance. I also have another one in the works where the old elements are "dropped" (goes out of scope) based on the time they were added, rather than a incremental index. Useful for putting random data in. Performs O(1) for lookup, O(1) insert, O(1) remove but cannot be resized and ofcourse if you enqueue more elements the old ones are dropped. That's what I'm using for my HashCash challenges where the initialization vectors are random.

    Edit: I have implemented it now, I called it "SlidingSet", you basically just throw items into it, and the old ones will disappear. The items does not need to be incremental, or even integers. They can be anything. Memory impact is tiny. O(1) memory. https://github.com/MidLevel/Ruffles/blob/master/Ruffles/Messaging/SlidingSet.cs
     
    Last edited: May 16, 2019
    Joe-Censored likes this.
  4. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,350
    I still think some examples for MLAPI would help a ton with getting more people to try that out and maybe use it..

    However this could be a good as a Mirror transport plugin, much like the LiteNetLib one for UDP, any plans to add that sort of transport support? Anyway I'm all for code bloat reduction, features and speed though benchmarks and being battle tested are the better ways of proving such claims.
     
  5. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Maybe, it's a 10 minute job actually creating transports for the high level libraries. I have created one for the MLAPI that works with Ruffles. But I don't use Mirror or UNET so I probably won't create one, but you are free to.


    About the sample projects. Everything I create is free and open source. I don't even use my own stuff. I don't have any need for any networking. I just enjoy it. I have never made a penny from any of this, just worth keeping in mind. But check this issue out for some of my arguments.
     
  6. Player7

    Player7

    Joined:
    Oct 21, 2015
    Posts:
    1,350
    How do you even test your libraries without an example project? I get the notion of not liking to make games or things like that, but as the github issue says even basic barebones example serve as a good overview of the library and a quick way to test and debug things on a working example that is maintained alongside the framework, even better if its updated with support to demonstrate new features in action... heck if you wanted to slap a low price on it, I'm sure you'd get more interest...but to expect anyone to go through and do this when other libraries do provide such things is just silly. Perhaps it is just a form of elitism and you don't care if anyone uses it.
     
  7. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Perhaps it is elitism. But I would argue it's not. For testing on my own, I just write tests for what I need, at that moment.

    But hey, right now the example project is not being done because of time. I have tons of FOSS projects that I work on (many unpublished) and it's all in my spare time. I also have two jobs (one full time and one part time). Time is a valuable asset of mine.

    And sure others have examples, but they also suffer the issues I described.

    I want people to get into my library, that makes me really happy. But I don't want to create any documentation resources that act as spoon feeding. But I am fine with barebones examples, and I will do it when I have time. But it's a bit besides the topic of this post. This is all about Ruffles, not MLAPI.
     
  8. PartyBoat

    PartyBoat

    Joined:
    Oct 21, 2012
    Posts:
    73
    I'm curious if this is meant as a replacement for the LLAPI. Is your plan to eventually make Ruffles the default transport for MLAPI, or is this meant as an option for some specific use case?
     
  9. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Yes this is meant to be the default for the MLAPI, but it can of course be used with UNET, Mirror or just by itself.

    But the MLAPI will continue to be configurable, we will just have a new default when the LLAPI disappears.
     
  10. Gyrase

    Gyrase

    Joined:
    Sep 7, 2018
    Posts:
    3
    Looks good, any chance of adding performance metrics using @nxrighthere benchmarknet ?
    Curious, why the name ruffles?
     
  11. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Sure, I am currently working towards v1.0.0 and then that stuff will come.

    And the name, keyboard hovering until my mind went somewhere. That's what I do with almost all my projects haha. Means nothing. Except for my previous RUDP library which I never finished cause I was unhappy with the core architecture. It's called Rudel and it's a play on RUDP and old Scandinavian female names such as Rakel.

    I usually just start of with the initial letter (you want a good first letter for future artwork and stuff. And the first letter kinda sets the tone for how it sounds)
     
    Last edited: May 17, 2019
  12. Jes28

    Jes28

    Joined:
    Sep 3, 2012
    Posts:
    348
    Goo job thanks :)

    Do you thought about using Unity Native Arrays and other primitives from DOTS and burst compile code for best performance?
     
  13. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    I have not thought about that, but it's something worth looking into!
     
  14. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    This is because high level libraries needs something to build on. I am not telling anyone to use this. I have a high level library myself called the MLAPI. It's as easy to use as UNET but has much more features, encryption, lag compensation and more. It's faster and less restrictive.

    I won't unite with a developer that has a so different view. I have given him hints and tips in PM but he has never actualy done any of them. He offered me to work on Mirror once. Why would I do that when there is the MLAPI which is in my opinion much better. I wrote an article where I described problems with Mirror (remember, Mirror is a open source project, not a product). Instead of taking any of my advice and fixing the problems he just said "lets keep out of drama". My article described problems with the library, it wasn't there to create drama. Another of the developers asked me "I don't know who pissed in your cornflakes". Yet noone has yet disputed any of my points. I don't like high horsing.

    I would love to collaborate on things, but on open terms with the community and the best outcome in mind. Not being seen as a "networking god" in order to sell assets.

    I have been proven wrong many times before, I am happy to be again.
     
    wobes and Joe-Censored like this.
  15. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    2,174
    I for one think this is great. When you actually narrow the field and remove all the solutions that have obvious problems, there really are not a lot of 'good' choices. The ones I would even touch all have some drawbacks, like enet is just a bit quirky and native.

    One question is why not SocketAsyncEventArgs ? Does it create garbage in Unity? I haven't ever tested it in Unity mainly due to no compelling reason. We use it server side in .Net core and it doesn't create garbage there. Assuming it's still problematic in some way in Unity?
     
    TwoTen likes this.
  16. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    I doubt there is any advantage to be had since it's purley a single socket.

    It's a different story for TCP where you interface which each connection itself.
     
  17. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    641
    I am curious to see Benchmarks.
    By the way, you may want to use Span<T> to avoid pooling.
    And use RingBuffer as primary queue.
     
  18. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Why use Span? I still have to pool to not leak to GC. I use arraySegments currently to avoid copies if thats what you mean.

    My queue is a ringbuffer already. I just do it all inhouse :p
     
    wobes likes this.
  19. dadude123

    dadude123

    Joined:
    Feb 26, 2014
    Posts:
    784
    What are the most important differences with LiteNetLib?
    When would I use Ruffles over LiteNetLib? And when not?
     
  20. wobes

    wobes

    Joined:
    Mar 9, 2013
    Posts:
    641
    Span will allow you to move from classes to structs. However, requires manual memory control. Also you might avoid Marshaling using Span<byte>.
     
  21. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Right now I just keep tabs of all array references and send ArraySegment pointers around, then manually return the memory to the MemoryManager in Ruffles. It’s 0 garbage, I fail to see your point.

    (remember, Ruffles is fully managed)
     
    Last edited: May 26, 2019
  22. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,122
    Ruffles is designed to be faster, LiteNetLib has more features.

    If you want the features of LiteNetLib, use it. If you dont need it. Wait for Ruffles benchmarks and then pick. I am still just guessing Ruffles is faster (it most certainly is, but there is not yet any evidence to this claim)
     
    Last edited: May 26, 2019