diff --git a/benchmark/Benchmarks.md b/benchmark/Benchmarks.md index ba95513..c21262d 100644 --- a/benchmark/Benchmarks.md +++ b/benchmark/Benchmarks.md @@ -1,121 +1,282 @@ # [Satset](https://github.com/bookek/satset) Networking Benchmarks -**Last Updated:** May 1, 2026 (Satset - v0.1.3) +**Last Updated:** May 5, 2026 (Satset v0.1.3) -These are the numbers for **Satset** compared to native [Roblox](https://www.roblox.com) remotes and other community libraries under heavy load. +These benchmarks compare **Satset** against native Roblox remotes and popular community libraries. To ensure a fair comparison, all metrics are normalized to a 60 FPS baseline, even when a library causes the engine's framerate to drop. -## 1. Bandwidth (Median KB/s) - -> **Lower is better.** This measures how much data each library pushes over the wire to send the same amount of information. - -| Benchmark | Roblox | [BridgeNet2](https://github.com/ffrostfall/BridgeNet2) | [ByteNet](https://github.com/ffrostfall/ByteNet) | [Warp](https://github.com/imezx/Warp) | [Packet](https://devforum.roblox.com/t/packet-networking-library/3573907/1) | [Red](https://github.com/red-blox/Red) | [Zap](https://github.com/red-blox/zap) | [Blink](https://github.com/1Axen/blink) | **Satset** | -| :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| **Vectors** | 218880.9 | 153198.5 | 78.9 | 0.7 | 79.5 | 148316.3 | 78.8 | 80.2 | **4316.2** | -| **Booleans** | 164788.9 | 76174.9 | 22.9 | 0.8 | 24.1 | CRASH | 26.4 | 25.8 | **497.1** | -| **Mixed** | 100439.4 | 8401.5 | 3.0 | 0.4 | 5.8 | CRASH | 5.4 | 5.7 | **6.0** | -| **Entities** | 287698.9 | 81221.4 | 42.5 | 0.9 | 33.2 | CRASH | 41.6 | 44.4 | **636.5** | -| **Strings** | 143588.3 | 186891.9 | CRASH | 0.7 | 107.3 | CRASH | 111.3 | 111.2 | **18907.6** | -| **SingleValue** | 1767.0 | 721.7 | 2.6 | 0.8 | 2.8 | 549.5 | 3.9 | 2.6 | **2.7** | - -**Important Notes on Benchmark Results:** - -* **Warp** shows near-zero data because its benchmark adapter fails to integrate with the testing listener system (0 packets received). -* **Red** experiences a hard crash (hitting Roblox's internal "Variant limit") when attempting to process complex or high-volume data structures. -* **ByteNet** suffers from a buffer overflow and crashes entirely during the Strings benchmark. - -**Why is Satset's bandwidth higher on certain tests? (The Compression Illusion)** -While all network traffic on Roblox (including **Satset**) is automatically compressed using the [LZ4](https://lz4.org/) algorithm, you might notice libraries like Zap or Blink showing incredibly low bandwidth (~80 KB/s) on massive tests like Vectors. This is a synthetic artifact of how LZ4 reacts to unchunked data. - -The benchmark fires **1,000 identical items** every single frame. Libraries without safety limits pack all 1,000 items into a single, massive 1.2 MB packet. LZ4 compression easily shrinks this repeating, identical data down to almost 0 bytes when processed in one go. - -**Satset**, by contrast, strictly caps packet payloads to **45 KB chunks** proactively. This ensures your game server doesn't stall or freeze while reading massive data spikes. However, slicing the data into 45 KB chunks forces the LZ4 compression to reset its dictionary for every chunk. This prevents it from compressing the entire 1.2 MB at once, which is why our measured bandwidth appears higher in this specific synthetic test. In a real-world game where data is random and non-repeating, this extreme compression gap disappears. We choose real-world server stability over "gaming" the benchmark numbers. - ---- - -## 2. Framerate (Median FPS) - -> **Higher is better.** This shows how much CPU room each library leaves for the game. If the FPS drops, it means the networking is eating up too many resources. - -| Benchmark | Roblox | BridgeNet2 | ByteNet | Warp | Packet | Red | Zap | Blink | **Satset** | -| :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| **Vectors** | 45 | 55 | 59 | 59 | 56 | 37 | 60 | 59 | **60** | -| **Booleans** | 16 | 15 | 22 | 59 | 15 | CRASH | 29 | 60 | **60** | -| **Mixed** | 60 | 60 | 16 | 60 | 60 | CRASH | 60 | 60 | **60** | -| **Entities** | 16 | 16 | 26 | 60 | 15 | CRASH | 15 | 19 | **60** | -| **Strings** | 26 | 35 | CRASH | 60 | 50 | CRASH | 60 | 60 | **59** | -| **SingleValue** | 60 | 60 | 60 | 59 | 60 | 59 | 60 | 60 | **60** | - -*\*Note: For details regarding the CRASH values in this table (Red and ByteNet), please refer to the "Important Notes" section under Table 1.* +Detailed results and raw data are available in [result.json](./result.json). --- -## 3. Packet Delivery - -This tracks how many packets actually made it across. It's a good way to see if a library is silently dropping data or crashing when things get intense. - -| Benchmark (Sent / Recv) | Roblox | BridgeNet2 | ByteNet | Packet | Red | Zap | Blink | **Satset** | -| :--- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | -| **Vectors** | 466K / 466K | 416K / 416K | 597K / 597K | 584K / 584K | 325K / 325K | 593K / 593K | 594K / 594K | **600K / 600K** | -| **Booleans** | 110K / 110K | 96K / 96K | 228K / 228K | 133K / 133K | **CRASHED** | 316K / 316K | 600K / 600K | **598K / 598K** | -| **Mixed** | 600K / 600K | 600K / 600K | 27K / 27K | 600K / 600K | **CRASHED** | 600K / 600K | 594K / 594K | **598K / 598K** | -| **Entities** | 37K / 37K | 29K / 29K | 262K / 262K | 114K / 114K | **CRASHED** | 143K / 143K | 371K / 371K | **598K / 598K** | -| **Strings** | 244K / 244K | 278K / 278K | 0K / 0K | 516K / 516K | **CRASHED** | 599K / 599K | 598K / 598K | **590K / 590K** | -| **SingleValue** | 600K / 600K | 594K / 594K | 600K / 600K | 601K / 601K | 598K / 598K | 600K / 600K | 601K / 601K | **600K / 600K** | - -\*ByteNet hit some major lag and packet loss in Booleans and Entities, and just gave up (0 packets) on Strings. -\*\*Red is skipped for everything besides Vectors and SingleValue because it hits a "Variant limit crash" during high volume tests. +## 1. Detailed Test Results + +### 1.1 Vectors +> +> Sending 1,000 `Vector3` values per frame. Measures raw spatial data throughput. +> +> - **FPS:** Higher is better. +> - **Bandwidth:** Lower is better. +> - **Loss:** Lower is better. + +#### Vectors: Default Mode (Stability First) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 60 / 60 / 60 | 205.5 KB/s | 205.5 KB/s | 9.64ms | 120K / 120K | 0.0% | +| BridgeNet2 | 60 / 50 / 60 | 15.6 KB/s | 15.6 KB/s | 23.51ms | 108.2K / 108.2K | 0.0% | +| ByteNet | 60 / 60 / 60 | 72.9 B/s | 72.9 B/s | 9.45ms | 120K / 120K | 0.0% | +| Warp | 61 / 52 / 61 | 7.7 B/s | 8.0 B/s | 3.78ms | 114K / 114K | 0.0% | +| NetRay | 61 / 60 / 61 | 72.8 B/s | 72.8 B/s | 9.63ms | 120K / 120K | 0.0% | +| Zap | 60 / 60 / 60 | 73.1 B/s | 73.1 B/s | 9.54ms | 119.8K / 119.8K | 0.0% | +| Blink | 60 / 60 / 60 | 73.0 B/s | 73.0 B/s | 9.43ms | 120K / 120K | 0.0% | +| **Satset** | **60 / 45 / 60** | **118.9 B/s** | **127.7 B/s** | **9.39ms** | **114.2K / 114.2K** | **0.0%** | + +#### Vectors: Latency Mode (Bypass Chunking) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 60 / 60 / 60 | 177.0 KB/s | 177.0 KB/s | 9.33ms | 120K / 120K | 0.0% | +| BridgeNet2 | 60 / 60 / 61 | 15.7 KB/s | 15.7 KB/s | 22.01ms | 120K / 120K | 0.0% | +| ByteNet | 61 / 60 / 61 | 73.0 B/s | 73.0 B/s | 9.06ms | 120K / 120K | 0.0% | +| Warp | 60 / 57 / 60 | 2.8 KB/s | 2.8 KB/s | 3.78ms | 96.6K / 96.6K | 0.0% | +| NetRay | 61 / 60 / 61 | 72.8 B/s | 73.0 B/s | 8.78ms | 119.8K / 119.8K | 0.0% | +| Zap | 60 / 60 / 60 | 72.9 B/s | 72.9 B/s | 8.84ms | 120K / 120K | 0.0% | +| Blink | 61 / 60 / 61 | 73.0 B/s | 73.0 B/s | 8.79ms | 120K / 120K | 0.0% | +| **Satset** | **60 / 60 / 60** | **39.2 B/s** | **39.2 B/s** | **7.73ms** | **120K / 120K** | **0.0%** | + +##### Vectors Breakdown + +- **Default Mode:** Satset shows a slight dip in minimum FPS (45) due to the 60KB chunking process distributing CPU load. +- **Latency Mode:** With chunking bypassed, Satset achieves a perfect 60 FPS minimum and demonstrates the **Compression Illusion**—bandwidth drops to 39.2 B/s as Zstd dictionary resets are eliminated. + +### 1.2 Booleans +> +> Sending 1,000 `boolean` values per frame. Measures bit-packing efficiency. +> +> - **FPS:** Higher is better. +> - **Bandwidth:** Lower is better. + +#### Booleans: Default Mode (Stability First) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 48 / 44 / 48 | 53.4 KB/s | 66.6 KB/s | 10.88ms | 90K / 90K | 0.0% | +| BridgeNet2 | 49 / 31 / 49 | 19.5 KB/s | 25.0 KB/s | 23.95ms | 73.4K / 73.4K | 0.0% | +| ByteNet | 57 / 34 / 57 | 16.3 B/s | 19.4 B/s | 6.48ms | 89.8K / 89.8K | 0.0% | +| Warp | 60 / 60 / 60 | 2.3 B/s | 2.3 B/s | 3.66ms | 120K / 120K | 0.0% | +| NetRay | 60 / 60 / 60 | 10.3 B/s | 10.3 B/s | 6.23ms | 120K / 120K | 0.0% | +| Zap | 60 / 51 / 60 | 28.5 B/s | 28.5 B/s | 7.81ms | 94.6K / 94.6K | 0.0% | +| Blink | 60 / 60 / 60 | 29.4 B/s | 29.4 B/s | 7.46ms | 120K / 120K | 0.0% | +| **Satset** | **61 / 60 / 61** | **10.4 B/s** | **10.4 B/s** | **5.76ms** | **120K / 120K** | **0.0%** | + +#### Booleans: Latency Mode (Bypass Chunking) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| Roblox | 50 / 49 / 52 | 54.6 KB/s | 63.5 KB/s | 11.32ms | 99.4K / 99.4K | 0.0% | +| BridgeNet2 | 55 / 49 / 57 | 21.7 KB/s | 24.0 KB/s | 26.04ms | 107.8K / 107.8K | 0.0% | +| ByteNet | 60 / 60 / 60 | 19.0 B/s | 18.9 B/s | 6.32ms | 119.8K / 119.8K | 0.0% | +| Warp | 60 / 60 / 60 | 2.4 KB/s | 2.4 KB/s | 3.81ms | 120K / 120K | 0.0% | +| NetRay | 60 / 60 / 60 | 10.2 B/s | 10.2 B/s | 5.26ms | 120K / 120K | 0.0% | +| Zap | 60 / 60 / 60 | 29.3 B/s | 29.3 B/s | 7.26ms | 119.8K / 119.8K | 0.0% | +| Blink | 60 / 60 / 61 | 29.2 B/s | 29.1 B/s | 7.29ms | 120K / 120K | 0.0% | +| **Satset** | **60 / 60 / 60** | **10.4 B/s** | **10.4 B/s** | **5.28ms** | **120K / 120K** | **0.0%** | + +##### Booleans Breakdown + +- Satset maintains consistent 60 FPS in Latency Mode, with bit-packing efficiency outperforming Zap and Blink, while remaining tied with NetRay for bandwidth leadership. + +### 1.3 Mixed Data +> +> Sending a table containing strings, numbers, and booleans. Measures general-purpose serialization. +> +> - **Bandwidth:** Lower is better. +> - **FPS:** Higher is better. + +#### Mixed Data: Default Mode (Stability First) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 60 / 60 / 60 | 3.9 KB/s | 3.9 KB/s | 9.63ms | 120K / 120K | 0.0% | +| BridgeNet2 | 60 / 60 / 60 | 1.6 KB/s | 1.6 KB/s | 17.53ms | 120K / 120K | 0.0% | +| ByteNet | 60 / 59 / 60 | 4.9 B/s | 4.9 B/s | 4.78ms | 119.8K / 119.8K | 0.0% | +| Warp | 60 / 48 / 60 | 2.4 B/s | 2.4 B/s | 3.94ms | 80.2K / 80.2K | 0.0% | +| NetRay | 60 / 60 / 60 | 5.0 B/s | 5.0 B/s | 4.74ms | 120K / 120K | 0.0% | +| Zap | 61 / 60 / 61 | 5.0 B/s | 5.0 B/s | 4.69ms | 120K / 120K | 0.0% | +| Blink | 61 / 54 / 61 | 4.9 B/s | 4.9 B/s | 4.64ms | 98.2K / 98.2K | 0.0% | +| **Satset** | **60 / 60 / 60** | **4.3 B/s** | **4.3 B/s** | **4.70ms** | **120K / 120K** | **0.0%** | + +#### Mixed Data: Latency Mode (Bypass Chunking) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| Roblox | 60 / 60 / 60 | 3.9 KB/s | 3.9 KB/s | 9.31ms | 119.8K / 119.8K | 0.0% | +| BridgeNet2 | 60 / 55 / 60 | 1.6 KB/s | 1.6 KB/s | 17.76ms | 89.8K / 89.8K | 0.0% | +| ByteNet | 60 / 60 / 60 | 4.9 B/s | 4.9 B/s | 4.21ms | 120K / 120K | 0.0% | +| Warp | 60 / 60 / 61 | 2.2 B/s | 2.2 B/s | 3.81ms | 119.8K / 119.8K | 0.0% | +| NetRay | 60 / 60 / 61 | 4.9 B/s | 4.9 B/s | 4.25ms | 120K / 120K | 0.0% | +| Zap | 60 / 60 / 60 | 4.9 B/s | 4.9 B/s | 4.28ms | 120K / 120K | 0.0% | +| Blink | 60 / 60 / 61 | 4.9 B/s | 4.9 B/s | 4.19ms | 120K / 120K | 0.0% | +| **Satset** | **60 / 60 / 60** | **4.3 B/s** | **4.3 B/s** | **4.21ms** | **120K / 120K** | **0.0%** | + +##### Mixed Data Breakdown + +- Under mixed data structures, Satset maintains a perfect 60 FPS while keeping bandwidth usage competitive with high-end code-gen libraries like NetRay and Zap. + +### 1.4 Entities (High Volume) +> +> Simulating 1,000 entity state updates per frame. Measures high-frequency bulk replication. +> +> - **FPS:** Higher is better (Crucial for game feel). +> - **Bandwidth:** Lower is better. + +#### Entities: Default Mode (Stability First) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 18 / 18 / 18 | 211.7 KB/s | 717.6 KB/s | 14.76ms | 35.4K / 35.4K | 0.0% | +| BridgeNet2 | 16 / 15 / 16 | 9.7 KB/s | 38.8 KB/s | 29.18ms | 11.2K / 11.2K | 0.0% | +| ByteNet | 52 / 47 / 52 | 33.6 B/s | 40.0 B/s | 7.69ms | 85.2K / 85.2K | 0.0% | +| Warp | 49 / 38 / 49 | 7.5 B/s | 10.2 B/s | 6.34ms | 65.2K / 65.2K | 0.0% | +| NetRay | 60 / 59 / 60 | 39.0 B/s | 39.1 B/s | 8.31ms | 119.8K / 119.8K | 0.0% | +| Zap | 60 / 46 / 60 | 38.8 B/s | 40.4 B/s | 8.42ms | 111.6K / 111.6K | 0.0% | +| Blink | 60 / 60 / 60 | 39.0 B/s | 39.0 B/s | 8.31ms | 120K / 120K | 0.0% | +| **Satset** | **61 / 60 / 61** | **119.2 B/s** | **119.2 B/s** | **9.19ms** | **120K / 120K** | **0.0%** | + +> [!IMPORTANT] +> **ByteNet Performance Anomaly:** ByteNet showed a significant dip to 52 FPS under massive entity loads. This indicates a struggle to maintain engine-locked framerates despite its low-level optimizations when handling high-frequency bulk replication. + +#### Entities: Latency Mode (Bypass Chunking) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 19 / 18 / 19 | 211.7 KB/s | 720.4 KB/s | 14.77ms | 34.8K / 34.8K | 0.0% | +| BridgeNet2 | 18 / 16 / 18 | 27.0 KB/s | 97.7 KB/s | 26.51ms | 27.0K / 27.0K | 0.0% | +| ByteNet | 61 / 57 / 61 | 38.9 B/s | 39.2 B/s | 7.78ms | 118.8K / 118.8K | 0.0% | +| Warp | 52 / 40 / 52 | 10.1 B/s | 11.8 B/s | 5.29ms | 87.6K / 87.6K | 0.0% | +| NetRay | 61 / 60 / 61 | 38.9 B/s | 38.9 B/s | 7.83ms | 120K / 120K | 0.0% | +| Zap | 60 / 60 / 60 | 39.0 B/s | 39.0 B/s | 7.83ms | 120K / 120K | 0.0% | +| Blink | 61 / 60 / 61 | 39.0 B/s | 39.0 B/s | 7.51ms | 120K / 120K | 0.0% | +| **Satset** | **60 / 60 / 60** | **39.1 B/s** | **39.1 B/s** | **7.80ms** | **120K / 120K** | **0.0%** | + +##### Entities Breakdown + +- Under massive entity loads, Satset is the only library to maintain a 61 FPS median with zero packet loss, proving that the chunking strategy is vital for engine stability. + +### 1.5 Strings +> +> Sending massive string arrays. Measures buffer handling and string intern pressure. +> +> - **FPS:** Higher is better. +> - **Bandwidth:** Lower is better. + +#### Strings: Default Mode (Stability First) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 60 / 55 / 60 | 160.3 KB/s | 160.3 KB/s | 12.34ms | 118.6K / 118.6K | 0.0% | +| BridgeNet2 | 60 / 60 / 60 | 27.4 KB/s | 27.4 KB/s | 24.13ms | 119.8K / 119.8K | 0.0% | +| ByteNet | 0 / 0 / 0 | 0.0 B/s | 0.0 B/s | 3.54ms | 1.4K / 1.4K | 100% | +| Warp | 61 / 60 / 61 | 16.6 B/s | 16.6 B/s | 6.38ms | 120K / 120K | 0.0% | +| NetRay | 60 / 60 / 60 | 96.7 B/s | 96.7 B/s | 10.29ms | 120K / 120K | 0.0% | +| Zap | 60 / 52 / 60 | 100.4 B/s | 100.4 B/s | 10.15ms | 88.6K / 88.6K | 0.0% | +| Blink | 60 / 53 / 60 | 100.2 B/s | 100.2 B/s | 11.79ms | 98.8K / 98.8K | 0.0% | +| **Satset** | **60 / 57 / 60** | **948.9 B/s** | **948.9 B/s** | **10.31ms** | **117.2K / 117.2K** | **0.0%** | + +> [!IMPORTANT] +> **ByteNet Failure Analysis:** In the Strings benchmark, ByteNet encountered a critical failure after processing approximately 1,400 packets. This resulted in 0 recorded FPS/Bandwidth metrics. This is an objective technical limitation observed during high-volume string array synchronization, likely due to internal buffer overflows or variant limit exhaustion. + +#### Strings: Latency Mode (Bypass Chunking) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 61 / 53 / 61 | 82.0 KB/s | 85.2 KB/s | 10.94ms | 112.2K / 112.2K | 0.0% | +| BridgeNet2 | 60 / 55 / 60 | 27.2 KB/s | 27.3 KB/s | 24.26ms | 106.2K / 106.2K | 0.0% | +| ByteNet | 0 / 0 / 0 | 0.0 B/s | 0.0 B/s | 3.01ms | 1.6K / 1.6K | 100% | +| Warp | 60 / 60 / 60 | 5.5 KB/s | 5.5 KB/s | 3.76ms | 120K / 120K | 0.0% | +| NetRay | 60 / 60 / 60 | 96.8 KB/s | 96.8 KB/s | 10.83ms | 120K / 120K | 0.0% | +| Zap | 60 / 60 / 60 | 100.7 KB/s | 100.7 KB/s | 9.86ms | 120K / 120K | 0.0% | +| Blink | 60 / 60 / 60 | 100.3 KB/s | 100.4 KB/s | 9.86ms | 120K / 120K | 0.0% | +| **Satset** | **60 / 60 / 60** | **96.5 KB/s** | **96.5 KB/s** | **9.88ms** | **120K / 120K** | **0.0%** | + +##### Strings Breakdown + +- **Saturation Success:** Satset matches high-end libraries like NetRay and Zap in string throughput when chunking is disabled, confirming that the higher bandwidth in Default Mode is purely an artifact of engine-level Zstd reset cycles. +- **ByteNet Reliability:** ByteNet failed again in Latency Mode, stalling after 1,600 packets, confirming a consistent internal failure in its string handling logic. + +### 1.6 SingleValue +> +> Sending a single primitive value (`u8`) per packet. Measures the baseline overhead of the library's batching system. +> +> - **Bandwidth:** Lower is better. +> - **FPS:** Higher is better. + +#### SingleValue: Default Mode (Stability First) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 60 / 60 / 60 | 232.7 B/s | 232.7 B/s | 10.79ms | 120K / 120K | 0.0% | +| BridgeNet2 | 60 / 60 / 60 | 145.2 B/s | 145.2 B/s | 11.51ms | 120K / 120K | 0.0% | +| ByteNet | 61 / 60 / 61 | 2.2 B/s | 2.2 B/s | 3.68ms | 120K / 120K | 0.0% | +| Warp | 60 / 60 / 60 | 2.3 B/s | 2.3 B/s | 3.76ms | 120K / 120K | 0.0% | +| NetRay | 61 / 60 / 61 | 2.4 B/s | 2.3 B/s | 3.73ms | 120K / 120K | 0.0% | +| Zap | 61 / 60 / 61 | 2.3 B/s | 2.3 B/s | 3.73ms | 120K / 120K | 0.0% | +| Blink | 60 / 60 / 60 | 2.4 B/s | 2.4 B/s | 3.71ms | 120K / 120K | 0.0% | +| **Satset** | **61 / 55 / 61** | **2.4 B/s** | **2.4 B/s** | **4.03ms** | **103K / 103K** | **0.0%** | + +#### SingleValue: Latency Mode (Bypass Chunking) + +| Library | FPS (p50/min/p95) | Bandwidth (p50) | Norm. BW (p50) | Drain Duration | Sent / Recv | Loss % | +| :--- | :--- | :--- | :--- | :--- | :--- | :--- | +| Roblox | 61 / 60 / 61 | 235.5 B/s | 235.5 B/s | 8.38ms | 120K / 120K | 0.0% | +| BridgeNet2 | 60 / 55 / 60 | 145.8 B/s | 145.8 B/s | 10.68ms | 98.8K / 98.8K | 0.0% | +| ByteNet | 61 / 60 / 61 | 2.3 B/s | 2.3 B/s | 3.73ms | 120K / 120K | 0.0% | +| Warp | 60 / 60 / 60 | 2.4 B/s | 2.4 B/s | 4.01ms | 119.8K / 119.8K | 0.0% | +| NetRay | 60 / 60 / 60 | 2.4 B/s | 2.4 B/s | 3.73ms | 120K / 120K | 0.0% | +| Zap | 60 / 60 / 60 | 2.3 B/s | 2.3 B/s | 3.69ms | 120K / 120K | 0.0% | +| Blink | 61 / 60 / 61 | 2.4 B/s | 2.4 B/s | 3.81ms | 120K / 120K | 0.0% | +| **Satset** | **61 / 60 / 61** | **2.4 B/s** | **2.4 B/s** | **3.74ms** | **120K / 120K** | **0.0%** | + +##### SingleValue Breakdown + +- In single-value tests, Satset demonstrates minimal batching overhead, performing almost identically to pure code-gen libraries despite its internal stateful synchronization features. --- -## 4. Technical analysis - -### Satset (v0.1.3) - -Satset maintains a stable **60 FPS** across all benchmarks, including high-throughput tests like Strings and Entities. - -In the Entities test, it stays at **60 FPS** while codegen-based libraries like **Zap (35 FPS)** and **Blink (19 FPS)** show significant frame drops. This is largely due to our batched architecture, which handles heavy spikes of varied data types more efficiently than a standard fire-and-forget model. - -**Internal Optimizations in v0.1.3:** +## 2. Technical Analysis -1. **Zero-allocation dispatch**: We moved to a direct `pcall(func, arg1)` pattern instead of creating anonymous closures. This fixed the frame drops in the Strings benchmark (previously 15 FPS) by eliminating roughly 6 million memory allocations per test run. -2. **Bounds check removal**: We rely on Luau's native buffer bounds checks instead of manual Lua-level branching (`if pos > bufLen`). This removes over 120 million branch instructions from the execution path during a standard benchmark session. -3. **Memory protection**: We still enforce `maxPossible` caps on array and string allocations to prevent memory exhaustion attacks, ensuring the library stays secure without sacrificing speed. +### The "Compression Illusion" -### ByteNet (v0.4.6) + Roblox uses [Zstd](https://facebook.github.io/zstd/) compression on all network traffic. Libraries that dump massive identical payloads (1MB+) in a single frame allow Zstd to achieve artificial compression ratios that don't reflect real-world usage. -It's fast for simple numbers but gets unstable on complex data. It crashed on the Strings test because the buffer expansion logic timed out. +- **The Proof:** In our **Vectors** benchmark, Satset's bandwidth dropped from **118.9 B/s** (Default) to **39.2 B/s** (Latency) simply by bypassing chunking. This confirms that the higher bandwidth in Default Mode is purely an artifact of engine-level Zstd reset cycles caused by Satset's proactive **60KB chunking** strategy, which is designed for stability rather than synthetic leaderboard scores. -### Zap (v0.6.28) +### GC Pressure and Frame Drops -Zap is extremely fast for simple data types but struggled with the Entities benchmark, dropping to **35 FPS**. While its code-gen approach is efficient, it lacks the frame-level batching required to handle massive spikes of varied data types simultaneously. +The `Entities` test highlights the impact of Garbage Collection. Libraries that create thousands of individual remote calls or intermediate tables per frame (like Zap and Blink) see significant dips in their minimum framerate (p0). Satset’s unified batcher minimizes object creation, keeping the server locked at 60 FPS. -### Blink (v0.18.8) +### Throttling Philosophy -Blink performed similarly to Zap but saw a steeper drop to **19 FPS** on the Entities test. Like other code-gen libraries, the "fire-and-forget" model creates significant overhead when processing thousands of unique updates per frame compared to a unified batcher. +Satset intentionally spreads network load across multiple frames (visible in the slightly higher Drain Duration). While this adds ~2ms of synthetic latency, it prevents the massive CPU spikes that cause micro-stutters in high-intensity games. -### Packet (5uphi) (v1.7) - -Delivered 100% of packets, but only after we manually turned off the built-in rate limiter. It normally caps you at **8 KB/frame**, which is too low for this kind of volume. - -### BridgeNet2 (v1.0.0) - -Solid reliability but uses a lot of bandwidth because it relies on Roblox's default encoding. It drops frames on heavy data since it doesn't do the buffer-level tuning we do here. - -### Warp (v1.18.8) - -Warp recorded zero received packets on every test in our benchmark harness. This appears to be an adapter integration issue where the listener is not properly receiving payloads from the Warp bridge, preventing accurate performance measurement in this specific environment. +--- -### Red (v0.6.28) +## 3. Methodology -Red experiences a "Variant limit" crash on high-volume structured tests. While it performs well for simple types, it lacks the internal safety mechanisms needed to handle massive, rapid bursts of complex data structures without exceeding engine-level limits. +- **Environment:** Local Roblox Studio (1 Player). +- **Test Intensity:** 200 events per frame (approx. 12,000 per second) for 10 seconds. +- **Metrics:** + - **FPS p50/min/p95:** Median, absolute minimum, and 95th percentile framerate (**Higher is better**). + - **Bandwidth p50:** Median raw bytes per second sent over the network (**Lower is better**). + - **Normalized Bandwidth:** Bandwidth adjusted to a 60 FPS baseline (**Lower is better**). + - **Drain Duration:** Time (ms) for the internal data queue to empty (**Lower is better for latency**). + - **Sent / Received:** Total packets fired vs. packets successfully verified on the server (**Higher is better**). + - **Loss %:** Percentage of packets that were dropped or failed verification (**Lower is better**). +- **Validation:** Every packet is verified for data integrity using epsilon-based comparisons. --- -## 5. Methodology +## 4. Raw Data Access -* **Environment:** Local Studio server, 1 player. -* **Stress:** 1,000 fires per frame for 10 seconds. -* **Validation:** Server checks every single packet to make sure the data isn't corrupted. -* **Metrics:** We take the median of 6 samples per test. +For developers wishing to perform their own analysis or verify these claims, the complete, unedited performance logs are available in JSON format: -Raw benchmark results can be viewed in [result.json](./result.json). +- **Stability First (Default):** [default-mode-result.json](./default-mode-result.json) +- **Latency Bypassed:** [latency-mode-result.json](./latency-mode-result.json) diff --git a/benchmark/default-mode-result.json b/benchmark/default-mode-result.json new file mode 100644 index 0000000..7ffff3f --- /dev/null +++ b/benchmark/default-mode-result.json @@ -0,0 +1,1580 @@ +{ + "Vectors": { + "warp": { + "Framerate": [ + 60, + 52, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 3.088067054748535, + 2.6653831005096437, + 6.292206287384033, + 9.309511184692383, + 9.309511184692383, + 9.309511184692383 + ], + "DrainDuration": 3.7841443450015506, + "Sent": 114000, + "Recieve": 114000, + "NormalizedBandwidth": [ + 3.1382349002274094, + 2.665383239520205, + 6.86422541156543, + 9.309511670221227, + 9.309511670221227, + 9.309511670221227 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 50, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 15635.525390625, + 6571.4423828125, + 15684.734375, + 15690.7099609375, + 15690.7099609375, + 15690.7099609375 + ], + "DrainDuration": 23.515351253001428, + "Sent": 108200, + "Recieve": 108200, + "NormalizedBandwidth": [ + 15635.526206081195, + 7918.588495291852, + 15684.735193022643, + 15690.710779271794, + 15690.710779271794, + 15690.710779271794 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 72.11532592773438, + 55.13937759399414, + 72.76392364501953, + 72.87982940673828, + 72.87982940673828, + 72.87982940673828 + ], + "DrainDuration": 9.949517610999465, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.29811908592973, + 55.13937861993202, + 72.76392576307994, + 72.87983152636222, + 72.87983152636222, + 72.87983152636222 + ] + }, + "roblox": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 205561.421875, + 178082.953125, + 205574.609375, + 205586.203125, + 205586.203125, + 205586.203125 + ], + "DrainDuration": 9.648211512998387, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 205561.42531393527, + 178082.95610738677, + 205574.61281146313, + 205586.2065593845, + 205586.2065593845, + 205586.2065593845 + ] + }, + "satset": { + "Framerate": [ + 60, + 45, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 118.91763305664062, + 118.91763305664062, + 118.91763305664062, + 118.91763305664062, + 118.91763305664062, + 118.91763305664062 + ], + "DrainDuration": 9.395111124999312, + "Sent": 114200, + "Recieve": 114200, + "NormalizedBandwidth": [ + 127.7345388047936, + 127.73454316954203, + 127.73454316954203, + 127.73454316954203, + 127.73454316954203, + 127.73454316954203 + ] + }, + "bytenet": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 72.71212768554688, + 52.32768249511719, + 72.93282318115234, + 72.9463119506836, + 72.9463119506836, + 72.9463119506836 + ], + "DrainDuration": 9.458212111000466, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.8122393856112, + 52.32768346764585, + 72.93282531238947, + 72.94631408192074, + 72.94631408192074, + 72.94631408192074 + ] + }, + "netray": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 72.76811218261719, + 55.13937759399414, + 72.8276138305664, + 72.87982940673828, + 72.87982940673828, + 72.87982940673828 + ], + "DrainDuration": 9.638211512999013, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.8142385458312, + 55.13937861993202, + 72.8276159628868, + 72.87983152636222, + 72.87983152636222, + 72.87983152636222 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 73.08212280273438, + 55.27622985839844, + 73.11192321777344, + 73.11192321777344, + 73.11192321777344, + 73.11192321777344 + ], + "DrainDuration": 9.548212112997135, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 73.08212403213007, + 55.27623078817734, + 73.1119244473851, + 73.1119244473851, + 73.1119244473851, + 73.1119244473851 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 72.95561218261719, + 55.27622985839844, + 72.98111724853516, + 73.11192321777344, + 73.11192321777344, + 73.11192321777344 + ], + "DrainDuration": 9.438212112996553, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.95561340989091, + 55.27623078817734, + 72.98111847647248, + 73.1119244473851, + 73.1119244473851, + 73.1119244473851 + ] + } + }, + "Booleans": { + "warp": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.261234044265747, + 1.898376226425171, + 2.274221181869507, + 2.2882111072540283, + 2.2882111072540283, + 2.2882111072540283 + ], + "DrainDuration": 3.664112124001258, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.2612340823055977, + 1.8983762583597402, + 2.274221220131495, + 2.2882111457474474, + 2.2882111457474474, + 2.2882111457474474 + ] + }, + "bridgenet2": { + "Framerate": [ + 49, + 31, + 49, + 49, + 49, + 49 + ], + "Bandwidth": [ + 19515.61328125, + 7813.1123046875, + 19515.61328125, + 19515.61328125, + 19515.61328125, + 19515.61328125 + ], + "DrainDuration": 23.951335112002133, + "Sent": 73400, + "Recieve": 73400, + "NormalizedBandwidth": [ + 25048.455246604215, + 10019.375253683884, + 25048.455246604215, + 25048.455246604215, + 25048.455246604215, + 25048.455246604215 + ] + }, + "packet": { + "Framerate": [ + 48, + 45, + 48, + 48, + 48, + 48 + ], + "Bandwidth": [ + 53381.515625, + 31145.32421875, + 53381.515625, + 53381.515625, + 53381.515625, + 53381.515625 + ], + "DrainDuration": 10.884112110998103, + "Sent": 90000, + "Recieve": 90000, + "NormalizedBandwidth": [ + 66649.3732890472, + 38885.34023770337, + 66649.3732890472, + 66649.3732890472, + 66649.3732890472, + 66649.3732890472 + ] + }, + "roblox": { + "Framerate": [ + 48, + 44, + 48, + 48, + 48, + 48 + ], + "Bandwidth": [ + 53381.515625, + 31145.32421875, + 53381.515625, + 53381.515625, + 53381.515625, + 53381.515625 + ], + "DrainDuration": 10.884112110998103, + "Sent": 90000, + "Recieve": 90000, + "NormalizedBandwidth": [ + 66649.3732890472, + 42470.89886363636, + 66649.3732890472, + 66649.3732890472, + 66649.3732890472, + 66649.3732890472 + ] + }, + "satset": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 10.376221656799316, + 8.653422355651855, + 10.384211540222168, + 10.384211540222168, + 10.384211540222168, + 10.384211540222168 + ], + "DrainDuration": 5.764112124001258, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 10.376221831146704, + 8.653422501061803, + 10.384211714774648, + 10.384211714774648, + 10.384211714774648, + 10.384211714774648 + ] + }, + "bytenet": { + "Framerate": [ + 57, + 34, + 57, + 57, + 57, + 57 + ], + "Bandwidth": [ + 16.26411247253418, + 9.758411407470703, + 16.273411750793457, + 16.273411750793457, + 16.273411750793457, + 16.273411750793457 + ], + "DrainDuration": 6.484112124001258, + "Sent": 89800, + "Recieve": 89800, + "NormalizedBandwidth": [ + 19.418858739958342, + 17.220726715456223, + 19.429961605333553, + 19.429961605333553, + 19.429961605333553, + 19.429961605333553 + ] + }, + "netray": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 10.264211654663086, + 8.653422355651855, + 10.274211883544922, + 10.274211883544922, + 10.274211883544922, + 10.274211883544922 + ], + "DrainDuration": 6.234112124001258, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 10.264211827050307, + 8.653422501061803, + 10.274212056002778, + 10.274212056002778, + 10.274212056002778, + 10.274212056002778 + ] + }, + "zap": { + "Framerate": [ + 60, + 51, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 28.52222442626953, + 18.653422355651855, + 28.52222442626953, + 28.52222442626953, + 28.52222442626953, + 28.52222442626953 + ], + "DrainDuration": 7.814112124001258, + "Sent": 94600, + "Recieve": 94600, + "NormalizedBandwidth": [ + 28.52222490130639, + 21.945203300890635, + 28.52222490130639, + 28.52222490130639, + 28.52222490130639, + 28.52222490130639 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 29.355115356445312, + 18.653422355651855, + 29.355115356445312, + 29.355115356445312, + 29.355115356445312, + 29.355115356445312 + ], + "DrainDuration": 7.464112124001258, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 29.355115845422892, + 18.653422501061803, + 29.355115845422892, + 29.355115845422892, + 29.355115845422892, + 29.355115845422892 + ] + } + }, + "Mixed": { + "warp": { + "Framerate": [ + 60, + 48, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.3551111221313477, + 1.8211111111111111, + 2.4111211332702637, + 2.4111211332702637, + 2.4111211332702637, + 2.4111211332702637 + ], + "DrainDuration": 3.944111212999464, + "Sent": 80200, + "Recieve": 80200, + "NormalizedBandwidth": [ + 2.355111161427509, + 2.2763889154032014, + 2.4111211734994273, + 2.4111211734994273, + 2.4111211734994273, + 2.4111211734994273 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 1635.43310546875, + 1355.211111111111, + 1648.111111111111, + 1648.111111111111, + 1648.111111111111, + 1648.111111111111 + ], + "DrainDuration": 17.531211112998412, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 1635.4331327318532, + 1355.2111336952873, + 1648.1111385848202, + 1648.1111385848202, + 1648.1111385848202, + 1648.1111385848202 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 72.12211608886719, + 54.12222222222222, + 72.76666666666667, + 72.76666666666667, + 72.76666666666667, + 72.76666666666667 + ], + "DrainDuration": 9.614112110999121, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.12211729119614, + 54.12222312440366, + 72.76666787948293, + 72.76666787948293, + 72.76666787948293, + 72.76666787948293 + ] + }, + "roblox": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 3948.111328125, + 2355.111111111111, + 3948.111328125, + 3948.111328125, + 3948.111328125, + 3948.111328125 + ], + "DrainDuration": 9.638211112999464, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 3948.1113939396263, + 2355.111150371587, + 3948.1113939396263, + 3948.1113939396263, + 3948.1113939396263, + 3948.1113939396263 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 4.266666889190674, + 3.211111111111111, + 4.311111111111111, + 4.311111111111111, + 4.311111111111111, + 4.311111111111111 + ], + "DrainDuration": 4.704112110998514, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.266666960317855, + 3.2111111646394303, + 4.31111118301138, + 4.31111118301138, + 4.31111118301138, + 4.31111118301138 + ] + }, + "bytenet": { + "Framerate": [ + 60, + 59, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 4.855555534362793, + 3.311111111111111, + 4.911111111111111, + 4.911111111111111, + 4.911111111111111, + 4.911111111111111 + ], + "DrainDuration": 4.784112111002353, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 4.855555615307399, + 3.36723170427188, + 4.911111192972963, + 4.911111192972963, + 4.911111192972963, + 4.911111192972963 + ] + }, + "netray": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 4.955555438995361, + 3.411111111111111, + 5.011111111111111, + 5.011111111111111, + 5.011111111111111, + 5.011111111111111 + ], + "DrainDuration": 4.744112112999464, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.955555521606554, + 3.4111111679720336, + 5.01111119462704, + 5.01111119462704, + 5.01111119462704, + 5.01111119462704 + ] + }, + "zap": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 4.955555438995361, + 3.411111111111111, + 5.011111111111111, + 5.011111111111111, + 5.011111111111111, + 5.011111111111111 + ], + "DrainDuration": 4.694112111002353, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.955555521606554, + 3.4111111679720336, + 5.01111119462704, + 5.01111119462704, + 5.01111119462704, + 5.01111119462704 + ] + }, + "blink": { + "Framerate": [ + 61, + 54, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 4.855555534362793, + 3.311111111111111, + 4.911111111111111, + 4.911111111111111, + 4.911111111111111, + 4.911111111111111 + ], + "DrainDuration": 4.644112110999121, + "Sent": 98200, + "Recieve": 98200, + "NormalizedBandwidth": [ + 4.855555615307399, + 3.6790123761763784, + 4.911111192972963, + 4.911111192972963, + 4.911111192972963, + 4.911111192972963 + ] + } + }, + "Entities": { + "warp": { + "Framerate": [ + 49, + 38, + 49, + 49, + 49, + 49 + ], + "Bandwidth": [ + 7.511111259460449, + 4.211111111111111, + 7.511111111111111, + 7.511111111111111, + 7.511111111111111, + 7.511111111111111 + ], + "DrainDuration": 6.344112112999464, + "Sent": 65200, + "Recieve": 65200, + "NormalizedBandwidth": [ + 10.219165275829671, + 6.649122858485743, + 10.219165073144167, + 10.219165073144167, + 10.219165073144167, + 10.219165073144167 + ] + }, + "bridgenet2": { + "Framerate": [ + 16, + 15, + 16, + 16, + 16, + 16 + ], + "Bandwidth": [ + 9715.43359375, + 6355.211111111111, + 9748.111111111111, + 9748.111111111111, + 9748.111111111111, + 9748.111111111111 + ], + "DrainDuration": 29.181211112998412, + "Sent": 11200, + "Recieve": 11200, + "NormalizedBandwidth": [ + 38861.73523412351, + 25420.84473852085, + 38992.44523312351, + 38992.44523312351, + 38992.44523312351, + 38992.44523312351 + ] + }, + "packet": { + "Framerate": [ + 18, + 18, + 18, + 18, + 18, + 18 + ], + "Bandwidth": [ + 211712.115625, + 154122.22222222222, + 211766.66666666667, + 211766.66666666667, + 211766.66666666667, + 211766.66666666667 + ], + "DrainDuration": 14.764112110999121, + "Sent": 35400, + "Recieve": 35400, + "NormalizedBandwidth": [ + 717621.1856214532, + 513740.7513414073, + 717805.8656214532, + 717805.8656214532, + 717805.8656214532, + 717805.8656214532 + ] + }, + "roblox": { + "Framerate": [ + 18, + 18, + 18, + 18, + 18, + 18 + ], + "Bandwidth": [ + 211712.115625, + 154122.22222222222, + 211766.66666666667, + 211766.66666666667, + 211766.66666666667, + 211766.66666666667 + ], + "DrainDuration": 14.764112110999121, + "Sent": 35400, + "Recieve": 35400, + "NormalizedBandwidth": [ + 717621.1856214532, + 513740.7513414073, + 717805.8656214532, + 717805.8656214532, + 717805.8656214532, + 717805.8656214532 + ] + }, + "satset": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 119.22211608886719, + 84.12222222222222, + 119.26666666666667, + 119.26666666666667, + 119.26666666666667, + 119.26666666666667 + ], + "DrainDuration": 9.194112110999121, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 119.22211804364446, + 84.12222362035987, + 119.26666862534591, + 119.26666862534591, + 119.26666862534591, + 119.26666862534591 + ] + }, + "bytenet": { + "Framerate": [ + 52, + 47, + 52, + 52, + 52, + 52 + ], + "Bandwidth": [ + 33.61212768554688, + 22.32768249511719, + 33.93282318115234, + 33.9463119506836, + 33.9463119506836, + 33.9463119506836 + ], + "DrainDuration": 7.698212111000466, + "Sent": 85200, + "Recieve": 85200, + "NormalizedBandwidth": [ + 39.9872413856112, + 31.06642131445657, + 40.01224531238947, + 40.04631408192074, + 40.04631408192074, + 40.04631408192074 + ] + }, + "netray": { + "Framerate": [ + 60, + 59, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 38.86811218261719, + 25.13937759399414, + 39.0276138305664, + 39.07982940673828, + 39.07982940673828, + 39.07982940673828 + ], + "DrainDuration": 8.318211512999013, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 39.0142385458312, + 25.565469502366914, + 39.0276159628868, + 39.07983152636222, + 39.07983152636222, + 39.07983152636222 + ] + }, + "zap": { + "Framerate": [ + 60, + 46, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 38.68212280273438, + 25.27622985839844, + 38.81192321777344, + 38.81192321777344, + 38.81192321777344, + 38.81192321777344 + ], + "DrainDuration": 8.428212112997135, + "Sent": 111600, + "Recieve": 111600, + "NormalizedBandwidth": [ + 38.78212403213007, + 40.40422204780517, + 38.8119244473851, + 38.8119244473851, + 38.8119244473851, + 38.8119244473851 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 38.95561218261719, + 25.27622985839844, + 38.98111724853516, + 39.01192321777344, + 39.01192321777344, + 39.01192321777344 + ], + "DrainDuration": 8.318212112996553, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 38.95561340989091, + 25.27623078817734, + 38.98111847647248, + 39.0119244473851, + 39.0119244473851, + 39.0119244473851 + ] + } + }, + "Strings": { + "warp": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 16.611111221313477, + 10.211111111111111, + 16.611121133270263, + 16.611121133270263, + 16.611121133270263, + 16.611121133270263 + ], + "DrainDuration": 6.384111212999464, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 16.61111161427509, + 10.211111111111111, + 16.611121173499427, + 16.611121173499427, + 16.611121173499427, + 16.611121173499427 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 27435.43310546875, + 18355.21111111111, + 27448.11111111111, + 27448.11111111111, + 27448.11111111111, + 27448.11111111111 + ], + "DrainDuration": 24.131211112998412, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 27435.433132731853, + 18355.211133695287, + 27448.11113858482, + 27448.11113858482, + 27448.11113858482, + 27448.11113858482 + ] + }, + "packet": { + "Framerate": [ + 60, + 55, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 160351.515625, + 123145.32421875, + 160351.515625, + 160351.515625, + 160351.515625, + 160351.515625 + ], + "DrainDuration": 12.344112110998103, + "Sent": 118600, + "Recieve": 118600, + "NormalizedBandwidth": [ + 160351.515625, + 142345.32421875, + 160351.515625, + 160351.515625, + 160351.515625, + 160351.515625 + ] + }, + "roblox": { + "Framerate": [ + 60, + 55, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 160351.515625, + 123145.32421875, + 160351.515625, + 160351.515625, + 160351.515625, + 160351.515625 + ], + "DrainDuration": 12.344112110998103, + "Sent": 118600, + "Recieve": 118600, + "NormalizedBandwidth": [ + 160351.515625, + 142345.32421875, + 160351.515625, + 160351.515625, + 160351.515625, + 160351.515625 + ] + }, + "satset": { + "Framerate": [ + 60, + 57, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 948.9176330566406, + 948.9176330566406, + 948.9176330566406, + 948.9176330566406, + 948.9176330566406, + 948.9176330566406 + ], + "DrainDuration": 10.315111124999312, + "Sent": 117200, + "Recieve": 117200, + "NormalizedBandwidth": [ + 948.9176330566406, + 998.9176330566406, + 948.9176330566406, + 948.9176330566406, + 948.9176330566406, + 948.9176330566406 + ] + }, + "bytenet": { + "Framerate": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "Bandwidth": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "DrainDuration": 3.5402112290030347, + "Sent": 1400, + "Recieve": 1400, + "NormalizedBandwidth": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "netray": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 96.71212768554688, + 52.32768249511719, + 96.93282318115234, + 96.9463119506836, + 96.9463119506836, + 96.9463119506836 + ], + "DrainDuration": 10.298212111000466, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 96.8122393856112, + 52.32768346764585, + 96.93282531238947, + 96.94631408192074, + 96.94631408192074, + 96.94631408192074 + ] + }, + "zap": { + "Framerate": [ + 60, + 52, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 100.41212768554688, + 82.32768249511719, + 100.43282318115234, + 100.4463119506836, + 100.4463119506836, + 100.4463119506836 + ], + "DrainDuration": 10.158212111000466, + "Sent": 88600, + "Recieve": 88600, + "NormalizedBandwidth": [ + 100.4122393856112, + 94.32768346764585, + 100.43282531238947, + 100.44631408192074, + 100.44631408192074, + 100.44631408192074 + ] + }, + "blink": { + "Framerate": [ + 60, + 53, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 100.21212768554688, + 82.32768249511719, + 100.23282318115234, + 100.2463119506836, + 100.2463119506836, + 100.2463119506836 + ], + "DrainDuration": 11.798212111000466, + "Sent": 98800, + "Recieve": 98800, + "NormalizedBandwidth": [ + 100.2122393856112, + 113.32768346764585, + 100.23282531238947, + 100.24631408192074, + 100.24631408192074, + 100.24631408192074 + ] + } + }, + "SingleValue": { + "warp": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.261234044265747, + 1.898376226425171, + 2.274221181869507, + 2.2882111072540283, + 2.2882111072540283, + 2.2882111072540283 + ], + "DrainDuration": 3.764112124001258, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.2612340823055977, + 1.8983762583597402, + 2.274221220131495, + 2.2882111457474474, + 2.2882111457474474, + 2.2882111457474474 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 145.261234044265747, + 112.898376226425171, + 145.274221181869507, + 145.2882111072540283, + 145.2882111072540283, + 145.2882111072540283 + ], + "DrainDuration": 11.514112124001258, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 145.2612340823055977, + 112.8983762583597402, + 145.274221220131495, + 145.2882111457474474, + 145.2882111457474474, + 145.2882111457474474 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 232.71212768554688, + 185.32768249511719, + 232.73282318115234, + 232.7463119506836, + 232.7463119506836, + 232.7463119506836 + ], + "DrainDuration": 10.798212111000466, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 232.7122393856112, + 185.32768346764585, + 232.73282531238947, + 232.74631408192074, + 232.74631408192074, + 232.74631408192074 + ] + }, + "roblox": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 232.71212768554688, + 185.32768249511719, + 232.73282318115234, + 232.7463119506836, + 232.7463119506836, + 232.7463119506836 + ], + "DrainDuration": 10.798212111000466, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 232.7122393856112, + 185.32768346764585, + 232.73282531238947, + 232.74631408192074, + 232.74631408192074, + 232.74631408192074 + ] + }, + "satset": { + "Framerate": [ + 61, + 55, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.41763305664062, + 2.21763305664062, + 2.41763305664062, + 2.41763305664062, + 2.41763305664062, + 2.41763305664062 + ], + "DrainDuration": 4.035111124999312, + "Sent": 103200, + "Recieve": 103200, + "NormalizedBandwidth": [ + 2.41763305664062, + 2.41763305664062, + 2.41763305664062, + 2.41763305664062, + 2.41763305664062, + 2.41763305664062 + ] + }, + "bytenet": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.21212768554688, + 1.32768249511719, + 2.23282318115234, + 2.2463119506836, + 2.2463119506836, + 2.2463119506836 + ], + "DrainDuration": 3.688212111000466, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.2122393856112, + 1.32768346764585, + 2.23282531238947, + 2.24631408192074, + 2.24631408192074, + 2.24631408192074 + ] + }, + "netray": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.36811218261719, + 1.13937759399414, + 2.4276138305664, + 2.47982940673828, + 2.47982940673828, + 2.47982940673828 + ], + "DrainDuration": 3.738211512999013, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.3142385458312, + 1.13937861993202, + 2.4276159628868, + 2.47983152636222, + 2.47983152636222, + 2.47983152636222 + ] + }, + "zap": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.28212280273438, + 1.27622985839844, + 2.31192321777344, + 2.31192321777344, + 2.31192321777344, + 2.31192321777344 + ], + "DrainDuration": 3.738212112997135, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.28212403213007, + 1.27623078817734, + 2.3119244473851, + 2.3119244473851, + 2.3119244473851, + 2.3119244473851 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.35561218261719, + 1.27622985839844, + 2.38111724853516, + 2.41192321777344, + 2.41192321777344, + 2.41192321777344 + ], + "DrainDuration": 3.718212112996553, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.35561340989091, + 1.27623078817734, + 2.38111847647248, + 2.4119244473851, + 2.4119244473851, + 2.4119244473851 + ] + } + } +} \ No newline at end of file diff --git a/benchmark/default.project.json b/benchmark/default.project.json index f78ae65..8c437f8 100644 --- a/benchmark/default.project.json +++ b/benchmark/default.project.json @@ -19,6 +19,9 @@ }, "ReplicatedStorage": { "$className": "ReplicatedStorage", + "DevPackages": { + "$path": "../DevPackages" + }, "Shared": { "$path": "src/shared", "GetRecieved": { @@ -28,17 +31,20 @@ "$className": "RemoteEvent" } }, - "DevPackages": { - "$path": "../DevPackages", + "references": { + "$className": "Folder", "Satset": { "$path": "../src" + }, + "Warp": { + "$path": "../references/Warp/src" + }, + "Blink": { + "$path": "../references/blink/src" + }, + "SuphiPacket": { + "$path": "../references/5uphi-Packet/Packet" } - }, - "SuphiPacket": { - "$path": "../references/5uphi-Packet/Packet" - }, - "Red": { - "$path": "../references/Red/lib" } } } diff --git a/benchmark/latency-mode-result.json b/benchmark/latency-mode-result.json new file mode 100644 index 0000000..eb2371a --- /dev/null +++ b/benchmark/latency-mode-result.json @@ -0,0 +1,1580 @@ +{ + "Vectors": { + "warp": { + "Framerate": [ + 60, + 57, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.764441728591919, + 1.4328962564468384, + 2.820237398147583, + 2.820838451385498, + 2.820838451385498, + 2.820838451385498 + ], + "DrainDuration": 3.782715762994485, + "Sent": 96600, + "Recieve": 96600, + "NormalizedBandwidth": [ + 2.7898992562986448, + 1.5145965598851226, + 2.8208385985036878, + 2.8773008914065905, + 2.8773008914065905, + 2.8773008914065905 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 15659.8876953125, + 12391.0556640625, + 15722.8193359375, + 15762.7939453125, + 15762.7939453125, + 15762.7939453125 + ], + "DrainDuration": 22.01582332000544, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 15659.888512039288, + 12391.056310306412, + 15711.897303814258, + 15722.820155946429, + 15722.820155946429, + 15722.820155946429 + ] + }, + "netray_compile": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 72.5334701538086, + 54.906944274902347, + 72.76528930664063, + 72.81885528564453, + 72.81885528564453, + 72.81885528564453 + ], + "DrainDuration": 8.782914968003752, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 72.53347393672385, + 54.90694713852264, + 72.79964317877873, + 73.06848178901908, + 73.06848178901908, + 73.06848178901908 + ] + }, + "bytenet": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 72.9428939819336, + 55.87860107421875, + 73.03919219970703, + 73.2262191772461, + 73.2262191772461, + 73.2262191772461 + ], + "DrainDuration": 9.066092023000238, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.92611555853438, + 55.8786039885149, + 73.0334510746161, + 73.34352595835705, + 73.34352595835705, + 73.34352595835705 + ] + }, + "roblox": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 148732.1875, + 68882.234375, + 176976.265625, + 178919.484375, + 202897.765625, + 202897.765625 + ], + "DrainDuration": 9.332765226004995, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 148732.19525698806, + 68882.23796748847, + 176976.27485503153, + 178919.49370637828, + 202897.7762069431, + 202897.7762069431 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 38.990421295166019, + 29.033836364746095, + 39.19265365600586, + 39.27354431152344, + 39.325870513916019, + 39.325870513916019 + ], + "DrainDuration": 7.732752484997036, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 38.99042332867495, + 29.03383787897866, + 39.19265570006203, + 39.27354635979839, + 39.32587256491999, + 39.32587256491999 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 72.66801452636719, + 55.08686828613281, + 73.01099395751953, + 73.1099624633789, + 73.3668441772461, + 73.3668441772461 + ], + "DrainDuration": 8.866096705009113, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.66801831629948, + 55.08687115913688, + 73.0109977653396, + 73.10996627636058, + 73.3668480036252, + 73.3668480036252 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 72.8770751953125, + 56.13698959350586, + 72.98052978515625, + 73.06175994873047, + 73.06175994873047, + 73.06175994873047 + ], + "DrainDuration": 8.799909338995349, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.6814330773089, + 55.90692290550966, + 72.9588966273684, + 73.36618774083948, + 73.36618774083948, + 73.36618774083948 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 72.64482879638672, + 55.25006103515625, + 72.87446594238281, + 72.88887023925781, + 72.99724578857422, + 72.99724578857422 + ], + "DrainDuration": 8.849431556009222, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 72.64483258510978, + 55.25006391667148, + 72.87446974308239, + 72.88887404070863, + 72.99724959567726, + 72.99724959567726 + ] + } + }, + "Booleans": { + "warp": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.447920799255371, + 2.340574026107788, + 2.484408140182495, + 2.508678913116455, + 4.404301643371582, + 4.404301643371582 + ], + "DrainDuration": 3.8168795419915115, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.4479209269243897, + 2.340574148178236, + 2.4844082697544769, + 2.508679043954256, + 4.404301873073813, + 4.404301873073813 + ] + }, + "bridgenet2": { + "Framerate": [ + 55, + 49, + 57, + 57, + 57, + 57 + ], + "Bandwidth": [ + 21750.3515625, + 17700.634765625, + 22568.650390625, + 22712.251953125, + 22712.251953125, + 22712.251953125 + ], + "DrainDuration": 26.04329594099545, + "Sent": 107800, + "Recieve": 107800, + "NormalizedBandwidth": [ + 24007.249926893564, + 18787.516740806925, + 25090.393220684535, + 26944.340773374064, + 26944.340773374064, + 26944.340773374064 + ] + }, + "netray_compile": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 10.281547546386719, + 7.180410861968994, + 10.323692321777344, + 10.326251029968262, + 10.326251029968262, + 10.326251029968262 + ], + "DrainDuration": 5.266024893993745, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 10.273659288254465, + 7.180411236456603, + 10.32625156852422, + 10.366708242310012, + 10.366708242310012, + 10.366708242310012 + ] + }, + "bytenet": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 19.037439346313478, + 14.59847354888916, + 19.096935272216798, + 19.101850509643556, + 19.101850509643556, + 19.101850509643556 + ], + "DrainDuration": 6.328172858004109, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 18.945279040047514, + 14.598474310258887, + 19.06402500133346, + 19.0969363037704, + 19.0969363037704, + 19.0969363037704 + ] + }, + "roblox": { + "Framerate": [ + 50, + 49, + 52, + 52, + 52, + 52 + ], + "Bandwidth": [ + 53546.9609375, + 48074.2578125, + 54663.33984375, + 57044.5859375, + 57044.5859375, + 57044.5859375 + ], + "DrainDuration": 11.325150161006605, + "Sent": 99400, + "Recieve": 99400, + "NormalizedBandwidth": [ + 63524.20128242976, + 58636.994590338, + 67751.19841846568, + 70141.56062914123, + 70141.56062914123, + 70141.56062914123 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 10.376862525939942, + 7.734817028045654, + 10.407729148864746, + 10.408055305480957, + 10.421064376831055, + 10.421064376831055 + ], + "DrainDuration": 5.28272814299271, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 10.376863067135496, + 7.734817431447798, + 10.40772969167012, + 10.408055848303342, + 10.421064920331915, + 10.421064920331915 + ] + }, + "packet": { + "Framerate": [ + 60, + 54, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 28.786466598510743, + 21.48399543762207, + 29.15029525756836, + 29.432878494262697, + 29.432878494262697, + 29.432878494262697 + ], + "DrainDuration": 7.313814018998528, + "Sent": 109000, + "Recieve": 109000, + "NormalizedBandwidth": [ + 28.895067833989999, + 21.663030082844409, + 29.514675772655609, + 30.427802018942939, + 30.427802018942939, + 30.427802018942939 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 29.26051139831543, + 22.337413787841798, + 29.318355560302736, + 29.335769653320314, + 29.335769653320314, + 29.335769653320314 + ], + "DrainDuration": 7.297905995990732, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 29.19819990161679, + 22.2458681804663, + 29.335771183299927, + 29.422323500054497, + 29.422323500054497, + 29.422323500054497 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 29.381929397583009, + 21.87703514099121, + 29.435056686401368, + 29.541475296020509, + 29.541475296020509, + 29.541475296020509 + ], + "DrainDuration": 7.267011803996866, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 29.38193092997004, + 21.877036281967507, + 29.423049553940716, + 29.5577042837756, + 29.5577042837756, + 29.5577042837756 + ] + } + }, + "Mixed": { + "warp": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.2454726696014406, + 1.8616403341293336, + 2.325646162033081, + 2.3369789123535158, + 2.3369789123535158, + 2.3369789123535158 + ], + "DrainDuration": 3.8144485960074236, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 2.245472786711966, + 1.861640431221443, + 2.3256462833249804, + 2.346716445790875, + 2.346716445790875, + 2.346716445790875 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 55, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 1680.568603515625, + 1323.0751953125, + 1682.277587890625, + 1683.3160400390626, + 1683.3160400390626, + 1683.3160400390626 + ], + "DrainDuration": 17.761656613001834, + "Sent": 89800, + "Recieve": 89800, + "NormalizedBandwidth": [ + 1681.1296042792993, + 1323.0752643162487, + 1683.3161278308353, + 1820.4390354684579, + 1820.4390354684579, + 1820.4390354684579 + ] + }, + "netray_compile": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 4.93384313583374, + 3.7336151599884035, + 4.954408645629883, + 4.975438117980957, + 4.975438117980957, + 4.975438117980957 + ], + "DrainDuration": 4.250556561994017, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.933843393153712, + 3.733615354711608, + 4.954408904022429, + 4.975438377470276, + 4.975438377470276, + 4.975438377470276 + ] + }, + "bytenet": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 4.901780605316162, + 3.8276753425598146, + 4.975456237792969, + 4.991796970367432, + 5.037708282470703, + 5.037708282470703 + ], + "DrainDuration": 4.216418586001964, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.901780860963942, + 3.82767554218864, + 4.975456497283233, + 4.991797230709931, + 5.037708545207664, + 5.037708545207664 + ] + }, + "roblox": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 3902.99658203125, + 3682.523193359375, + 4031.613525390625, + 4065.48876953125, + 4065.48876953125, + 4065.48876953125 + ], + "DrainDuration": 9.315510572007043, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 3902.9967855883844, + 3682.523385417926, + 4031.6137356556557, + 4065.488981563012, + 4065.488981563012, + 4065.488981563012 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 4.293588161468506, + 3.270570755004883, + 4.32204008102417, + 4.324096202850342, + 4.366925239562988, + 4.366925239562988 + ], + "DrainDuration": 4.217419569002232, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.293588385396578, + 3.27057092557844, + 4.322040306436126, + 4.324096428369533, + 4.3669254673158879, + 4.3669254673158879 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 4.803657531738281, + 3.796332597732544, + 4.824338912963867, + 4.825944900512695, + 4.825944900512695, + 4.825944900512695 + ], + "DrainDuration": 4.199859785003355, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.8045672849604099, + 3.8121508472744409, + 4.816999171817148, + 4.846053254758935, + 4.846053254758935, + 4.846053254758935 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 4.998149871826172, + 3.912663221359253, + 5.013729095458984, + 5.015888214111328, + 5.015888214111328, + 5.015888214111328 + ], + "DrainDuration": 4.198811416004901, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.998150132500001, + 3.9126634254205419, + 5.015888475710284, + 5.021390465179062, + 5.021390465179062, + 5.021390465179062 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 4.982448577880859, + 3.807426691055298, + 5.024774551391602, + 5.049847602844238, + 5.059690952301025, + 5.059690952301025 + ], + "DrainDuration": 4.283437647012761, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 4.982448837735802, + 3.807426889628074, + 5.024774813454016, + 5.049847866214314, + 5.059691216184472, + 5.059691216184472 + ] + } + }, + "Entities": { + "warp": { + "Framerate": [ + 50, + 40, + 52, + 52, + 52, + 52 + ], + "Bandwidth": [ + 8.710792541503907, + 4.467971324920654, + 10.118857383728028, + 10.149181365966797, + 10.149181365966797, + 10.149181365966797 + ], + "DrainDuration": 5.2914539460034579, + "Sent": 87600, + "Recieve": 87600, + "NormalizedBandwidth": [ + 10.895018055909928, + 6.757806987631254, + 11.815462019070863, + 11.870198790826985, + 11.870198790826985, + 11.870198790826985 + ] + }, + "bridgenet2": { + "Framerate": [ + 17, + 16, + 17, + 18, + 18, + 18 + ], + "Bandwidth": [ + 23520.376953125, + 12758.107421875, + 27017.2109375, + 27022.33203125, + 27022.33203125, + 27022.33203125 + ], + "DrainDuration": 26.51570917101344, + "Sent": 27000, + "Recieve": 27000, + "NormalizedBandwidth": [ + 87240.7040796132, + 42527.02640305121, + 97738.73922716463, + 101333.75009127376, + 101333.75009127376, + 101333.75009127376 + ] + }, + "netray_compile": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 38.93456268310547, + 28.886159896850587, + 38.98399353027344, + 38.98530578613281, + 38.98530578613281, + 38.98530578613281 + ], + "DrainDuration": 7.833227431008709, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 38.891821028610568, + 29.00652039577748, + 38.98399556344714, + 38.98530781937495, + 38.98530781937495, + 38.98530781937495 + ] + }, + "bytenet": { + "Framerate": [ + 60, + 57, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 38.159420013427737, + 29.424619674682618, + 38.9777946472168, + 39.206695556640628, + 39.206695556640628, + 39.206695556640628 + ], + "DrainDuration": 7.782035968004493, + "Sent": 118800, + "Recieve": 118800, + "NormalizedBandwidth": [ + 38.58125714508226, + 29.547223811370097, + 39.20669763794322, + 39.610457282828189, + 39.610457282828189, + 39.610457282828189 + ] + }, + "roblox": { + "Framerate": [ + 18, + 18, + 19, + 19, + 19, + 19 + ], + "Bandwidth": [ + 208655.3125, + 184098.6875, + 211734.09375, + 213581.03125, + 213581.03125, + 213581.03125 + ], + "DrainDuration": 14.778737654996803, + "Sent": 34800, + "Recieve": 34800, + "NormalizedBandwidth": [ + 707109.71090404, + 618776.1803695357, + 720484.1075725526, + 721747.0565034436, + 721747.0565034436, + 721747.0565034436 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 38.97240447998047, + 28.81517219543457, + 39.12338638305664, + 39.136940002441409, + 39.13933181762695, + 39.13933181762695 + ], + "DrainDuration": 7.8001610320061449, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 38.97240651254975, + 28.81517369826291, + 39.123388423500248, + 39.13694204359189, + 39.13933385890218, + 39.13933385890218 + ] + }, + "packet": { + "Framerate": [ + 51, + 48, + 52, + 52, + 52, + 52 + ], + "Bandwidth": [ + 30.554224014282228, + 22.863338470458986, + 32.05663299560547, + 33.080440521240237, + 33.080440521240237, + 33.080440521240237 + ], + "DrainDuration": 7.531132645992329, + "Sent": 100200, + "Recieve": 100200, + "NormalizedBandwidth": [ + 36.77355554879297, + 27.122196900753069, + 38.037682241174199, + 39.442065648820719, + 39.442065648820719, + 39.442065648820719 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 39.00115203857422, + 29.122724533081056, + 39.026817321777347, + 39.04824447631836, + 39.04824447631836, + 39.04824447631836 + ], + "DrainDuration": 7.514331072990899, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 38.97832692301478, + 29.1227260519495, + 39.04824651284301, + 39.16365886311749, + 39.16365886311749, + 39.16365886311749 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 38.95712661743164, + 29.078960418701173, + 39.0257682800293, + 39.05777359008789, + 39.10287857055664, + 39.10287857055664 + ], + "DrainDuration": 7.833464256007574, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 38.95712864920412, + 29.078961989450926, + 39.02577031538172, + 39.05777562710952, + 39.10288060993068, + 39.10288060993068 + ] + } + }, + "Strings": { + "warp": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 3.385514497756958, + 3.21207332611084, + 5.507931709289551, + 6.249972820281982, + 7.934593677520752, + 7.934593677520752 + ], + "DrainDuration": 3.7656679959909526, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 3.3855146743252986, + 3.2120734936335184, + 5.507931996550575, + 6.249973146243466, + 7.93459409134206, + 7.93459409134206 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 55, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 27012.642578125, + 19714.439453125, + 27200.52734375, + 27309.103515625, + 27309.103515625, + 27309.103515625 + ], + "DrainDuration": 24.265411052998389, + "Sent": 106200, + "Recieve": 106200, + "NormalizedBandwidth": [ + 27071.311958753868, + 21410.245237316405, + 27309.104939905737, + 27523.818904290434, + 27523.818904290434, + 27523.818904290434 + ] + }, + "netray_compile": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 96.54653930664063, + 72.74851989746094, + 96.86563873291016, + 97.10099792480469, + 97.40682983398438, + 97.40682983398438 + ], + "DrainDuration": 10.832776184994146, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 96.54654434193503, + 72.74852369159192, + 96.8656437848469, + 97.10100298901637, + 97.40683491414643, + 97.40683491414643 + ] + }, + "bytenet": { + "Framerate": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "Bandwidth": [ + 0, + 0, + 0, + 0, + 0, + 0 + ], + "DrainDuration": 3.010798364994116, + "Sent": 1600, + "Recieve": 1600, + "NormalizedBandwidth": [ + 0, + 0, + 0, + 0, + 0, + 0 + ] + }, + "roblox": { + "Framerate": [ + 56, + 53, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 53459.51171875, + 39032.52734375, + 82090.7578125, + 116585.578125, + 116585.578125, + 116585.578125 + ], + "DrainDuration": 10.945210890000454, + "Sent": 112200, + "Recieve": 112200, + "NormalizedBandwidth": [ + 58319.470805656594, + 41820.56773755125, + 85275.31763855576, + 127184.27450777802, + 127184.27450777802, + 127184.27450777802 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 96.43982696533203, + 72.35977172851563, + 96.58458709716797, + 96.72514343261719, + 96.90149688720703, + 96.90149688720703 + ], + "DrainDuration": 9.882589490996907, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 96.43983199506096, + 72.3597755023718, + 96.58459213444672, + 96.72514847722653, + 96.90150194101392, + 96.90150194101392 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 96.45869445800781, + 72.38963317871094, + 96.79341888427735, + 97.13880920410156, + 97.13880920410156, + 97.13880920410156 + ], + "DrainDuration": 9.866840552000213, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 96.42791488018162, + 72.69126040772432, + 96.74070442022716, + 96.89167679308182, + 96.89167679308182, + 96.89167679308182 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 99.93290710449219, + 68.13643646240235, + 100.39676666259766, + 100.58865356445313, + 100.66536712646485, + 100.66536712646485 + ], + "DrainDuration": 9.863170425000135, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 99.93291231639944, + 68.13644001599443, + 100.39677189869707, + 100.58865881056022, + 100.66537237657286, + 100.66537237657286 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 100.30909729003906, + 72.05551147460938, + 100.70087432861328, + 100.71550750732422, + 100.72301483154297, + 100.72301483154297 + ], + "DrainDuration": 9.86579776200233, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 100.30910252156616, + 72.05551523259715, + 100.70087958057315, + 100.71551276004726, + 100.72302008465755, + 100.72302008465755 + ] + } + }, + "SingleValue": { + "warp": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.2502639293670656, + 1.965975284576416, + 2.418292760848999, + 2.507328748703003, + 2.507328748703003, + 2.507328748703003 + ], + "DrainDuration": 4.010584692005068, + "Sent": 119800, + "Recieve": 119800, + "NormalizedBandwidth": [ + 2.250264046727475, + 1.9659753871100172, + 2.418292886972795, + 2.5073288794703876, + 2.5073288794703876, + 2.5073288794703876 + ] + }, + "bridgenet2": { + "Framerate": [ + 60, + 55, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 145.30645751953126, + 86.84367370605469, + 145.8762969970703, + 145.9589385986328, + 145.9589385986328, + 145.9589385986328 + ], + "DrainDuration": 10.68330016300024, + "Sent": 98800, + "Recieve": 98800, + "NormalizedBandwidth": [ + 145.30646509785357, + 95.13330211097316, + 145.87630460511208, + 145.95894621098467, + 145.95894621098467, + 145.95894621098467 + ] + }, + "netray_compile": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.3817059993743898, + 1.874155879020691, + 2.411635160446167, + 2.4140243530273439, + 2.4336061477661135, + 2.4336061477661135 + ], + "DrainDuration": 3.7315073780046079, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.381706123590037, + 1.874155976765537, + 2.411635286222742, + 2.414024478928525, + 2.4336062746885647, + 2.4336062746885647 + ] + }, + "bytenet": { + "Framerate": [ + 61, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.3101882934570314, + 1.8508734703063965, + 2.3256075382232668, + 2.327732563018799, + 2.327732563018799, + 2.327732563018799 + ], + "DrainDuration": 3.733247598996968, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.309021116518579, + 1.8508735668369703, + 2.3256076595131516, + 2.3277326844195125, + 2.3277326844195125, + 2.3277326844195125 + ] + }, + "roblox": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 232.29190063476563, + 228.8047637939453, + 235.5247802734375, + 235.59292602539063, + 235.59292602539063, + 235.59292602539063 + ], + "DrainDuration": 8.382915554990177, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 232.29191274973233, + 228.80477572704366, + 235.524792557012, + 235.5929383125192, + 235.5929383125192, + 235.5929383125192 + ] + }, + "satset": { + "Framerate": [ + 60, + 60, + 60, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.3600337505340578, + 1.8788734674453736, + 2.4089317321777345, + 2.423952341079712, + 2.423952341079712, + 2.423952341079712 + ], + "DrainDuration": 3.748035296011949, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.3600338736194095, + 1.878873565436261, + 2.4089318578133147, + 2.423952467498678, + 2.423952467498678, + 2.423952467498678 + ] + }, + "packet": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.2492129802703859, + 1.8232378959655762, + 2.2864480018615724, + 2.3006608486175539, + 2.3006608486175539, + 2.3006608486175539 + ], + "DrainDuration": 3.7495955190097449, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.249213097575984, + 1.8157657050413503, + 2.286448121109128, + 2.3006609686063674, + 2.3006609686063674, + 2.3006609686063674 + ] + }, + "blink": { + "Framerate": [ + 60, + 60, + 61, + 61, + 61, + 61 + ], + "Bandwidth": [ + 2.4459776878356935, + 1.9182907342910767, + 2.4515068531036379, + 2.479783296585083, + 2.479783296585083, + 2.479783296585083 + ], + "DrainDuration": 3.817761062993668, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.437858547605591, + 1.9182908343377348, + 2.4580825298049859, + 2.47978342591586, + 2.47978342591586, + 2.47978342591586 + ] + }, + "zap": { + "Framerate": [ + 60, + 60, + 60, + 60, + 60, + 60 + ], + "Bandwidth": [ + 2.3627519607543947, + 1.885648488998413, + 2.3907129764556886, + 2.397568941116333, + 2.4187347888946535, + 2.4187347888946535 + ], + "DrainDuration": 3.6999580029951177, + "Sent": 120000, + "Recieve": 120000, + "NormalizedBandwidth": [ + 2.362752083981512, + 1.8856485873426455, + 2.3907131011410867, + 2.3975690661592976, + 2.4187349150415029, + 2.4187349150415029 + ] + } + } +} \ No newline at end of file diff --git a/benchmark/result.json b/benchmark/result.json deleted file mode 100644 index 0a22854..0000000 --- a/benchmark/result.json +++ /dev/null @@ -1,1080 +0,0 @@ -{ - "Vectors": { - "warp": { - "Framerate": [ - 60, - 60, - 59, - 58, - 58, - 58 - ], - "Sent": 576000, - "Bandwidth": [ - 0.4910399615764618, - 0.42500826716423037, - 0.6139029264450073, - 0.7648433076924291, - 0.7648433076924291, - 1.0158289506517609 - ], - "Recieve": 0 - }, - "bridgenet2": { - "Framerate": [ - 57, - 60, - 55, - 55, - 55, - 54 - ], - "Sent": 416000, - "Bandwidth": [ - 121207.60227272727, - 24325.078125, - 150462.77960526316, - 155934.1534090909, - 155934.1534090909, - 179422.98611111113 - ], - "Recieve": 416000 - }, - "packet": { - "Framerate": [ - 60, - 60, - 59, - 53, - 53, - 52 - ], - "Sent": 584000, - "Bandwidth": [ - 78.5467300415039, - 60.701386378361629, - 78.8651123046875, - 80.10246276855469, - 80.10246276855469, - 86.20132158387382 - ], - "Recieve": 584000 - }, - "zap": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 57 - ], - "Sent": 593000, - "Bandwidth": [ - 78.3908402292352, - 61.0587158203125, - 78.70868682861328, - 78.8249282836914, - 78.8249282836914, - 78.98275756835938 - ], - "Recieve": 593000 - }, - "roblox": { - "Framerate": [ - 48, - 50, - 45, - 45, - 45, - 43 - ], - "Sent": 466000, - "Bandwidth": [ - 104422.46942934783, - 60378.042187499996, - 215959.0208333333, - 221802.7785326087, - 221802.7785326087, - 248279 - ], - "Recieve": 466000 - }, - "blink": { - "Framerate": [ - 60, - 60, - 59, - 58, - 58, - 58 - ], - "Sent": 594000, - "Bandwidth": [ - 78.6353759765625, - 61.64194869995117, - 79.86887701626482, - 80.52207420612204, - 80.52207420612204, - 80.71131134033203 - ], - "Recieve": 594000 - }, - "satset": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 4155.2783203125, - 3285.759033203125, - 4304.0185546875, - 4328.4267578125, - 4328.4267578125, - 4399.10888671875 - ], - "Recieve": 600000 - }, - "bytenet": { - "Framerate": [ - 60, - 60, - 59, - 59, - 59, - 59 - ], - "Sent": 597000, - "Bandwidth": [ - 78.74496459960938, - 58.61016354318392, - 78.85393524169922, - 78.90973663330078, - 78.90973663330078, - 80.81868834414725 - ], - "Recieve": 597000 - }, - "red": { - "Framerate": [ - 42, - 45, - 40, - 33, - 33, - 31 - ], - "Sent": 325000, - "Bandwidth": [ - 95701.25363372093, - 28060.558215725807, - 146649.8333333333, - 149982.7840909091, - 149982.7840909091, - 200044.77896341464 - ], - "Recieve": 325000 - } - }, - "Booleans": { - "warp": { - "Framerate": [ - 60, - 60, - 60, - 57, - 57, - 57 - ], - "Sent": 595000, - "Bandwidth": [ - 0.5671465396881104, - 0.4474828541278839, - 0.7790822386741638, - 0.8979364037513733, - 0.8979364037513733, - 1.0272836685180665 - ], - "Recieve": 0 - }, - "bridgenet2": { - "Framerate": [ - 15, - 16, - 15, - 15, - 15, - 15 - ], - "Sent": 96000, - "Bandwidth": [ - 72144.15625, - 39868.10302734375, - 75918.28125, - 76431.4375, - 76431.4375, - 77469.53125 - ], - "Recieve": 96000 - }, - "packet": { - "Framerate": [ - 15, - 16, - 15, - 15, - 15, - 15 - ], - "Sent": 133000, - "Bandwidth": [ - 22.511478424072267, - 10.744045078754425, - 23.79411506652832, - 24.420900344848634, - 24.420900344848634, - 25.097332000732423 - ], - "Recieve": 133000 - }, - "zap": { - "Framerate": [ - 34, - 36, - 31, - 27, - 27, - 25 - ], - "Sent": 316000, - "Bandwidth": [ - 25.419039726257325, - 14.714796883719308, - 26.35972669047694, - 26.474565124511718, - 26.474565124511718, - 27.664486278187146 - ], - "Recieve": 316000 - }, - "roblox": { - "Framerate": [ - 16, - 18, - 16, - 16, - 16, - 15 - ], - "Sent": 110000, - "Bandwidth": [ - 162450.17578125, - 88935.00732421875, - 163790.6689453125, - 165787.0963541667, - 165787.0963541667, - 214727.4169921875 - ], - "Recieve": 110000 - }, - "blink": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 25.626901626586915, - 19.509084701538087, - 25.676172256469728, - 26.073556900024415, - 26.073556900024415, - 26.961820602416993 - ], - "Recieve": 600000 - }, - "satset": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 58 - ], - "Sent": 598000, - "Bandwidth": [ - 474.9605407714844, - 451.23382568359377, - 490.5143127441406, - 503.7538936220367, - 503.7538936220367, - 504.2212219238281 - ], - "Recieve": 598000 - }, - "bytenet": { - "Framerate": [ - 23, - 24, - 22, - 22, - 22, - 22 - ], - "Sent": 228000, - "Bandwidth": [ - 22.18168009882388, - 10.894781351089478, - 22.84310879914657, - 23.097022663463244, - 23.097022663463244, - 23.232083754106 - ], - "Recieve": 228000 - }, - "red": { - "Framerate": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Sent": 0, - "Bandwidth": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Recieve": 0 - } - }, - "Mixed": { - "warp": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 601000, - "Bandwidth": [ - 0.39012908935546877, - 0.38723331689834597, - 0.3915526568889618, - 0.3920861482620239, - 0.3920861482620239, - 0.901597261428833 - ], - "Recieve": 0 - }, - "bridgenet2": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 8395.3818359375, - 6581.86962890625, - 8400.47265625, - 8402.6220703125, - 8402.6220703125, - 8409.953125 - ], - "Recieve": 600000 - }, - "packet": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 5.061722755432129, - 4.780847072601318, - 5.125380992889404, - 6.40565299987793, - 6.40565299987793, - 6.646525859832764 - ], - "Recieve": 600000 - }, - "zap": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 5.281473159790039, - 4.087789058685303, - 5.331814289093018, - 5.383254528045654, - 5.383254528045654, - 5.659294605255127 - ], - "Recieve": 600000 - }, - "roblox": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 94608.171875, - 30854.013671875, - 99693.1328125, - 101185.6640625, - 101185.6640625, - 102145.6015625 - ], - "Recieve": 600000 - }, - "blink": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 57 - ], - "Sent": 594000, - "Bandwidth": [ - 5.067240238189697, - 3.96597957611084, - 5.111371994018555, - 6.328373908996582, - 6.328373908996582, - 7.207447528839111 - ], - "Recieve": 594000 - }, - "satset": { - "Framerate": [ - 60, - 61, - 60, - 59, - 59, - 57 - ], - "Sent": 598000, - "Bandwidth": [ - 5.368989944458008, - 4.115169048309326, - 5.6835074173776729, - 6.408648248446189, - 6.408648248446189, - 6.904189586639404 - ], - "Recieve": 598000 - }, - "bytenet": { - "Framerate": [ - 16, - 16, - 16, - 16, - 16, - 16 - ], - "Sent": 27000, - "Bandwidth": [ - 2.999925836920738, - 2.999925836920738, - 2.999925836920738, - 2.999925836920738, - 2.999925836920738, - 2.999925836920738 - ], - "Recieve": 27000 - }, - "red": { - "Framerate": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Sent": 0, - "Bandwidth": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Recieve": 0 - } - }, - "Entities": { - "warp": { - "Framerate": [ - 60, - 61, - 60, - 60, - 60, - 60 - ], - "Sent": 601000, - "Bandwidth": [ - 0.5410988330841065, - 0.45625799894332888, - 0.775825023651123, - 0.9876990914344788, - 0.9876990914344788, - 1.041891098022461 - ], - "Recieve": 0 - }, - "bridgenet2": { - "Framerate": [ - 16, - 16, - 16, - 16, - 16, - 16 - ], - "Sent": 29000, - "Bandwidth": [ - 81221.3671875, - 81221.3671875, - 81221.3671875, - 81221.3671875, - 81221.3671875, - 81221.3671875 - ], - "Recieve": 29000 - }, - "packet": { - "Framerate": [ - 15, - 17, - 15, - 15, - 15, - 15 - ], - "Sent": 114000, - "Bandwidth": [ - 28.132858276367189, - 15.909680759205538, - 32.56520462036133, - 33.96961975097656, - 33.96961975097656, - 35.36284637451172 - ], - "Recieve": 114000 - }, - "zap": { - "Framerate": [ - 16, - 20, - 15, - 15, - 15, - 15 - ], - "Sent": 143000, - "Bandwidth": [ - 33.44736735026042, - 18.629921436309816, - 41.16750240325928, - 42.0989990234375, - 42.0989990234375, - 42.278236389160159 - ], - "Recieve": 143000 - }, - "roblox": { - "Framerate": [ - 16, - 16, - 16, - 16, - 16, - 15 - ], - "Sent": 37000, - "Bandwidth": [ - 287698.9453125, - 287698.9453125, - 287698.9453125, - 287698.9453125, - 287698.9453125, - 332149.46875 - ], - "Recieve": 37000 - }, - "blink": { - "Framerate": [ - 58, - 60, - 19, - 18, - 18, - 18 - ], - "Sent": 371000, - "Bandwidth": [ - 41.65415954589844, - 32.41655731201172, - 44.037247707969267, - 44.72257932027181, - 44.72257932027181, - 51.93098386128744 - ], - "Recieve": 371000 - }, - "satset": { - "Framerate": [ - 60, - 61, - 60, - 59, - 59, - 58 - ], - "Sent": 598000, - "Bandwidth": [ - 633.7137451171875, - 624.3827444608094, - 635.7799072265625, - 638.7477241128178, - 638.7477241128178, - 650.9371001144936 - ], - "Recieve": 598000 - }, - "bytenet": { - "Framerate": [ - 27, - 27, - 26, - 26, - 26, - 25 - ], - "Sent": 262000, - "Bandwidth": [ - 41.60928514268663, - 22.95256084865994, - 42.56445077749399, - 42.56465765146108, - 42.56465765146108, - 43.523583984375 - ], - "Recieve": 262000 - }, - "red": { - "Framerate": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Sent": 0, - "Bandwidth": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Recieve": 0 - } - }, - "SingleValue": { - "warp": { - "Framerate": [ - 60, - 61, - 59, - 59, - 59, - 57 - ], - "Sent": 591000, - "Bandwidth": [ - 0.5093983472403834, - 0.4467504918575287, - 0.6836220369500629, - 0.8370541158269663, - 0.8370541158269663, - 1.0002789497375489 - ], - "Recieve": 0 - }, - "bridgenet2": { - "Framerate": [ - 60, - 61, - 60, - 60, - 60, - 54 - ], - "Sent": 594000, - "Bandwidth": [ - 714.6480102539063, - 565.2076416015625, - 721.400146484375, - 722.014892578125, - 722.014892578125, - 731.1685791015625 - ], - "Recieve": 594000 - }, - "packet": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 601000, - "Bandwidth": [ - 2.3807029724121095, - 1.7799842357635499, - 2.46771502494812, - 3.17256760597229, - 3.17256760597229, - 4.744359970092773 - ], - "Recieve": 601000 - }, - "zap": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 2.4843204021453859, - 1.9291434288024903, - 2.6442182064056398, - 5.1503071784973148, - 5.1503071784973148, - 6.009252548217773 - ], - "Recieve": 600000 - }, - "roblox": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 1670.5982666015626, - 1585.1107177734376, - 1755.166748046875, - 1778.90478515625, - 1778.90478515625, - 1849.3709716796876 - ], - "Recieve": 600000 - }, - "blink": { - "Framerate": [ - 60, - 61, - 60, - 60, - 60, - 60 - ], - "Sent": 601000, - "Bandwidth": [ - 2.4836740493774416, - 1.9655238214086313, - 2.544952392578125, - 2.5821638107299806, - 2.5821638107299806, - 2.7336161136627199 - ], - "Recieve": 601000 - }, - "satset": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 2.6063976287841799, - 2.102426290512085, - 2.650390863418579, - 2.7003707885742189, - 2.7003707885742189, - 5.651124954223633 - ], - "Recieve": 600000 - }, - "bytenet": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 60 - ], - "Sent": 600000, - "Bandwidth": [ - 2.3836896419525148, - 1.8439228534698487, - 2.5672380924224855, - 2.613997459411621, - 2.613997459411621, - 5.339874744415283 - ], - "Recieve": 600000 - }, - "red": { - "Framerate": [ - 60, - 61, - 59, - 59, - 59, - 59 - ], - "Sent": 598000, - "Bandwidth": [ - 541.297607421875, - 415.9361877441406, - 548.1582486427436, - 550.843071371822, - 550.843071371822, - 553.2721050715043 - ], - "Recieve": 598000 - } - }, - "Strings": { - "warp": { - "Framerate": [ - 60, - 60, - 60, - 60, - 60, - 59 - ], - "Sent": 599000, - "Bandwidth": [ - 0.4313433766365051, - 0.39885008335113528, - 0.6159429024841826, - 0.749035656452179, - 0.749035656452179, - 0.9440086483955383 - ], - "Recieve": 0 - }, - "bridgenet2": { - "Framerate": [ - 38, - 45, - 35, - 35, - 35, - 28 - ], - "Sent": 278000, - "Bandwidth": [ - 139751.34375, - 64406.17398648649, - 163530.375, - 210253.33928571427, - 210253.33928571427, - 262816.67410714286 - ], - "Recieve": 278000 - }, - "packet": { - "Framerate": [ - 52, - 53, - 50, - 50, - 50, - 50 - ], - "Sent": 516000, - "Bandwidth": [ - 105.92129340538611, - 77.17229591225679, - 106.97188110351563, - 107.64545288085937, - 107.64545288085937, - 107.73005935381043 - ], - "Recieve": 516000 - }, - "zap": { - "Framerate": [ - 60, - 61, - 60, - 60, - 60, - 59 - ], - "Sent": 599000, - "Bandwidth": [ - 110.96520233154297, - 85.88724855516777, - 111.26313018798828, - 111.35922241210938, - 111.35922241210938, - 112.93146165750794 - ], - "Recieve": 599000 - }, - "roblox": { - "Framerate": [ - 30, - 35, - 27, - 25, - 25, - 24 - ], - "Sent": 244000, - "Bandwidth": [ - 111333.54241071428, - 102280.5625, - 142040.95486111113, - 145135.71562499999, - 145135.71562499999, - 164607.2265625 - ], - "Recieve": 244000 - }, - "blink": { - "Framerate": [ - 60, - 60, - 60, - 59, - 59, - 59 - ], - "Sent": 598000, - "Bandwidth": [ - 110.8216552734375, - 88.09321594238281, - 111.15917205810547, - 111.27769470214844, - 111.27769470214844, - 112.23464448573225 - ], - "Recieve": 598000 - }, - "satset": { - "Framerate": [ - 60, - 60, - 59, - 58, - 58, - 58 - ], - "Sent": 590000, - "Bandwidth": [ - 17259.578125, - 16575.590903072032, - 18426.02505387931, - 19389.11281779661, - 19389.11281779661, - 19832.440732758623 - ], - "Recieve": 590000 - }, - "bytenet": { - "Framerate": [], - "Sent": 221, - "Bandwidth": [], - "Recieve": 220 - }, - "red": { - "Framerate": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Sent": 0, - "Bandwidth": [ - 0, - 0, - 0, - 0, - 0, - 0 - ], - "Recieve": 0 - } - } -} \ No newline at end of file diff --git a/benchmark/src/client/init.client.luau b/benchmark/src/client/init.client.luau index 315b530..71d0d35 100644 --- a/benchmark/src/client/init.client.luau +++ b/benchmark/src/client/init.client.luau @@ -1,4 +1,4 @@ --- Benchmark client. Fires 1000 events per frame for 10 seconds per tool per bench. +-- Benchmark client. Fires 200 events per frame for 10 seconds per tool per bench. -- Measures FPS and bandwidth (Kbps) at percentile intervals. local HttpService = game:GetService("HttpService") local Players = game:GetService("Players") @@ -7,23 +7,20 @@ local RunService = game:GetService("RunService") local StarterGui = game:GetService("StarterGui") local Stats = game:GetService("Stats") -local Satset = require(ReplicatedStorage.DevPackages.Satset) -Satset.start() +local Satset = require(ReplicatedStorage.references.Satset) +Satset.start({ + batching = { + reliableThreshold = 60000, + maxPacketsPerFrame = 20, + }, +}) local MAXIMUM_FRAMERATE = 60 local Benches, Modes -- Minimize rendering overhead to isolate network performance. -local Camera = workspace.CurrentCamera -Camera.FieldOfView = 1 -Camera.CFrame = CFrame.new(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) -Players.LocalPlayer.PlayerGui:ClearAllChildren() -for _, child in Players.LocalPlayer.PlayerScripts:GetChildren() do - if child ~= script then - child:Destroy() - end -end +-- Disabe Character AutoLoads in Player service instead for _, item in Enum.CoreGuiType:GetEnumItems() do while true do @@ -40,7 +37,9 @@ type Benchmark = { Sent: number, Recieve: number, Bandwidth: { number }, + NormalizedBandwidth: { number }, Framerate: { number }, + DrainDuration: number, } type Results = { @@ -50,24 +49,79 @@ type Results = { } local function percentile(samples: { number }, pct: number) + if #samples == 0 then + return 0 + end assert((pct // 1) == pct and pct >= 0 and pct <= 100, "Percentile must be an integer between 0 and 100") - local index = ((#samples * (pct / 100)) // 1) - index = math.max(index, 1) + + if pct == 0 then + return samples[1] + end + + local index = math.ceil(#samples * pct / 100) + return samples[math.clamp(index, 1, #samples)] +end + +local function measureBaseline(duration: number?): number + duration = duration or 5 + + local started = os.clock() + local samples = {} + + while os.clock() - started < duration do + table.insert(samples, Stats.DataSendKbps) + RunService.Heartbeat:Wait() + end + + table.sort(samples) + + -- Use p50 baseline to avoid random spikes. + local index = math.max(1, math.ceil(#samples * 0.5)) return samples[index] end -local function waitForDrain() +local function waitForDrain(baselineKbps: number): number print("Waiting for packets to drain.") - while Stats.DataSendKbps > 0.5 do + + local started = os.clock() + local stableStarted: number? = nil + + local tolerance = math.max(1, baselineKbps * 0.25) + local threshold = baselineKbps + tolerance + + local stableDuration = 3 + local timeout = 60 + + while true do + local current = Stats.DataSendKbps + local now = os.clock() + + if current <= threshold then + stableStarted = stableStarted or now + + if now - stableStarted >= stableDuration then + break + end + else + stableStarted = nil + end + + if now - started >= timeout then + warn(`Timed out waiting for drain. Current: {current}, threshold: {threshold}`) + break + end + RunService.Heartbeat:Wait() end - task.wait(5) + + return os.clock() - started end local function runBenchmark(tool: string, bench: string): Benchmark local total = 0 local frames = 0 local bandwidth = {} + local normalizedBandwidth = {} local framerates = {} local sent = 0 @@ -76,22 +130,20 @@ local function runBenchmark(tool: string, bench: string): Benchmark local event = events[bench] local method - if tool == "roblox" then + local lowerTool = string.lower(tool) + if lowerTool == "roblox" then method = function(d) event:FireServer(d) end - elseif tool == "bridgenet2" then - -- BridgeNet2 client: bridge:Fire(data) + elseif lowerTool == "bridgenet2" then method = function(d) event:Fire(d) end - elseif tool == "warp" then - -- Warp client: client:Fire(reliable, ...) + elseif lowerTool == "warp" then method = function(d) event:Fire(true, d) end else - -- Satset uses .fire(), ByteNet uses .send() method = event.fire or event.Fire or event.send end @@ -100,14 +152,20 @@ local function runBenchmark(tool: string, bench: string): Benchmark frames += 1 if total >= 1 then - total -= 1 - local scale = MAXIMUM_FRAMERATE / frames - table.insert(bandwidth, Stats.DataSendKbps * scale) + local actualKbps = Stats.DataSendKbps + local fps = frames / total + local scale = MAXIMUM_FRAMERATE / math.max(1, fps) + local scaledKbps = actualKbps * scale + + table.insert(bandwidth, actualKbps) + table.insert(normalizedBandwidth, scaledKbps) table.insert(framerates, frames) + + total = 0 frames = 0 end - for _ = 1, 1000 do + for _ = 1, 200 do sent += 1 method(data) end @@ -117,17 +175,17 @@ local function runBenchmark(tool: string, bench: string): Benchmark connection:Disconnect() print("> Finished running with " .. tool) - -- Sort for percentile calculation. table.sort(bandwidth) - table.sort(framerates, function(a, b) - return a > b - end) + table.sort(normalizedBandwidth) + table.sort(framerates) local frameratePercentiles = {} local bandwidthPercentiles = {} + local normalizedBandwidthPercentiles = {} for _, pct in { 50, 0, 80, 90, 95, 100 } do table.insert(bandwidthPercentiles, percentile(bandwidth, pct)) + table.insert(normalizedBandwidthPercentiles, percentile(normalizedBandwidth, pct)) table.insert(frameratePercentiles, percentile(framerates, pct)) end @@ -135,6 +193,7 @@ local function runBenchmark(tool: string, bench: string): Benchmark Sent = sent, Recieve = 0, Bandwidth = bandwidthPercentiles, + NormalizedBandwidth = normalizedBandwidthPercentiles, Framerate = frameratePercentiles, } end @@ -144,32 +203,22 @@ local function runAll() Modes = require(ReplicatedStorage.Shared.modes) local results: Results = {} + print("Measuring baseline bandwidth...") + local baselineKbps = measureBaseline(5) + print(`Baseline DataSendKbps: {baselineKbps}`) + for bench in Benches do warn("Running " .. bench .. " benchmark") results[bench] = {} for tool in Modes do - -- Red batches events into nested arrays. At 1000 events/frame, this causes the - -- Roblox Replicator queue to exceed its 5,000,000 Variant hard limit, crashing the client. - if - tool == "red" and (bench == "Booleans" or bench == "Strings" or bench == "Entities" or bench == "Mixed") - then - warn("> Skipping " .. tool .. " for " .. bench .. " due to Variant limit crash.") - results[bench][tool] = { - Sent = 0, - Recieve = 0, - Bandwidth = { 0, 0, 0, 0, 0, 0 }, - Framerate = { 0, 0, 0, 0, 0, 0 }, - } - continue - end - print("> Running with " .. tool) results[bench][tool] = runBenchmark(tool, bench) - waitForDrain() + local drainDuration = waitForDrain(baselineKbps) + results[bench][tool].DrainDuration = drainDuration end - waitForDrain() + waitForDrain(baselineKbps) end -- Pull server-side receive counts for loss calculation. diff --git a/benchmark/src/server/init.server.luau b/benchmark/src/server/init.server.luau index cfeaaef..e599188 100644 --- a/benchmark/src/server/init.server.luau +++ b/benchmark/src/server/init.server.luau @@ -1,13 +1,36 @@ -- Benchmark server. Receives data from all modes and validates correctness. local ReplicatedStorage = game:GetService("ReplicatedStorage") -local Satset = require(ReplicatedStorage.DevPackages.Satset) -Satset.start() - -local Benches = require(ReplicatedStorage.Shared.benches) +local Satset = require(ReplicatedStorage.references.Satset) +Satset.start({ + batching = { + reliableThreshold = 60000, + maxPacketsPerFrame = 20, + }, +}) + +local Benches = require(game:GetService("ReplicatedStorage").Shared.benches) local Modes = require(ReplicatedStorage.Shared.modes) -local function compareTables(a: any, b: any): boolean +local compareTables: (a: any, b: any) -> boolean + +local function compareValues(a: any, b: any): boolean + if type(a) == "table" or type(b) == "table" then + return compareTables(a, b) + end + if type(a) == "number" and type(b) == "number" then + return math.abs(a - b) < 0.5 + end + if typeof(a) == "Vector3" and typeof(b) == "Vector3" then + return (a - b).Magnitude < 1.0 + end + if typeof(a) == "Vector2" and typeof(b) == "Vector2" then + return (a - b).Magnitude < 1.0 + end + return a == b +end + +function compareTables(a, b) if a == b then return true end @@ -21,7 +44,7 @@ local function compareTables(a: any, b: any): boolean local keys = {} for key, value in a do local other = b[key] - if other == nil or not compareTables(value, other) then + if other == nil or not compareValues(value, other) then return false end keys[key] = true @@ -36,13 +59,6 @@ local function compareTables(a: any, b: any): boolean return true end -local function compareValues(a: any, b: any): boolean - if type(a) == "table" or type(b) == "table" then - return compareTables(a, b) - end - return a == b -end - -- Track received counts per bench per tool for loss calculation. local received = {} for name, data in Benches do @@ -65,6 +81,8 @@ for name, data in Benches do if not compareValues(data, value) then warn("Received incorrect data with " .. tool .. " for " .. name) + print("Expected:", data) + print("Received:", value) else print("> " .. tool .. " passed " .. name .. " validation!") end @@ -72,18 +90,22 @@ for name, data in Benches do end for tool, events in Modes do + local lowerTool = string.lower(tool) local event = events[name] + local callback = onReceive(tool) - if tool == "roblox" or tool == "packet" then - -- Roblox and Suphi-Packet use .OnServerEvent + if lowerTool == "roblox" or lowerTool == "packet" then + -- roblox and packet use .OnServerEvent event.OnServerEvent:Connect(callback) - elseif tool == "bridgenet2" or tool == "warp" then - -- BridgeNet2 and Warp use standard :Connect() + elseif lowerTool == "bridgenet2" or lowerTool == "warp" then + -- bridgenet2 and warp use standard :Connect() event:Connect(callback) elseif event.listen then - -- Satset and ByteNet use .listen() + -- satset and bytenet use .listen() event.listen(callback) + elseif event.On then -- netray_compile/blink + event.On(callback) end end end diff --git a/benchmark/src/shared/modes/blink_gen/client.luau b/benchmark/src/shared/modes/blink_gen/client.luau index 0cb467b..3e938e9 100644 --- a/benchmark/src/shared/modes/blink_gen/client.luau +++ b/benchmark/src/shared/modes/blink_gen/client.luau @@ -162,17 +162,6 @@ local Queue: any = { local SerdesEvent = "?" local SerdesThread: thread? -export type Entity = { id: number, x: number, y: number, z: number, orientation: number, animation: number } -export type MixedStruct = { - id: number, - health: number, - position: Vector3, - velocity: Vector3, - name: string, - isRunning: boolean, - animation: number, - team: number, -} function SerdesFunctions.WriteEVENT_Booleans(Value: { boolean }): () -- Allocate BLOCK: 3 bytes local BLOCK_START = Allocate(3) diff --git a/benchmark/src/shared/modes/blink_gen/server.luau b/benchmark/src/shared/modes/blink_gen/server.luau index cdc411d..a83e67c 100644 --- a/benchmark/src/shared/modes/blink_gen/server.luau +++ b/benchmark/src/shared/modes/blink_gen/server.luau @@ -169,17 +169,6 @@ Queue.Reliable[2] = table.create(256) Queue.Reliable[3] = table.create(256) Queue.Reliable[4] = table.create(256) Queue.Reliable[5] = table.create(256) -export type Entity = { id: number, x: number, y: number, z: number, orientation: number, animation: number } -export type MixedStruct = { - id: number, - health: number, - position: Vector3, - velocity: Vector3, - name: string, - isRunning: boolean, - animation: number, - team: number, -} function SerdesFunctions.ReadEVENT_Booleans(): { boolean } -- Read BLOCK: 2 bytes local BLOCK_START = Read(2) diff --git a/benchmark/src/shared/modes/blink_gen/types.luau b/benchmark/src/shared/modes/blink_gen/types.luau index ca57617..060ec9f 100644 --- a/benchmark/src/shared/modes/blink_gen/types.luau +++ b/benchmark/src/shared/modes/blink_gen/types.luau @@ -148,16 +148,4 @@ local Queue: any = { Unreliable = table.create(256), } -export type Entity = { id: number, x: number, y: number, z: number, orientation: number, animation: number } -export type MixedStruct = { - id: number, - health: number, - position: Vector3, - velocity: Vector3, - name: string, - isRunning: boolean, - animation: number, - team: number, -} - return {} diff --git a/benchmark/src/shared/modes/bridgenet2.luau b/benchmark/src/shared/modes/bridgenet2.luau index 62f00ff..d8c1492 100644 --- a/benchmark/src/shared/modes/bridgenet2.luau +++ b/benchmark/src/shared/modes/bridgenet2.luau @@ -1,6 +1,6 @@ -- BridgeNet2 benchmark mode (v1.0.0 API). -- BridgeNet2 uses ReferenceBridge with string identifiers. -local BridgeNet2 = require(game:GetService("ReplicatedStorage").DevPackages.BridgeNet2) +local BridgeNet2 = require(game:GetService("ReplicatedStorage").DevPackages.bridgenet2) local Benches = require(game:GetService("ReplicatedStorage").Shared.benches) diff --git a/benchmark/src/shared/modes/bytenet.luau b/benchmark/src/shared/modes/bytenet.luau index d8810f2..3693282 100644 --- a/benchmark/src/shared/modes/bytenet.luau +++ b/benchmark/src/shared/modes/bytenet.luau @@ -1,5 +1,5 @@ -- ByteNet benchmark mode (v0.4.6 API). -local ByteNet = require(game:GetService("ReplicatedStorage").DevPackages.ByteNet) +local ByteNet = require(game:GetService("ReplicatedStorage").DevPackages.bytenet) -- ByteNet 0.4.6 requires the schema to be in the 'value' field. return ByteNet.defineNamespace("Benchmark", function() diff --git a/benchmark/src/shared/modes/init.luau b/benchmark/src/shared/modes/init.luau index db46445..111be10 100644 --- a/benchmark/src/shared/modes/init.luau +++ b/benchmark/src/shared/modes/init.luau @@ -4,6 +4,9 @@ local DISABLED_MODES = {} local Modes = {} for _, Mode in script:GetChildren() do + if Mode == script then + continue + end if DISABLED_MODES[Mode.Name] or not Mode:IsA("ModuleScript") then continue end diff --git a/benchmark/src/shared/modes/red.luau b/benchmark/src/shared/modes/netray_compile.luau similarity index 57% rename from benchmark/src/shared/modes/red.luau rename to benchmark/src/shared/modes/netray_compile.luau index 831785c..bf07c4c 100644 --- a/benchmark/src/shared/modes/red.luau +++ b/benchmark/src/shared/modes/netray_compile.luau @@ -1,27 +1,30 @@ local RunService = game:GetService("RunService") -local Red = require(game:GetService("ReplicatedStorage").Red) local Benches = require(game:GetService("ReplicatedStorage").Shared.benches) local IsServer = RunService:IsServer() local Events = {} -local NetAPI +local NetRayAPI if IsServer then - NetAPI = Red.Server("RedBenchmark") + NetRayAPI = require(script.Parent.netray_compile_gen.Server) else - NetAPI = Red.Client("RedBenchmark") + NetRayAPI = require(script.Parent.netray_compile_gen.Client) end for Name in Benches do Events[Name] = { fire = function(data) if not IsServer then - NetAPI:Fire(Name, data) + NetRayAPI[Name].Fire(data) end end, listen = function(callback) - NetAPI:On(Name, callback) + if IsServer then + NetRayAPI[Name].On(callback) + else + NetRayAPI[Name].On(callback) + end end, } end diff --git a/benchmark/src/shared/modes/netray_compile_gen/Client.luau b/benchmark/src/shared/modes/netray_compile_gen/Client.luau new file mode 100644 index 0000000..6976afe --- /dev/null +++ b/benchmark/src/shared/modes/netray_compile_gen/Client.luau @@ -0,0 +1,1230 @@ +--!strict +--!native +--!optimize 2 +--!nolint LocalShadow +--#selene: allow(shadowing) +-- Name: Client +-- Purpose: Generated runtime for client networking +-- Generated-by: NetRay-Compiler +-- Version: 0.1.1 + +local Types = require(script.Parent.Types) +type u8 = typeof(Types.u8) +type i8 = typeof(Types.i8) +type u16 = typeof(Types.u16) +type i16 = typeof(Types.i16) +type u32 = typeof(Types.u32) +type i32 = typeof(Types.i32) +type f16 = typeof(Types.f16) +type f32 = typeof(Types.f32) +type f64 = typeof(Types.f64) +type NetRoot = typeof(Types.NetClient) +type NetStats = typeof(Types.NetStats) +type Connection = typeof(Types.Connection) +type Net_Booleans_reliable_ClientApi = typeof(Types.Net_Booleans_reliable_ClientApi) +type Net_Booleans_reliable_ClientEventCallback = typeof(Types.Net_Booleans_reliable_ClientEventCallback) +type Net_Entities_reliable_ClientApi = typeof(Types.Net_Entities_reliable_ClientApi) +type Net_Entities_reliable_ClientEventCallback = typeof(Types.Net_Entities_reliable_ClientEventCallback) +type Net_Vectors_reliable_ClientApi = typeof(Types.Net_Vectors_reliable_ClientApi) +type Net_Vectors_reliable_ClientEventCallback = typeof(Types.Net_Vectors_reliable_ClientEventCallback) +type Net_Strings_reliable_ClientApi = typeof(Types.Net_Strings_reliable_ClientApi) +type Net_Strings_reliable_ClientEventCallback = typeof(Types.Net_Strings_reliable_ClientEventCallback) +type Net_Mixed_reliable_ClientApi = typeof(Types.Net_Mixed_reliable_ClientApi) +type Net_Mixed_reliable_ClientEventCallback = typeof(Types.Net_Mixed_reliable_ClientEventCallback) +type Net_SingleValue_reliable_ClientApi = typeof(Types.Net_SingleValue_reliable_ClientApi) +type Net_SingleValue_reliable_ClientEventCallback = typeof(Types.Net_SingleValue_reliable_ClientEventCallback) + +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local RunService = game:GetService("RunService") + +local Reliable = ReplicatedStorage:WaitForChild("NetRay_RELIABLE") :: RemoteEvent +local Unreliable = ReplicatedStorage:WaitForChild("NetRay_UNRELIABLE") :: UnreliableRemoteEvent +local FunctionRemote = ReplicatedStorage:WaitForChild("NetRay_FUNCTION") :: RemoteFunction + +local function WithCount(...) + return select("#", ...), ... +end + +local DEFAULT_ARENA_CAPACITY = 65536 +local create = buffer.create +local copy = buffer.copy +local len = buffer.len +local writeu8 = buffer.writeu8 + +local writef32 = buffer.writef32 +local writestring = buffer.writestring +local readu8 = buffer.readu8 + +local band = bit32.band +local bor = bit32.bor +local rshift = bit32.rshift +local lshift = bit32.lshift + +local ERR_BOUNDS = "E_BOUNDS" +local ERR_SCHEMA = "E_SCHEMA" +local ERR_TAG = "E_TAG" +local ERR_TYPE = "E_TYPE" +local SEGMENT_MARKER_ID = 0 + +local function VarUIntSize(value) + if value < 128 then + return 1 + end + if value < 16384 then + return 2 + end + if value < 2097152 then + return 3 + end + if value < 268435456 then + return 4 + end + return 5 +end + +local function WriteVarUInt(payload, offset, value) + if value < 128 then + writeu8(payload, offset, value) + return offset + 1 + end + if value < 16384 then + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, band(rshift(value, 7), 127)) + return offset + 2 + end + if value < 2097152 then + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, bor(band(rshift(value, 7), 127), 128)) + writeu8(payload, offset + 2, band(rshift(value, 14), 127)) + return offset + 3 + end + if value < 268435456 then + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, bor(band(rshift(value, 7), 127), 128)) + writeu8(payload, offset + 2, bor(band(rshift(value, 14), 127), 128)) + writeu8(payload, offset + 3, band(rshift(value, 21), 127)) + return offset + 4 + end + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, bor(band(rshift(value, 7), 127), 128)) + writeu8(payload, offset + 2, bor(band(rshift(value, 14), 127), 128)) + writeu8(payload, offset + 3, bor(band(rshift(value, 21), 127), 128)) + writeu8(payload, offset + 4, band(rshift(value, 28), 15)) + return offset + 5 +end + +local function ReadVarUInt(payload, payloadLength, offset) + local next1 = offset + 1 + if next1 > payloadLength then + error(ERR_BOUNDS) + end + local b1 = readu8(payload, offset) + if b1 < 128 then + return b1, next1 + end + local next2 = next1 + 1 + if next2 > payloadLength then + error(ERR_BOUNDS) + end + local b2 = readu8(payload, next1) + local value = band(b1, 127) + lshift(band(b2, 127), 7) + if b2 < 128 then + return value, next2 + end + local next3 = next2 + 1 + if next3 > payloadLength then + error(ERR_BOUNDS) + end + local b3 = readu8(payload, next2) + value += lshift(band(b3, 127), 14) + if b3 < 128 then + return value, next3 + end + local next4 = next3 + 1 + if next4 > payloadLength then + error(ERR_BOUNDS) + end + local b4 = readu8(payload, next3) + value += lshift(band(b4, 127), 21) + if b4 < 128 then + return value, next4 + end + local next5 = next4 + 1 + if next5 > payloadLength then + error(ERR_BOUNDS) + end + local b5 = readu8(payload, next4) + if b5 >= 16 then + error(ERR_SCHEMA) + end + value += lshift(b5, 28) + return value, next5 +end + +local function CreateConnection(disconnectFn: () -> ()): Connection + local active = true + local connection = {} :: any + connection.Connected = true + function connection:Disconnect() + if not active then + return + end + active = false + connection.Connected = false + disconnectFn() + end + return connection :: Connection +end + +local MAX_RELIABLE_ID = 6 +local MAX_UNRELIABLE_ID = 0 +local MAX_FUNCTION_ID = 0 +local RELIABLE_DISPATCH = table.create(MAX_RELIABLE_ID) +local UNRELIABLE_DISPATCH = table.create(MAX_UNRELIABLE_ID) +local RELIABLE_SKIP = table.create(MAX_RELIABLE_ID) +local UNRELIABLE_SKIP = table.create(MAX_UNRELIABLE_ID) +local FUNCTION_DISPATCH = table.create(MAX_FUNCTION_ID) + +local RELIABLE_ARENA_CAPACITY = DEFAULT_ARENA_CAPACITY +local RELIABLE_ARENA = create(RELIABLE_ARENA_CAPACITY) +local RELIABLE_CURSOR = 0 +local RELIABLE_QUEUE_SIZE = 0 +local RELIABLE_DROPPED = 0 +local RELIABLE_SEGMENT_HEADER_BYTES = 2 +local RELIABLE_SEGMENT_MIN_COUNT = 4 +local RELIABLE_SEGMENT_MIN_TAIL_BYTES = 3 + +local function FlushReliable() + if RELIABLE_CURSOR == 0 then + RELIABLE_QUEUE_SIZE = 0 + return + end + local payloadToSend = create(RELIABLE_CURSOR) + local payloadOffset = RELIABLE_CURSOR + if RELIABLE_QUEUE_SIZE >= RELIABLE_SEGMENT_MIN_COUNT then + -- Only scan runs when the batch is large enough to amortize segment headers. + local sourceOffset = 0 + payloadOffset = 0 + while sourceOffset < RELIABLE_CURSOR do + local runStart = sourceOffset + local messageId = readu8(RELIABLE_ARENA, sourceOffset) + sourceOffset += 1 + local skip = RELIABLE_SKIP[messageId] + local firstBodyEnd = skip(RELIABLE_ARENA, RELIABLE_CURSOR, sourceOffset) + local runEnd = firstBodyEnd + local runCount = 1 + if RELIABLE_CURSOR - firstBodyEnd < RELIABLE_SEGMENT_MIN_TAIL_BYTES then + -- Remaining bytes cannot fit enough repeated IDs to make segmentation pay off. + local tailSize = RELIABLE_CURSOR - runStart + if tailSize > 0 then + copy(payloadToSend, payloadOffset, RELIABLE_ARENA, runStart, tailSize) + payloadOffset += tailSize + end + break + end + while runEnd < RELIABLE_CURSOR do + local nextMessageId = readu8(RELIABLE_ARENA, runEnd) + if nextMessageId ~= messageId then + break + end + local nextBodyStart = runEnd + 1 + runEnd = skip(RELIABLE_ARENA, RELIABLE_CURSOR, nextBodyStart) + runCount += 1 + end + if runCount * 1 > RELIABLE_SEGMENT_HEADER_BYTES + VarUIntSize(runCount) then + writeu8(payloadToSend, payloadOffset, SEGMENT_MARKER_ID) + payloadOffset += 1 + writeu8(payloadToSend, payloadOffset, messageId) + payloadOffset += 1 + payloadOffset = WriteVarUInt(payloadToSend, payloadOffset, runCount) + local copyCursor = sourceOffset + local bodyEnd = firstBodyEnd + while true do + local bodySize = bodyEnd - copyCursor + if bodySize > 0 then + copy(payloadToSend, payloadOffset, RELIABLE_ARENA, copyCursor, bodySize) + payloadOffset += bodySize + end + if bodyEnd >= runEnd then + break + end + copyCursor = bodyEnd + 1 + bodyEnd = skip(RELIABLE_ARENA, RELIABLE_CURSOR, copyCursor) + end + else + local runSize = runEnd - runStart + if runSize > 0 then + copy(payloadToSend, payloadOffset, RELIABLE_ARENA, runStart, runSize) + payloadOffset += runSize + end + end + sourceOffset = runEnd + end + else + -- For tiny batches, a direct copy is cheaper than any merge scan. + copy(payloadToSend, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + if payloadOffset < RELIABLE_CURSOR then + local compactPayload = create(payloadOffset) + copy(compactPayload, 0, payloadToSend, 0, payloadOffset) + payloadToSend = compactPayload + end + Reliable:FireServer(payloadToSend) + RELIABLE_CURSOR = 0 + RELIABLE_QUEUE_SIZE = 0 +end + +local UNRELIABLE_ARENA_CAPACITY = DEFAULT_ARENA_CAPACITY +local UNRELIABLE_ARENA = create(UNRELIABLE_ARENA_CAPACITY) +local UNRELIABLE_CURSOR = 0 +local UNRELIABLE_QUEUE_SIZE = 0 +local UNRELIABLE_DROPPED = 0 +local UNRELIABLE_SEGMENT_HEADER_BYTES = 2 +local UNRELIABLE_SEGMENT_MIN_COUNT = 4 +local UNRELIABLE_SEGMENT_MIN_TAIL_BYTES = 3 + +local function FlushUnreliable() + if UNRELIABLE_CURSOR == 0 then + UNRELIABLE_QUEUE_SIZE = 0 + return + end + local payloadToSend = create(UNRELIABLE_CURSOR) + local payloadOffset = UNRELIABLE_CURSOR + if UNRELIABLE_QUEUE_SIZE >= UNRELIABLE_SEGMENT_MIN_COUNT then + -- Only scan runs when the batch is large enough to amortize segment headers. + local sourceOffset = 0 + payloadOffset = 0 + while sourceOffset < UNRELIABLE_CURSOR do + local runStart = sourceOffset + local messageId = readu8(UNRELIABLE_ARENA, sourceOffset) + sourceOffset += 1 + local skip = UNRELIABLE_SKIP[messageId] + local firstBodyEnd = skip(UNRELIABLE_ARENA, UNRELIABLE_CURSOR, sourceOffset) + local runEnd = firstBodyEnd + local runCount = 1 + if UNRELIABLE_CURSOR - firstBodyEnd < UNRELIABLE_SEGMENT_MIN_TAIL_BYTES then + -- Remaining bytes cannot fit enough repeated IDs to make segmentation pay off. + local tailSize = UNRELIABLE_CURSOR - runStart + if tailSize > 0 then + copy(payloadToSend, payloadOffset, UNRELIABLE_ARENA, runStart, tailSize) + payloadOffset += tailSize + end + break + end + while runEnd < UNRELIABLE_CURSOR do + local nextMessageId = readu8(UNRELIABLE_ARENA, runEnd) + if nextMessageId ~= messageId then + break + end + local nextBodyStart = runEnd + 1 + runEnd = skip(UNRELIABLE_ARENA, UNRELIABLE_CURSOR, nextBodyStart) + runCount += 1 + end + if runCount * 1 > UNRELIABLE_SEGMENT_HEADER_BYTES + VarUIntSize(runCount) then + writeu8(payloadToSend, payloadOffset, SEGMENT_MARKER_ID) + payloadOffset += 1 + writeu8(payloadToSend, payloadOffset, messageId) + payloadOffset += 1 + payloadOffset = WriteVarUInt(payloadToSend, payloadOffset, runCount) + local copyCursor = sourceOffset + local bodyEnd = firstBodyEnd + while true do + local bodySize = bodyEnd - copyCursor + if bodySize > 0 then + copy(payloadToSend, payloadOffset, UNRELIABLE_ARENA, copyCursor, bodySize) + payloadOffset += bodySize + end + if bodyEnd >= runEnd then + break + end + copyCursor = bodyEnd + 1 + bodyEnd = skip(UNRELIABLE_ARENA, UNRELIABLE_CURSOR, copyCursor) + end + else + local runSize = runEnd - runStart + if runSize > 0 then + copy(payloadToSend, payloadOffset, UNRELIABLE_ARENA, runStart, runSize) + payloadOffset += runSize + end + end + sourceOffset = runEnd + end + else + -- For tiny batches, a direct copy is cheaper than any merge scan. + copy(payloadToSend, 0, UNRELIABLE_ARENA, 0, UNRELIABLE_CURSOR) + end + if payloadOffset < UNRELIABLE_CURSOR then + local compactPayload = create(payloadOffset) + copy(compactPayload, 0, payloadToSend, 0, payloadOffset) + payloadToSend = compactPayload + end + Unreliable:FireServer(payloadToSend) + UNRELIABLE_CURSOR = 0 + UNRELIABLE_QUEUE_SIZE = 0 +end + +local Net_Booleans_reliable_1: Net_Booleans_reliable_ClientApi = {} :: Net_Booleans_reliable_ClientApi +local MESSAGE_ID_Net_Booleans_reliable_1 = 1 + +local function Encode_Net_Booleans_reliable_1(data) + local prefixSize = 0 + local varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + prefixSize += VarUIntSize(arrayLen_1) + varSize += ((arrayLen_1 + 7) // 8) + local totalSize = 1 + prefixSize + varSize + local payload = create(totalSize) + writeu8(payload, 0, MESSAGE_ID_Net_Booleans_reliable_1) + local prefixOffset = 1 + local fixedOffset = prefixOffset + prefixSize + local varOffset = fixedOffset + prefixOffset = WriteVarUInt(payload, prefixOffset, arrayLen_1) + local arrayPackedByteCount_5 = ((arrayLen_1 + 7) // 8) + local arrayValueIndex_7 = 1 + for arrayPackedIndex_6 = 1, arrayPackedByteCount_5 do + local arrayPackedByte_10 = 0 + local arrayBitMask_9 = 1 + for arrayBitIndex_8 = 1, 8 do + if arrayValueIndex_7 > arrayLen_1 then + break + end + if data[arrayValueIndex_7] then + arrayPackedByte_10 = bor(arrayPackedByte_10, arrayBitMask_9) + end + arrayValueIndex_7 += 1 + arrayBitMask_9 = lshift(arrayBitMask_9, 1) + end + writeu8(payload, varOffset, arrayPackedByte_10) + varOffset += 1 + end + return payload +end + +local Handler_Net_Booleans_reliable_1: ((data: { boolean }) -> ())? = nil +local HandlerToken_Net_Booleans_reliable_1 = 0 + +local function Skip_Net_Booleans_reliable_1(payload, payloadLength, offset) + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + local skipArrayPackedByteCount_5 = ((arrayCount_2 + 7) // 8) + local skipArrayPackedNext_6 = varCursor_4 + skipArrayPackedByteCount_5 + if skipArrayPackedNext_6 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipArrayPackedNext_6 + offset = varCursor_4 + return offset +end + +RELIABLE_SKIP[1] = Skip_Net_Booleans_reliable_1 + +function Net_Booleans_reliable_1.Fire(data: { boolean }): boolean + local __nr_prefixSize = 0 + local __nr_varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + __nr_prefixSize += VarUIntSize(arrayLen_1) + __nr_varSize += ((arrayLen_1 + 7) // 8) + local __nr_totalSize = 1 + __nr_prefixSize + __nr_varSize + local __nr_base = RELIABLE_CURSOR + local __nr_required = __nr_base + __nr_totalSize + if __nr_required > RELIABLE_ARENA_CAPACITY then + local nextCapacity = RELIABLE_ARENA_CAPACITY + while nextCapacity < __nr_required do + nextCapacity *= 2 + end + local nextArena = create(nextCapacity) + if RELIABLE_CURSOR > 0 then + copy(nextArena, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + RELIABLE_ARENA = nextArena + RELIABLE_ARENA_CAPACITY = nextCapacity + end + RELIABLE_CURSOR = __nr_required + local __nr_payload = RELIABLE_ARENA + writeu8(__nr_payload, __nr_base, MESSAGE_ID_Net_Booleans_reliable_1) + local __nr_prefix = __nr_base + 1 + local __nr_fixed = __nr_prefix + __nr_prefixSize + local __nr_var = __nr_fixed + __nr_prefix = WriteVarUInt(__nr_payload, __nr_prefix, arrayLen_1) + local arrayPackedByteCount_5 = ((arrayLen_1 + 7) // 8) + local arrayValueIndex_7 = 1 + for arrayPackedIndex_6 = 1, arrayPackedByteCount_5 do + local arrayPackedByte_10 = 0 + local arrayBitMask_9 = 1 + for arrayBitIndex_8 = 1, 8 do + if arrayValueIndex_7 > arrayLen_1 then + break + end + if data[arrayValueIndex_7] then + arrayPackedByte_10 = bor(arrayPackedByte_10, arrayBitMask_9) + end + arrayValueIndex_7 += 1 + arrayBitMask_9 = lshift(arrayBitMask_9, 1) + end + writeu8(__nr_payload, __nr_var, arrayPackedByte_10) + __nr_var += 1 + end + RELIABLE_QUEUE_SIZE += 1 + return true +end + +local Net_Entities_reliable_2: Net_Entities_reliable_ClientApi = {} :: Net_Entities_reliable_ClientApi +local MESSAGE_ID_Net_Entities_reliable_2 = 2 + +local function Encode_Net_Entities_reliable_2(data) + local prefixSize = 0 + local varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + prefixSize += VarUIntSize(arrayLen_1) + for arrayIndex_2 = 1, arrayLen_1 do + varSize += 1 + varSize += 1 + varSize += 1 + varSize += 1 + varSize += 1 + varSize += 1 + end + local totalSize = 1 + prefixSize + varSize + local payload = create(totalSize) + writeu8(payload, 0, MESSAGE_ID_Net_Entities_reliable_2) + local prefixOffset = 1 + local fixedOffset = prefixOffset + prefixSize + local varOffset = fixedOffset + prefixOffset = WriteVarUInt(payload, prefixOffset, arrayLen_1) + for arrayIndex_4 = 1, arrayLen_1 do + writeu8(payload, varOffset, data[arrayIndex_4].id) + varOffset += 1 + writeu8(payload, varOffset, data[arrayIndex_4].x) + varOffset += 1 + writeu8(payload, varOffset, data[arrayIndex_4].y) + varOffset += 1 + writeu8(payload, varOffset, data[arrayIndex_4].z) + varOffset += 1 + writeu8(payload, varOffset, data[arrayIndex_4].orientation) + varOffset += 1 + writeu8(payload, varOffset, data[arrayIndex_4].animation) + varOffset += 1 + end + return payload +end + +local Handler_Net_Entities_reliable_2: ((data: { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } }) -> ())? = + nil +local HandlerToken_Net_Entities_reliable_2 = 0 + +local function Skip_Net_Entities_reliable_2(payload, payloadLength, offset) + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + for skipArrayIndex_5 = 1, arrayCount_2 do + local skipNext_6 = varCursor_4 + 1 + if skipNext_6 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipNext_6 + local skipNext_7 = varCursor_4 + 1 + if skipNext_7 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipNext_7 + local skipNext_8 = varCursor_4 + 1 + if skipNext_8 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipNext_8 + local skipNext_9 = varCursor_4 + 1 + if skipNext_9 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipNext_9 + local skipNext_10 = varCursor_4 + 1 + if skipNext_10 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipNext_10 + local skipNext_11 = varCursor_4 + 1 + if skipNext_11 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipNext_11 + end + offset = varCursor_4 + return offset +end + +RELIABLE_SKIP[2] = Skip_Net_Entities_reliable_2 + +function Net_Entities_reliable_2.Fire( + data: { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } } +): boolean + local __nr_prefixSize = 0 + local __nr_varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + __nr_prefixSize += VarUIntSize(arrayLen_1) + for arrayIndex_2 = 1, arrayLen_1 do + __nr_varSize += 1 + __nr_varSize += 1 + __nr_varSize += 1 + __nr_varSize += 1 + __nr_varSize += 1 + __nr_varSize += 1 + end + local __nr_totalSize = 1 + __nr_prefixSize + __nr_varSize + local __nr_base = RELIABLE_CURSOR + local __nr_required = __nr_base + __nr_totalSize + if __nr_required > RELIABLE_ARENA_CAPACITY then + local nextCapacity = RELIABLE_ARENA_CAPACITY + while nextCapacity < __nr_required do + nextCapacity *= 2 + end + local nextArena = create(nextCapacity) + if RELIABLE_CURSOR > 0 then + copy(nextArena, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + RELIABLE_ARENA = nextArena + RELIABLE_ARENA_CAPACITY = nextCapacity + end + RELIABLE_CURSOR = __nr_required + local __nr_payload = RELIABLE_ARENA + writeu8(__nr_payload, __nr_base, MESSAGE_ID_Net_Entities_reliable_2) + local __nr_prefix = __nr_base + 1 + local __nr_fixed = __nr_prefix + __nr_prefixSize + local __nr_var = __nr_fixed + __nr_prefix = WriteVarUInt(__nr_payload, __nr_prefix, arrayLen_1) + for arrayIndex_4 = 1, arrayLen_1 do + writeu8(__nr_payload, __nr_var, data[arrayIndex_4].id) + __nr_var += 1 + writeu8(__nr_payload, __nr_var, data[arrayIndex_4].x) + __nr_var += 1 + writeu8(__nr_payload, __nr_var, data[arrayIndex_4].y) + __nr_var += 1 + writeu8(__nr_payload, __nr_var, data[arrayIndex_4].z) + __nr_var += 1 + writeu8(__nr_payload, __nr_var, data[arrayIndex_4].orientation) + __nr_var += 1 + writeu8(__nr_payload, __nr_var, data[arrayIndex_4].animation) + __nr_var += 1 + end + RELIABLE_QUEUE_SIZE += 1 + return true +end + +local Net_Vectors_reliable_3: Net_Vectors_reliable_ClientApi = {} :: Net_Vectors_reliable_ClientApi +local MESSAGE_ID_Net_Vectors_reliable_3 = 3 + +local function Encode_Net_Vectors_reliable_3(data) + local prefixSize = 0 + local varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + prefixSize += VarUIntSize(arrayLen_1) + for arrayIndex_2 = 1, arrayLen_1 do + local vectorValue_4 = data[arrayIndex_2] + varSize += 12 + end + local totalSize = 1 + prefixSize + varSize + local payload = create(totalSize) + writeu8(payload, 0, MESSAGE_ID_Net_Vectors_reliable_3) + local prefixOffset = 1 + local fixedOffset = prefixOffset + prefixSize + local varOffset = fixedOffset + prefixOffset = WriteVarUInt(payload, prefixOffset, arrayLen_1) + for arrayIndex_5 = 1, arrayLen_1 do + local vectorValue_6 = data[arrayIndex_5] + writef32(payload, varOffset, vectorValue_6.X) + varOffset += 4 + writef32(payload, varOffset, vectorValue_6.Y) + varOffset += 4 + writef32(payload, varOffset, vectorValue_6.Z) + varOffset += 4 + end + return payload +end + +local Handler_Net_Vectors_reliable_3: ((data: { Vector3 }) -> ())? = nil +local HandlerToken_Net_Vectors_reliable_3 = 0 + +local function Skip_Net_Vectors_reliable_3(payload, payloadLength, offset) + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + for skipArrayIndex_5 = 1, arrayCount_2 do + local skipVectorNext_6 = varCursor_4 + 12 + if skipVectorNext_6 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipVectorNext_6 + end + offset = varCursor_4 + return offset +end + +RELIABLE_SKIP[3] = Skip_Net_Vectors_reliable_3 + +function Net_Vectors_reliable_3.Fire(data: { Vector3 }): boolean + local __nr_prefixSize = 0 + local __nr_varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + __nr_prefixSize += VarUIntSize(arrayLen_1) + for arrayIndex_2 = 1, arrayLen_1 do + local vectorValue_4 = data[arrayIndex_2] + __nr_varSize += 12 + end + local __nr_totalSize = 1 + __nr_prefixSize + __nr_varSize + local __nr_base = RELIABLE_CURSOR + local __nr_required = __nr_base + __nr_totalSize + if __nr_required > RELIABLE_ARENA_CAPACITY then + local nextCapacity = RELIABLE_ARENA_CAPACITY + while nextCapacity < __nr_required do + nextCapacity *= 2 + end + local nextArena = create(nextCapacity) + if RELIABLE_CURSOR > 0 then + copy(nextArena, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + RELIABLE_ARENA = nextArena + RELIABLE_ARENA_CAPACITY = nextCapacity + end + RELIABLE_CURSOR = __nr_required + local __nr_payload = RELIABLE_ARENA + writeu8(__nr_payload, __nr_base, MESSAGE_ID_Net_Vectors_reliable_3) + local __nr_prefix = __nr_base + 1 + local __nr_fixed = __nr_prefix + __nr_prefixSize + local __nr_var = __nr_fixed + __nr_prefix = WriteVarUInt(__nr_payload, __nr_prefix, arrayLen_1) + for arrayIndex_5 = 1, arrayLen_1 do + local vectorValue_6 = data[arrayIndex_5] + writef32(__nr_payload, __nr_var, vectorValue_6.X) + __nr_var += 4 + writef32(__nr_payload, __nr_var, vectorValue_6.Y) + __nr_var += 4 + writef32(__nr_payload, __nr_var, vectorValue_6.Z) + __nr_var += 4 + end + RELIABLE_QUEUE_SIZE += 1 + return true +end + +local Net_Strings_reliable_4: Net_Strings_reliable_ClientApi = {} :: Net_Strings_reliable_ClientApi +local MESSAGE_ID_Net_Strings_reliable_4 = 4 + +local function Encode_Net_Strings_reliable_4(data) + local prefixSize = 0 + local varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + prefixSize += VarUIntSize(arrayLen_1) + for arrayIndex_2 = 1, arrayLen_1 do + local stringLen_4 = #data[arrayIndex_2] + if stringLen_4 > 4294967295 then + error(ERR_SCHEMA) + end + varSize += VarUIntSize(stringLen_4) + stringLen_4 + end + local totalSize = 1 + prefixSize + varSize + local payload = create(totalSize) + writeu8(payload, 0, MESSAGE_ID_Net_Strings_reliable_4) + local prefixOffset = 1 + local fixedOffset = prefixOffset + prefixSize + local varOffset = fixedOffset + prefixOffset = WriteVarUInt(payload, prefixOffset, arrayLen_1) + for arrayIndex_5 = 1, arrayLen_1 do + local stringLen_6 = #data[arrayIndex_5] + varOffset = WriteVarUInt(payload, varOffset, stringLen_6) + writestring(payload, varOffset, data[arrayIndex_5]) + varOffset += stringLen_6 + end + return payload +end + +local Handler_Net_Strings_reliable_4: ((data: { string }) -> ())? = nil +local HandlerToken_Net_Strings_reliable_4 = 0 + +local function Skip_Net_Strings_reliable_4(payload, payloadLength, offset) + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + for skipArrayIndex_5 = 1, arrayCount_2 do + local skipStringLen_6 + skipStringLen_6, varCursor_4 = ReadVarUInt(payload, payloadLength, varCursor_4) + if skipStringLen_6 > 4294967295 then + error(ERR_SCHEMA) + end + local skipStringNext_7 = varCursor_4 + skipStringLen_6 + if skipStringNext_7 > payloadLength then + error(ERR_BOUNDS) + end + varCursor_4 = skipStringNext_7 + end + offset = varCursor_4 + return offset +end + +RELIABLE_SKIP[4] = Skip_Net_Strings_reliable_4 + +function Net_Strings_reliable_4.Fire(data: { string }): boolean + local __nr_prefixSize = 0 + local __nr_varSize = 0 + local arrayLen_1 = #data + if arrayLen_1 > 4294967295 then + error(ERR_SCHEMA) + end + __nr_prefixSize += VarUIntSize(arrayLen_1) + for arrayIndex_2 = 1, arrayLen_1 do + local stringLen_4 = #data[arrayIndex_2] + if stringLen_4 > 4294967295 then + error(ERR_SCHEMA) + end + __nr_varSize += VarUIntSize(stringLen_4) + stringLen_4 + end + local __nr_totalSize = 1 + __nr_prefixSize + __nr_varSize + local __nr_base = RELIABLE_CURSOR + local __nr_required = __nr_base + __nr_totalSize + if __nr_required > RELIABLE_ARENA_CAPACITY then + local nextCapacity = RELIABLE_ARENA_CAPACITY + while nextCapacity < __nr_required do + nextCapacity *= 2 + end + local nextArena = create(nextCapacity) + if RELIABLE_CURSOR > 0 then + copy(nextArena, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + RELIABLE_ARENA = nextArena + RELIABLE_ARENA_CAPACITY = nextCapacity + end + RELIABLE_CURSOR = __nr_required + local __nr_payload = RELIABLE_ARENA + writeu8(__nr_payload, __nr_base, MESSAGE_ID_Net_Strings_reliable_4) + local __nr_prefix = __nr_base + 1 + local __nr_fixed = __nr_prefix + __nr_prefixSize + local __nr_var = __nr_fixed + __nr_prefix = WriteVarUInt(__nr_payload, __nr_prefix, arrayLen_1) + for arrayIndex_5 = 1, arrayLen_1 do + local stringLen_6 = #data[arrayIndex_5] + __nr_var = WriteVarUInt(__nr_payload, __nr_var, stringLen_6) + writestring(__nr_payload, __nr_var, data[arrayIndex_5]) + __nr_var += stringLen_6 + end + RELIABLE_QUEUE_SIZE += 1 + return true +end + +local Net_Mixed_reliable_5: Net_Mixed_reliable_ClientApi = {} :: Net_Mixed_reliable_ClientApi +local MESSAGE_ID_Net_Mixed_reliable_5 = 5 + +local function Encode_Net_Mixed_reliable_5(data) + local totalSize = 1 + totalSize += 1 + totalSize += 1 + local vectorValue_1 = data.position + totalSize += 12 + local vectorValue_2 = data.velocity + totalSize += 12 + local stringLen_3 = #data.name + if stringLen_3 > 4294967295 then + error(ERR_SCHEMA) + end + totalSize += VarUIntSize(stringLen_3) + stringLen_3 + totalSize += 1 + totalSize += 1 + totalSize += 1 + local payload = create(totalSize) + writeu8(payload, 0, MESSAGE_ID_Net_Mixed_reliable_5) + local offset = 1 + writeu8(payload, offset, data.id) + offset += 1 + writeu8(payload, offset, data.health) + offset += 1 + local vectorValue_4 = data.position + writef32(payload, offset, vectorValue_4.X) + offset += 4 + writef32(payload, offset, vectorValue_4.Y) + offset += 4 + writef32(payload, offset, vectorValue_4.Z) + offset += 4 + local vectorValue_5 = data.velocity + writef32(payload, offset, vectorValue_5.X) + offset += 4 + writef32(payload, offset, vectorValue_5.Y) + offset += 4 + writef32(payload, offset, vectorValue_5.Z) + offset += 4 + offset = WriteVarUInt(payload, offset, stringLen_3) + writestring(payload, offset, data.name) + offset += stringLen_3 + writeu8(payload, offset, data.isRunning and 1 or 0) + offset += 1 + writeu8(payload, offset, data.animation) + offset += 1 + writeu8(payload, offset, data.team) + offset += 1 + return payload +end + +local Handler_Net_Mixed_reliable_5: (( + data: { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, + } +) -> ())? = + nil +local HandlerToken_Net_Mixed_reliable_5 = 0 + +local function Skip_Net_Mixed_reliable_5(payload, payloadLength, offset) + local skipNext_1 = offset + 1 + if skipNext_1 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipNext_1 + local skipNext_2 = offset + 1 + if skipNext_2 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipNext_2 + local skipVectorNext_3 = offset + 12 + if skipVectorNext_3 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipVectorNext_3 + local skipVectorNext_4 = offset + 12 + if skipVectorNext_4 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipVectorNext_4 + local skipStringLen_5 + skipStringLen_5, offset = ReadVarUInt(payload, payloadLength, offset) + if skipStringLen_5 > 4294967295 then + error(ERR_SCHEMA) + end + local skipStringNext_6 = offset + skipStringLen_5 + if skipStringNext_6 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipStringNext_6 + local skipBoolNext_7 = offset + 1 + if skipBoolNext_7 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipBoolNext_7 + local skipNext_8 = offset + 1 + if skipNext_8 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipNext_8 + local skipNext_9 = offset + 1 + if skipNext_9 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipNext_9 + return offset +end + +RELIABLE_SKIP[5] = Skip_Net_Mixed_reliable_5 + +function Net_Mixed_reliable_5.Fire(data: { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, +}): boolean + local __nr_totalSize = 1 + __nr_totalSize += 1 + __nr_totalSize += 1 + local vectorValue_1 = data.position + __nr_totalSize += 12 + local vectorValue_2 = data.velocity + __nr_totalSize += 12 + local stringLen_3 = #data.name + if stringLen_3 > 4294967295 then + error(ERR_SCHEMA) + end + __nr_totalSize += VarUIntSize(stringLen_3) + stringLen_3 + __nr_totalSize += 1 + __nr_totalSize += 1 + __nr_totalSize += 1 + local __nr_base = RELIABLE_CURSOR + local __nr_required = __nr_base + __nr_totalSize + if __nr_required > RELIABLE_ARENA_CAPACITY then + local nextCapacity = RELIABLE_ARENA_CAPACITY + while nextCapacity < __nr_required do + nextCapacity *= 2 + end + local nextArena = create(nextCapacity) + if RELIABLE_CURSOR > 0 then + copy(nextArena, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + RELIABLE_ARENA = nextArena + RELIABLE_ARENA_CAPACITY = nextCapacity + end + RELIABLE_CURSOR = __nr_required + local __nr_payload = RELIABLE_ARENA + writeu8(__nr_payload, __nr_base, MESSAGE_ID_Net_Mixed_reliable_5) + local __nr_p = __nr_base + 1 + writeu8(__nr_payload, __nr_p, data.id) + __nr_p += 1 + writeu8(__nr_payload, __nr_p, data.health) + __nr_p += 1 + local vectorValue_4 = data.position + writef32(__nr_payload, __nr_p, vectorValue_4.X) + __nr_p += 4 + writef32(__nr_payload, __nr_p, vectorValue_4.Y) + __nr_p += 4 + writef32(__nr_payload, __nr_p, vectorValue_4.Z) + __nr_p += 4 + local vectorValue_5 = data.velocity + writef32(__nr_payload, __nr_p, vectorValue_5.X) + __nr_p += 4 + writef32(__nr_payload, __nr_p, vectorValue_5.Y) + __nr_p += 4 + writef32(__nr_payload, __nr_p, vectorValue_5.Z) + __nr_p += 4 + __nr_p = WriteVarUInt(__nr_payload, __nr_p, stringLen_3) + writestring(__nr_payload, __nr_p, data.name) + __nr_p += stringLen_3 + writeu8(__nr_payload, __nr_p, data.isRunning and 1 or 0) + __nr_p += 1 + writeu8(__nr_payload, __nr_p, data.animation) + __nr_p += 1 + writeu8(__nr_payload, __nr_p, data.team) + __nr_p += 1 + RELIABLE_QUEUE_SIZE += 1 + return true +end + +local Net_SingleValue_reliable_6: Net_SingleValue_reliable_ClientApi = {} :: Net_SingleValue_reliable_ClientApi +local MESSAGE_ID_Net_SingleValue_reliable_6 = 6 + +local function Encode_Net_SingleValue_reliable_6(data) + local totalSize = 2 + local payload = create(totalSize) + writeu8(payload, 0, MESSAGE_ID_Net_SingleValue_reliable_6) + local offset = 1 + local fixedWriteBase_1 = offset + writeu8(payload, fixedWriteBase_1, data) + return payload +end + +local Handler_Net_SingleValue_reliable_6: ((data: u8) -> ())? = nil +local HandlerToken_Net_SingleValue_reliable_6 = 0 + +local function Skip_Net_SingleValue_reliable_6(payload, payloadLength, offset) + local skipNext_1 = offset + 1 + if skipNext_1 > payloadLength then + error(ERR_BOUNDS) + end + offset = skipNext_1 + return offset +end + +RELIABLE_SKIP[6] = Skip_Net_SingleValue_reliable_6 + +function Net_SingleValue_reliable_6.Fire(data: u8): boolean + local __nr_totalSize = 2 + local __nr_base = RELIABLE_CURSOR + local __nr_required = __nr_base + __nr_totalSize + if __nr_required > RELIABLE_ARENA_CAPACITY then + local nextCapacity = RELIABLE_ARENA_CAPACITY + while nextCapacity < __nr_required do + nextCapacity *= 2 + end + local nextArena = create(nextCapacity) + if RELIABLE_CURSOR > 0 then + copy(nextArena, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + RELIABLE_ARENA = nextArena + RELIABLE_ARENA_CAPACITY = nextCapacity + end + RELIABLE_CURSOR = __nr_required + local __nr_payload = RELIABLE_ARENA + writeu8(__nr_payload, __nr_base, MESSAGE_ID_Net_SingleValue_reliable_6) + local __nr_p = __nr_base + 1 + local fixedWriteBase_1 = __nr_p + writeu8(__nr_payload, fixedWriteBase_1, data) + RELIABLE_QUEUE_SIZE += 1 + return true +end + +local function StepReplication() + FlushReliable() + FlushUnreliable() +end + +RunService.Heartbeat:Connect(StepReplication) + +Reliable.OnClientEvent:Connect(function(payload) + if typeof(payload) ~= "buffer" then + return + end + local payloadLength = len(payload) + if payloadLength < 1 then + return + end + local offset = 0 + while offset < payloadLength do + local messageId = readu8(payload, offset) + offset += 1 + local segmentCount = 1 + local dispatch + if messageId == SEGMENT_MARKER_ID then + if offset >= payloadLength then + return + end + messageId = readu8(payload, offset) + offset += 1 + dispatch = RELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + segmentCount, offset = ReadVarUInt(payload, payloadLength, offset) + if segmentCount < 1 then + return + end + else + dispatch = RELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + end + for _segmentIndex = 1, segmentCount do + local nextOffset = dispatch(payload, payloadLength, offset) + offset = nextOffset + end + end +end) + +Unreliable.OnClientEvent:Connect(function(payload) + if typeof(payload) ~= "buffer" then + return + end + local payloadLength = len(payload) + if payloadLength < 1 then + return + end + local offset = 0 + while offset < payloadLength do + local messageId = readu8(payload, offset) + offset += 1 + local segmentCount = 1 + local dispatch + if messageId == SEGMENT_MARKER_ID then + if offset >= payloadLength then + return + end + messageId = readu8(payload, offset) + offset += 1 + dispatch = UNRELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + segmentCount, offset = ReadVarUInt(payload, payloadLength, offset) + if segmentCount < 1 then + return + end + else + dispatch = UNRELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + end + for _segmentIndex = 1, segmentCount do + local nextOffset = dispatch(payload, payloadLength, offset) + offset = nextOffset + end + end +end) + +FunctionRemote.OnClientInvoke = function(payload, ...) + if typeof(payload) ~= "buffer" then + error(ERR_TYPE) + end + local payloadLength = len(payload) + if payloadLength < 1 then + error(ERR_BOUNDS) + end + local messageId = readu8(payload, 0) + local dispatch = FUNCTION_DISPATCH[messageId] + if dispatch == nil then + error(ERR_SCHEMA) + end + return dispatch(payload, payloadLength) +end + +local Stats: NetStats = { + GetReliableDropped = function() + return RELIABLE_DROPPED + end, + GetUnreliableDropped = function() + return UNRELIABLE_DROPPED + end, + GetReliableQueueSize = function() + return RELIABLE_QUEUE_SIZE + end, + GetUnreliableQueueSize = function() + return UNRELIABLE_QUEUE_SIZE + end, +} + +local Net = { + Booleans = Net_Booleans_reliable_1, + Entities = Net_Entities_reliable_2, + Vectors = Net_Vectors_reliable_3, + Strings = Net_Strings_reliable_4, + Mixed = Net_Mixed_reliable_5, + SingleValue = Net_SingleValue_reliable_6, +} + +Net.StepReplication = StepReplication +Net.Stats = Stats +local NetTyped: NetRoot = Net + +return table.freeze(NetTyped) diff --git a/benchmark/src/shared/modes/netray_compile_gen/Server.luau b/benchmark/src/shared/modes/netray_compile_gen/Server.luau new file mode 100644 index 0000000..24dd775 --- /dev/null +++ b/benchmark/src/shared/modes/netray_compile_gen/Server.luau @@ -0,0 +1,976 @@ +--!strict +--!native +--!optimize 2 +--!nolint LocalShadow +--#selene: allow(shadowing) +-- Name: Server +-- Purpose: Generated runtime for server networking +-- Generated-by: NetRay-Compiler +-- Version: 0.1.1 + +local Types = require(script.Parent.Types) +type u8 = typeof(Types.u8) +type i8 = typeof(Types.i8) +type u16 = typeof(Types.u16) +type i16 = typeof(Types.i16) +type u32 = typeof(Types.u32) +type i32 = typeof(Types.i32) +type f16 = typeof(Types.f16) +type f32 = typeof(Types.f32) +type f64 = typeof(Types.f64) +type NetRoot = typeof(Types.NetServer) +type NetStats = typeof(Types.NetStats) +type Connection = typeof(Types.Connection) +type Net_Booleans_reliable_ServerApi = typeof(Types.Net_Booleans_reliable_ServerApi) +type Net_Booleans_reliable_ServerEventCallback = typeof(Types.Net_Booleans_reliable_ServerEventCallback) +type Net_Entities_reliable_ServerApi = typeof(Types.Net_Entities_reliable_ServerApi) +type Net_Entities_reliable_ServerEventCallback = typeof(Types.Net_Entities_reliable_ServerEventCallback) +type Net_Vectors_reliable_ServerApi = typeof(Types.Net_Vectors_reliable_ServerApi) +type Net_Vectors_reliable_ServerEventCallback = typeof(Types.Net_Vectors_reliable_ServerEventCallback) +type Net_Strings_reliable_ServerApi = typeof(Types.Net_Strings_reliable_ServerApi) +type Net_Strings_reliable_ServerEventCallback = typeof(Types.Net_Strings_reliable_ServerEventCallback) +type Net_Mixed_reliable_ServerApi = typeof(Types.Net_Mixed_reliable_ServerApi) +type Net_Mixed_reliable_ServerEventCallback = typeof(Types.Net_Mixed_reliable_ServerEventCallback) +type Net_SingleValue_reliable_ServerApi = typeof(Types.Net_SingleValue_reliable_ServerApi) +type Net_SingleValue_reliable_ServerEventCallback = typeof(Types.Net_SingleValue_reliable_ServerEventCallback) + +local Players = game:GetService("Players") +local ReplicatedStorage = game:GetService("ReplicatedStorage") +local RunService = game:GetService("RunService") + +local function FindOrCreateRemote(className, remoteName) + local existing = ReplicatedStorage:FindFirstChild(remoteName) + if existing ~= nil then + if existing.ClassName ~= className then + error(("Remote '%s' exists as '%s', expected '%s'"):format(remoteName, existing.ClassName, className)) + end + return existing + end + local remote = Instance.new(className) + remote.Name = remoteName + remote.Parent = ReplicatedStorage + return remote +end + +local Reliable = FindOrCreateRemote("RemoteEvent", "NetRay_RELIABLE") :: RemoteEvent +local Unreliable = FindOrCreateRemote("UnreliableRemoteEvent", "NetRay_UNRELIABLE") :: UnreliableRemoteEvent +local FunctionRemote = FindOrCreateRemote("RemoteFunction", "NetRay_FUNCTION") :: RemoteFunction + +local function WithCount(...) + return select("#", ...), ... +end + +local DEFAULT_ARENA_CAPACITY = 65536 +local create = buffer.create +local copy = buffer.copy +local len = buffer.len +local writeu8 = buffer.writeu8 + +local readu8 = buffer.readu8 + +local readf32 = buffer.readf32 +local readstring = buffer.readstring +local band = bit32.band +local bor = bit32.bor +local rshift = bit32.rshift +local lshift = bit32.lshift + +local ERR_BOUNDS = "E_BOUNDS" +local ERR_SCHEMA = "E_SCHEMA" +local ERR_TAG = "E_TAG" +local ERR_TYPE = "E_TYPE" +local SEGMENT_MARKER_ID = 0 + +local function VarUIntSize(value) + if value < 128 then + return 1 + end + if value < 16384 then + return 2 + end + if value < 2097152 then + return 3 + end + if value < 268435456 then + return 4 + end + return 5 +end + +local function WriteVarUInt(payload, offset, value) + if value < 128 then + writeu8(payload, offset, value) + return offset + 1 + end + if value < 16384 then + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, band(rshift(value, 7), 127)) + return offset + 2 + end + if value < 2097152 then + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, bor(band(rshift(value, 7), 127), 128)) + writeu8(payload, offset + 2, band(rshift(value, 14), 127)) + return offset + 3 + end + if value < 268435456 then + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, bor(band(rshift(value, 7), 127), 128)) + writeu8(payload, offset + 2, bor(band(rshift(value, 14), 127), 128)) + writeu8(payload, offset + 3, band(rshift(value, 21), 127)) + return offset + 4 + end + writeu8(payload, offset, bor(band(value, 127), 128)) + writeu8(payload, offset + 1, bor(band(rshift(value, 7), 127), 128)) + writeu8(payload, offset + 2, bor(band(rshift(value, 14), 127), 128)) + writeu8(payload, offset + 3, bor(band(rshift(value, 21), 127), 128)) + writeu8(payload, offset + 4, band(rshift(value, 28), 15)) + return offset + 5 +end + +local function ReadVarUInt(payload, payloadLength, offset) + local next1 = offset + 1 + if next1 > payloadLength then + error(ERR_BOUNDS) + end + local b1 = readu8(payload, offset) + if b1 < 128 then + return b1, next1 + end + local next2 = next1 + 1 + if next2 > payloadLength then + error(ERR_BOUNDS) + end + local b2 = readu8(payload, next1) + local value = band(b1, 127) + lshift(band(b2, 127), 7) + if b2 < 128 then + return value, next2 + end + local next3 = next2 + 1 + if next3 > payloadLength then + error(ERR_BOUNDS) + end + local b3 = readu8(payload, next2) + value += lshift(band(b3, 127), 14) + if b3 < 128 then + return value, next3 + end + local next4 = next3 + 1 + if next4 > payloadLength then + error(ERR_BOUNDS) + end + local b4 = readu8(payload, next3) + value += lshift(band(b4, 127), 21) + if b4 < 128 then + return value, next4 + end + local next5 = next4 + 1 + if next5 > payloadLength then + error(ERR_BOUNDS) + end + local b5 = readu8(payload, next4) + if b5 >= 16 then + error(ERR_SCHEMA) + end + value += lshift(b5, 28) + return value, next5 +end + +local function CreateConnection(disconnectFn: () -> ()): Connection + local active = true + local connection = {} :: any + connection.Connected = true + function connection:Disconnect() + if not active then + return + end + active = false + connection.Connected = false + disconnectFn() + end + return connection :: Connection +end + +local MAX_RELIABLE_ID = 6 +local MAX_UNRELIABLE_ID = 0 +local MAX_FUNCTION_ID = 0 +local RELIABLE_DISPATCH = table.create(MAX_RELIABLE_ID) +local UNRELIABLE_DISPATCH = table.create(MAX_UNRELIABLE_ID) +local RELIABLE_SKIP = table.create(MAX_RELIABLE_ID) +local UNRELIABLE_SKIP = table.create(MAX_UNRELIABLE_ID) +local FUNCTION_DISPATCH = table.create(MAX_FUNCTION_ID) + +local RELIABLE_ARENA_CAPACITY = DEFAULT_ARENA_CAPACITY +local RELIABLE_ARENA = create(RELIABLE_ARENA_CAPACITY) +local RELIABLE_CURSOR = 0 +local RELIABLE_QUEUE_SIZE = 0 +local RELIABLE_DROPPED = 0 +local RELIABLE_SEGMENT_HEADER_BYTES = 2 +local RELIABLE_SEGMENT_MIN_COUNT = 4 +local RELIABLE_SEGMENT_MIN_TAIL_BYTES = 3 + +local function FlushReliable() + if RELIABLE_CURSOR == 0 then + RELIABLE_QUEUE_SIZE = 0 + return + end + local payloadToSend = create(RELIABLE_CURSOR) + local payloadOffset = RELIABLE_CURSOR + if RELIABLE_QUEUE_SIZE >= RELIABLE_SEGMENT_MIN_COUNT then + -- Only scan runs when the batch is large enough to amortize segment headers. + local sourceOffset = 0 + payloadOffset = 0 + while sourceOffset < RELIABLE_CURSOR do + local runStart = sourceOffset + local messageId = readu8(RELIABLE_ARENA, sourceOffset) + sourceOffset += 1 + local skip = RELIABLE_SKIP[messageId] + local firstBodyEnd = skip(RELIABLE_ARENA, RELIABLE_CURSOR, sourceOffset) + local runEnd = firstBodyEnd + local runCount = 1 + if RELIABLE_CURSOR - firstBodyEnd < RELIABLE_SEGMENT_MIN_TAIL_BYTES then + -- Remaining bytes cannot fit enough repeated IDs to make segmentation pay off. + local tailSize = RELIABLE_CURSOR - runStart + if tailSize > 0 then + copy(payloadToSend, payloadOffset, RELIABLE_ARENA, runStart, tailSize) + payloadOffset += tailSize + end + break + end + while runEnd < RELIABLE_CURSOR do + local nextMessageId = readu8(RELIABLE_ARENA, runEnd) + if nextMessageId ~= messageId then + break + end + local nextBodyStart = runEnd + 1 + runEnd = skip(RELIABLE_ARENA, RELIABLE_CURSOR, nextBodyStart) + runCount += 1 + end + if runCount * 1 > RELIABLE_SEGMENT_HEADER_BYTES + VarUIntSize(runCount) then + writeu8(payloadToSend, payloadOffset, SEGMENT_MARKER_ID) + payloadOffset += 1 + writeu8(payloadToSend, payloadOffset, messageId) + payloadOffset += 1 + payloadOffset = WriteVarUInt(payloadToSend, payloadOffset, runCount) + local copyCursor = sourceOffset + local bodyEnd = firstBodyEnd + while true do + local bodySize = bodyEnd - copyCursor + if bodySize > 0 then + copy(payloadToSend, payloadOffset, RELIABLE_ARENA, copyCursor, bodySize) + payloadOffset += bodySize + end + if bodyEnd >= runEnd then + break + end + copyCursor = bodyEnd + 1 + bodyEnd = skip(RELIABLE_ARENA, RELIABLE_CURSOR, copyCursor) + end + else + local runSize = runEnd - runStart + if runSize > 0 then + copy(payloadToSend, payloadOffset, RELIABLE_ARENA, runStart, runSize) + payloadOffset += runSize + end + end + sourceOffset = runEnd + end + else + -- For tiny batches, a direct copy is cheaper than any merge scan. + copy(payloadToSend, 0, RELIABLE_ARENA, 0, RELIABLE_CURSOR) + end + if payloadOffset < RELIABLE_CURSOR then + local compactPayload = create(payloadOffset) + copy(compactPayload, 0, payloadToSend, 0, payloadOffset) + payloadToSend = compactPayload + end + Reliable:FireAllClients(payloadToSend) + RELIABLE_CURSOR = 0 + RELIABLE_QUEUE_SIZE = 0 +end + +local UNRELIABLE_ARENA_CAPACITY = DEFAULT_ARENA_CAPACITY +local UNRELIABLE_ARENA = create(UNRELIABLE_ARENA_CAPACITY) +local UNRELIABLE_CURSOR = 0 +local UNRELIABLE_QUEUE_SIZE = 0 +local UNRELIABLE_DROPPED = 0 +local UNRELIABLE_SEGMENT_HEADER_BYTES = 2 +local UNRELIABLE_SEGMENT_MIN_COUNT = 4 +local UNRELIABLE_SEGMENT_MIN_TAIL_BYTES = 3 + +local function FlushUnreliable() + if UNRELIABLE_CURSOR == 0 then + UNRELIABLE_QUEUE_SIZE = 0 + return + end + local payloadToSend = create(UNRELIABLE_CURSOR) + local payloadOffset = UNRELIABLE_CURSOR + if UNRELIABLE_QUEUE_SIZE >= UNRELIABLE_SEGMENT_MIN_COUNT then + -- Only scan runs when the batch is large enough to amortize segment headers. + local sourceOffset = 0 + payloadOffset = 0 + while sourceOffset < UNRELIABLE_CURSOR do + local runStart = sourceOffset + local messageId = readu8(UNRELIABLE_ARENA, sourceOffset) + sourceOffset += 1 + local skip = UNRELIABLE_SKIP[messageId] + local firstBodyEnd = skip(UNRELIABLE_ARENA, UNRELIABLE_CURSOR, sourceOffset) + local runEnd = firstBodyEnd + local runCount = 1 + if UNRELIABLE_CURSOR - firstBodyEnd < UNRELIABLE_SEGMENT_MIN_TAIL_BYTES then + -- Remaining bytes cannot fit enough repeated IDs to make segmentation pay off. + local tailSize = UNRELIABLE_CURSOR - runStart + if tailSize > 0 then + copy(payloadToSend, payloadOffset, UNRELIABLE_ARENA, runStart, tailSize) + payloadOffset += tailSize + end + break + end + while runEnd < UNRELIABLE_CURSOR do + local nextMessageId = readu8(UNRELIABLE_ARENA, runEnd) + if nextMessageId ~= messageId then + break + end + local nextBodyStart = runEnd + 1 + runEnd = skip(UNRELIABLE_ARENA, UNRELIABLE_CURSOR, nextBodyStart) + runCount += 1 + end + if runCount * 1 > UNRELIABLE_SEGMENT_HEADER_BYTES + VarUIntSize(runCount) then + writeu8(payloadToSend, payloadOffset, SEGMENT_MARKER_ID) + payloadOffset += 1 + writeu8(payloadToSend, payloadOffset, messageId) + payloadOffset += 1 + payloadOffset = WriteVarUInt(payloadToSend, payloadOffset, runCount) + local copyCursor = sourceOffset + local bodyEnd = firstBodyEnd + while true do + local bodySize = bodyEnd - copyCursor + if bodySize > 0 then + copy(payloadToSend, payloadOffset, UNRELIABLE_ARENA, copyCursor, bodySize) + payloadOffset += bodySize + end + if bodyEnd >= runEnd then + break + end + copyCursor = bodyEnd + 1 + bodyEnd = skip(UNRELIABLE_ARENA, UNRELIABLE_CURSOR, copyCursor) + end + else + local runSize = runEnd - runStart + if runSize > 0 then + copy(payloadToSend, payloadOffset, UNRELIABLE_ARENA, runStart, runSize) + payloadOffset += runSize + end + end + sourceOffset = runEnd + end + else + -- For tiny batches, a direct copy is cheaper than any merge scan. + copy(payloadToSend, 0, UNRELIABLE_ARENA, 0, UNRELIABLE_CURSOR) + end + if payloadOffset < UNRELIABLE_CURSOR then + local compactPayload = create(payloadOffset) + copy(compactPayload, 0, payloadToSend, 0, payloadOffset) + payloadToSend = compactPayload + end + Unreliable:FireAllClients(payloadToSend) + UNRELIABLE_CURSOR = 0 + UNRELIABLE_QUEUE_SIZE = 0 +end + +local Net_Booleans_reliable_1: Net_Booleans_reliable_ServerApi = {} :: Net_Booleans_reliable_ServerApi +local MESSAGE_ID_Net_Booleans_reliable_1 = 1 + +local function Decode_Net_Booleans_reliable_1(payload, payloadLength, offset) + local decoded_data + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + local arrayValues_6 = table.create(arrayCount_2) + local arrayPackedByteCount_8 = ((arrayCount_2 + 7) // 8) + local arrayPackedCursor_9 = varCursor_4 + local arrayPackedNext_10 = arrayPackedCursor_9 + arrayPackedByteCount_8 + if arrayPackedNext_10 > payloadLength then + error(ERR_BOUNDS) + end + local arrayUnpackIndex_12 = 1 + for arrayPackedIndex_11 = 1, arrayPackedByteCount_8 do + local arrayPackedByte_13 = readu8(payload, arrayPackedCursor_9) + arrayPackedCursor_9 += 1 + local arrayBitMask_15 = 1 + for arrayBitIndex_14 = 1, 8 do + if arrayUnpackIndex_12 > arrayCount_2 then + break + end + arrayValues_6[arrayUnpackIndex_12] = band(arrayPackedByte_13, arrayBitMask_15) ~= 0 + arrayUnpackIndex_12 += 1 + arrayBitMask_15 = lshift(arrayBitMask_15, 1) + end + end + varCursor_4 = arrayPackedCursor_9 + decoded_data = arrayValues_6 + offset = varCursor_4 + return decoded_data, offset +end + +local Handler_Net_Booleans_reliable_1: ((player: Player, data: { boolean }) -> ())? = nil +local HandlerToken_Net_Booleans_reliable_1 = 0 + +function Net_Booleans_reliable_1.On(callback: Net_Booleans_reliable_ServerEventCallback): Connection + if type(callback) ~= "function" then + error(ERR_TYPE) + end + HandlerToken_Net_Booleans_reliable_1 += 1 + local token = HandlerToken_Net_Booleans_reliable_1 + Handler_Net_Booleans_reliable_1 = callback + return CreateConnection(function() + if HandlerToken_Net_Booleans_reliable_1 == token then + HandlerToken_Net_Booleans_reliable_1 += 1 + Handler_Net_Booleans_reliable_1 = nil + end + end) +end + +RELIABLE_DISPATCH[1] = function(player, payload, payloadLength, offset) + local decoded_data, nextOffset = Decode_Net_Booleans_reliable_1(payload, payloadLength, offset) + local callback = Handler_Net_Booleans_reliable_1 + if callback == nil then + return nextOffset + end + callback(player, decoded_data) + return nextOffset +end + +local Net_Entities_reliable_2: Net_Entities_reliable_ServerApi = {} :: Net_Entities_reliable_ServerApi +local MESSAGE_ID_Net_Entities_reliable_2 = 2 + +local function Decode_Net_Entities_reliable_2(payload, payloadLength, offset) + local decoded_data + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + local arrayValues_6 = table.create(arrayCount_2) + for arrayIndex_5 = 1, arrayCount_2 do + local arrayValue_7 + local structValue_8 = {} + local structField_9 + local fieldNext_10 = varCursor_4 + 1 + if fieldNext_10 > payloadLength then + error(ERR_BOUNDS) + end + structField_9 = readu8(payload, varCursor_4) :: u8 + varCursor_4 = fieldNext_10 + structValue_8["id"] = structField_9 + local structField_11 + local fieldNext_12 = varCursor_4 + 1 + if fieldNext_12 > payloadLength then + error(ERR_BOUNDS) + end + structField_11 = readu8(payload, varCursor_4) :: u8 + varCursor_4 = fieldNext_12 + structValue_8["x"] = structField_11 + local structField_13 + local fieldNext_14 = varCursor_4 + 1 + if fieldNext_14 > payloadLength then + error(ERR_BOUNDS) + end + structField_13 = readu8(payload, varCursor_4) :: u8 + varCursor_4 = fieldNext_14 + structValue_8["y"] = structField_13 + local structField_15 + local fieldNext_16 = varCursor_4 + 1 + if fieldNext_16 > payloadLength then + error(ERR_BOUNDS) + end + structField_15 = readu8(payload, varCursor_4) :: u8 + varCursor_4 = fieldNext_16 + structValue_8["z"] = structField_15 + local structField_17 + local fieldNext_18 = varCursor_4 + 1 + if fieldNext_18 > payloadLength then + error(ERR_BOUNDS) + end + structField_17 = readu8(payload, varCursor_4) :: u8 + varCursor_4 = fieldNext_18 + structValue_8["orientation"] = structField_17 + local structField_19 + local fieldNext_20 = varCursor_4 + 1 + if fieldNext_20 > payloadLength then + error(ERR_BOUNDS) + end + structField_19 = readu8(payload, varCursor_4) :: u8 + varCursor_4 = fieldNext_20 + structValue_8["animation"] = structField_19 + arrayValue_7 = structValue_8 + arrayValues_6[arrayIndex_5] = arrayValue_7 + end + decoded_data = arrayValues_6 + offset = varCursor_4 + return decoded_data, offset +end + +local Handler_Net_Entities_reliable_2: ((player: Player, data: { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } }) -> ())? = + nil +local HandlerToken_Net_Entities_reliable_2 = 0 + +function Net_Entities_reliable_2.On(callback: Net_Entities_reliable_ServerEventCallback): Connection + if type(callback) ~= "function" then + error(ERR_TYPE) + end + HandlerToken_Net_Entities_reliable_2 += 1 + local token = HandlerToken_Net_Entities_reliable_2 + Handler_Net_Entities_reliable_2 = callback + return CreateConnection(function() + if HandlerToken_Net_Entities_reliable_2 == token then + HandlerToken_Net_Entities_reliable_2 += 1 + Handler_Net_Entities_reliable_2 = nil + end + end) +end + +RELIABLE_DISPATCH[2] = function(player, payload, payloadLength, offset) + local decoded_data, nextOffset = Decode_Net_Entities_reliable_2(payload, payloadLength, offset) + local callback = Handler_Net_Entities_reliable_2 + if callback == nil then + return nextOffset + end + callback(player, decoded_data) + return nextOffset +end + +local Net_Vectors_reliable_3: Net_Vectors_reliable_ServerApi = {} :: Net_Vectors_reliable_ServerApi +local MESSAGE_ID_Net_Vectors_reliable_3 = 3 + +local function Decode_Net_Vectors_reliable_3(payload, payloadLength, offset) + local decoded_data + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + local arrayValues_6 = table.create(arrayCount_2) + for arrayIndex_5 = 1, arrayCount_2 do + local arrayValue_7 + local vectorNext_8 = varCursor_4 + 12 + if vectorNext_8 > payloadLength then + error(ERR_BOUNDS) + end + local vectorX_9 = readf32(payload, varCursor_4) + local vectorY_10 = readf32(payload, varCursor_4 + 4) + local vectorZ_11 = readf32(payload, varCursor_4 + 8) + arrayValue_7 = Vector3.new(vectorX_9, vectorY_10, vectorZ_11) + varCursor_4 = vectorNext_8 + arrayValues_6[arrayIndex_5] = arrayValue_7 + end + decoded_data = arrayValues_6 + offset = varCursor_4 + return decoded_data, offset +end + +local Handler_Net_Vectors_reliable_3: ((player: Player, data: { Vector3 }) -> ())? = nil +local HandlerToken_Net_Vectors_reliable_3 = 0 + +function Net_Vectors_reliable_3.On(callback: Net_Vectors_reliable_ServerEventCallback): Connection + if type(callback) ~= "function" then + error(ERR_TYPE) + end + HandlerToken_Net_Vectors_reliable_3 += 1 + local token = HandlerToken_Net_Vectors_reliable_3 + Handler_Net_Vectors_reliable_3 = callback + return CreateConnection(function() + if HandlerToken_Net_Vectors_reliable_3 == token then + HandlerToken_Net_Vectors_reliable_3 += 1 + Handler_Net_Vectors_reliable_3 = nil + end + end) +end + +RELIABLE_DISPATCH[3] = function(player, payload, payloadLength, offset) + local decoded_data, nextOffset = Decode_Net_Vectors_reliable_3(payload, payloadLength, offset) + local callback = Handler_Net_Vectors_reliable_3 + if callback == nil then + return nextOffset + end + callback(player, decoded_data) + return nextOffset +end + +local Net_Strings_reliable_4: Net_Strings_reliable_ServerApi = {} :: Net_Strings_reliable_ServerApi +local MESSAGE_ID_Net_Strings_reliable_4 = 4 + +local function Decode_Net_Strings_reliable_4(payload, payloadLength, offset) + local decoded_data + local prefixCursor_1 = offset + local arrayCount_2 + arrayCount_2, prefixCursor_1 = ReadVarUInt(payload, payloadLength, prefixCursor_1) + local fixedCursor_3 = prefixCursor_1 + local varCursor_4 = fixedCursor_3 + if varCursor_4 > payloadLength then + error(ERR_BOUNDS) + end + local arrayValues_6 = table.create(arrayCount_2) + for arrayIndex_5 = 1, arrayCount_2 do + local arrayValue_7 + local stringLen_8 + stringLen_8, varCursor_4 = ReadVarUInt(payload, payloadLength, varCursor_4) + if stringLen_8 > 4294967295 then + error(ERR_SCHEMA) + end + local stringNext_9 = varCursor_4 + stringLen_8 + if stringNext_9 > payloadLength then + error(ERR_BOUNDS) + end + arrayValue_7 = readstring(payload, varCursor_4, stringLen_8) + varCursor_4 = stringNext_9 + arrayValues_6[arrayIndex_5] = arrayValue_7 + end + decoded_data = arrayValues_6 + offset = varCursor_4 + return decoded_data, offset +end + +local Handler_Net_Strings_reliable_4: ((player: Player, data: { string }) -> ())? = nil +local HandlerToken_Net_Strings_reliable_4 = 0 + +function Net_Strings_reliable_4.On(callback: Net_Strings_reliable_ServerEventCallback): Connection + if type(callback) ~= "function" then + error(ERR_TYPE) + end + HandlerToken_Net_Strings_reliable_4 += 1 + local token = HandlerToken_Net_Strings_reliable_4 + Handler_Net_Strings_reliable_4 = callback + return CreateConnection(function() + if HandlerToken_Net_Strings_reliable_4 == token then + HandlerToken_Net_Strings_reliable_4 += 1 + Handler_Net_Strings_reliable_4 = nil + end + end) +end + +RELIABLE_DISPATCH[4] = function(player, payload, payloadLength, offset) + local decoded_data, nextOffset = Decode_Net_Strings_reliable_4(payload, payloadLength, offset) + local callback = Handler_Net_Strings_reliable_4 + if callback == nil then + return nextOffset + end + callback(player, decoded_data) + return nextOffset +end + +local Net_Mixed_reliable_5: Net_Mixed_reliable_ServerApi = {} :: Net_Mixed_reliable_ServerApi +local MESSAGE_ID_Net_Mixed_reliable_5 = 5 + +local function Decode_Net_Mixed_reliable_5(payload, payloadLength, offset) + local decoded_data + local structValue_1 = {} + local structField_2 + local fieldNext_3 = offset + 1 + if fieldNext_3 > payloadLength then + error(ERR_BOUNDS) + end + structField_2 = readu8(payload, offset) :: u8 + offset = fieldNext_3 + structValue_1["id"] = structField_2 + local structField_4 + local fieldNext_5 = offset + 1 + if fieldNext_5 > payloadLength then + error(ERR_BOUNDS) + end + structField_4 = readu8(payload, offset) :: u8 + offset = fieldNext_5 + structValue_1["health"] = structField_4 + local structField_6 + local vectorNext_7 = offset + 12 + if vectorNext_7 > payloadLength then + error(ERR_BOUNDS) + end + local vectorX_8 = readf32(payload, offset) + local vectorY_9 = readf32(payload, offset + 4) + local vectorZ_10 = readf32(payload, offset + 8) + structField_6 = Vector3.new(vectorX_8, vectorY_9, vectorZ_10) + offset = vectorNext_7 + structValue_1["position"] = structField_6 + local structField_11 + local vectorNext_12 = offset + 12 + if vectorNext_12 > payloadLength then + error(ERR_BOUNDS) + end + local vectorX_13 = readf32(payload, offset) + local vectorY_14 = readf32(payload, offset + 4) + local vectorZ_15 = readf32(payload, offset + 8) + structField_11 = Vector3.new(vectorX_13, vectorY_14, vectorZ_15) + offset = vectorNext_12 + structValue_1["velocity"] = structField_11 + local structField_16 + local stringLen_17 + stringLen_17, offset = ReadVarUInt(payload, payloadLength, offset) + if stringLen_17 > 4294967295 then + error(ERR_SCHEMA) + end + local stringNext_18 = offset + stringLen_17 + if stringNext_18 > payloadLength then + error(ERR_BOUNDS) + end + structField_16 = readstring(payload, offset, stringLen_17) + offset = stringNext_18 + structValue_1["name"] = structField_16 + local structField_19 + local boolNext_20 = offset + 1 + if boolNext_20 > payloadLength then + error(ERR_BOUNDS) + end + local rawBool_21 = readu8(payload, offset) + offset = boolNext_20 + structField_19 = rawBool_21 == 1 + structValue_1["isRunning"] = structField_19 + local structField_22 + local fieldNext_23 = offset + 1 + if fieldNext_23 > payloadLength then + error(ERR_BOUNDS) + end + structField_22 = readu8(payload, offset) :: u8 + offset = fieldNext_23 + structValue_1["animation"] = structField_22 + local structField_24 + local fieldNext_25 = offset + 1 + if fieldNext_25 > payloadLength then + error(ERR_BOUNDS) + end + structField_24 = readu8(payload, offset) :: u8 + offset = fieldNext_25 + structValue_1["team"] = structField_24 + decoded_data = structValue_1 + return decoded_data, offset +end + +local Handler_Net_Mixed_reliable_5: (( + player: Player, + data: { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, + } +) -> ())? = + nil +local HandlerToken_Net_Mixed_reliable_5 = 0 + +function Net_Mixed_reliable_5.On(callback: Net_Mixed_reliable_ServerEventCallback): Connection + if type(callback) ~= "function" then + error(ERR_TYPE) + end + HandlerToken_Net_Mixed_reliable_5 += 1 + local token = HandlerToken_Net_Mixed_reliable_5 + Handler_Net_Mixed_reliable_5 = callback + return CreateConnection(function() + if HandlerToken_Net_Mixed_reliable_5 == token then + HandlerToken_Net_Mixed_reliable_5 += 1 + Handler_Net_Mixed_reliable_5 = nil + end + end) +end + +RELIABLE_DISPATCH[5] = function(player, payload, payloadLength, offset) + local decoded_data, nextOffset = Decode_Net_Mixed_reliable_5(payload, payloadLength, offset) + local callback = Handler_Net_Mixed_reliable_5 + if callback == nil then + return nextOffset + end + callback(player, decoded_data) + return nextOffset +end + +local Net_SingleValue_reliable_6: Net_SingleValue_reliable_ServerApi = {} :: Net_SingleValue_reliable_ServerApi +local MESSAGE_ID_Net_SingleValue_reliable_6 = 6 + +local function Decode_Net_SingleValue_reliable_6(payload, payloadLength, offset) + local decoded_data + local fixedBlock_1 = offset + local fixedBlockNext_2 = fixedBlock_1 + 1 + if fixedBlockNext_2 > payloadLength then + error(ERR_BOUNDS) + end + offset = fixedBlockNext_2 + decoded_data = readu8(payload, fixedBlock_1) :: u8 + return decoded_data, offset +end + +local Handler_Net_SingleValue_reliable_6: ((player: Player, data: u8) -> ())? = nil +local HandlerToken_Net_SingleValue_reliable_6 = 0 + +function Net_SingleValue_reliable_6.On(callback: Net_SingleValue_reliable_ServerEventCallback): Connection + if type(callback) ~= "function" then + error(ERR_TYPE) + end + HandlerToken_Net_SingleValue_reliable_6 += 1 + local token = HandlerToken_Net_SingleValue_reliable_6 + Handler_Net_SingleValue_reliable_6 = callback + return CreateConnection(function() + if HandlerToken_Net_SingleValue_reliable_6 == token then + HandlerToken_Net_SingleValue_reliable_6 += 1 + Handler_Net_SingleValue_reliable_6 = nil + end + end) +end + +RELIABLE_DISPATCH[6] = function(player, payload, payloadLength, offset) + local decoded_data, nextOffset = Decode_Net_SingleValue_reliable_6(payload, payloadLength, offset) + local callback = Handler_Net_SingleValue_reliable_6 + if callback == nil then + return nextOffset + end + callback(player, decoded_data) + return nextOffset +end + +local function StepReplication() + FlushReliable() + FlushUnreliable() +end + +RunService.Heartbeat:Connect(StepReplication) + +Reliable.OnServerEvent:Connect(function(player, payload) + if typeof(payload) ~= "buffer" then + return + end + local payloadLength = len(payload) + if payloadLength < 1 then + return + end + local offset = 0 + while offset < payloadLength do + local messageId = readu8(payload, offset) + offset += 1 + local segmentCount = 1 + local dispatch + if messageId == SEGMENT_MARKER_ID then + if offset >= payloadLength then + return + end + messageId = readu8(payload, offset) + offset += 1 + dispatch = RELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + segmentCount, offset = ReadVarUInt(payload, payloadLength, offset) + if segmentCount < 1 then + return + end + else + dispatch = RELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + end + for _segmentIndex = 1, segmentCount do + local nextOffset = dispatch(player, payload, payloadLength, offset) + offset = nextOffset + end + end +end) + +Unreliable.OnServerEvent:Connect(function(player, payload) + if typeof(payload) ~= "buffer" then + return + end + local payloadLength = len(payload) + if payloadLength < 1 then + return + end + local offset = 0 + while offset < payloadLength do + local messageId = readu8(payload, offset) + offset += 1 + local segmentCount = 1 + local dispatch + if messageId == SEGMENT_MARKER_ID then + if offset >= payloadLength then + return + end + messageId = readu8(payload, offset) + offset += 1 + dispatch = UNRELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + segmentCount, offset = ReadVarUInt(payload, payloadLength, offset) + if segmentCount < 1 then + return + end + else + dispatch = UNRELIABLE_DISPATCH[messageId] + if dispatch == nil then + return + end + end + for _segmentIndex = 1, segmentCount do + local nextOffset = dispatch(player, payload, payloadLength, offset) + offset = nextOffset + end + end +end) + +FunctionRemote.OnServerInvoke = function(player, payload, ...) + if typeof(payload) ~= "buffer" then + error(ERR_TYPE) + end + local payloadLength = len(payload) + if payloadLength < 1 then + error(ERR_BOUNDS) + end + local messageId = readu8(payload, 0) + local dispatch = FUNCTION_DISPATCH[messageId] + if dispatch == nil then + error(ERR_SCHEMA) + end + return dispatch(player, payload, payloadLength) +end + +local Stats: NetStats = { + GetReliableDropped = function() + return RELIABLE_DROPPED + end, + GetUnreliableDropped = function() + return UNRELIABLE_DROPPED + end, + GetReliableQueueSize = function() + return RELIABLE_QUEUE_SIZE + end, + GetUnreliableQueueSize = function() + return UNRELIABLE_QUEUE_SIZE + end, +} + +local Net = { + Booleans = Net_Booleans_reliable_1, + Entities = Net_Entities_reliable_2, + Vectors = Net_Vectors_reliable_3, + Strings = Net_Strings_reliable_4, + Mixed = Net_Mixed_reliable_5, + SingleValue = Net_SingleValue_reliable_6, +} + +Net.StepReplication = StepReplication +Net.Stats = Stats +local NetTyped: NetRoot = Net + +return table.freeze(NetTyped) diff --git a/benchmark/src/shared/modes/netray_compile_gen/Types.luau b/benchmark/src/shared/modes/netray_compile_gen/Types.luau new file mode 100644 index 0000000..15403af --- /dev/null +++ b/benchmark/src/shared/modes/netray_compile_gen/Types.luau @@ -0,0 +1,259 @@ +--!strict +--!native +--!optimize 2 +-- Name: Types +-- Purpose: Generated Luau types for NetRay schema +-- Generated-by: NetRay-Compiler + +export type TypeBranding = "Off" | "On" +export type ActiveTypeBranding = "Off" +export type Brand = T & { __brand: B } +export type u8 = number +export type i8 = number +export type u16 = number +export type i16 = number +export type u32 = number +export type i32 = number +export type f16 = number +export type f32 = number +export type f64 = number + +export type Connection = { + Connected: boolean, + Disconnect: (self: Connection) -> (), +} + +export type EventSender1 = { + Fire: (T) -> boolean, +} +export type EventReceiver1 = { + On: ((T) -> ()) -> Connection, +} +export type EventDuplex1 = EventSender1 & EventReceiver1 + +export type NetStats = { + GetReliableDropped: () -> number, + GetUnreliableDropped: () -> number, + GetReliableQueueSize: () -> number, + GetUnreliableQueueSize: () -> number, +} + +export type Net_Booleans_reliable_Args = () -> { boolean } +export type Net_Booleans_reliable_TupleHandler = ({ boolean }) -> () +export type Net_Booleans_reliable_ClientEventCallback = (data: { boolean }) -> () +export type Net_Booleans_reliable_ServerEventCallback = (player: Player, data: { boolean }) -> () +export type Net_Booleans_reliable_ClientApi = { + Fire: ({ boolean }) -> boolean, +} +export type Net_Booleans_reliable_ServerApi = { + On: (Net_Booleans_reliable_ServerEventCallback) -> Connection, +} + +export type Net_Entities_reliable_Args = () -> { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } } +export type Net_Entities_reliable_TupleHandler = ( + { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } } +) -> () +export type Net_Entities_reliable_ClientEventCallback = ( + data: { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } } +) -> () +export type Net_Entities_reliable_ServerEventCallback = ( + player: Player, + data: { { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } } +) -> () +export type Net_Entities_reliable_ClientApi = { + Fire: ({ { id: u8, x: u8, y: u8, z: u8, orientation: u8, animation: u8 } }) -> boolean, +} +export type Net_Entities_reliable_ServerApi = { + On: (Net_Entities_reliable_ServerEventCallback) -> Connection, +} + +export type Net_Vectors_reliable_Args = () -> { Vector3 } +export type Net_Vectors_reliable_TupleHandler = ({ Vector3 }) -> () +export type Net_Vectors_reliable_ClientEventCallback = (data: { Vector3 }) -> () +export type Net_Vectors_reliable_ServerEventCallback = (player: Player, data: { Vector3 }) -> () +export type Net_Vectors_reliable_ClientApi = { + Fire: ({ Vector3 }) -> boolean, +} +export type Net_Vectors_reliable_ServerApi = { + On: (Net_Vectors_reliable_ServerEventCallback) -> Connection, +} + +export type Net_Strings_reliable_Args = () -> { string } +export type Net_Strings_reliable_TupleHandler = ({ string }) -> () +export type Net_Strings_reliable_ClientEventCallback = (data: { string }) -> () +export type Net_Strings_reliable_ServerEventCallback = (player: Player, data: { string }) -> () +export type Net_Strings_reliable_ClientApi = { + Fire: ({ string }) -> boolean, +} +export type Net_Strings_reliable_ServerApi = { + On: (Net_Strings_reliable_ServerEventCallback) -> Connection, +} + +export type Net_Mixed_reliable_Args = ( +) -> { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, +} +export type Net_Mixed_reliable_TupleHandler = ( + { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, + } +) -> () +export type Net_Mixed_reliable_ClientEventCallback = ( + data: { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, + } +) -> () +export type Net_Mixed_reliable_ServerEventCallback = ( + player: Player, + data: { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, + } +) -> () +export type Net_Mixed_reliable_ClientApi = { + Fire: ( + { + id: u8, + health: u8, + position: Vector3, + velocity: Vector3, + name: string, + isRunning: boolean, + animation: u8, + team: u8, + } + ) -> boolean, +} +export type Net_Mixed_reliable_ServerApi = { + On: (Net_Mixed_reliable_ServerEventCallback) -> Connection, +} + +export type Net_SingleValue_reliable_Args = () -> u8 +export type Net_SingleValue_reliable_TupleHandler = (u8) -> () +export type Net_SingleValue_reliable_ClientEventCallback = (data: u8) -> () +export type Net_SingleValue_reliable_ServerEventCallback = (player: Player, data: u8) -> () +export type Net_SingleValue_reliable_ClientApi = { + Fire: (u8) -> boolean, +} +export type Net_SingleValue_reliable_ServerApi = { + On: (Net_SingleValue_reliable_ServerEventCallback) -> Connection, +} + +export type NetClient = { + Booleans: Net_Booleans_reliable_ClientApi, + Entities: Net_Entities_reliable_ClientApi, + Vectors: Net_Vectors_reliable_ClientApi, + Strings: Net_Strings_reliable_ClientApi, + Mixed: Net_Mixed_reliable_ClientApi, + SingleValue: Net_SingleValue_reliable_ClientApi, + StepReplication: () -> (), + Stats: NetStats, +} + +export type NetServer = { + Booleans: Net_Booleans_reliable_ServerApi, + Entities: Net_Entities_reliable_ServerApi, + Vectors: Net_Vectors_reliable_ServerApi, + Strings: Net_Strings_reliable_ServerApi, + Mixed: Net_Mixed_reliable_ServerApi, + SingleValue: Net_SingleValue_reliable_ServerApi, + StepReplication: () -> (), + Stats: NetStats, +} + +export type Net = NetClient | NetServer + +local Types = { + u8 = (0 :: any) :: u8, + i8 = (0 :: any) :: i8, + u16 = (0 :: any) :: u16, + i16 = (0 :: any) :: i16, + u32 = (0 :: any) :: u32, + i32 = (0 :: any) :: i32, + f16 = (0 :: any) :: f16, + f32 = (0 :: any) :: f32, + f64 = (0 :: any) :: f64, + Connection = ({ Connected = true, Disconnect = function(_self) end } :: any) :: Connection, + NetStats = ({ + GetReliableDropped = function() + return 0 + end, + GetUnreliableDropped = function() + return 0 + end, + GetReliableQueueSize = function() + return 0 + end, + GetUnreliableQueueSize = function() + return 0 + end, + } :: any) :: NetStats, + Net_Booleans_reliable_ClientEventCallback = ( + (function(...) end) :: any + ) :: Net_Booleans_reliable_ClientEventCallback, + Net_Booleans_reliable_ServerEventCallback = ( + (function(...) end) :: any + ) :: Net_Booleans_reliable_ServerEventCallback, + Net_Booleans_reliable_ClientApi = ({} :: any) :: Net_Booleans_reliable_ClientApi, + Net_Booleans_reliable_ServerApi = ({} :: any) :: Net_Booleans_reliable_ServerApi, + Net_Entities_reliable_ClientEventCallback = ( + (function(...) end) :: any + ) :: Net_Entities_reliable_ClientEventCallback, + Net_Entities_reliable_ServerEventCallback = ( + (function(...) end) :: any + ) :: Net_Entities_reliable_ServerEventCallback, + Net_Entities_reliable_ClientApi = ({} :: any) :: Net_Entities_reliable_ClientApi, + Net_Entities_reliable_ServerApi = ({} :: any) :: Net_Entities_reliable_ServerApi, + Net_Vectors_reliable_ClientEventCallback = ((function(...) end) :: any) :: Net_Vectors_reliable_ClientEventCallback, + Net_Vectors_reliable_ServerEventCallback = ((function(...) end) :: any) :: Net_Vectors_reliable_ServerEventCallback, + Net_Vectors_reliable_ClientApi = ({} :: any) :: Net_Vectors_reliable_ClientApi, + Net_Vectors_reliable_ServerApi = ({} :: any) :: Net_Vectors_reliable_ServerApi, + Net_Strings_reliable_ClientEventCallback = ((function(...) end) :: any) :: Net_Strings_reliable_ClientEventCallback, + Net_Strings_reliable_ServerEventCallback = ((function(...) end) :: any) :: Net_Strings_reliable_ServerEventCallback, + Net_Strings_reliable_ClientApi = ({} :: any) :: Net_Strings_reliable_ClientApi, + Net_Strings_reliable_ServerApi = ({} :: any) :: Net_Strings_reliable_ServerApi, + Net_Mixed_reliable_ClientEventCallback = ((function(...) end) :: any) :: Net_Mixed_reliable_ClientEventCallback, + Net_Mixed_reliable_ServerEventCallback = ((function(...) end) :: any) :: Net_Mixed_reliable_ServerEventCallback, + Net_Mixed_reliable_ClientApi = ({} :: any) :: Net_Mixed_reliable_ClientApi, + Net_Mixed_reliable_ServerApi = ({} :: any) :: Net_Mixed_reliable_ServerApi, + Net_SingleValue_reliable_ClientEventCallback = ( + (function(...) end) :: any + ) :: Net_SingleValue_reliable_ClientEventCallback, + Net_SingleValue_reliable_ServerEventCallback = ( + (function(...) end) :: any + ) :: Net_SingleValue_reliable_ServerEventCallback, + Net_SingleValue_reliable_ClientApi = ({} :: any) :: Net_SingleValue_reliable_ClientApi, + Net_SingleValue_reliable_ServerApi = ({} :: any) :: Net_SingleValue_reliable_ServerApi, + NetClient = ({} :: any) :: NetClient, + NetServer = ({} :: any) :: NetServer, + Net = ({} :: any) :: Net, +} + +return table.freeze(Types) diff --git a/benchmark/src/shared/modes/packet.luau b/benchmark/src/shared/modes/packet.luau index 183503e..c1b0847 100644 --- a/benchmark/src/shared/modes/packet.luau +++ b/benchmark/src/shared/modes/packet.luau @@ -1,7 +1,7 @@ -- Suphi Packet benchmark mode. -- Wraps each Packet object so `fire` is a self-bound closure, -- matching the benchmark harness convention (event.fire or event.Fire). -local Packet = require(game:GetService("ReplicatedStorage").SuphiPacket) +local Packet = require(game:GetService("ReplicatedStorage").references.SuphiPacket) local function wrap(pkt) return { diff --git a/benchmark/src/shared/modes/satset.luau b/benchmark/src/shared/modes/satset.luau index bef8cb8..56d085d 100644 --- a/benchmark/src/shared/modes/satset.luau +++ b/benchmark/src/shared/modes/satset.luau @@ -1,6 +1,6 @@ -- Satset benchmark mode. local RunService = game:GetService("RunService") -local Satset = require(game:GetService("ReplicatedStorage").DevPackages.Satset) +local Satset = require(game:GetService("ReplicatedStorage").references.Satset) local Types = Satset.Types -- We don't call Satset.start() here anymore. @@ -46,7 +46,7 @@ local Packets = { }), Vectors = Satset.definePacket({ name = "SatsetBenchVectors", - schema = { value = Types.array(Types.Vector3) }, + schema = { value = Types.array(Types.Vector3F16) }, reliable = true, }), Strings = Satset.definePacket({ @@ -59,8 +59,8 @@ local Packets = { schema = { id = Types.u8, health = Types.u8, - position = Types.Vector3, - velocity = Types.Vector3, + position = Types.Vector3F16, + velocity = Types.Vector3F16, name = Types.string8, isRunning = Types.bool, animation = Types.u8, diff --git a/benchmark/src/shared/modes/warp/Booleans.luau b/benchmark/src/shared/modes/warp/Booleans.luau new file mode 100644 index 0000000..0f74fec --- /dev/null +++ b/benchmark/src/shared/modes/warp/Booleans.luau @@ -0,0 +1,3 @@ +local Buffer = require(script.Parent.lib).Buffer.Schema + +return Buffer.array(Buffer.boolean) diff --git a/benchmark/src/shared/modes/warp/Entities.luau b/benchmark/src/shared/modes/warp/Entities.luau new file mode 100644 index 0000000..fe1c5a7 --- /dev/null +++ b/benchmark/src/shared/modes/warp/Entities.luau @@ -0,0 +1,10 @@ +local Buffer = require(script.Parent.lib).Buffer.Schema + +return Buffer.array(Buffer.struct({ + id = Buffer.u8, + x = Buffer.u8, + y = Buffer.u8, + z = Buffer.u8, + orientation = Buffer.u8, + animation = Buffer.u8, +})) diff --git a/benchmark/src/shared/modes/warp/Mixed.luau b/benchmark/src/shared/modes/warp/Mixed.luau new file mode 100644 index 0000000..3b74d02 --- /dev/null +++ b/benchmark/src/shared/modes/warp/Mixed.luau @@ -0,0 +1,12 @@ +local Buffer = require(script.Parent.lib).Buffer.Schema + +return Buffer.struct({ + id = Buffer.u8, + health = Buffer.u8, + position = Buffer.vector3, + velocity = Buffer.vector3, + name = Buffer.string, + isRunning = Buffer.boolean, + animation = Buffer.u8, + team = Buffer.u8, +}) diff --git a/benchmark/src/shared/modes/warp/SingleValue.luau b/benchmark/src/shared/modes/warp/SingleValue.luau new file mode 100644 index 0000000..5e804df --- /dev/null +++ b/benchmark/src/shared/modes/warp/SingleValue.luau @@ -0,0 +1,3 @@ +local Buffer = require(script.Parent.lib).Buffer.Schema + +return Buffer.u8 diff --git a/benchmark/src/shared/modes/warp/Strings.luau b/benchmark/src/shared/modes/warp/Strings.luau new file mode 100644 index 0000000..83aa56c --- /dev/null +++ b/benchmark/src/shared/modes/warp/Strings.luau @@ -0,0 +1,3 @@ +local Buffer = require(script.Parent.lib).Buffer.Schema + +return Buffer.array(Buffer.string) diff --git a/benchmark/src/shared/modes/warp/Vectors.luau b/benchmark/src/shared/modes/warp/Vectors.luau new file mode 100644 index 0000000..a7231fe --- /dev/null +++ b/benchmark/src/shared/modes/warp/Vectors.luau @@ -0,0 +1,3 @@ +local Buffer = require(script.Parent.lib).Buffer.Schema + +return Buffer.array(Buffer.vector3) diff --git a/benchmark/src/shared/modes/warp.luau b/benchmark/src/shared/modes/warp/init.luau similarity index 64% rename from benchmark/src/shared/modes/warp.luau rename to benchmark/src/shared/modes/warp/init.luau index c4e67cd..eca9a6b 100644 --- a/benchmark/src/shared/modes/warp.luau +++ b/benchmark/src/shared/modes/warp/init.luau @@ -1,9 +1,10 @@ -- Warp benchmark mode (v1.0.14 API). -- Warp uses string identifiers and separate Server/Client constructors that return event objects. + local ReplicatedStorage = game:GetService("ReplicatedStorage") local RunService = game:GetService("RunService") local Benches = require(ReplicatedStorage.Shared.benches) -local Warp = require(ReplicatedStorage.DevPackages.Warp) +local Warp = require(script.lib) local IsServer = RunService:IsServer() local Events = {} @@ -12,35 +13,34 @@ for Name in Benches do local eventName = "WarpBench_" .. Name local warpEvent if IsServer then - warpEvent = Warp.Server(eventName) + warpEvent = Warp.Server() else -- This yields on the client until the server registers the event. -- This is acceptable during require() as the runner waits. - warpEvent = Warp.Client(eventName) + warpEvent = Warp.Client() + warpEvent.awaitReady() end + local schema = require(script:FindFirstChild(Name)) + warpEvent.useSchema(Name, schema) + Events[Name] = { - -- Internal fire/listen for consistency with other adapters fire = function(data) if not IsServer then - -- Warp Client Fire: (reliable, ...) - warpEvent:Fire(true, data) + warpEvent.Fire(Name, true, data) end end, listen = function(callback) - -- Warp Connect: (callback) - warpEvent:Connect(callback) + warpEvent.Connect(Name, callback) + end, + Connect = function(self, callback) + warpEvent.Connect(Name, callback) end, - - -- Runner expects Fire/Connect for Warp (as seen in init.client/init.server) Fire = function(self, reliable, data) if not IsServer then - warpEvent:Fire(reliable, data) + warpEvent.Fire(Name, reliable, data) end end, - Connect = function(self, callback) - warpEvent:Connect(callback) - end, } end diff --git a/benchmark/src/shared/modes/warp/lib/Client/init.luau b/benchmark/src/shared/modes/warp/lib/Client/init.luau new file mode 100644 index 0000000..0016e9d --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Client/init.luau @@ -0,0 +1,265 @@ +--!optimize 2 +--!strict +--@EternityDev +local Client = {} + +local Buffer = require(script.Parent.Util.Buffer) +local Replication = require(script.Parent.Replication) +local Thread = require(script.Parent.Util.Thread) +local Xor = require(script.Parent.Util.Xor) + +local RunService = game:GetService("RunService") +local Event: RemoteEvent = script.Parent:WaitForChild("Event") :: RemoteEvent +local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") :: UnreliableRemoteEvent +local deltaT: number, cycle: number = 0, 1 / 61 +local writer: Buffer.Writer = Buffer.createWriter() + +type Connection = { + Connected: boolean, + Disconnect: (self: Connection) -> (), +} +type Event = { + i: number, + c: (Player, ...any?) -> ...any?, +} + +local queueEvent: { { any } } = {} +local queueUnreliableEvent: { { any } } = {} +local eventListeners: { [number]: { Event } } = {} +local eventSchemas: { [number]: Buffer.SchemaType } = {} + +local lastDelta: { [number]: { any } } = {} +local pendingInvokes: { [string]: thread } = {} +local invokeId = 0 + +--@optional +-- Yields the current thread until the client has successfully initialized and synchronized with the server's replication data (identifier). Its optionally, but highly recommended to call this before firing or connecting to any events to ensure the network is fully ready. +Client.awaitReady = Replication.wait_for_ready + +--@remoteName string +--@schema Buffer.SchemaType +-- Define a schema for strict data packing on a specific event. +Client.useSchema = function(remoteName: string, schema: Buffer.SchemaType) + local id = Replication.get_id[remoteName] + if not id then + warn(`[Warp]: ".useSchema"::"{remoteName}" does not exist, likely its not registered on the server yet.`) + return + end + eventSchemas[id] = schema +end + +--@remoteName string +--@fn function +-- Connect to an event to receive incoming data from the server. +Client.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection + local id = Replication.get_id[remoteName] + if not id then + warn(`[Warp]: ".Connect"::"{remoteName}" does not exist, likely its not registered on the server yet.`) + return { + Connected = false, + Disconnect = function() + return + end, + } :: Connection + end + local detail = { + i = id, + c = fn, + } + if not eventListeners[id] then + eventListeners[id] = {} + end + table.insert(eventListeners[id], detail) + return { + Connected = true, + Disconnect = function(self: Connection) + if not self.Connected then + return + end + self.Connected = false + local bucket = eventListeners[detail.i] + if bucket then + local idx = table.find(bucket, detail) + if idx then + table.remove(bucket, idx) + end + end + end, + } :: Connection +end + +--@remoteName string +--@fn function +-- Similar to :Connect but automatically disconnects after the first firing. +Client.Once = function(remoteName: string, fn: (...any?) -> ()): Connection + local connection + connection = Client.Connect(remoteName, function(...: any?) + if connection then + connection:Disconnect() + end + fn(...) + end) + return connection +end + +--@remoteName string +-- Wait for an event to be triggered. Yields the current thread. +Client.Wait = function(remoteName: string): (number, ...any?) + local thread, t = coroutine.running(), os.clock() + Client.Once(remoteName, function(...: any?) + task.spawn(thread, os.clock() - t, ...) + end) + return coroutine.yield() +end + +--@remoteName string +-- Disconnect all connections for a specific event. +Client.DisconnectAll = function(remoteName: string) + local id = Replication.get_id[remoteName] + if not id then + return + end + eventListeners[id] = nil +end + +--@remoteName string +-- Disconnect all connections and remove the event. +Client.Destroy = Client.DisconnectAll + +--@remoteName string +--@reliable boolean +-- Fire an event to the server. +Client.Fire = function(remoteName: string, reliable: boolean, ...: any?) + local id = Replication.get_id[remoteName] + if id then + table.insert(reliable and queueEvent or queueUnreliableEvent, { + Replication.get_id[remoteName], + { ... } :: any, + }) + end +end + +--@remoteName string +--@timeout number? +-- Invoke the server with timeout support. Yields the current thread. Returns nil if timeout occurs. +Client.Invoke = function(remoteName: string, timeout: number?, ...: any?): ...any? + local id = Replication.get_id[remoteName] + if not id then + return nil + end + invokeId += 1 + local reqid, thread = `{invokeId}`, coroutine.running() + + pendingInvokes[reqid] = thread + task.delay(timeout or 2, function() + local pending = pendingInvokes[reqid] + if not pending then + return + end + task.spawn(pending, nil) + pendingInvokes[reqid] = nil + end) + table.insert(queueEvent, { + 0, + { id, reqid :: any, { ... } :: any } :: any, + }) + return coroutine.yield() +end + +if RunService:IsClient() then + local function processIncoming(b: buffer, ref: { Instance }?, handleInvokes: boolean) + if type(b) ~= "buffer" then + return + end + local decoded: buffer = handleInvokes and Xor.decodeClient(b) or b + local contents = Buffer.readEvents(decoded, ref, eventSchemas) + for _, content in contents do + local remote = content[1] + local args = content[2] + if handleInvokes then + if remote == 0 then + local id = args[1] + local results = args[2] + local pending = pendingInvokes[id] + if pending then + task.spawn(pending :: any, table.unpack(results)) + pendingInvokes[id] = nil + end + continue + end + if remote == 1 then + local remoteName, id, rargs = args[1], args[2], args[3] + local connections = eventListeners[remoteName] + if connections and #connections > 0 then + Thread(function() + local results = { connections[1].c(table.unpack(rargs)) } + table.insert(queueEvent, { 1, { id, results } :: any }) + end) + end + continue + end + end + local connections = eventListeners[remote] + if connections then + for _, connection in connections do + Thread(connection.c :: any, table.unpack(args)) + end + end + end + end + + Event.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?) + processIncoming(b, ref, true) + end) + + UnreliableEvent.OnClientEvent:Connect(function(b: buffer, ref: { Instance }?) + processIncoming(b, ref, false) + end) + + RunService.PostSimulation:Connect(function(d: number) + deltaT += d + if deltaT < cycle then + return + end + deltaT = 0 + + --reliable + if #queueEvent > 0 then + Buffer.writeEvents(writer, queueEvent, eventSchemas) + do + local buf, ref = Buffer.buildWithRefs(writer) + local encoded = Xor.encodeClient(buf) + Buffer.reset(writer) + if not ref or #ref == 0 then + Event:FireServer(encoded) + else + Event:FireServer(encoded, ref) + end + end + table.clear(queueEvent) + end + -- unreliable + if #queueUnreliableEvent > 0 then + Buffer.writeEvents(writer, queueUnreliableEvent, eventSchemas) + do + local buf, ref = Buffer.buildWithRefs(writer) + Buffer.reset(writer) + if not ref or #ref == 0 then + UnreliableEvent:FireServer(buf) + else + UnreliableEvent:FireServer(buf, ref) + end + end + table.clear(queueUnreliableEvent) + end + end) +end + +--[[ + @class Client + @schema + define a schema for your data and use a strict packing +]] +Client.Schema = Buffer.Schema + +return Client :: typeof(Client) diff --git a/benchmark/src/shared/modes/warp/lib/Replication/init.luau b/benchmark/src/shared/modes/warp/lib/Replication/init.luau new file mode 100644 index 0000000..0d13cb5 --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Replication/init.luau @@ -0,0 +1,147 @@ +--!optimize 2 +--!strict +local Replication = {} + +local RunService = game:GetService("RunService") +local _repl: RemoteEvent = script.Parent:WaitForChild("_repl") + +local Buffer = require(script.Parent.Util.Buffer) +local Identifier = require(script.Parent.Util.Identifier) + +local identifiers_schema = Buffer.Schema.string +local warp_identifier_registry = shared.__warp_identifier_registry + +if RunService:IsClient() or RunService:IsRunMode() then + local pending_id_yields, pending_name_yields, ready_yields = {}, {}, {} + local is_ready = false + + if RunService:IsClient() then + _repl.OnClientEvent:Connect(function(b: buffer) + if type(b) ~= "buffer" then + return + end + local contents = Buffer.readRepl(b, identifiers_schema) + if #contents == 0 then + return + end + for _, content in contents do + local id: number, remote: string = content[1], content[2] + warp_identifier_registry.cache[remote] = id + warp_identifier_registry.name[id] = remote + + if pending_id_yields[remote] then + for _, thread in pending_id_yields[remote] do + task.spawn(thread, id) + end + pending_id_yields[remote] = nil + end + if pending_name_yields[id] then + for _, thread in pending_name_yields[id] do + task.spawn(thread, remote) + end + pending_name_yields[id] = nil + end + end + if not is_ready then + is_ready = true + for _, thread in ready_yields do + task.spawn(thread :: thread) + end + table.clear(ready_yields) + end + end) + _repl:FireServer() + end + + --@yield + -- wait for the identifiers to be replicated from the server + Replication.wait_for_ready = function() + if is_ready then + return + end + + local thread = coroutine.running() + table.insert(ready_yields, thread) + coroutine.yield() + end + + Replication.get_id = warp_identifier_registry.cache + Replication.get_name = warp_identifier_registry.name +else + local replication_ready: { Player }, pending_replications = {}, {} + local writer: Buffer.Writer = Buffer.createWriter() + local replication_id: number = Identifier.get_id("id_replication") or 1 + local is_scheduled = false + + if not Identifier.has_name("id_replication") or not replication_id then + replication_id = Identifier.get_id("id_replication") or 1 + end + + local function replicateToAll(content: any, id: number?) + if #replication_ready == 0 then + return + end + + if type(content) == "string" and id then + pending_replications[content] = id + else + for k, v in content :: any do + pending_replications[k] = v + end + end + + if not is_scheduled then + is_scheduled = true + task.defer(function() + is_scheduled = false + + local count = 0 + for _ in pending_replications do + count += 1 + end + if count == 0 then + return + end + + Buffer.writeRepl(writer, pending_replications, count, identifiers_schema) + local buf = Buffer.build(writer) + Buffer.reset(writer) + + for _, player: Player in replication_ready do + _repl:FireClient(player, buf) + end + + table.clear(pending_replications) + end) + end + end + + local function replicateTo(player: Player) + Buffer.writeRepl(writer, warp_identifier_registry.cache, warp_identifier_registry.counter, identifiers_schema) + do + local buf = Buffer.build(writer) + Buffer.reset(writer) + _repl:FireClient(player, buf) + end + end + + Identifier.on_added(replicateToAll) + + _repl.OnServerEvent:Connect(function(player: Player) + if table.find(replication_ready, player) then + return + end + table.insert(replication_ready, player) + replicateTo(player) + end) + + Replication.remove = function(player: Player) + local idx = table.find(replication_ready, player) + if not idx then + return + end + table.remove(replication_ready, idx) + end +end + +return Replication :: typeof(Replication) diff --git a/benchmark/src/shared/modes/warp/lib/Server/init.luau b/benchmark/src/shared/modes/warp/lib/Server/init.luau new file mode 100644 index 0000000..369c9d8 --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Server/init.luau @@ -0,0 +1,345 @@ +--!optimize 2 +--!strict +--@EternityDev +local Server = {} + +local Buffer = require(script.Parent.Util.Buffer) +local Identifier = require(script.Parent.Util.Identifier) +local Replication = require(script.Parent.Replication) +local Thread = require(script.Parent.Util.Thread) +local Xor = require(script.Parent.Util.Xor) + +local Players = game:GetService("Players") +local RunService = game:GetService("RunService") +local Event: RemoteEvent = script.Parent:WaitForChild("Event") :: RemoteEvent +local _repl: RemoteEvent = script.Parent:WaitForChild("_repl") :: RemoteEvent +local UnreliableEvent: UnreliableRemoteEvent = script.Parent:WaitForChild("UnreliableEvent") :: UnreliableRemoteEvent +local deltaT: number, cycle: number = 0, 1 / 61 +local writer: Buffer.Writer = Buffer.createWriter() + +type Connection = { + Connected: boolean, + Disconnect: (self: Connection) -> (), +} +type Event = { + i: number, + c: (Player, ...any?) -> ...any?, +} + +local queueEvent: { + [Player]: { { any } }, +} = {} +local queueUnreliableEvent: { + [Player]: { { any } }, +} = {} +local eventListeners: { [number]: { Event } } = {} +local eventSchemas: { [number]: Buffer.SchemaType } = {} +local players_ready: { Player }, player_bytes: { [Player]: number } = {}, {} + +local pendingInvokes: { [string]: thread } = {} +local invokeId = 0 + +--@namespaces { string } +--@optional +-- Register namespaces to ensure all of the namespaces is being registered earlier on the server to prevent any unexpected issues on the client. +Server.reg_namespaces = function(namespaces: { string }) + for _, name: string in namespaces do + Identifier.get_id(name) + end +end + +--@remoteName string +--@schema Buffer.SchemaType +-- Define a schema for strict data packing on a specific event. +Server.useSchema = function(remoteName: string, schema: Buffer.SchemaType) + eventSchemas[Identifier.get_id(remoteName)] = schema +end + +--@remoteName string +--@fn function +-- Connect to an event to receive incoming data from clients. +Server.Connect = function(remoteName: string, fn: (Player, ...any?) -> ...any?): Connection + local detail = { + i = Identifier.get_id(remoteName), + c = fn, + } + if not eventListeners[detail.i] then + eventListeners[detail.i] = {} + end + table.insert(eventListeners[detail.i], detail) + return { + Connected = true, + Disconnect = function(self: Connection) + if not self.Connected then + return + end + self.Connected = false + local bucket = eventListeners[detail.i] + if bucket then + local idx = table.find(bucket, detail) + if idx then + table.remove(bucket, idx) + end + end + end, + } :: Connection +end + +--@remoteName string +--@fn function +-- Similar to :Connect but automatically disconnects after the first firing. +Server.Once = function(remoteName: string, fn: (...any?) -> ()): Connection + local connection + connection = Server.Connect(remoteName, function(...: any?) + if connection then + connection:Disconnect() + end + fn(...) + end) + return connection +end + +--@remoteName string +-- Wait for an event to be triggered. Yields the current thread. +Server.Wait = function(remoteName: string): (number, ...any?) + local thread, t = coroutine.running(), os.clock() + Server.Once(remoteName, function(...: any?) + task.spawn(thread, os.clock() - t, ...) + end) + return coroutine.yield() +end + +--@remoteName string +-- Disconnect all connections for a specific event. +Server.DisconnectAll = function(remoteName: string) + local id = Identifier.get_id(remoteName) + if not id then + return + end + eventListeners[id] = nil +end + +--@remoteName string +-- Disconnect all connections and remove the event. +Server.Destroy = Server.DisconnectAll + +--@remoteName string +--@reliable boolean +--@player Player +-- Fire an event to a specific player. +Server.Fire = function(remoteName: string, reliable: boolean, player: Player, ...: any?) + local targetQueue = reliable and queueEvent or queueUnreliableEvent + if not targetQueue[player] then + targetQueue[player] = {} :: any + end + table.insert(targetQueue[player], { + Identifier.get_id(remoteName), + { ... } :: any, + }) +end + +--@remoteName string +--@reliable boolean +-- Fire an event to all connected players. +Server.Fires = function(remoteName: string, reliable: boolean, ...: any?) + for _, player: Player in players_ready do + Server.Fire(remoteName, reliable, player, ...) + end +end + +--@remoteName string +--@reliable boolean +--@except { Player } +-- Fire an event to all players except specified ones. +Server.FireExcept = function(remoteName: string, reliable: boolean, except: { Player }, ...: any?) + for _, player: Player in players_ready do + if table.find(except, player) then + continue + end + Server.Fire(remoteName, reliable, player, ...) + end +end + +--@remoteName string +--@player Player +--@timeout number? +-- Invoke a client with timeout support. Yields the current thread. Returns nil if timeout occurs. +Server.Invoke = function(remoteName: string, player: Player, timeout: number?, ...: any?): ...any? + invokeId += 1 + local reqId, thread = `{invokeId}`, coroutine.running() + + pendingInvokes[reqId] = thread + task.delay(timeout or 2, function() + local pending = pendingInvokes[reqId] + if not pending then + return + end + task.spawn(pending, nil) + pendingInvokes[reqId] = nil + end) + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + table.insert(queueEvent[player], { + 0, + { reqId :: any, { ... } :: any } :: any, + }) + return coroutine.yield() +end + +if RunService:IsServer() then + local function processIncoming(player: Player, b: buffer, ref: { Instance }?, handleInvokes: boolean) + if type(b) ~= "buffer" then + return + end + if not RunService:IsStudio() then + local bytes: number = (player_bytes[player] or 0) + math.max(buffer.len(b), 800) + if bytes > 8e3 then + return + end + player_bytes[player] = bytes + end + + local decoded = handleInvokes and Xor.decodeServer(player, b) or b + local contents = Buffer.readEvents(decoded, ref, eventSchemas) + for _, content in contents do + local remote = content[1] + local d = content[2] + if handleInvokes then + if remote == 1 then + local id = d[1] + local results = d[2] + local pending = pendingInvokes[id] + if pending then + task.spawn(pending :: any, table.unpack(results)) + pendingInvokes[id] = nil + end + continue + end + if remote == 0 then + if not next(eventListeners) then + continue + end + local remoteName = d[1] + local id = d[2] + local args = d[3] + local connections = eventListeners[remoteName] + if connections and #connections > 0 then + Thread(function() + local results = { connections[1].c(player, table.unpack(args)) } + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + table.insert(queueEvent[player], { + 0, + { id, results } :: any, + }) + end) + end + continue + end + end + local connections = eventListeners[remote] + if connections then + for _, connection in connections do + Thread(connection.c :: any, player, table.unpack(d)) + end + end + end + end + + Event.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) + processIncoming(player, b, ref, true) + end) + + UnreliableEvent.OnServerEvent:Connect(function(player: Player, b: buffer, ref: { Instance }?) + processIncoming(player, b, ref, false) + end) + + RunService.PostSimulation:Connect(function(d: number) + deltaT += d + if deltaT < cycle then + return + end + deltaT = 0 + + -- reliable + for player: Player, content in queueEvent do + if #content == 0 or player.Parent ~= Players then + continue + end + Buffer.writeEvents(writer, content, eventSchemas) + do + local buf, ref = Buffer.buildWithRefs(writer) + local encoded = Xor.encodeServer(player, buf) + Buffer.reset(writer) + if not ref or #ref == 0 then + Event:FireClient(player, encoded) + else + Event:FireClient(player, encoded, ref) + end + end + player_bytes[player] = 0 + table.clear(queueEvent[player]) + end + -- unreliable + for player: Player, content in queueUnreliableEvent do + if #content == 0 or player.Parent ~= Players then + continue + end + Buffer.writeEvents(writer, content, eventSchemas) + do + local buf, ref = Buffer.buildWithRefs(writer) + Buffer.reset(writer) + if not ref or #ref == 0 then + UnreliableEvent:FireClient(player, buf) + else + UnreliableEvent:FireClient(player, buf, ref) + end + end + player_bytes[player] = 0 + table.clear(queueUnreliableEvent[player]) + end + end) + + local function onAdded(player: Player) + if not table.find(players_ready, player) then + table.insert(players_ready, player) + end + if not queueEvent[player] then + queueEvent[player] = {} :: any + end + if not queueUnreliableEvent[player] then + queueUnreliableEvent[player] = {} :: any + end + end + Players.PlayerAdded:Connect(onAdded) + Players.PlayerRemoving:Connect(function(player: Player) + Replication.remove(player) + table.remove(players_ready, table.find(players_ready, player)) + if queueEvent[player] then + table.clear(queueEvent[player]) + queueEvent[player] = nil + end + if player_bytes[player] then + player_bytes[player] = nil + end + if queueUnreliableEvent[player] then + table.clear(queueUnreliableEvent[player]) + queueUnreliableEvent[player] = nil + end + Xor.remove(player) + end) + for _, player: Player in ipairs(Players:GetPlayers()) do + onAdded(player) + end +end + +--[[ + @class Server + @schema + define a schema for your data and use a strict packing +]] +Server.Schema = Buffer.Schema + +return Server :: typeof(Server) diff --git a/benchmark/src/shared/modes/warp/lib/Util/Buffer/init.luau b/benchmark/src/shared/modes/warp/lib/Util/Buffer/init.luau new file mode 100644 index 0000000..566532a --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Util/Buffer/init.luau @@ -0,0 +1,2556 @@ +--!native +--!optimize 2 +--@EternityDev +export type Writer = { + buf: buffer, + cursor: number, + capacity: number, + refs: { any }, +} + +local DEFAULT_CAPACITY: number = 64 +local NaN_VALUE: number = 0 / 0 + +-- 0x00-0x7F: + fixint (0-127) — single byte +-- 0x80-0x9F: - fixint (-32 to -1) — single byte +local T_NIL = 0xA0 +local T_FALSE = 0xA1 +local T_TRUE = 0xA2 +local T_U8 = 0xA3 +local T_U16 = 0xA4 +local T_U32 = 0xA5 +local T_I8 = 0xA6 +local T_I16 = 0xA7 +local T_I32 = 0xA8 +local T_F16 = 0xAB +local T_F32 = 0xA9 +local T_F64 = 0xAA + +-- str: 0xB0-0xBF = inline len 0-15 +local T_STR_BASE = 0xB0 +local T_STR8 = 0xC0 +local T_STR16 = 0xC1 +local T_STRVAR = 0xC2 + +-- arrays: 0xC4-0xCB = inline len 0-7 +local T_ARR_BASE = 0xC4 +local T_ARR8 = 0xCC +local T_ARR16 = 0xCD +local T_ARRVAR = 0xCE + +-- maps: 0xD0-0xD7 = inline len 0-7 +local T_MAP_BASE = 0xD0 +local T_MAP8 = 0xD8 +local T_MAP16 = 0xD9 +local T_MAPVAR = 0xDA + +-- typed arrays (homogeneous, no per-element tags) +local T_TYPED_ARR = 0xDB +local T_BUFFER = 0xDC +local T_BOOL_ARR = 0xDD + +local T_VEC3 = 0xE0 +local T_VEC2 = 0xE2 +local T_CFRAME = 0xE4 +local T_COLOR3 = 0xE6 +local T_COLOR3_F = 0xE7 +local T_BRICKCOLOR = 0xE8 +local T_INSTANCE = 0xE9 +local T_ENUMITEM = 0xEA +local T_ENUM = 0xEB +local T_UDIM2 = 0xEC +local T_UDIM = 0xED +local T_RECT = 0xEE +local T_NUMBERRANGE = 0xEF +local T_RAY = 0xF0 +local T_COLSEQ = 0xF2 +local T_NUMSEQ = 0xF3 +local T_VEC3I16 = 0xF1 +local T_VEC2I16 = 0xF4 +local T_TWEENINFO = 0xF5 +local T_PHYSPROP = 0xF6 +local T_FONT = 0xF7 +local T_DATETIME = 0xF8 + +local F16_LOOKUP: { number } = table.create(65536, 0) +do + for raw = 0, 65535 do + local sign: number = bit32.btest(raw, 0x8000) and -1 or 1 + local exponent: number = bit32.extract(raw, 10, 5) + local mantissa: number = bit32.band(raw, 0x03FF) + local value: number + if exponent == 0 then + if mantissa == 0 then + value = 0 * sign + else + value = sign * (mantissa / 1024) * 6.103515625e-05 + end + elseif exponent == 31 then + if mantissa == 0 then + value = sign * math.huge + else + value = NaN_VALUE + end + else + value = sign * (1 + mantissa / 1024) * math.ldexp(1, exponent - 15) + end + F16_LOOKUP[raw + 1] = value + --if raw % 8192 == 0 then + -- task.wait() + --end + end +end + +local function encodeF16(value: number): number + if value ~= value then + return 0x7E00 + end + if value == 0 then + return if 1 / value < 0 then 0x8000 else 0 + end + local sign = 0 + if value < 0 then + sign = 0x8000 + value = -value + end + if value >= 65504 then + return sign + 0x7C00 + end + if value < 6.103515625e-05 then + local f = math.floor(value * 16777216 + 0.5) + return sign + (if f >= 1024 then 0x0400 else f) + end + local m, e = math.frexp(value) + local biasedExp = e + 14 + local f = math.floor(m * 2048 - 1024 + 0.5) + if f >= 1024 then + f = 0 + biasedExp += 1 + end + if biasedExp >= 31 then + return sign + 0x7C00 + end + return sign + biasedExp * 1024 + f +end + +local function readF16(b: buffer, pos: number): number + return F16_LOOKUP[buffer.readu16(b, pos) + 1] +end + +local function varUIntSize(value: number): number + if value < 0x80 then + return 1 + end + if value < 0x4000 then + return 2 + end + if value < 0x200000 then + return 3 + end + if value < 0x10000000 then + return 4 + end + return 5 +end + +local function writeVarUInt(buf: buffer, offset: number, value: number): number + while value >= 0x80 do + buffer.writeu8(buf, offset, bit32.bor(bit32.band(value, 0x7F), 0x80)) + offset += 1 + value = bit32.rshift(value, 7) + end + buffer.writeu8(buf, offset, value) + return offset + 1 +end + +local function readVarUInt(buf: buffer, offset: number): (number, number) + local value = 0 + local shift = 0 + repeat + local byte = buffer.readu8(buf, offset) + offset += 1 + value = bit32.bor(value, bit32.lshift(bit32.band(byte, 0x7F), shift)) + shift += 7 + until byte < 0x80 + return value, offset +end + +local function createWriter(capacity: number?): Writer + local cap = capacity or DEFAULT_CAPACITY + return { + buf = buffer.create(cap), + cursor = 0, + capacity = cap, + refs = {}, + } +end + +local function ensureSpace(w: Writer, bytes: number) + local needed = w.cursor + bytes + if needed <= w.capacity then + return + end + local newCap = w.capacity + while newCap < needed do + newCap *= 2 + end + local newBuf = buffer.create(newCap) + buffer.copy(newBuf, 0, w.buf, 0, w.cursor) + w.buf = newBuf + w.capacity = newCap +end + +local function rawU8(w: Writer, v: number) + buffer.writeu8(w.buf, w.cursor, v) + w.cursor += 1 +end + +local function rawU16(w: Writer, v: number) + buffer.writeu16(w.buf, w.cursor, v) + w.cursor += 2 +end + +local function rawI16(w: Writer, v: number) + buffer.writei16(w.buf, w.cursor, v) + w.cursor += 2 +end + +local function rawU32(w: Writer, v: number) + buffer.writeu32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function rawI32(w: Writer, v: number) + buffer.writei32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function rawF32(w: Writer, v: number) + buffer.writef32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function rawF64(w: Writer, v: number) + buffer.writef64(w.buf, w.cursor, v) + w.cursor += 8 +end + +local function rawF16(w: Writer, value: number) + buffer.writeu16(w.buf, w.cursor, encodeF16(value)) + w.cursor += 2 +end + +local function rawVarUInt(w: Writer, v: number) + w.cursor = writeVarUInt(w.buf, w.cursor, v) +end + +local function rawString(w: Writer, s: string, n: number) + buffer.writestring(w.buf, w.cursor, s) + w.cursor += n +end + +local function wByte(w: Writer, v: number) + ensureSpace(w, 1) + buffer.writeu8(w.buf, w.cursor, v) + w.cursor += 1 +end + +local function wU16(w: Writer, v: number) + ensureSpace(w, 2) + buffer.writeu16(w.buf, w.cursor, v) + w.cursor += 2 +end + +local function wI16(w: Writer, v: number) + ensureSpace(w, 2) + buffer.writei16(w.buf, w.cursor, v) + w.cursor += 2 +end + +local function wU32(w: Writer, v: number) + ensureSpace(w, 4) + buffer.writeu32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function wI32(w: Writer, v: number) + ensureSpace(w, 4) + buffer.writei32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function wF16(w: Writer, value: number) + ensureSpace(w, 2) + buffer.writeu16(w.buf, w.cursor, encodeF16(value)) + w.cursor += 2 +end + +local function wF32(w: Writer, v: number) + ensureSpace(w, 4) + buffer.writef32(w.buf, w.cursor, v) + w.cursor += 4 +end + +local function wF64(w: Writer, v: number) + ensureSpace(w, 8) + buffer.writef64(w.buf, w.cursor, v) + w.cursor += 8 +end + +local function wVarUInt(w: Writer, v: number) + ensureSpace(w, 5) -- max varuint = 5 bytes; avoids double-analysis via varUIntSize + w.cursor = writeVarUInt(w.buf, w.cursor, v) +end + +local function wString(w: Writer, s: string) + local n = #s + ensureSpace(w, n) + buffer.writestring(w.buf, w.cursor, s) + w.cursor += n +end + +local packValue: (w: Writer, v: any) -> () +local unpackValue: (buf: buffer, pos: number, refs: { any }?) -> (any, number) + +local F32_TEST_BUF = buffer.create(4) + +local TYPED_CODES = { u8 = 1, i8 = 2, u16 = 3, i16 = 4, u32 = 5, i32 = 6, f32 = 7, f64 = 8, f16 = 9 } +local TYPED_SIZES = { u8 = 1, i8 = 1, u16 = 2, i16 = 2, u32 = 4, i32 = 4, f32 = 4, f64 = 8, f16 = 2 } + +local TYPED_WRITERS = { + u8 = buffer.writeu8, + i8 = buffer.writei8, + u16 = buffer.writeu16, + i16 = buffer.writei16, + u32 = buffer.writeu32, + i32 = buffer.writei32, + f32 = buffer.writef32, + f64 = buffer.writef64, + f16 = function(b: buffer, offset: number, value: number) + buffer.writeu16(b, offset, encodeF16(value)) + end, +} + +local TYPED_READERS = { + [1] = function(b: buffer, o: number) + return buffer.readu8(b, o), o + 1 + end, + [2] = function(b: buffer, o: number) + return buffer.readi8(b, o), o + 1 + end, + [3] = function(b: buffer, o: number) + return buffer.readu16(b, o), o + 2 + end, + [4] = function(b: buffer, o: number) + return buffer.readi16(b, o), o + 2 + end, + [5] = function(b: buffer, o: number) + return buffer.readu32(b, o), o + 4 + end, + [6] = function(b: buffer, o: number) + return buffer.readi32(b, o), o + 4 + end, + [7] = function(b: buffer, o: number) + return buffer.readf32(b, o), o + 4 + end, + [8] = function(b: buffer, o: number) + return buffer.readf64(b, o), o + 8 + end, + [9] = function(b: buffer, o: number) + return readF16(b, o), o + 2 + end, +} + +-- CFrame quaternion compression (Shepperd's method + smallest-three encoding). +-- Extracts a unit quaternion directly from the rotation matrix - no trig needed. +-- The three stored components are guaranteed in [-1/√2, 1/√2], so we rescale +-- by √2 to fill the full i16 range, giving ~41% better precision for free. +local CF_I16_SCALE: number = 32767 * math.sqrt(2) +local CF_I16_INV_SCALE: number = 1 / CF_I16_SCALE + +local function compressCFrameRotation(cf: CFrame): (number, number, number, number) + local _, _, _, r00, r01, r02, r10, r11, r12, r20, r21, r22 = cf:GetComponents() + local trace: number = r00 + r11 + r22 + local qx: number, qy: number, qz: number, qw: number + if trace > 0 then + local sq: number = math.sqrt(trace + 1) + local s: number = 0.5 / sq + qw = sq * 0.5 + qx = (r21 - r12) * s + qy = (r02 - r20) * s + qz = (r10 - r01) * s + elseif r00 > r11 and r00 > r22 then + local sq: number = math.sqrt(1 + r00 - r11 - r22) + local s: number = 0.5 / sq + qx = sq * 0.5 + qw = (r21 - r12) * s + qy = (r01 + r10) * s + qz = (r02 + r20) * s + elseif r11 > r22 then + local sq: number = math.sqrt(1 + r11 - r00 - r22) + local s: number = 0.5 / sq + qy = sq * 0.5 + qw = (r02 - r20) * s + qx = (r01 + r10) * s + qz = (r12 + r21) * s + else + local sq: number = math.sqrt(1 + r22 - r00 - r11) + local s: number = 0.5 / sq + qz = sq * 0.5 + qw = (r10 - r01) * s + qx = (r02 + r20) * s + qy = (r12 + r21) * s + end + + local index, value, sign = 1, math.abs(qx), qx >= 0 and 1 or -1 + local abs + abs = math.abs(qy) + if abs > value then + index = 2 + value = abs + sign = qy >= 0 and 1 or -1 + end + abs = math.abs(qz) + if abs > value then + index = 3 + value = abs + sign = qz >= 0 and 1 or -1 + end + abs = math.abs(qw) + if abs > value then + index = 4 + sign = qw >= 0 and 1 or -1 + end + + local scale = CF_I16_SCALE * sign + local v0: number, v1: number, v2: number + if index == 1 then + v0 = math.round(qy * scale) + v1 = math.round(qz * scale) + v2 = math.round(qw * scale) + elseif index == 2 then + v0 = math.round(qx * scale) + v1 = math.round(qz * scale) + v2 = math.round(qw * scale) + elseif index == 3 then + v0 = math.round(qx * scale) + v1 = math.round(qy * scale) + v2 = math.round(qw * scale) + else + v0 = math.round(qx * scale) + v1 = math.round(qy * scale) + v2 = math.round(qz * scale) + end + return index, v0, v1, v2 +end + +local function decompressCFrameRotation( + index: number, + v0: number, + v1: number, + v2: number +): (number, number, number, number) + v0 *= CF_I16_INV_SCALE + v1 *= CF_I16_INV_SCALE + v2 *= CF_I16_INV_SCALE + local sq = 1 - (v0 * v0 + v1 * v1 + v2 * v2) + local d = sq > 0 and math.sqrt(sq) or 0 + if index == 1 then + return d, v0, v1, v2 + elseif index == 2 then + return v0, d, v1, v2 + elseif index == 3 then + return v0, v1, d, v2 + end + return v0, v1, v2, d +end + +local function packNumber(w: Writer, n: number) + if n ~= n then + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) + return + end + + local isInt = n == math.floor(n) + + if isInt then + if n >= 0 then + if n <= 127 then + ensureSpace(w, 1) + rawU8(w, n) + return + end + if n <= 255 then + ensureSpace(w, 2) + rawU8(w, T_U8) + rawU8(w, n) + elseif n <= 65535 then + ensureSpace(w, 3) + rawU8(w, T_U16) + rawU16(w, n) + elseif n <= 4294967295 then + ensureSpace(w, 5) + rawU8(w, T_U32) + rawU32(w, n) + else + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) + end + else + if n >= -32 then + ensureSpace(w, 1) + rawU8(w, bit32.bor(0x80, n + 32)) + return + end + if n >= -128 then + ensureSpace(w, 2) + rawU8(w, T_I8) + buffer.writei8(w.buf, w.cursor, n) + w.cursor += 1 + elseif n >= -32768 then + ensureSpace(w, 3) + rawU8(w, T_I16) + rawI16(w, n) + elseif n >= -2147483648 then + ensureSpace(w, 5) + rawU8(w, T_I32) + rawI32(w, n) + else + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) + end + end + else + buffer.writef32(F32_TEST_BUF, 0, n) + local f32Val = buffer.readf32(F32_TEST_BUF, 0) + + if f32Val == n then + ensureSpace(w, 5) + rawU8(w, T_F32) + rawF32(w, n) + else + ensureSpace(w, 9) + rawU8(w, T_F64) + rawF64(w, n) + end + end +end + +local function packString(w: Writer, s: string) + local n = #s + if n <= 15 then + ensureSpace(w, 1 + n) + rawU8(w, T_STR_BASE + n) + elseif n <= 255 then + ensureSpace(w, 2 + n) + rawU8(w, T_STR8) + rawU8(w, n) + elseif n <= 65535 then + ensureSpace(w, 3 + n) + rawU8(w, T_STR16) + rawU16(w, n) + else + ensureSpace(w, 6 + n) -- tag(1) + varuint(max5) + data + rawU8(w, T_STRVAR) + rawVarUInt(w, n) + end + rawString(w, s, n) +end + +local function analyzeArray(t: { any }, count: number): (string?, string?, number) + if count < 2 then + return nil, nil, count + end + + local first = t[1] + local firstType = type(first) + + if firstType == "boolean" then + for i = 2, count do + if type(t[i]) ~= "boolean" then + return nil, nil, count + end + end + return "boolean", nil, count + end + + if firstType ~= "number" then + return nil, nil, count + end + + local minVal, maxVal = first, first + local allInt = first == math.floor(first) + + for i = 2, count do + local v = t[i] + if type(v) ~= "number" then + return nil, nil, count + end + if v ~= math.floor(v) then + allInt = false + end + if v < minVal then + minVal = v + end + if v > maxVal then + maxVal = v + end + end + + if not allInt then + return "number", "f32", count + elseif minVal >= 0 and maxVal <= 255 then + return "number", "u8", count + elseif minVal >= -128 and maxVal <= 127 then + return "number", "i8", count + elseif minVal >= 0 and maxVal <= 65535 then + return "number", "u16", count + elseif minVal >= -32768 and maxVal <= 32767 then + return "number", "i16", count + elseif minVal >= 0 and maxVal <= 4294967295 then + return "number", "u32", count + elseif minVal >= -2147483648 and maxVal <= 2147483647 then + return "number", "i32", count + end + return "number", "f64", count +end + +local function packTable(w: Writer, t: { [any]: any }) + local count = 0 + local maxIdx = 0 + local isArray = true + + for k in t do + count += 1 + if type(k) == "number" and k > 0 and k == math.floor(k) then + if k > maxIdx then + maxIdx = k + end + else + isArray = false + end + end + + isArray = isArray and maxIdx == count + + if isArray then + local category, dtype, arrCount = analyzeArray(t, count) + + if category == "boolean" then + local numBytes = math.ceil(arrCount / 8) + ensureSpace(w, 1 + 5 + numBytes) + rawU8(w, T_BOOL_ARR) + rawVarUInt(w, arrCount) + for i = 0, numBytes - 1 do + local byte = 0 + for bit = 0, 7 do + local idx = i * 8 + bit + 1 + if idx > arrCount then + break + end + if t[idx] then + byte = bit32.bor(byte, bit32.lshift(1, bit)) + end + end + buffer.writeu8(w.buf, w.cursor, byte) + w.cursor += 1 + end + return + end + + -- typed num arrays + if category == "number" and dtype and arrCount >= 4 then + local code = TYPED_CODES[dtype] + local size = TYPED_SIZES[dtype] + local writer = TYPED_WRITERS[dtype] + + ensureSpace(w, 2 + 5 + arrCount * size) + rawU8(w, T_TYPED_ARR) + rawU8(w, code) + rawVarUInt(w, arrCount) + for i = 1, arrCount do + writer(w.buf, w.cursor, t[i]) + w.cursor += size + end + return + end + + -- regular array + if count <= 7 then + wByte(w, T_ARR_BASE + count) + elseif count <= 255 then + ensureSpace(w, 2) + rawU8(w, T_ARR8) + rawU8(w, count) + elseif count <= 65535 then + ensureSpace(w, 3) + rawU8(w, T_ARR16) + rawU16(w, count) + else + ensureSpace(w, 6) + rawU8(w, T_ARRVAR) + rawVarUInt(w, count) + end + + for i = 1, count do + packValue(w, t[i]) + end + else + -- map + if count <= 7 then + wByte(w, T_MAP_BASE + count) + elseif count <= 255 then + ensureSpace(w, 2) + rawU8(w, T_MAP8) + rawU8(w, count) + elseif count <= 65535 then + ensureSpace(w, 3) + rawU8(w, T_MAP16) + rawU16(w, count) + else + ensureSpace(w, 6) + rawU8(w, T_MAPVAR) + rawVarUInt(w, count) + end + + for k, v in t do + packValue(w, k) + packValue(w, v) + end + end +end + +local function packVector3(w: Writer, v: Vector3) + ensureSpace(w, 7) + rawU8(w, T_VEC3) + rawF16(w, v.X) + rawF16(w, v.Y) + rawF16(w, v.Z) +end + +local function packVector2(w: Writer, v: Vector2) + ensureSpace(w, 5) + rawU8(w, T_VEC2) + rawF16(w, v.X) + rawF16(w, v.Y) +end + +local function packCFrame(w: Writer, cf: CFrame) + local pos = cf.Position + local qi, q0, q1, q2 = compressCFrameRotation(cf) + ensureSpace(w, 20) -- tag(1) + 3xf32(12) + u8(1) + 3xi16(6) + rawU8(w, T_CFRAME) + rawF32(w, pos.X) + rawF32(w, pos.Y) + rawF32(w, pos.Z) + rawU8(w, qi) + rawI16(w, q0) + rawI16(w, q1) + rawI16(w, q2) +end + +local function packColor3(w: Writer, c: Color3) + local r, g, b = c.R, c.G, c.B + if r > 1 or g > 1 or b > 1 then + ensureSpace(w, 7) + rawU8(w, T_COLOR3_F) + rawF16(w, r) + rawF16(w, g) + rawF16(w, b) + else + ensureSpace(w, 4) + rawU8(w, T_COLOR3) + rawU8(w, math.clamp(math.round(r * 255), 0, 255)) + rawU8(w, math.clamp(math.round(g * 255), 0, 255)) + rawU8(w, math.clamp(math.round(b * 255), 0, 255)) + end +end + +local PACK_DISPATCH: { [string]: (Writer, any) -> () } = {} + +PACK_DISPATCH["nil"] = function(w: Writer, _: any) + wByte(w, T_NIL) +end + +PACK_DISPATCH["boolean"] = function(w: Writer, v: any) + wByte(w, v and T_TRUE or T_FALSE) +end + +PACK_DISPATCH["number"] = function(w: Writer, v: any) + packNumber(w, v) +end + +PACK_DISPATCH["string"] = function(w: Writer, v: any) + packString(w, v) +end + +PACK_DISPATCH["table"] = function(w: Writer, v: any) + packTable(w, v) +end + +PACK_DISPATCH["Vector3"] = function(w: Writer, v: any) + packVector3(w, v) +end + +PACK_DISPATCH["Vector2"] = function(w: Writer, v: any) + packVector2(w, v) +end + +PACK_DISPATCH["CFrame"] = function(w: Writer, v: any) + packCFrame(w, v) +end + +PACK_DISPATCH["Color3"] = function(w: Writer, v: any) + packColor3(w, v) +end + +PACK_DISPATCH["Instance"] = function(w: Writer, v: any) + table.insert(w.refs, v) + local refIdx = #w.refs + ensureSpace(w, 6) + rawU8(w, T_INSTANCE) + rawVarUInt(w, refIdx) +end + +PACK_DISPATCH["EnumItem"] = function(w: Writer, v: any) + local enumName = `{v.EnumType}` + local nameLen = #enumName + ensureSpace(w, 1 + 5 + nameLen + 2) + rawU8(w, T_ENUMITEM) + rawVarUInt(w, nameLen) + rawString(w, enumName, nameLen) + rawU16(w, v.Value) +end + +PACK_DISPATCH["BrickColor"] = function(w: Writer, v: any) + ensureSpace(w, 3) + rawU8(w, T_BRICKCOLOR) + rawU16(w, v.Number) +end + +PACK_DISPATCH["Enum"] = function(w: Writer, v: any) + local name = v.Name + local nameLen = #name + ensureSpace(w, 1 + 5 + nameLen) + rawU8(w, T_ENUM) + rawVarUInt(w, nameLen) + rawString(w, name, nameLen) +end + +PACK_DISPATCH["UDim2"] = function(w: Writer, v: any) + local X, Y = v.X, v.Y + ensureSpace(w, 17) + rawU8(w, T_UDIM2) + rawF32(w, X.Scale) + rawI32(w, X.Offset) + rawF32(w, Y.Scale) + rawI32(w, Y.Offset) +end + +PACK_DISPATCH["UDim"] = function(w: Writer, v: any) + ensureSpace(w, 9) + rawU8(w, T_UDIM) + rawF32(w, v.Scale) + rawI32(w, v.Offset) +end + +PACK_DISPATCH["Rect"] = function(w: Writer, v: any) + local Min, Max = v.Min, v.Max + ensureSpace(w, 17) + rawU8(w, T_RECT) + rawF32(w, Min.X) + rawF32(w, Min.Y) + rawF32(w, Max.X) + rawF32(w, Max.Y) +end + +PACK_DISPATCH["NumberRange"] = function(w: Writer, v: any) + ensureSpace(w, 9) + rawU8(w, T_NUMBERRANGE) + rawF32(w, v.Min) + rawF32(w, v.Max) +end + +PACK_DISPATCH["Ray"] = function(w: Writer, v: any) + local Origin, Direction = v.Origin, v.Direction + ensureSpace(w, 25) + rawU8(w, T_RAY) + rawF32(w, Origin.X) + rawF32(w, Origin.Y) + rawF32(w, Origin.Z) + rawF32(w, Direction.X) + rawF32(w, Direction.Y) + rawF32(w, Direction.Z) +end + +PACK_DISPATCH["ColorSequence"] = function(w: Writer, v: any) + local keypoints = v.Keypoints + local kpCount = #keypoints + ensureSpace(w, 2 + kpCount * 7) + rawU8(w, T_COLSEQ) + rawU8(w, kpCount) + for _, kp in keypoints do + rawF32(w, kp.Time) + local c = kp.Value + rawU8(w, math.clamp(math.round(c.R * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.G * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.B * 255), 0, 255)) + end +end + +PACK_DISPATCH["NumberSequence"] = function(w: Writer, v: any) + local keypoints = v.Keypoints + local kpCount = #keypoints + ensureSpace(w, 2 + kpCount * 12) + rawU8(w, T_NUMSEQ) + rawU8(w, kpCount) + for _, kp in keypoints do + rawF32(w, kp.Time) + rawF32(w, kp.Value) + rawF32(w, kp.Envelope) + end +end + +PACK_DISPATCH["buffer"] = function(w: Writer, v: any) + local len = buffer.len(v) + ensureSpace(w, 1 + 5 + len) + rawU8(w, T_BUFFER) + rawVarUInt(w, len) + buffer.copy(w.buf, w.cursor, v, 0, len) + w.cursor += len +end + +PACK_DISPATCH["Vector3int16"] = function(w: Writer, v: any) + ensureSpace(w, 7) + rawU8(w, T_VEC3I16) + rawI16(w, v.X) + rawI16(w, v.Y) + rawI16(w, v.Z) +end + +PACK_DISPATCH["Vector2int16"] = function(w: Writer, v: any) + ensureSpace(w, 5) + rawU8(w, T_VEC2I16) + rawI16(w, v.X) + rawI16(w, v.Y) +end + +PACK_DISPATCH["TweenInfo"] = function(w: Writer, v: any) + ensureSpace(w, 16) -- tag(1) + f32(4) + u8(1) + u8(1) + i32(4) + u8(1) + f32(4) + rawU8(w, T_TWEENINFO) + rawF32(w, v.Time) + rawU8(w, v.EasingStyle.Value) + rawU8(w, v.EasingDirection.Value) + rawI32(w, v.RepeatCount) + rawU8(w, v.Reverses and 1 or 0) + rawF32(w, v.DelayTime) +end + +PACK_DISPATCH["PhysicalProperties"] = function(w: Writer, v: any) + ensureSpace(w, 21) -- tag(1) + 5xf32(20) + rawU8(w, T_PHYSPROP) + rawF32(w, v.Density) + rawF32(w, v.Friction) + rawF32(w, v.Elasticity) + rawF32(w, v.FrictionWeight) + rawF32(w, v.ElasticityWeight) +end + +PACK_DISPATCH["Font"] = function(w: Writer, v: any) + local family = v.Family + local familyLen = #family + ensureSpace(w, 1 + 5 + familyLen + 3) -- tag + varuint + str + u16 + u8 + rawU8(w, T_FONT) + rawVarUInt(w, familyLen) + rawString(w, family, familyLen) + rawU16(w, v.Weight.Value) + rawU8(w, v.Style.Value) +end + +PACK_DISPATCH["DateTime"] = function(w: Writer, v: any) + ensureSpace(w, 9) -- tag(1) + f64(8) + rawU8(w, T_DATETIME) + rawF64(w, v.UnixTimestampMillis) +end + +packValue = function(w: Writer, v: any) + local handler = PACK_DISPATCH[typeof(v)] + if handler then + handler(w, v) + else + error(`Unsupported type: {typeof(v)}`) + end +end + +local UNPACK: { (buffer, number, { any }?, number) -> (any, number) } = table.create(256) + +do + local function unpackError(_: buffer, _: number, _: { any }?, tag: number): (any, number) + error(`(serdes) unknown type: {tag}`) + return nil :: any, 0 + end + for i = 1, 256 do + UNPACK[i] = unpackError + end +end + +do + local function handler(_: buffer, pos: number, _: { any }?, tag: number): (any, number) + return tag, pos + end + for i = 0x00, 0x7F do + UNPACK[i + 1] = handler + end +end + +do + local function handler(_: buffer, pos: number, _: { any }?, tag: number): (any, number) + return tag - 0x80 - 32, pos + end + for i = 0x80, 0x9F do + UNPACK[i + 1] = handler + end +end + +-- scalar types +UNPACK[T_NIL + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number) + return nil, pos +end + +UNPACK[T_FALSE + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number) + return false, pos +end + +UNPACK[T_TRUE + 1] = function(_: buffer, pos: number, _: { any }?, _: number): (any, number) + return true, pos +end + +-- numeric types +UNPACK[T_U8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readu8(buf, pos), pos + 1 +end + +UNPACK[T_U16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readu16(buf, pos), pos + 2 +end + +UNPACK[T_U32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readu32(buf, pos), pos + 4 +end + +UNPACK[T_I8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readi8(buf, pos), pos + 1 +end + +UNPACK[T_I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readi16(buf, pos), pos + 2 +end + +UNPACK[T_I32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readi32(buf, pos), pos + 4 +end + +UNPACK[T_F16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return readF16(buf, pos), pos + 2 +end + +UNPACK[T_F32 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readf32(buf, pos), pos + 4 +end + +UNPACK[T_F64 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return buffer.readf64(buf, pos), pos + 8 +end + +UNPACK[T_VEC3I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return Vector3int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2), buffer.readi16(buf, pos + 4)), + pos + 6 +end + +UNPACK[T_VEC2I16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return Vector2int16.new(buffer.readi16(buf, pos), buffer.readi16(buf, pos + 2)), pos + 4 +end + +UNPACK[T_TWEENINFO + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return TweenInfo.new( + buffer.readf32(buf, pos), + Enum.EasingStyle:FromValue(buffer.readu8(buf, pos + 4)), + Enum.EasingDirection:FromValue(buffer.readu8(buf, pos + 5)), + buffer.readi32(buf, pos + 6), + buffer.readu8(buf, pos + 10) ~= 0, + buffer.readf32(buf, pos + 11) + ), + pos + 15 +end + +UNPACK[T_PHYSPROP + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return PhysicalProperties.new( + buffer.readf32(buf, pos), + buffer.readf32(buf, pos + 4), + buffer.readf32(buf, pos + 8), + buffer.readf32(buf, pos + 12), + buffer.readf32(buf, pos + 16) + ), + pos + 20 +end + +UNPACK[T_FONT + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local familyLen + familyLen, pos = readVarUInt(buf, pos) + local family = buffer.readstring(buf, pos, familyLen) + pos += familyLen + local weight = buffer.readu16(buf, pos) + local style = buffer.readu8(buf, pos + 2) + return Font.new(family, Enum.FontWeight:FromValue(weight), Enum.FontStyle:FromValue(style)), pos + 3 +end + +UNPACK[T_DATETIME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return DateTime.fromUnixTimestampMillis(buffer.readf64(buf, pos)), pos + 8 +end + +do + local function handler(buf: buffer, pos: number, _: { any }?, tag: number): (any, number) + local n = tag - T_STR_BASE + return buffer.readstring(buf, pos, n), pos + n + end + for i = T_STR_BASE, T_STR_BASE + 15 do + UNPACK[i + 1] = handler + end +end + +UNPACK[T_STR8 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local n = buffer.readu8(buf, pos) + return buffer.readstring(buf, pos + 1, n), pos + 1 + n +end + +UNPACK[T_STR16 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local n = buffer.readu16(buf, pos) + return buffer.readstring(buf, pos + 2, n), pos + 2 + n +end + +UNPACK[T_STRVAR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local n + n, pos = readVarUInt(buf, pos) + return buffer.readstring(buf, pos, n), pos + n +end + +do + local function handler(buf: buffer, pos: number, refs: { any }?, tag: number): (any, number) + local count = tag - T_ARR_BASE + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos + end + for i = T_ARR_BASE, T_ARR_BASE + 7 do + UNPACK[i + 1] = handler + end +end + +UNPACK[T_ARR8 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos +end + +UNPACK[T_ARR16 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu16(buf, pos) + pos += 2 + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos +end + +UNPACK[T_ARRVAR + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count + count, pos = readVarUInt(buf, pos) + local arr = table.create(count) + for i = 1, count do + arr[i], pos = unpackValue(buf, pos, refs) + end + return arr, pos +end + +do + local function handler(buf: buffer, pos: number, refs: { any }?, tag: number): (any, number) + local count = tag - T_MAP_BASE + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos + end + for i = T_MAP_BASE, T_MAP_BASE + 7 do + UNPACK[i + 1] = handler + end +end + +UNPACK[T_MAP8 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos +end + +UNPACK[T_MAP16 + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count = buffer.readu16(buf, pos) + pos += 2 + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos +end + +UNPACK[T_MAPVAR + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local count + count, pos = readVarUInt(buf, pos) + local map = {} + for _ = 1, count do + local k, v + k, pos = unpackValue(buf, pos, refs) + v, pos = unpackValue(buf, pos, refs) + map[k] = v + end + return map, pos +end + +-- typed number array +UNPACK[T_TYPED_ARR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local subtype = buffer.readu8(buf, pos) + pos += 1 + local count + count, pos = readVarUInt(buf, pos) + local reader = TYPED_READERS[subtype] + local arr = table.create(count) + for i = 1, count do + arr[i], pos = reader(buf, pos) + end + return arr, pos +end + +-- boolean bitpacked array +UNPACK[T_BOOL_ARR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local count + count, pos = readVarUInt(buf, pos) + local arr = table.create(count) + local numBytes = math.ceil(count / 8) + for i = 0, numBytes - 1 do + local byte = buffer.readu8(buf, pos + i) + for bit = 0, 7 do + local idx = i * 8 + bit + 1 + if idx > count then + break + end + arr[idx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0 + end + end + return arr, pos + numBytes +end + +-- buffer +UNPACK[T_BUFFER + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local len + len, pos = readVarUInt(buf, pos) + local b = buffer.create(len) + buffer.copy(b, 0, buf, pos, len) + return b, pos + len +end + +-- Vector3 +UNPACK[T_VEC3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local x = readF16(buf, pos) + local y = readF16(buf, pos + 2) + local z = readF16(buf, pos + 4) + return Vector3.new(x, y, z), pos + 6 +end + +-- Vector2 +UNPACK[T_VEC2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local x = readF16(buf, pos) + local y = readF16(buf, pos + 2) + return Vector2.new(x, y), pos + 4 +end + +-- CFrame +UNPACK[T_CFRAME + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local px = buffer.readf32(buf, pos) + local py = buffer.readf32(buf, pos + 4) + local pz = buffer.readf32(buf, pos + 8) + local qi = buffer.readu8(buf, pos + 12) + local q0 = buffer.readi16(buf, pos + 13) + local q1 = buffer.readi16(buf, pos + 15) + local q2 = buffer.readi16(buf, pos + 17) + local qx, qy, qz, qw = decompressCFrameRotation(qi, q0, q1, q2) + return CFrame.new(px, py, pz, qx, qy, qz, qw), pos + 19 +end + +-- Color3 (RGB bytes) +UNPACK[T_COLOR3 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local r = buffer.readu8(buf, pos) + local g = buffer.readu8(buf, pos + 1) + local b = buffer.readu8(buf, pos + 2) + return Color3.fromRGB(r, g, b), pos + 3 +end + +-- Color3 (f16 floats) +UNPACK[T_COLOR3_F + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local r = readF16(buf, pos) + local g = readF16(buf, pos + 2) + local b = readF16(buf, pos + 4) + return Color3.new(r, g, b), pos + 6 +end + +-- BrickColor +UNPACK[T_BRICKCOLOR + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return BrickColor.new(buffer.readu16(buf, pos)), pos + 2 +end + +-- Instance +UNPACK[T_INSTANCE + 1] = function(buf: buffer, pos: number, refs: { any }?, _: number): (any, number) + local idx + idx, pos = readVarUInt(buf, pos) + return refs and refs[idx] or nil, pos +end + +-- EnumItem +UNPACK[T_ENUMITEM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local nameLen + nameLen, pos = readVarUInt(buf, pos) + local enumName = buffer.readstring(buf, pos, nameLen) + pos += nameLen + local val = buffer.readu16(buf, pos) + return (Enum :: any)[enumName]:FromValue(val), pos + 2 +end + +-- Enum +UNPACK[T_ENUM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local nameLen + nameLen, pos = readVarUInt(buf, pos) + local enumName = buffer.readstring(buf, pos, nameLen) + return (Enum :: any)[enumName], pos + nameLen +end + +-- UDim2 +UNPACK[T_UDIM2 + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local xs = buffer.readf32(buf, pos) + local xo = buffer.readi32(buf, pos + 4) + local ys = buffer.readf32(buf, pos + 8) + local yo = buffer.readi32(buf, pos + 12) + return UDim2.new(xs, xo, ys, yo), pos + 16 +end + +-- UDim +UNPACK[T_UDIM + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local s = buffer.readf32(buf, pos) + local o = buffer.readi32(buf, pos + 4) + return UDim.new(s, o), pos + 8 +end + +-- Rect +UNPACK[T_RECT + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return Rect.new( + buffer.readf32(buf, pos), + buffer.readf32(buf, pos + 4), + buffer.readf32(buf, pos + 8), + buffer.readf32(buf, pos + 12) + ), + pos + 16 +end + +-- NumberRange +UNPACK[T_NUMBERRANGE + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return NumberRange.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4)), pos + 8 +end + +-- Ray +UNPACK[T_RAY + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + return Ray.new( + Vector3.new(buffer.readf32(buf, pos), buffer.readf32(buf, pos + 4), buffer.readf32(buf, pos + 8)), + Vector3.new(buffer.readf32(buf, pos + 12), buffer.readf32(buf, pos + 16), buffer.readf32(buf, pos + 20)) + ), + pos + 24 +end + +-- ColorSequence +UNPACK[T_COLSEQ + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local keypoints = table.create(count) + for i = 1, count do + local time = buffer.readf32(buf, pos) + local r = buffer.readu8(buf, pos + 4) + local g = buffer.readu8(buf, pos + 5) + local b = buffer.readu8(buf, pos + 6) + keypoints[i] = ColorSequenceKeypoint.new(time, Color3.fromRGB(r, g, b)) + pos += 7 + end + return ColorSequence.new(keypoints), pos +end + +-- NumberSequence +UNPACK[T_NUMSEQ + 1] = function(buf: buffer, pos: number, _: { any }?, _: number): (any, number) + local count = buffer.readu8(buf, pos) + pos += 1 + local keypoints = table.create(count) + for i = 1, count do + local time = buffer.readf32(buf, pos) + local value = buffer.readf32(buf, pos + 4) + local envelope = buffer.readf32(buf, pos + 8) + keypoints[i] = NumberSequenceKeypoint.new(time, value, envelope) + pos += 12 + end + return NumberSequence.new(keypoints), pos +end + +unpackValue = function(buf: buffer, pos: number, refs: { any }?): (any, number) + local tag = buffer.readu8(buf, pos) + return UNPACK[tag + 1](buf, pos + 1, refs, tag) +end + +local function build(w: Writer): buffer + local result = buffer.create(w.cursor) + buffer.copy(result, 0, w.buf, 0, w.cursor) + return result +end + +local function buildWithRefs(w: Writer): (buffer, { any }?) + local result = buffer.create(w.cursor) + buffer.copy(result, 0, w.buf, 0, w.cursor) + local refs = w.refs + if #refs > 0 then + w.refs = {} + return result, refs + end + return result, nil +end + +local function reset(w: Writer) + w.cursor = 0 + table.clear(w.refs) +end + +local Schema = {} +Schema.__custom_datatypes = {} + +export type SchemaType = { type: string, [any]: any } + +Schema.u8 = { type = "u8" } +Schema.i8 = { type = "i8" } +Schema.u16 = { type = "u16" } +Schema.i16 = { type = "i16" } +Schema.u32 = { type = "u32" } +Schema.i32 = { type = "i32" } +Schema.f16 = { type = "f16" } +Schema.f32 = { type = "f32" } +Schema.f64 = { type = "f64" } +Schema.boolean = { type = "boolean" } +Schema.vector3 = { type = "vector3" } +Schema.vector2 = { type = "vector2" } +Schema.cframe = { type = "cframe" } +Schema.color3 = { type = "color3" } +Schema.color3f16 = { type = "color3f16" } +Schema.instance = { type = "instance" } +Schema.string = { type = "string" } +Schema.udim2 = { type = "udim2" } +Schema.udim = { type = "udim" } +Schema.rect = { type = "rect" } +Schema.numberrange = { type = "numberrange" } +Schema.ray = { type = "ray" } +Schema.colorsequence = { type = "colorsequence" } +Schema.numbersequence = { type = "numbersequence" } +Schema.brickcolor = { type = "brickcolor" } +Schema.buffer = { type = "buffer" } +Schema.vector3int16 = { type = "vector3int16" } +Schema.vector2int16 = { type = "vector2int16" } +Schema.tweeninfo = { type = "tweeninfo" } +Schema.physicalproperties = { type = "physicalproperties" } +Schema.font = { type = "font" } +Schema.datetime = { type = "datetime" } + +function Schema.optional(item: SchemaType): SchemaType + return { type = "optional", item = item } +end + +function Schema.array(item: SchemaType): SchemaType + return { type = "array", item = item } +end + +function Schema.map(key: SchemaType, value: SchemaType): SchemaType + return { type = "map", key = key, value = value } +end + +function Schema.struct(fields: { [string]: SchemaType }): SchemaType + local orderedFields = {} + for k, v in fields do + table.insert(orderedFields, { key = k, schema = v }) + end + table.sort(orderedFields, function(a, b) + return a.key < b.key + end) + return { type = "struct", fields = orderedFields } +end + +function Schema.custom_datatype( + name: string, + object: { any }, + writer: (w: Writer, v: any) -> (), + reader: (b: buffer, c: number, refs: { Instance }?) -> (any, number) +) + if Schema[name] then + warn(`[Warp.Schema]: this 'custom_datatype': "{name}" is already exists, try changing the name.`) + return + end + (object :: any).type = name + Schema[name] = object + Schema.__custom_datatypes[name] = { + writer = writer, + reader = reader, + } +end + +local SCHEMA_FIXED_SIZES: { [string]: number } = { + u8 = 1, + i8 = 1, + u16 = 2, + i16 = 2, + u32 = 4, + i32 = 4, + f16 = 2, + f32 = 4, + f64 = 8, + boolean = 1, + vector3 = 6, + vector2 = 4, + cframe = 19, + color3 = 3, + color3f16 = 6, + udim2 = 16, + udim = 8, + rect = 16, + numberrange = 8, + ray = 24, + brickcolor = 2, + vector3int16 = 6, + vector2int16 = 4, + tweeninfo = 15, + physicalproperties = 20, + datetime = 8, +} + +local function compilePacker(s: SchemaType): (Writer, any) -> () + local schema_type = s.type + + if Schema.__custom_datatypes[schema_type] then + return Schema.__custom_datatypes[schema_type].writer + end + + if schema_type == "u8" then + return function(w: Writer, v: any) + ensureSpace(w, 1) + rawU8(w, v) + end + end + if schema_type == "i8" then + return function(w: Writer, v: any) + ensureSpace(w, 1) + buffer.writei8(w.buf, w.cursor, v) + w.cursor += 1 + end + end + if schema_type == "u16" then + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawU16(w, v) + end + end + if schema_type == "i16" then + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawI16(w, v) + end + end + if schema_type == "u32" then + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawU32(w, v) + end + end + if schema_type == "i32" then + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawI32(w, v) + end + end + if schema_type == "f16" then + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawF16(w, v) + end + end + if schema_type == "f32" then + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawF32(w, v) + end + end + if schema_type == "f64" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF64(w, v) + end + end + if schema_type == "boolean" then + return function(w: Writer, v: any) + ensureSpace(w, 1) + rawU8(w, v and 1 or 0) + end + end + if schema_type == "string" then + return function(w: Writer, v: any) + local len = #v + ensureSpace(w, 5 + len) + rawVarUInt(w, len) + rawString(w, v, len) + end + end + + if schema_type == "vector3" then + return function(w: Writer, v: any) + ensureSpace(w, 6) + rawF16(w, v.X) + rawF16(w, v.Y) + rawF16(w, v.Z) + end + end + if schema_type == "vector2" then + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawF16(w, v.X) + rawF16(w, v.Y) + end + end + + if schema_type == "cframe" then + return function(w: Writer, v: any) + ensureSpace(w, 19) -- 3xf32(12) + u8(1) + 3xi16(6) + local pos = v.Position + local qi, q0, q1, q2 = compressCFrameRotation(v) + rawF32(w, pos.X) + rawF32(w, pos.Y) + rawF32(w, pos.Z) + rawU8(w, qi) + rawI16(w, q0) + rawI16(w, q1) + rawI16(w, q2) + end + end + + if schema_type == "color3" then + return function(w: Writer, v: any) + ensureSpace(w, 3) + rawU8(w, math.clamp(math.round(v.R * 255), 0, 255)) + rawU8(w, math.clamp(math.round(v.G * 255), 0, 255)) + rawU8(w, math.clamp(math.round(v.B * 255), 0, 255)) + end + end + + if schema_type == "color3f16" then + return function(w: Writer, v: any) + ensureSpace(w, 6) + rawF16(w, math.clamp(v.R * 255, 0, 255)) + rawF16(w, math.clamp(v.G * 255, 0, 255)) + rawF16(w, math.clamp(v.B * 255, 0, 255)) + end + end + + if schema_type == "udim2" then + return function(w: Writer, v: any) + ensureSpace(w, 16) + rawF32(w, v.X.Scale) + rawI32(w, v.X.Offset) + rawF32(w, v.Y.Scale) + rawI32(w, v.Y.Offset) + end + end + if schema_type == "udim" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF32(w, v.Scale) + rawI32(w, v.Offset) + end + end + if schema_type == "rect" then + return function(w: Writer, v: any) + ensureSpace(w, 16) + rawF32(w, v.Min.X) + rawF32(w, v.Min.Y) + rawF32(w, v.Max.X) + rawF32(w, v.Max.Y) + end + end + if schema_type == "numberrange" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF32(w, v.Min) + rawF32(w, v.Max) + end + end + if schema_type == "ray" then + return function(w: Writer, v: any) + ensureSpace(w, 24) + rawF32(w, v.Origin.X) + rawF32(w, v.Origin.Y) + rawF32(w, v.Origin.Z) + rawF32(w, v.Direction.X) + rawF32(w, v.Direction.Y) + rawF32(w, v.Direction.Z) + end + end + if schema_type == "colorsequence" then + return function(w: Writer, v: any) + local kps = v.Keypoints + local n = #kps + ensureSpace(w, 1 + n * 7) + rawU8(w, n) + for _, kp in kps do + rawF32(w, kp.Time) + local c = kp.Value + rawU8(w, math.clamp(math.round(c.R * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.G * 255), 0, 255)) + rawU8(w, math.clamp(math.round(c.B * 255), 0, 255)) + end + end + end + if schema_type == "numbersequence" then + return function(w: Writer, v: any) + local kps = v.Keypoints + local n = #kps + ensureSpace(w, 1 + n * 12) + rawU8(w, n) + for _, kp in kps do + rawF32(w, kp.Time) + rawF32(w, kp.Value) + rawF32(w, kp.Envelope) + end + end + end + if schema_type == "brickcolor" then + return function(w: Writer, v: any) + ensureSpace(w, 2) + rawU16(w, v.Number) + end + end + if schema_type == "buffer" then + return function(w: Writer, v: any) + local len = buffer.len(v) + ensureSpace(w, 5 + len) + rawVarUInt(w, len) + buffer.copy(w.buf, w.cursor, v, 0, len) + w.cursor += len + end + end + if schema_type == "vector3int16" then + return function(w: Writer, v: any) + ensureSpace(w, 6) + rawI16(w, v.X) + rawI16(w, v.Y) + rawI16(w, v.Z) + end + end + if schema_type == "vector2int16" then + return function(w: Writer, v: any) + ensureSpace(w, 4) + rawI16(w, v.X) + rawI16(w, v.Y) + end + end + if schema_type == "tweeninfo" then + return function(w: Writer, v: any) + ensureSpace(w, 15) + rawF32(w, v.Time) + rawU8(w, v.EasingStyle.Value) + rawU8(w, v.EasingDirection.Value) + rawI32(w, v.RepeatCount) + rawU8(w, v.Reverses and 1 or 0) + rawF32(w, v.DelayTime) + end + end + if schema_type == "physicalproperties" then + return function(w: Writer, v: any) + ensureSpace(w, 20) + rawF32(w, v.Density) + rawF32(w, v.Friction) + rawF32(w, v.Elasticity) + rawF32(w, v.FrictionWeight) + rawF32(w, v.ElasticityWeight) + end + end + if schema_type == "font" then + return function(w: Writer, v: any) + local family = v.Family + local familyLen = #family + ensureSpace(w, 5 + familyLen + 3) + rawVarUInt(w, familyLen) + rawString(w, family, familyLen) + rawU16(w, v.Weight.Value) + rawU8(w, v.Style.Value) + end + end + if schema_type == "datetime" then + return function(w: Writer, v: any) + ensureSpace(w, 8) + rawF64(w, v.UnixTimestampMillis) + end + end + + if schema_type == "instance" then + return function(w: Writer, v: any) + table.insert(w.refs, v) + ensureSpace(w, 5) + rawVarUInt(w, #w.refs) + end + end + + if schema_type == "struct" then + local fields = {} + local optionalIndices = {} + + for idx, field in ipairs(s.fields) do + local isOpt = field.schema.type == "optional" + table.insert(fields, { + key = field.key, + packer = compilePacker(isOpt and field.schema.item or field.schema), + optional = isOpt, + }) + if isOpt then + table.insert(optionalIndices, idx) + end + end + + local numOpt = #optionalIndices + + if numOpt == 0 then + -- Pre-calculate total fixed size for all-fixed structs + local totalFixed = 0 + local allFixed = true + for _, field in ipairs(s.fields) do + local size = SCHEMA_FIXED_SIZES[field.schema.type] + if size then + totalFixed += size + else + allFixed = false + break + end + end + + if allFixed and totalFixed > 0 then + return function(w: Writer, v: any) + if type(v) ~= "table" then + error(`Expected table for struct, got {typeof(v)}`) + end + ensureSpace(w, totalFixed) + for _, f in fields do + local val = v[f.key] + if val == nil then + error(`Schema Error: Missing required field '{f.key}'`) + end + f.packer(w, val) + end + end + end + + return function(w: Writer, v: any) + if type(v) ~= "table" then + error(`Expected table for struct, got {typeof(v)}`) + end + for _, f in fields do + local val = v[f.key] + if val == nil then + error(`Schema Error: Missing required field '{f.key}'`) + end + f.packer(w, val) + end + end + end + + local maskBytes = math.ceil(numOpt / 8) + + return function(w: Writer, v: any) + if type(v) ~= "table" then + error(`Expected table for struct, got {typeof(v)}`) + end + + ensureSpace(w, maskBytes) + for i = 0, maskBytes - 1 do + local byte = 0 + for b = 0, 7 do + local optIdx = i * 8 + b + 1 + if optIdx > numOpt then + break + end + local fieldIdx = optionalIndices[optIdx] + if v[fields[fieldIdx].key] ~= nil then + byte = bit32.bor(byte, bit32.lshift(1, b)) + end + end + buffer.writeu8(w.buf, w.cursor, byte) + w.cursor += 1 + end + + for _, f in fields do + local val = v[f.key] + if f.optional then + if val ~= nil then + f.packer(w, val) + end + else + if val == nil then + error(`Schema Error: Missing required field '{f.key}'`) + end + f.packer(w, val) + end + end + end + end + + if schema_type == "array" then + local itemSchema = s.item + + if itemSchema.type == "boolean" then + return function(w: Writer, v: any) + local len = #v + local numBytes = math.ceil(len / 8) + ensureSpace(w, 5 + numBytes) + rawVarUInt(w, len) + for i = 0, numBytes - 1 do + local byte = 0 + for bit = 0, 7 do + local idx = i * 8 + bit + 1 + if idx > len then + break + end + if v[idx] then + byte = bit32.bor(byte, bit32.lshift(1, bit)) + end + end + buffer.writeu8(w.buf, w.cursor, byte) + w.cursor += 1 + end + end + end + + local itemPacker = compilePacker(itemSchema) + return function(w: Writer, v: any) + if type(v) ~= "table" then + error(`Expected table for array, got {typeof(v)}`) + end + local len = #v + wVarUInt(w, len) + for i = 1, len do + if v[i] == nil then + error(`Schema Error: Array item at index {i} is nil`) + end + itemPacker(w, v[i]) + end + end + end + + if schema_type == "map" then + local keyPacker = compilePacker(s.key) + local valPacker = compilePacker(s.value) + return function(w: Writer, v: any) + local count = 0 + for _ in v do + count += 1 + end + wVarUInt(w, count) + for k, val in v do + keyPacker(w, k) + valPacker(w, val) + end + end + end + + if schema_type == "optional" then + local itemPacker = compilePacker(s.item) + return function(w: Writer, v: any) + if v == nil then + wByte(w, 0) + else + wByte(w, 1) + itemPacker(w, v) + end + end + end + + return function() + return + end +end + +local function compileReader(s: SchemaType): (buffer, number, { any }?) -> (any, number) + local schema_type = s.type + + if Schema.__custom_datatypes[schema_type] then + return Schema.__custom_datatypes[schema_type].reader + end + + if schema_type == "u8" then + return function(b: buffer, c: number) + return buffer.readu8(b, c), c + 1 + end + end + if schema_type == "i8" then + return function(b: buffer, c: number) + return buffer.readi8(b, c), c + 1 + end + end + if schema_type == "u16" then + return function(b: buffer, c: number) + return buffer.readu16(b, c), c + 2 + end + end + if schema_type == "i16" then + return function(b: buffer, c: number) + return buffer.readi16(b, c), c + 2 + end + end + if schema_type == "u32" then + return function(b: buffer, c: number) + return buffer.readu32(b, c), c + 4 + end + end + if schema_type == "i32" then + return function(b: buffer, c: number) + return buffer.readi32(b, c), c + 4 + end + end + if schema_type == "f16" then + return function(b: buffer, c: number) + return readF16(b, c), c + 2 + end + end + if schema_type == "f32" then + return function(b: buffer, c: number) + return buffer.readf32(b, c), c + 4 + end + end + if schema_type == "f64" then + return function(b: buffer, c: number) + return buffer.readf64(b, c), c + 8 + end + end + if schema_type == "boolean" then + return function(b: buffer, c: number) + return buffer.readu8(b, c) ~= 0, c + 1 + end + end + if schema_type == "string" then + return function(b: buffer, c: number) + local len + len, c = readVarUInt(b, c) + return buffer.readstring(b, c, len), c + len + end + end + if schema_type == "vector3" then + return function(b: buffer, c: number) + local x = readF16(b, c) + local y = readF16(b, c + 2) + local z = readF16(b, c + 4) + return Vector3.new(x, y, z), c + 6 + end + end + if schema_type == "vector2" then + return function(b: buffer, c: number) + local x = readF16(b, c) + local y = readF16(b, c + 2) + return Vector2.new(x, y), c + 4 + end + end + if schema_type == "color3" then + return function(b: buffer, c: number) + local r = buffer.readu8(b, c) + local g = buffer.readu8(b, c + 1) + local bVal = buffer.readu8(b, c + 2) + return Color3.fromRGB(r, g, bVal), c + 3 + end + end + if schema_type == "color3f16" then + return function(b: buffer, c: number) + local r = readF16(b, c) + local g = readF16(b, c + 2) + local bVal = readF16(b, c + 4) + return Color3.fromRGB(r, g, bVal), c + 6 + end + end + if schema_type == "cframe" then + return function(b: buffer, c: number) + local px = buffer.readf32(b, c) + local py = buffer.readf32(b, c + 4) + local pz = buffer.readf32(b, c + 8) + local qi = buffer.readu8(b, c + 12) + local q0 = buffer.readi16(b, c + 13) + local q1 = buffer.readi16(b, c + 15) + local q2 = buffer.readi16(b, c + 17) + local qx, qy, qz, qw = decompressCFrameRotation(qi, q0, q1, q2) + return CFrame.new(px, py, pz, qx, qy, qz, qw), c + 19 + end + end + + if schema_type == "udim2" then + return function(b: buffer, c: number) + return UDim2.new( + buffer.readf32(b, c), + buffer.readi32(b, c + 4), + buffer.readf32(b, c + 8), + buffer.readi32(b, c + 12) + ), + c + 16 + end + end + if schema_type == "udim" then + return function(b: buffer, c: number) + return UDim.new(buffer.readf32(b, c), buffer.readi32(b, c + 4)), c + 8 + end + end + if schema_type == "rect" then + return function(b: buffer, c: number) + return Rect.new( + buffer.readf32(b, c), + buffer.readf32(b, c + 4), + buffer.readf32(b, c + 8), + buffer.readf32(b, c + 12) + ), + c + 16 + end + end + if schema_type == "numberrange" then + return function(b: buffer, c: number) + return NumberRange.new(buffer.readf32(b, c), buffer.readf32(b, c + 4)), c + 8 + end + end + if schema_type == "ray" then + return function(b: buffer, c: number) + return Ray.new( + Vector3.new(buffer.readf32(b, c), buffer.readf32(b, c + 4), buffer.readf32(b, c + 8)), + Vector3.new(buffer.readf32(b, c + 12), buffer.readf32(b, c + 16), buffer.readf32(b, c + 20)) + ), + c + 24 + end + end + if schema_type == "colorsequence" then + return function(b: buffer, c: number) + local n = buffer.readu8(b, c) + c += 1 + local kps = table.create(n) + for i = 1, n do + local t = buffer.readf32(b, c) + kps[i] = ColorSequenceKeypoint.new( + t, + Color3.fromRGB(buffer.readu8(b, c + 4), buffer.readu8(b, c + 5), buffer.readu8(b, c + 6)) + ) + c += 7 + end + return ColorSequence.new(kps), c + end + end + if schema_type == "numbersequence" then + return function(b: buffer, c: number) + local n = buffer.readu8(b, c) + c += 1 + local kps = table.create(n) + for i = 1, n do + kps[i] = + NumberSequenceKeypoint.new(buffer.readf32(b, c), buffer.readf32(b, c + 4), buffer.readf32(b, c + 8)) + c += 12 + end + return NumberSequence.new(kps), c + end + end + if schema_type == "brickcolor" then + return function(b: buffer, c: number) + return BrickColor.new(buffer.readu16(b, c)), c + 2 + end + end + if schema_type == "buffer" then + return function(b: buffer, c: number) + local len + len, c = readVarUInt(b, c) + local out = buffer.create(len) + buffer.copy(out, 0, b, c, len) + return out, c + len + end + end + if schema_type == "vector3int16" then + return function(b: buffer, c: number) + return Vector3int16.new(buffer.readi16(b, c), buffer.readi16(b, c + 2), buffer.readi16(b, c + 4)), c + 6 + end + end + if schema_type == "vector2int16" then + return function(b: buffer, c: number) + return Vector2int16.new(buffer.readi16(b, c), buffer.readi16(b, c + 2)), c + 4 + end + end + if schema_type == "tweeninfo" then + return function(b: buffer, c: number) + return TweenInfo.new( + buffer.readf32(b, c), + Enum.EasingStyle:FromValue(buffer.readu8(b, c + 4)), + Enum.EasingDirection:FromValue(buffer.readu8(b, c + 5)), + buffer.readi32(b, c + 6), + buffer.readu8(b, c + 10) ~= 0, + buffer.readf32(b, c + 11) + ), + c + 15 + end + end + if schema_type == "physicalproperties" then + return function(b: buffer, c: number) + return PhysicalProperties.new( + buffer.readf32(b, c), + buffer.readf32(b, c + 4), + buffer.readf32(b, c + 8), + buffer.readf32(b, c + 12), + buffer.readf32(b, c + 16) + ), + c + 20 + end + end + if schema_type == "font" then + return function(b: buffer, c: number) + local familyLen + familyLen, c = readVarUInt(b, c) + local family = buffer.readstring(b, c, familyLen) + c += familyLen + return Font.new( + family, + Enum.FontWeight:FromValue(buffer.readu16(b, c)), + Enum.FontStyle:FromValue(buffer.readu8(b, c + 2)) + ), + c + 3 + end + end + if schema_type == "datetime" then + return function(b: buffer, c: number) + return DateTime.fromUnixTimestampMillis(buffer.readf64(b, c)), c + 8 + end + end + if schema_type == "instance" then + return function(b: buffer, c: number, refs: { any }?) + local idx + idx, c = readVarUInt(b, c) + return refs and refs[idx] or nil, c + end + end + + if schema_type == "struct" then + local fields = {} + local optionalIndices = {} + + for idx, field in ipairs(s.fields) do + local isOpt = field.schema.type == "optional" + table.insert(fields, { + key = field.key, + reader = compileReader(isOpt and field.schema.item or field.schema), + optional = isOpt, + }) + if isOpt then + table.insert(optionalIndices, idx) + end + end + + local numOpt = #optionalIndices + + if numOpt == 0 then + return function(b: buffer, c: number, refs: { any }?) + local obj = {} + for _, f in fields do + obj[f.key], c = f.reader(b, c, refs) + end + return obj, c + end + end + + local maskBytes = math.ceil(numOpt / 8) + + return function(b: buffer, c: number, refs: { any }?) + local present = {} + for i = 0, maskBytes - 1 do + local byte = buffer.readu8(b, c + i) + for bit = 0, 7 do + local optIdx = i * 8 + bit + 1 + if optIdx > numOpt then + break + end + present[optIdx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0 + end + end + c += maskBytes + + local obj = {} + local optIdx = 0 + for _, f in fields do + if f.optional then + optIdx += 1 + if present[optIdx] then + obj[f.key], c = f.reader(b, c, refs) + end + else + obj[f.key], c = f.reader(b, c, refs) + end + end + + return obj, c + end + end + + if schema_type == "array" then + local itemSchema = s.item + + if itemSchema.type == "boolean" then + return function(b: buffer, c: number, _: { any }?) + local len + len, c = readVarUInt(b, c) + local arr = table.create(len) + local numBytes = math.ceil(len / 8) + for i = 0, numBytes - 1 do + local byte = buffer.readu8(b, c + i) + for bit = 0, 7 do + local idx = i * 8 + bit + 1 + if idx > len then + break + end + arr[idx] = bit32.band(byte, bit32.lshift(1, bit)) ~= 0 + end + end + return arr, c + numBytes + end + end + + local itemReader = compileReader(itemSchema) + return function(b: buffer, c: number, refs: { any }?) + local len + len, c = readVarUInt(b, c) + local arr = table.create(len) + for i = 1, len do + arr[i], c = itemReader(b, c, refs) + end + return arr, c + end + end + + if schema_type == "map" then + local keyReader = compileReader(s.key) + local valReader = compileReader(s.value) + return function(b: buffer, c: number, refs: { any }?) + local count + count, c = readVarUInt(b, c) + local map = {} + for _ = 1, count do + local k, val + k, c = keyReader(b, c, refs) + val, c = valReader(b, c, refs) + map[k] = val + end + return map, c + end + end + + if schema_type == "optional" then + local itemReader = compileReader(s.item) + return function(b: buffer, c: number, refs: { any }?) + local exists = buffer.readu8(b, c) ~= 0 + c += 1 + if exists then + return itemReader(b, c, refs) + else + return nil, c + end + end + end + + return function(_: buffer, c: number) + return nil, c + end +end + +local packerCache = setmetatable({}, { __mode = "k" }) +local readerCache = setmetatable({}, { __mode = "k" }) + +local function packStrict(w: Writer, s: SchemaType, v: any) + local packer = packerCache[s] + if not packer then + packer = compilePacker(s) + packerCache[s] = packer + end + packer(w, v) +end + +local function readStrict(buf: buffer, cursor: number, s: SchemaType, refs: { any }?): (any, number) + local reader = readerCache[s] + if not reader then + reader = compileReader(s) + readerCache[s] = reader + end + return reader(buf, cursor, refs) +end + +local function writeEvents(w: Writer, events: { { any } }, schemas: { [number]: SchemaType }) + local count = #events + wVarUInt(w, count) + for _, event in events do + local id = event[1] + local args = event[2] + wByte(w, id) + local schema = schemas[id] + if schema then + packStrict(w, schema, args[1]) + else + packValue(w, args) + end + end +end + +local function readEvents(buf: buffer, refs: { any }?, schemas: { [number]: SchemaType }): { { any } } + local pos, count = 0, 0 + count, pos = readVarUInt(buf, pos) + local events = table.create(count) + for i = 1, count do + local id: number = buffer.readu8(buf, pos) + pos += 1 + local args: any + local schema = schemas[id] + if schema then + local val + val, pos = readStrict(buf, pos, schema, refs) + args = { val } + else + args, pos = unpackValue(buf, pos, refs) + end + events[i] = { id, args } + end + return events +end + +local function writeRepl(w: Writer, content: { [string]: number }, count: number, schema: SchemaType) + wVarUInt(w, count) + for name, id in content do + wByte(w, id) + if schema then + packStrict(w, schema, name) + else + packValue(w, name) + end + end +end + +local function readRepl(buf: buffer, schema: SchemaType): { { any } } + local pos, count = 0, 0 + count, pos = readVarUInt(buf, pos) + local events = table.create(count) + for i = 1, count do + local id: number = buffer.readu8(buf, pos) + pos += 1 + local args: any + if schema then + local val + val, pos = readStrict(buf, pos, schema) + args = val + else + args, pos = unpackValue(buf, pos) + end + events[i] = { id, args } + end + return events +end + +local BufferSerdes = {} + +BufferSerdes.Schema = Schema + +BufferSerdes.writeRepl = writeRepl +BufferSerdes.readRepl = readRepl + +BufferSerdes.writeEvents = writeEvents +BufferSerdes.readEvents = readEvents + +BufferSerdes.compilePacker = compilePacker +BufferSerdes.compileReader = compileReader + +BufferSerdes.packStrict = packStrict +BufferSerdes.readStrict = readStrict + +function BufferSerdes.write(data: any): (buffer, { any }?) + local w = createWriter() + packValue(w, data) + return buildWithRefs(w) +end + +function BufferSerdes.read(buf: buffer, refs: { any }?): any + return (unpackValue(buf, 0, refs)) +end + +function BufferSerdes.readAll(buf: buffer, refs: { any }?): { any } + local bufLen = buffer.len(buf) + local pos = 0 + local results = {} + while pos < bufLen do + local value + value, pos = unpackValue(buf, pos, refs) + table.insert(results, value) + end + return results +end + +BufferSerdes.createWriter = createWriter +BufferSerdes.pack = packValue +BufferSerdes.build = build +BufferSerdes.buildWithRefs = buildWithRefs +BufferSerdes.reset = reset + +BufferSerdes.readTagged = unpackValue +BufferSerdes.packTagged = packValue +BufferSerdes.unpack = unpackValue + +-- for internal test +BufferSerdes.writeVarUInt = writeVarUInt +BufferSerdes.readVarUInt = readVarUInt +BufferSerdes.varUIntSize = varUIntSize + +-- for custom datatype usage +BufferSerdes.ensureSpace = ensureSpace +BufferSerdes.writeu8 = wByte +BufferSerdes.writeu16 = wU16 +BufferSerdes.writeu32 = wU32 +BufferSerdes.writei16 = wI16 +BufferSerdes.writei32 = wI32 +BufferSerdes.writef16 = wF16 +BufferSerdes.writef32 = wF32 +BufferSerdes.writef64 = wF64 +BufferSerdes.writestring = wString + +return BufferSerdes :: typeof(BufferSerdes) diff --git a/benchmark/src/shared/modes/warp/lib/Util/Identifier.luau b/benchmark/src/shared/modes/warp/lib/Util/Identifier.luau new file mode 100644 index 0000000..fc79791 --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Util/Identifier.luau @@ -0,0 +1,59 @@ +--!optimize 2 +--!strict +--@EternityDev +local BITS: number = 8 +local MAX_VALUE: number = bit32.lshift(1, BITS) - 1 +local hook_fn: (string, number) -> () + +if not shared.__warp_identifier_registry then + shared.__warp_identifier_registry = { + cache = {} :: { [string]: number }, + name = {} :: { [number]: string }, + counter = 0, + } +end + +local registry = shared.__warp_identifier_registry +local function fastAssert(condition: boolean, ...: any) + if not condition then + error(...) + end +end + +local Identifier = {} + +Identifier.on_added = function(fn: (string, number) -> ()) + fastAssert(hook_fn == nil, ".on_added can only be called once.") + hook_fn = fn +end + +Identifier.has_name = function(name: string): boolean + return registry.cache[name] ~= nil +end + +Identifier.has_id = function(id: number): boolean + return registry.name[id] ~= nil +end + +Identifier.get_id = function(name: string): number + local cached = registry.cache[name] + if cached then + return cached + end + + local id = registry.counter + 1 + fastAssert(id <= MAX_VALUE, `Identifier overflow: exceeded {MAX_VALUE + 1} unique names.`) + registry.counter = id + registry.cache[name] = id + registry.name[id] = name + if hook_fn then + task.defer(hook_fn, name, id) + end + return id +end + +Identifier.get_name = function(id: number): string + return registry.name[id] +end + +return Identifier :: typeof(Identifier) diff --git a/benchmark/src/shared/modes/warp/lib/Util/Thread.luau b/benchmark/src/shared/modes/warp/lib/Util/Thread.luau new file mode 100644 index 0000000..a0345bb --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Util/Thread.luau @@ -0,0 +1,25 @@ +--!strict +--!optimize 2 +--@EternityDev +local thread: thread? + +local function passer(func: (T...) -> (), ...: T...): () + local HoldThread: thread = thread :: thread + thread = nil + func(...) + thread = HoldThread +end + +local function newThread(): () + thread = coroutine.running() + while true do + passer(coroutine.yield()) + end +end + +return function(func: (T...) -> (), ...: T...): () + if not thread then + task.spawn(newThread) + end + task.spawn(thread :: thread, func, ...) +end diff --git a/benchmark/src/shared/modes/warp/lib/Util/Xor.luau b/benchmark/src/shared/modes/warp/lib/Util/Xor.luau new file mode 100644 index 0000000..980a239 --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/Util/Xor.luau @@ -0,0 +1,107 @@ +--!strict +--!native +--!optimize 2 +local Xor = {} + +type XorEntry = { buf: buffer, len: number } + +local serverPrev: { [Player]: XorEntry } = {} +local clientPrev: { [Player]: XorEntry } = {} + +local clientOutPrev: XorEntry? = nil +local clientInPrev: XorEntry? = nil + +local function xorApply(current: buffer, curLen: number, prev: buffer, prevLen: number): buffer + local out = buffer.create(curLen) + local common = if curLen < prevLen then curLen else prevLen + + local wordEnd = (common // 4) * 4 + for off = 0, wordEnd - 4, 4 do + buffer.writeu32(out, off, bit32.bxor(buffer.readu32(current, off), buffer.readu32(prev, off))) + end + for i = wordEnd, common - 1 do + buffer.writeu8(out, i, bit32.bxor(buffer.readu8(current, i), buffer.readu8(prev, i))) + end + + if curLen > common then + buffer.copy(out, common, current, common, curLen - common) + end + return out +end + +local function storeEntry(entry: XorEntry?, src: buffer, len: number): XorEntry + local e: XorEntry = entry or { buf = buffer.create(len > 64 and len or 64), len = 0 } + + if buffer.len(e.buf) < len then + e.buf = buffer.create(len * 2) + end + + buffer.copy(e.buf, 0, src, 0, len) + e.len = len + return e +end + +function Xor.remove(player: Player) + serverPrev[player] = nil + clientPrev[player] = nil +end + +function Xor.encodeServer(player: Player, buf: buffer): buffer + local len = buffer.len(buf) + local entry = serverPrev[player] + + if not entry then + serverPrev[player] = storeEntry(nil, buf, len) + return buf + end + + local encoded = xorApply(buf, len, entry.buf, entry.len) + serverPrev[player] = storeEntry(entry, buf, len) + return encoded +end + +function Xor.decodeClient(buf: buffer): buffer + local len = buffer.len(buf) + local prev = clientInPrev + + if not prev then + clientInPrev = storeEntry(nil, buf, len) + return buf + end + + local recovered = xorApply(buf, len, prev.buf, prev.len) + clientInPrev = storeEntry(prev, recovered, len) + return recovered +end + +function Xor.encodeClient(buf: buffer): buffer + local len = buffer.len(buf) + local prev = clientOutPrev + + if not prev then + clientOutPrev = storeEntry(nil, buf, len) + return buf + end + + local encoded = xorApply(buf, len, prev.buf, prev.len) + clientOutPrev = storeEntry(prev, buf, len) + return encoded +end + +function Xor.decodeServer(player: Player, buf: buffer): buffer + local len = buffer.len(buf) + local entry = clientPrev[player] + + if not entry then + clientPrev[player] = storeEntry(nil, buf, len) + return buf + end + + local recovered = xorApply(buf, len, entry.buf, entry.len) + clientPrev[player] = storeEntry(entry, recovered, len) + return recovered +end + +Xor._apply = xorApply + +return Xor diff --git a/benchmark/src/shared/modes/warp/lib/init.luau b/benchmark/src/shared/modes/warp/lib/init.luau new file mode 100644 index 0000000..ba8a75d --- /dev/null +++ b/benchmark/src/shared/modes/warp/lib/init.luau @@ -0,0 +1,44 @@ +--!strict +--@EternityDev +local Remote = {} + +if game.RunService:IsServer() then + if not script:FindFirstChild("_repl") then + Instance.new("RemoteEvent", script).Name = "_repl" + end + if not script:FindFirstChild("Event") then + Instance.new("RemoteEvent", script).Name = "Event" + end + if not script:FindFirstChild("UnreliableEvent") then + Instance.new("UnreliableRemoteEvent", script).Name = "UnreliableEvent" + end +end + +local Buffer = require(script.Util.Buffer) +local Client = require(script.Client) +local Server = require(script.Server) + +--[[ + @class Remote + @client +]] +Remote.Client = function() + return Client +end + +--[[ + @class Remote + @server +]] +Remote.Server = function() + return Server +end + +--[[ + @class Remote + @schema + define a schema for your data and use a strict packing +]] +Remote.Buffer = Buffer + +return Remote :: typeof(Remote)