Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ wally.lock
# Sourcemap
sourcemap.json
Packages/
references/
references/
DevPackages/
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Satset is designed for high-throughput scenarios. We maintain a [benchmark suite
- **[ByteNet](https://github.com/ffrostfall/ByteNet)**: A buffer-based serialization library.
- **[BridgeNet2](https://github.com/ffrostfall/BridgeNet2)**: A high-level batching library.
- **[Warp](https://github.com/imezx/Warp)**: A rapidly-fast networking library.
- **[Packet](https://devforum.roblox.com/t/packet-networking-library/3573907/414)**: A binary-heavy packet library with built-in rate limiting.

Detailed methodology and raw data can be found in the [Benchmarks Report](benchmark/Benchmarks.md).

Expand Down Expand Up @@ -138,7 +139,8 @@ local Satset = require(path.to.Satset)
Satset.start({
guard = {
maxTokens = 60,
refillRate = 30
refillRate = 30,
studioBypass = true -- Enabled by default
}
})
```
Expand Down Expand Up @@ -193,6 +195,6 @@ end)

# License

Satset is distributed under the terms of the [MIT License](https://github.com/protheeuz/satset/blob/main/LICENSE).
Satset is distributed under the terms of the [MIT License](LICENSE).

When Satset is integrated into external projects, we ask that you honor the license agreement and include Satset attribution into the user-facing product documentation.
72 changes: 40 additions & 32 deletions benchmark/Benchmarks.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Satset Networking Benchmarks

*Last Updated: 2026-04-30 — v0.1.2 (with buffer bounds checking, xpcall listener protection, and double-start guard)*
*Last Updated: 2026-05-01 — v0.1.2 (with buffer bounds checking, xpcall listener protection, and double-start guard)*

This document records the measured performance of **Satset** against native Roblox remotes and other community networking libraries under identical stress conditions.

## 1. Bandwidth Comparison (Median KB/s)

**Lower is better.** This measures how much raw network traffic each library generates to transmit the same logical data.

| Benchmark | Native Roblox | BridgeNet2 | ByteNet 0.4.6 | Warp | **Satset** |
| :--- | ---: | ---: | ---: | ---: | ---: |
| **Vectors** (12B × array) | 178,955 | 169,992 | 79 | 0.8\* | **80** |
| **Booleans** (1B × array) | 177,841 | 71,073 | 23 | 0.8\* | **14** |
| **Mixed** (30B struct) | 118,132 | 8,544 | 2.9 | 0.9\* | **5** |
| **Entities** (6B × array) | 300,342 | 82,577 | 43 | 0.7\* | **43** |
| **Strings** (var-len × array) | 134,870 | 168,468 | **CRASHED** | 0.7\* | **107** |
| **SingleValue** (1B) | 1,794 | 722 | 2.5 | 0.8\* | **3** |
| Benchmark | Native Roblox | BridgeNet2 | ByteNet 0.4.6 | Warp | Packet | **Satset** |
| :--- | ---: | ---: | ---: | ---: | ---: | ---: |
| **Vectors** (12B × array) | 178,955 | 169,992 | 79 | 0.8\* | 78 | **80** |
| **Booleans** (1B × array) | 177,841 | 71,073 | 23 | 0.8\* | 26 | **14** |
| **Mixed** (30B struct) | 118,132 | 8,544 | 2.9 | 0.9\* | 5 | **5** |
| **Entities** (6B × array) | 300,342 | 82,577 | 43 | 0.7\* | 35 | **43** |
| **Strings** (var-len × array) | 134,870 | 168,468 | **CRASHED** | 0.7\* | 106 | **107** |
| **SingleValue** (1B) | 1,794 | 722 | 2.5 | 0.8\* | 2.4 | **3** |

\*Warp consistently shows near-zero bandwidth and zero received packets across all tests. This indicates a listener integration issue in the benchmark adapter rather than actual Warp performance.

Expand All @@ -25,50 +25,59 @@ This document records the measured performance of **Satset** against native Robl

**Higher is better.** This shows how much CPU headroom each library leaves for the game while processing network traffic.

| Benchmark | Native Roblox | BridgeNet2 | ByteNet 0.4.6 | Warp | **Satset** |
| :--- | ---: | ---: | ---: | ---: | ---: |
| **Vectors** | 42 | 44 | 60 | 60\* | **37** |
| **Booleans** | 16 | 15 | 22 | 60\* | **15** |
| **Mixed** | 60 | 59 | 16 | 60\* | **60** |
| **Entities** | 16 | 16 | 19 | 60\* | **46** |
| **Strings** | 29 | 36 | — | 60\* | **44** |
| **SingleValue** | 60 | 60 | 60 | 60\* | **60** |
| Benchmark | Native Roblox | BridgeNet2 | ByteNet 0.4.6 | Warp | Packet | **Satset** |
| :--- | ---: | ---: | ---: | ---: | ---: | ---: |
| **Vectors** | 49 | 58 | 60 | 60\* | 60 | **38** |
| **Booleans** | 16 | 15 | 22 | 60\* | 15 | **15** |
| **Mixed** | 60 | 60 | 16 | 60\* | 60 | **60** |
| **Entities** | 16 | 16 | 19 | 60\* | 15 | **47** |
| **Strings** | 28 | 38 | — | 60\* | 50 | **46** |
| **SingleValue** | 60 | 60 | 60 | 60\* | 60 | **60** |

---

## 3. Packet Delivery

Total packets sent vs received over each 10-second test window.

| Benchmark | Roblox (Sent/Recv) | BridgeNet2 | ByteNet | **Satset** |
| :--- | ---: | ---: | ---: | ---: |
| **Vectors** | 428K / 428K | 397K / 397K | 598K / 598K | 369K / 83K |
| **Booleans** | 110K / 110K | 93K / 93K | 219K / 219K | 86K / 20K |
| **Mixed** | 599K / 599K | 598K / 598K | 34K / 34K | 599K / 83K |
| **Entities** | 38K / 38K | 33K / 33K | 191K / 191K | 460K / 30K |
| **Strings** | 263K / 263K | 258K / 258K | 354 / 353 | 437K / 81K |
| **SingleValue** | 600K / 600K | 599K / 599K | 591K / 591K | 590K / 83K |
| Benchmark | Roblox (Sent/Recv) | BridgeNet2 | ByteNet | Packet | **Satset** |
| :--- | ---: | ---: | ---: | ---: | ---: |
| **Vectors** | 464K / 464K | 455K / 455K | 598K / 598K | 600K / 600K | 375K / 83K |
| **Booleans** | 109K / 109K | 97K / 97K | 216K / 216K | 146K / 146K | 84K / 19K |
| **Mixed** | 600K / 600K | 600K / 600K | 36K / 36K | 600K / 600K | 600K / 83K |
| **Entities** | 37K / 37K | 40K / 40K | 191K / 191K | 128K / 128K | 463K / 83K |
| **Strings** | 221K / 221K | 242K / 242K | 347 / 346 | 494K / 494K | 445K / 83K |
| **SingleValue** | 600K / 600K | 599K / 599K | 591K / 591K | 598K / 598K | 599K / 83K |

> Satset's lower "Recv" count is by design. The batcher packs hundreds of individual `fireServer` calls into a single remote invocation per frame. The server receives fewer, larger payloads instead of many small ones, which drastically reduces per-packet engine overhead.

---

## 4. Technical Analysis

### Satset
### Satset (0.1.2)

- **Zero failures.** Every benchmark ran to completion and passed server-side validation. No timeouts, no data corruption.
- **Bandwidth leader in structured data.** On the **Booleans** test, Satset used 14 KB/s where Roblox used 178 MB/s — roughly **12,700x more efficient**. Even compared to BridgeNet2 (71 MB/s), Satset is over **5,000x leaner**.
- **Entities performance.** Satset maintained 46 FPS while Roblox and BridgeNet2 both dropped to 15-16 FPS sending the same entity data. This is the direct result of zero-allocation buffer serialization: no tables are created during encode/decode, so the GC stays quiet.
- **Entities performance.** Satset maintained 47 FPS while Roblox and BridgeNet2 both dropped to 15-16 FPS sending the same entity data. This is the direct result of zero-allocation buffer serialization: no tables are created during encode/decode, so the GC stays quiet.
- **Hardening overhead.** The newly added `sanitizeFloat`, buffer bounds checks, and `xpcall` listener wrapping had **no measurable impact** on throughput. Bandwidth figures are within margin of error compared to pre-hardening runs.

### Packet (5uphi) (v1.7)

- **100% delivery** — but only after modification. Zero packet loss on every benchmark; all sent packets were received and validated by the server.
- **Rate limiter had to be disabled.** Packet ships with a hardcoded server-side rate limiter of **8 KB/frame** (`init.lua:270`). Under the benchmark's 1,000-fires-per-frame load, this limit was immediately exceeded and the server **silently dropped all incoming data** — no error, no validation, no results. To produce any meaningful numbers, the limit had to be raised from `8_000` to `10_000_000` bytes. This means Packet's benchmark results reflect a **modified build**, not the out-of-the-box library.
- **Bandwidth efficiency.** Competitive with ByteNet and Satset on binary serialization, producing near-identical KB/s on Vectors (~78), Mixed (~5), and SingleValue (~2.4). This is expected — all three libraries use the same underlying `buffer.write*` primitives.
- **FPS matches ByteNet** on most tests (60 FPS on Vectors, Mixed, SingleValue). However, drops to 15 FPS on Booleans and Entities due to per-element serialization overhead on large arrays.
- **Strings handling.** Maintained 50 FPS on the Strings benchmark — better than Roblox (28), BridgeNet2 (38), and Satset (46). Unlike ByteNet, it did not crash.
- **No batching.** Unlike Satset, Packet fires each serialized payload as an individual `RemoteEvent:FireServer()` call. This gives it a 1:1 sent/received ratio but means the Roblox engine must process every packet separately. In production with many players, this per-packet overhead can become a bottleneck that batching-based libraries like Satset avoid entirely.

### ByteNet (v0.4.6)

- Excellent bandwidth efficiency on numeric types (competitive with Satset).
- Excellent bandwidth efficiency on numeric types (competitive with Satset and Packet).
- **Crashed on Strings** with `Script timeout: exhausted allowed execution time` inside its `dyn_alloc` buffer writer. This is a known limitation of its dynamic buffer growth strategy under high-frequency string serialization.
- Severe FPS degradation on the **Mixed** benchmark (16 FPS vs Satset's 60 FPS), suggesting allocation pressure during complex struct encoding.

### BridgeNet2
### BridgeNet2 (v1.0.0)

- Solid batching implementation with good reliability (100% delivery on all tests).
- Bandwidth consumption remains high compared to binary-native libraries because it serializes through Roblox's internal encoding rather than raw buffers.
Expand All @@ -85,13 +94,12 @@ Total packets sent vs received over each 10-second test window.
- **Environment:** Roblox Studio, local server with 1 player.
- **Duration:** 10-second sustained fire per tool per benchmark.
- **Packet rate:** Maximum throughput (fire every frame).
- **Runs:** Each benchmark was executed twice. Results shown are averaged across both runs.
- **Metrics:** Bandwidth sampled 6 times during each run. Framerate captured at 6 intervals. Packet counts are cumulative totals.
- **Validation:** Server verifies decoded data matches the original input for every library. A library that fails validation is flagged.

---

## 6. Raw Data (JSON)

```json
{"Vectors":{"roblox":{"Framerate":[46,48,42,39,39,37],"Sent":428000,"Bandwidth":[87040.93410326087,81283.48188920455,178954.8071808511,283949.3080357143,283949.3080357143,305261.27533783789],"Recieve":428000},"satset":{"Framerate":[37,38,37,36,36,35],"Sent":369000,"Bandwidth":[77.87874322188529,47.13940369455438,79.75733988993878,79.90910091915647,79.90910091915647,79.92751044196052],"Recieve":83056},"bridgenet2":{"Framerate":[49,58,44,43,43,43],"Sent":397000,"Bandwidth":[123741.09375,53015.42054521277,169992.36530172415,169992.36530172415,169992.36530172415,201215.45280612247],"Recieve":397000},"bytenet":{"Framerate":[60,61,60,59,59,58],"Sent":598000,"Bandwidth":[78.59542846679688,61.881290435791019,78.79617309570313,80.38316888324285,80.38316888324285,81.70402789938039],"Recieve":598000},"warp":{"Framerate":[60,61,60,59,59,58],"Sent":598000,"Bandwidth":[0.5147415399551392,0.44034865498542788,0.7243754863739014,0.8366319537162781,0.8366319537162781,0.9511645292413646],"Recieve":0}},"Booleans":{"roblox":{"Framerate":[17,17,16,16,16,16],"Sent":110000,"Bandwidth":[142082.21966911766,76111.29638671875,177840.77205882353,192069.7705078125,192069.7705078125,203953.08363970588],"Recieve":110000},"satset":{"Framerate":[15,16,15,15,15,15],"Sent":86000,"Bandwidth":[11.692818641662598,7.7717337012290959,13.931751251220704,13.931751251220704,13.931751251220704,14.575695037841797],"Recieve":19952},"bridgenet2":{"Framerate":[15,17,15,15,15,15],"Sent":93000,"Bandwidth":[70987.4609375,39258.11236213235,71073.2109375,74063.1015625,74063.1015625,74427.6875],"Recieve":93000},"bytenet":{"Framerate":[22,23,22,21,21,21],"Sent":219000,"Bandwidth":[22.66985633156516,10.74752600296684,23.389805385044644,23.63697832280939,23.63697832280939,27.266837528773718],"Recieve":219000},"warp":{"Framerate":[60,60,60,60,60,58],"Sent":599000,"Bandwidth":[0.5947974920272827,0.4490039348602295,0.8027398586273193,0.9442576169967651,0.9442576169967651,1.0962249903843322],"Recieve":0}},"Mixed":{"roblox":{"Framerate":[60,60,60,60,60,59],"Sent":599000,"Bandwidth":[115569.5859375,44538.390625,118132.140625,118968.6328125,118968.6328125,122733.3515625],"Recieve":599000},"satset":{"Framerate":[60,60,60,60,60,59],"Sent":599000,"Bandwidth":[5.1287617683410648,3.960188388824463,5.188577651977539,5.3851728439331059,5.3851728439331059,5.832243773896815],"Recieve":83056},"bridgenet2":{"Framerate":[60,61,59,59,59,58],"Sent":598000,"Bandwidth":[8285.783491290984,6737.84423828125,8543.777145127118,8564.720934851695,8564.720934851695,8603.907260237069],"Recieve":598000},"bytenet":{"Framerate":[16,16,16,16,16,15],"Sent":34000,"Bandwidth":[2.888798475265503,2.888798475265503,2.888798475265503,2.888798475265503,2.888798475265503,3.27033631503582],"Recieve":34000},"warp":{"Framerate":[60,61,59,59,59,59],"Sent":599000,"Bandwidth":[0.6307408848746878,0.4667607247829437,0.8878631865391966,1.0436007936122054,1.0436007936122054,1.0588467727273197],"Recieve":0}},"Entities":{"roblox":{"Framerate":[16,16,16,16,16,15],"Sent":38000,"Bandwidth":[300342.09375,300342.09375,300342.09375,300342.09375,300342.09375,422848.53515625],"Recieve":38000},"satset":{"Framerate":[46,47,46,46,46,46],"Sent":460000,"Bandwidth":[42.37300375233526,27.61377251666525,42.57011081861413,42.606965769892159,42.606965769892159,42.66785331394362],"Recieve":30160},"bridgenet2":{"Framerate":[16,16,16,16,16,15],"Sent":33000,"Bandwidth":[82576.5380859375,82576.5380859375,82576.5380859375,82576.5380859375,82576.5380859375,106273.328125],"Recieve":33000},"bytenet":{"Framerate":[19,20,19,19,19,19],"Sent":191000,"Bandwidth":[40.747538566589359,18.241161346435548,42.843029624537418,43.57598455328691,43.57598455328691,44.926556035092009],"Recieve":191000},"warp":{"Framerate":[60,60,60,60,60,59],"Sent":599000,"Bandwidth":[0.5736324787139893,0.4431200623512268,0.7422022223472595,0.8961828947067261,0.8961828947067261,1.0356555146686102],"Recieve":0}},"SingleValue":{"roblox":{"Framerate":[60,60,60,60,60,60],"Sent":600000,"Bandwidth":[1716.58984375,1624.1629638671876,1794.3988037109376,1862.046875,1862.046875,1881.8546142578126],"Recieve":600000},"satset":{"Framerate":[60,60,60,59,59,55],"Sent":590000,"Bandwidth":[2.5207481384277345,2.009467840194702,2.5850415229797365,4.586867809295654,4.586867809295654,5.134705042434951],"Recieve":83288},"bridgenet2":{"Framerate":[60,60,60,60,60,59],"Sent":599000,"Bandwidth":[721.5235595703125,562.8475341796875,722.303466796875,723.0374145507813,723.0374145507813,724.3161993511652],"Recieve":599000},"bytenet":{"Framerate":[60,60,60,60,60,57],"Sent":591000,"Bandwidth":[2.4351614399960166,1.9272409677505494,2.486407995223999,2.6438539028167726,2.6438539028167726,3.8990156650543215],"Recieve":591000},"warp":{"Framerate":[60,60,60,60,60,59],"Sent":600000,"Bandwidth":[0.6137112379074097,0.44908496737480166,0.833755612373352,0.9832913390660689,0.9832913390660689,1.0921508073806763],"Recieve":0}},"Strings":{"roblox":{"Framerate":[30,32,29,28,28,27],"Sent":263000,"Bandwidth":[127762.390625,76995.57861328125,134869.80034722223,144698.8415948276,144698.8415948276,160304.7509765625],"Recieve":263000},"satset":{"Framerate":[46,47,44,41,41,36],"Sent":437000,"Bandwidth":[105.8647714010099,64.59545135498047,106.79381999563664,108.3080590289572,108.3080590289572,112.52992109818891],"Recieve":81432},"bridgenet2":{"Framerate":[37,42,36,36,36,27],"Sent":258000,"Bandwidth":[124839.83072916667,53948.13151041667,168467.62957317075,191865.91145833335,191865.91145833335,255821.21527777779],"Recieve":258000},"bytenet":{"Framerate":[],"Sent":354,"Bandwidth":[],"Recieve":353},"warp":{"Framerate":[60,60,60,59,59,59],"Sent":599000,"Bandwidth":[0.567111074924469,0.40372195839881899,0.7347226142883301,0.8855521880974203,0.8855521880974203,0.9359435307777534],"Recieve":0}}}
```
See [result.json](./result.json) in this directory for the raw benchmark output.
7 changes: 5 additions & 2 deletions benchmark/default.project.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@
"$className": "RemoteEvent"
}
},
"Packages": {
"$path": "../Packages",
"DevPackages": {
"$path": "../DevPackages",
"Satset": {
"$path": "../src"
}
},
"SuphiPacket": {
"$path": "../references/Suphi-Packet/Packet"
}
}
}
Expand Down
Loading
Loading