QuickNet v0.3.2 | Up to 10x Faster than RemoteEvents | Drop-in Networking Library

QuickNet

Download | Documentation | GitHub | Benchmarks Code

High performance networking simplified

About

QuickNet is a high performance networking library meant as an alternative to default RemoteEvents and RemoteFunctions. I made this library for personal use in my projects, but I’ve decided to open source it in case anyone else finds it helpful.

Why Make QuickNet?

While there’s already many networking libraries out there, I wanted to make something that requires minimal setup without compromising the performance. Most of the best performing networking libraries are IDL (Interface Description Language) compilers, which require the user to download stuff like plugins, files, etc, run commands to generate code files for each network event, and so on. But if you’re lazy like me, you don’t want to do any of that tedious stuff. You want something that just works.

Why Use QuickNet?

QuickNet is super easy to pick up if you’ve never used a networking library before. If you’re already using RemoteEvents in your game, you can drop in QuickNet without changing any of your main code. This is possible because the API for QuickNet is the same as that of normal RemoteEvents and RemoteFunctions, meaning there is very little learning needed to start using QuickNet.

Performance

QuickNet’s ease of use does not come at the cost of performance. When compared to default RemoteEvents, QuickNet can reduce the network traffic by as much as 1,000x, while running at up to 10x higher FPS. Also, QuickNet’s performance is very competitive with other popular networking libraries, such as Packet, ByteNet, and Blink.

Performance Explanation

How is this possible? Under the hood, QuickNet serializes data into a buffer before sending it across the network, then on the receiving end the buffer is decoded back into raw data. When sending a buffer across the network, the Roblox engine will use much less CPU than when sending normal data. Therefore, the trick to get a network library faster than default Remotes is to make the buffer serialization cost less than the default engine serialization cost. QuickNet uses a ton of clever optimization techniques to minimize the CPU usage when encoding and decoding data. See source code for more details.

Features

  • Optimized for CPU performance
  • Fast path: extra performance boost for most common data types
  • Supports 59 different data types + more composite types
  • Supports dynamic types and nested structures
  • Data serialization and buffer based networking
  • Batched calls with action routing
  • Protection against invalid data/“DDoS”
  • Custom rate limiting on network events
  • Type-safe network events
  • Supports call-response events (RemoteFunctions) with custom timeouts
  • Supports unreliable events
  • Familiar API (same as default Remotes)

Benchmarks

Benchmarks are conducted by firing the network event 1000 times per frame with the same data for 10 seconds, at the end of which the average FPS and Kbps are recorded. Each data contains 500-2000 individual elements, depending on the test. The result shown for each entry is a 3-run average. All benchmarks are conducted in studio. See the benchmarks source code for more details.

Entities Test (100 dictionaries each with 5 entries per fire)

Networking Tool FPS FPS Scale Kbps
RemoteEvent 3.91 1x 1853031.17
Jolt 3.62 0.96x 95.11
Packet 10.26 2.62x 41.33
ByteNet 14.66 3.75x 38.66
Blink 23.39 5.98x 39.33
NetRay Compile 23.97 6.13x 39.52
QuickNet 34.86 8.92x 40.78

Booleans Test (1000 booleans per fire)

Networking Tool FPS FPS Scale Kbps
RemoteEvent 7.36 1x 523520.05
Jolt 3.39 0.46x 124.53
Packet 11.19 1.52x 9.83
ByteNet 12.98 1.76x 9.25
Blink 46.56 6.65x 8.07
NetRay Compile 77.97 10.59x 3.10
QuickNet 141.23 19.19x 2.89

Strings Test (500 strings per fire)

Networking Tool FPS FPS Scale Kbps
RemoteEvent 10.82 1x 814948.14
Jolt 5.19 0.48x 56.22
Packet 10.53 0.97x 21.38
ByteNet 0.21 0.02x 39.83
Blink 22.20 2.05x 21.62
NetRay Compile 15.98 1.47x 19.39
QuickNet 44.15 4.08x 18.63

Numbers Test (2000 numbers per fire)

Networking Tool FPS FPS Scale Kbps
RemoteEvent 5.45 1x 2115972.12
Jolt 2.01 0.39x 332.85
Packet 6.20 1.14x 129.53
ByteNet 6.84 1.26x 120.27
Blink 23.11 4.24x 122.20
NetRay Compile 24.79 4.54x 123.86
QuickNet 60.23 11.05x 125.42

Dictionary Test (1 dictionary with 500 key-value pairs per fire)

Networking Tool FPS FPS Scale Kbps
RemoteEvent 6.12 1x 1923782.58
Jolt 4.58 0.75x 128.16
Packet 4.72 0.77x 145.78
ByteNet 0.10 0.02x 246.81
Blink 12.14 1.98x 127.51
NetRay Compile 8.78 1.43x 145.04
QuickNet 22.52 3.68x 142.24

RemoteFunction Test

This test is conducted by sending 500 numbers 1000 times per frame with a response of 500 booleans for every send. Each fire is wrapped in a task.spawn to ensure calls aren’t blocked. The metrics are recorded at the end of a 5 minute period. This test can show us the performance degradation over a long period of heavy load.

Networking Tool FPS FPS Scale Kbps
RemoteFunction 3.28 1x 1435503.71
Jolt 3.24 0.99x 307.97
Packet Crash
ByteNet N/A
Blink Crash
QuickNet 58.29 17.77x 159.12

Usage

Register network events on both client and server, or in a shared module:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local QuickNet = require(ReplicatedStorage.QuickNet)
local data = QuickNet.Data

