Apathy

Apathy is a fast, lightweight, allocation-free low level TCP library for Unity developed by vis2k. Apathy was developed in native C for maximum MMO Scale networking performance.

Why Apathy

  • TCP guarantees 100% stress free networking. All messages are delivered reliably and in order all the time.
  • Native C sockets for high performance, bug free & predictable code. The odds of encountering bugs in native C’s sockets are virtually zero.
  • Allocation Free: Apathy uses ArraySegments for both receive and send to allow for maximum performance and no garbage collection at runtime.
  • Synchronous code: the server uses no threads, so there will be no race conditions or deadlocks. It just works.
  • Lightweight: Apathy is the most CPU & Memory friendly TCP transport for Unity. You can use it to run a lot of servers with low overhead, as well as big servers with giant amounts of CCU.
  • Simple: Apathy was intentionally designed to be as simple as possible. We followed the KISS principle to reduce complexity to the absolute minimum.
  • Large Testsuite: Apathy comes with 14 native layer and 24 C# layer tests. Our extensive test suite is the reason why Apathy runs forever.
  • Designed for MMO Scale networking. We made this for our own MMOs. The code was crafted very carefully to run as efficient and stable as possible.
  • Deterministic behavior. No exceptions, no strange errors. If something fails, you get a Native Socket Error right from native C. It tells you exactly what went wrong. It will never be some strange issue in Unity or in Mono again.
  • Used in Production. Apathy is used by vis2k (Mirror, uMMORPG) and Paul (Mirror, Cubica) for their own games. There is 0 tolerance for bugs.
  • It Scales. Telepathy is limited to 600 CCU because of Unity’s internal 1200 thread limit. Unlike Telepathy, Apathy has no such limitations. It will scale to large amounts of CCU.
  • Compatible with Telepathy. Apathy is fully compatible with Telepathy, so you could use Apathy for your Server that runs on Windows/Mac/Linux and Telepathy for a client that runs on all platforms.
  • Highest Quality Code. Apathy was designed to be our last TCP Transport, ever. We expect to build our 5-10 year MMO projects on top of it. It has to work, period.

Platforms

Apathy currently runs on Windows/Mac/Linux/iOS. The native binaries need to be compiled for each platform separately. It’s recommended to use Apathy for server builds, and C#’s platform independent sockets for client builds

(Telepathy is 100% compatible with Apathy and can be used for clients on all platforms).

Download

Apathy comes with Mirror PRO.

The Story behind Apathy

Apathy was created for uMMORPG and Mirror as a last resort solution to high speed TCP networking in Unity. Here is an overview of our previous attempts, and why they were slower:

  • Async-Await: C#’s new async-await model allows for high speed TCP in regular C# programs. Unity’s mono backend proved to be extremely slow for anything async though.
  • SocketAsyncEventArgs: the SAEA model was designed for high speed TCP by making use of IO completion ports. We achieved extremely high throughput with SAEA in our regular C# tests. In Unity however, SAEA performs poorly again because of the mono async limitations.
  • Nonblocking TCP in C#: initially we developed Apathy in C#. This worked, but only with large limitations. Unity’s mono backend has a small chance of still blocking when using nonblocking TCP sockets. In fact, there is a whole class to deal with nonblocking TCP sockets that accidentally started blocking. This is not a good foundation to build a network library on.
  • Thread-per-Connection Model: we used the old Thread-per-Connection model for Telepathy. Telepathy was our fastest TCP transport for Unity before, but it still wouldn’t scale well enough because Telepathy uses 2 threads per connection, which limits us to 600 CCU because of Unity’s 1200 thread limit.

It was obvious that we needed to avoid Unity’s built in mono Socket library. We went native to solve this challenge once and for all, so we could finally make our multiplayer games.

Using the Apathy Server

// create and start the server
Apathy.Server server = new Apathy.Server();
server.Start(1337);
 
// create a queue for new messages. do this only once to avoid allocations.
Queue<Apathy.Message> queue = new Queue<Apathy.Message>();
 
// grab all new messages. do this in your Update loop.
server.GetNextMessages(queue);
while (queue.Count > 0)
{
    Apathy.Message message = queue.Dequeue();
    switch (message.eventType)
    {
        case Apathy.EventType.Connected:
            Debug.Log(message.connectionId + " Connected");
            break;
        case Apathy.EventType.Data:
            Debug.Log(message.connectionId + " Data: " + BitConverter.ToString(message.data.Array, message.data.Offset, message.data.Count));
            break;
        case Apathy.EventType.Disconnected:
            Debug.Log(message.connectionId + " Disconnected");
            break;
    }
}
 
