QuickNet v0.4.0 | 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 60+ 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 this period, the average FPS and Kbps are recorded. The Kbps value is normalized to 60 FPS. All benchmarks are conducted in Roblox studio. See the benchmarks source code for more details.
Note: results from this benchmark cannot be compared to results from another benchmark, even if the benchmark is the same: different PCs will achieve different FPS on the benchmarks.

Entities Test (100 entities)

Networking Tool FPS Kbps
RemoteEvent 4.24 2165272.99
Packet 13.38 38.88
ByteNet 12.51 37.66
Jolt 21.85 41.82
NetRay Compile 25.12 39.61
Lync 14.77 39.34
Blink 24.94 39.65
QuickNet 34.45 39.68

Booleans Test (1000 booleans)

Networking Tool FPS Kbps
RemoteEvent 9.81 291769.91
Packet 10.70 20.98
ByteNet 11.32 20.01
Jolt 84.80 10.18
NetRay Compile 73.68 10.13
Lync 23.07 11.06
Blink 42.70 20.66
QuickNet 97.74 10.03

Strings Test (300 strings)

Networking Tool FPS Kbps
RemoteEvent 22.31 718753.84
Packet 17.98 108.12
ByteNet Crash
Jolt 40.59 110.59
NetRay Compile 23.13 109.48
Lync 19.61 319.44
Blink 30.53 102.33
QuickNet 57.26 103.76

Numbers Test (1000 numbers)

Networking Tool FPS Kbps
RemoteEvent 10.43 1123124.57
Packet 6.55 99.45
ByteNet 12.79 59.69
Jolt 57.04 65.24
NetRay Compile 45.23 64.53
Lync 11.25 122.40
Blink 44.56 64.77
QuickNet 97.01 65.60

Dictionary Test (200 key-value pairs)

Networking Tool FPS Kbps
RemoteEvent 16.34 1466655.19
Packet 1.08 58.14
ByteNet Crash
Jolt 49.55 70.33
NetRay Compile 20.49 62.65
Lync 6.19 116.34
Blink 28.22 58.49
QuickNet 53.66 62.65

Vector3 Test (100 vector3s)

Networking Tool FPS Kbps
RemoteEvent 27.47 13665554.86
Packet 38.37 79.22
ByteNet 35.18 69.56
Jolt 116.70 74.22
NetRay Compile N/A*
Lync 24.64 138.41
Blink 147.14 74.54
QuickNet 190.90 74.63

Call-Response Test (500 numbers)

Networking Tool FPS Send Kbps Receive Kbps
RemoteFunction 15.44 764181.29 764181.29
Packet Crash
ByteNet N/A
Jolt Crash
NetRay Compile 44.18 136083.19 136083.19
Lync N/A
Blink Crash
QuickNet 59.97 150.12 150.12

JSON output of benchmark:

{"Blink":{"Vectors":{"kbps":74.542,"fps":147.146},"Numbers":{"kbps":64.77,"fps":44.566},"Dict":{"kbps":58.494,"fps":28.222},"Booleans":{"kbps":20.666,"fps":42.701},"Entities":{"kbps":39.654,"fps":24.946},"Strings":{"kbps":102.336,"fps":30.533}},"RemoteEvent":{"Vectors":{"kbps":13665554.869,"fps":27.477},"Numbers":{"kbps":1123124.572,"fps":10.436},"Dict":{"kbps":1466655.193,"fps":16.343},"Booleans":{"kbps":291769.916,"fps":9.812},"Entities":{"kbps":2165272.999,"fps":4.248},"Strings":{"kbps":718753.844,"fps":22.313}},"Lync":{"Vectors":{"kbps":138.413,"fps":24.641},"Numbers":{"kbps":122.407,"fps":11.25},"Dict":{"kbps":116.348,"fps":6.19},"Booleans":{"kbps":11.062,"fps":23.072},"Entities":{"kbps":39.344,"fps":14.775},"Strings":{"kbps":319.445,"fps":19.614}},"Packet":{"Vectors":{"kbps":79.22,"fps":38.375},"Numbers":{"kbps":99.459,"fps":6.558},"Dict":{"kbps":58.149,"fps":1.089},"Booleans":{"kbps":20.986,"fps":10.705},"Entities":{"kbps":38.889,"fps":13.384},"Strings":{"kbps":108.121,"fps":17.988}},"NetRay":{"Vectors":{"kbps":0,"fps":0},"Numbers":{"kbps":64.536,"fps":45.232},"Dict":{"kbps":62.656,"fps":20.492},"Booleans":{"kbps":10.136,"fps":73.686},"Entities":{"kbps":39.618,"fps":25.129},"Strings":{"kbps":109.489,"fps":23.136}},"QuickNet":{"Vectors":{"kbps":74.638,"fps":190.9},"Numbers":{"kbps":65.602,"fps":97.015},"Dict":{"kbps":62.653,"fps":53.665},"Booleans":{"kbps":10.039,"fps":97.744},"Entities":{"kbps":39.684,"fps":34.457},"Strings":{"kbps":103.763,"fps":57.269}},"Jolt":{"Vectors":{"kbps":74.228,"fps":116.701},"Numbers":{"kbps":65.245,"fps":57.044},"Dict":{"kbps":70.338,"fps":49.554},"Booleans":{"kbps":10.185,"fps":84.804},"Entities":{"kbps":41.828,"fps":21.856},"Strings":{"kbps":110.596,"fps":40.598}}}

Note: due to issues with crashing ByteNet has to be tested individually.

*NetRay Compile refused to compile the files with vector3 data type.

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.4.0

95 Likes

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

7 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?!

3 Likes

Sometimes you just need to see that true-false

3 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.

4 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!

2 Likes

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!

2 Likes