local event1 = QuickNet:register("SomeEventName", data.NumberU8, data.String)
local function1 = QuickNet:register("SomeFunctionName", data.Color3, data.Vector3F32):Response(data.Boolean)

--how to define events with table types
--arrays
local staticArray = QuickNet:register("SomeEvent1", {data.NumberF32, data.String, data.CFrameF32})
local uniformArray = QuickNet:register("SomeEvent2", {data.Color3})
local dynamicArray = QuickNet:register("SomeEvent3", {data.Any})

--dictionaries
local staticDict = QuickNet:register("SomeEvent4", {health = data.NumberU8, damage = data.NumberI32, name = data.String})
local uniformDict = QuickNet:register("SomeEvent5", {[data.String] = data.NumberI32})
local dynamicDict = QuickNet:register("SomeEvent6", {[data.Any] = data.Any})

Client:

event1:FireServer(123, "whatever")

event1.OnClientEvent:Connect(function(num, str)
   print(num)
   print(str)
end)

local color = Color3.new(1, 1, 1)
local vector3 = Vector3.new(1, 2, 3)
local result = function1:InvokeServer(color, vector3)
print(result)

Server:

event1.OnServerEvent:Connect(function(player, num, str)
   print(num)
   print(str)
end)

function1.OnServerInvoke = function(player, color, vector3)
   print(color)
   print(vector3)
   return true
end

event1:FireAllClients(456, "somerandomstring")

Arguments must match the types defined in schema. See documentation for more details.

Note

QuickNet is currently in beta and has only been tested in controlled environments, meaning bugs can arise in edge cases. If you encounter a bug please report it below and I will try to patch it ASAP. On a more general note, any feedback is welcome, such as optimizations I missed, features I should add, etc.

Current version: v0.3.2

59 Likes

Pretty nice. I’ve been using Jolt for sometime now, glad to see theres a replacement now. Thanks for sharing

6 Likes

I appreciate it! Let me know if you run into any issues

4 Likes

is there no unreliable support?

3 Likes

you can destroy the registred remotes?

3 Likes

Not yet, but support for it is planned next update

1 Like

You can disconnect listeners but there is no way to “deregister” a network event. I can definitely think about adding it, just curious though what use case would you need it for?

1 Like

Just finished the Types page, check it out for a detailed explanation of how they work: QuickNet/docs/types.md at main · breadboardengineer1234/QuickNet · GitHub

Will finish the rest of the documentation in the next few days

1 Like

Looks like a great resource, though the benchmarks raise some suspicion of being engineered if you do not attach the code that gave you those results from each library. I’m sure you know making up benchmarks isn’t exactly difficult.

Also replicating the RemoteEvent API exactly is a bit of a downside in my opinion. Performance is only half the problem for most people, they also want to avoid duplicating endpoint declarations across the client & server. While your claim about IDLs is mostly true, it doesn’t really justify the lack of a shared interface.

3 Likes

Nah, why are booleans 2.89 kbps when bro can just make it as bits?!

2 Likes

Sometimes you just need to see that true-false

2 Likes

Thanks for the feedback. The code used for the benchmarks is attached in the post, they are not fake. I can provide any video of me running the benchmarks, and obviously you could read through the code and run the benchmarks yourself. The point of replicating default RemoteEvent syntax is so if you’re already using RemoteEvents and you want to switch you wouldn’t have to change a lot of your code, you would just need to make a definitions module. Also, if you’ve never used a network library before you wouldn’t have to learn any new syntax. However, you brought up some great points about the downsides of this which I did not think about. I can consider making some changes to the API in the future.

2 Likes

QuickNet automatically does bit packing on booleans. Before I added bit packing it was getting around 8 Kbps on the booleans test, so the network traffic is already reduced by ~2.7x.

3 Likes

Finished the documentation and updated the post with it

1 Like

Could you add this to Wally? Thanks.

2 Likes

Good resource! Just one question, why not create an internal tracker for created events instead of having the user create their own?

2 Likes

I assume you mean something like a :get method to retrieve event objects. This was actually a thing before I released the project, but I ended up getting rid of it right before release because it was messing with the auto complete. I’ll look into fixing those issues and bringing it back for v0.2.0, which should be coming in the next few days with a ton of new features. Thanks for the suggestion!

1 Like

Just released v0.2.0! Here’s a quick summary of the update:

  • Added unreliable support, simply call :Unreliable when registering an event
  • Fixed a bug where sending a table throws error when using the Any type
  • Added new serialized types: BrickColor, DateTime32, DateTime64, CFrameF32Aligned, TweenInfo (will explain how they work on documentation)
  • Added :Once method
  • Fixed an issue where calling :Wait doesn’t return the arguments
  • Optimized connection objects: no longer creates new closures for each connection and tagged code with native
  • FX16 scale factor changed from 128 → 100
  • Adjusted some other internal parameters

Will update the documentation with new types and methods.
Get the new release here: Download

1 Like

QuickNet should still be as fast as before with this update. I quickly ran the benchmarks just to make sure, here are the results:

Entities: 33.78 FPS, 39.58 Kbps
Booleans: 133.68 FPS, 2.87 Kbps
Strings: 43.59 FPS, 18.64 Kbps
Numbers: 54.51 FPS, 126.58 Kbps
Dictionary: 22.69 FPS, 142.94 Kbps

Overall it’s a little slower, but this probably has more to do with natural variation and my PC being extra slow today than the update itself. Please report here if there is any issue with the release, thanks!

1 Like

Could you please add it to Wally, would appreciate it!

1 Like