// send a message to client with connectionId = 0 (first one)
server.Send(0, new byte[]{0x42, 0x13, 0x37});
 
// stop the server when you don't need it anymore
server.Stop();

Using the Apathy Client

// create and connect the client
Apathy.Client client = new Apathy.Client();
client.Connect("127.0.0.1", 1337);
 
// grab all new messages. do this in your Update loop.
while (client.GetNextMessage(out Apathy.Message message))
{
    switch (message.eventType)
    {
        case Apathy.EventType.Connected:
            Debug.Log("Connected");
            break;
        case Apathy.EventType.Data:
            Debug.Log("Data: " + BitConverter.ToString(message.data.Array, message.data.Offset, message.data.Count));
            break;
        case Apathy.EventType.Disconnected:
            Debug.Log("Disconnected");
            break;
    }
}
 
// send a message to server
client.Send(new byte[]{0xFF});
 
// disconnect from the server when we are done
client.Disconnect();

Benchmarks

We always test our Transports in a heavy real world scenario. We use uMMORPG to run a worst-case test where all players are next to each other in a small scene. This is very difficult to handle even for modern MMORPGs.

uMMORPG worst case stress test

Test results [2019-08-14]

CCULatencyCPU %CPU Total %RAMSys Threads
10050 ms45%4%601 MB301
20082 ms62%5%635 MB301
250101 ms75%7%662 MB301
320131 ms95%8%700 MB301
420136 ms98%8%715 MB301

Understanding the results

The most important metric in our CCU tests is latency, because it indicates the server load.
=> As server update ticks require longer to process, the latency increase.

For 420 CCU, we achieved the exact same performance as with our Booster.
=> We conclude that both Apathy and Booster are no longer the bottleneck.
=> Going forward, we will further uncork Mirror & uMMORPG.
=> Apathy will simply scale along nicely, and latency will decrease.

Screenshots

Apathy Package in Unity
Apathy Asset in Unity
Apathy Source Code
Apathy Native C code snippet
Apathy C# code
Apathy C# code snippet
Apathy Test Suite
Apathy Test Suite. Most of them run for 10k-100k iterations each.

Package Contents

  • Precompiled native C libraries for Windows/Mac/Linux
  • C# code for Server and Client
  • Test framework with 36 tests in total
  • Simple server & client examples
  • Transport components for Mirror
    • ApathyTransport: uses Apathy for both Server & Client
    • ApathyTelepathyTransport: uses Apathy for the Server & Telepathy for the Client

FAQ

  • If I use Apathy for a Server, should I use Telepathy or Apathy for the client? If your game is only supposed to run on Windows/Mac/Linux, then we highly recommend using Apathy for the client. You will have a higher frame rate and less GC spikes. If you are targeting a lot of different platforms like Mobile, Consoles, etc. then use Telepathy for the client. Telepathy runs anywhere but is slower and causes more GC spikes because it uses Unity’s built in Mono sockets.
  • Is Apathy open source? Kind of. You get all the C# code, but the C code was already compiled into a .DLL library for Windows, a .so library for Linux and a .bundle for Mac. In other words, you can just drop it in and use it right away.
  • What if I need source code access to the native C code? Talk to us. If you are working on a huge game and this is absolutely needed then we will figure something out.
  • Can I fork / redistribute Apathy like I can with Telepathy? No, sorry. You can embed it in your game though.
  • Why not replace Telepathy with Apathy as the default Transport for Mirror? Apathy only runs on Windows/Mac/Linux right now. We have to build it for all platforms manually, which is a very tedious process and it’s unlikely that we will ever support all of them. Telepathy runs on all platforms, and we want Mirror to work out of the box without headaches.
  • Do I need Apathy for my Project? If you use TCP, then you should absolutely use Apathy for your server. It’s just way faster, way more stable and causes way less headaches than anything else out there. Telepathy is limited to 600 CCU because of Unity’s internal thread limit. Apathy has no such limits. That’s why we made it. If your game is just a small hobby project to play with a handful of friends, then you don’t need Apathy.
  • What about the Booster? We originally made the Booster to circumvent Unity’s Mono Socket pitfalls. The booster works fine and can still handle 100k+ CCU. Apathy also circumvents Unity’s Mono Socket pitfalls. 99% of the projects should be fine with just Apathy, without the booster. If you are really impacted by gigantic amounts of players, traffic and network attacks then you can still consider the Booster.