@axpecter/lync 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +342 -428
- package/package.json +1 -1
- package/src/Types.luau +63 -31
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +187 -186
- package/src/api/Query.luau +223 -284
- package/src/api/Scope.luau +46 -57
- package/src/api/Signal.luau +104 -137
- package/src/codec/Base.luau +69 -20
- package/src/codec/composite/Array.luau +179 -270
- package/src/codec/composite/Map.luau +97 -347
- package/src/codec/composite/Optional.luau +27 -24
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +200 -360
- package/src/codec/composite/Tagged.luau +62 -187
- package/src/codec/composite/Tuple.luau +79 -103
- package/src/codec/datatype/Buffer.luau +49 -21
- package/src/codec/datatype/CFrame.luau +207 -30
- package/src/codec/datatype/Color.luau +21 -11
- package/src/codec/datatype/Instance.luau +28 -21
- package/src/codec/datatype/IntVector.luau +33 -21
- package/src/codec/datatype/NumberRange.luau +19 -10
- package/src/codec/datatype/Ray.luau +26 -22
- package/src/codec/datatype/Rect.luau +20 -16
- package/src/codec/datatype/Region.luau +51 -45
- package/src/codec/datatype/Sequence.luau +64 -56
- package/src/codec/datatype/String.luau +89 -56
- package/src/codec/datatype/UDim.luau +40 -22
- package/src/codec/datatype/Vector.luau +181 -17
- package/src/codec/meta/Auto.luau +295 -253
- package/src/codec/meta/Bitfield.luau +104 -148
- package/src/codec/meta/Custom.luau +8 -4
- package/src/codec/meta/Enum.luau +29 -57
- package/src/codec/meta/Float.luau +64 -0
- package/src/codec/meta/Nothing.luau +9 -5
- package/src/codec/meta/Unknown.luau +44 -36
- package/src/codec/primitive/Bool.luau +16 -26
- package/src/codec/primitive/Float16.luau +58 -64
- package/src/codec/primitive/Int.luau +68 -0
- package/src/codec/primitive/Number.luau +21 -43
- package/src/codec/primitive/Varint.luau +67 -46
- package/src/index.d.ts +251 -335
- package/src/init.luau +319 -207
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +333 -278
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +56 -113
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +59 -170
- package/src/transport/Gate.luau +282 -142
- package/src/transport/Reader.luau +148 -140
- package/src/transport/Server.luau +138 -488
- package/src/api/Namespace.luau +0 -256
- package/src/codec/meta/Quantized.luau +0 -174
package/README.md
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
<h1 align="center">Lync</h1>
|
|
2
|
-
<p align="center">Buffer networking for Roblox
|
|
2
|
+
<p align="center">Buffer networking for Roblox.</p>
|
|
3
3
|
<p align="center">
|
|
4
4
|
<a href="https://github.com/Axp3cter/Lync/releases/latest">Releases</a> ·
|
|
5
|
+
<a href="#install">Install</a> ·
|
|
5
6
|
<a href="#example">Example</a> ·
|
|
6
|
-
<a href="#
|
|
7
|
-
<a href="#
|
|
7
|
+
<a href="#codecs">Codecs</a> ·
|
|
8
|
+
<a href="#benchmarks">Benchmarks</a>
|
|
8
9
|
</p>
|
|
9
10
|
|
|
11
|
+
Lync batches all sends into a single buffer per player per frame, applies XOR compression across frames, validates and rate-limits every incoming payload, and does it all without code generation.
|
|
12
|
+
|
|
10
13
|
## Install
|
|
11
14
|
|
|
12
|
-
**Wally
|
|
15
|
+
**Wally**
|
|
13
16
|
|
|
14
17
|
```toml
|
|
15
18
|
[dependencies]
|
|
16
|
-
Lync = "axp3cter/lync@2.
|
|
19
|
+
Lync = "axp3cter/lync@2.1.1"
|
|
17
20
|
```
|
|
18
21
|
|
|
19
22
|
**npm (roblox-ts)**
|
|
@@ -26,52 +29,46 @@ npm install @axpecter/lync
|
|
|
26
29
|
import Lync from "@axpecter/lync";
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
Or grab the `.rbxm` from [
|
|
32
|
+
Or grab the `.rbxm` from [Releases](https://github.com/Axp3cter/Lync/releases/latest).
|
|
30
33
|
|
|
31
34
|
> [!IMPORTANT]
|
|
32
|
-
> Define
|
|
35
|
+
> Define all packets, queries, and groups before calling `Lync.start()`.
|
|
33
36
|
|
|
34
37
|
## Example
|
|
35
38
|
|
|
36
|
-
**Shared**
|
|
39
|
+
**Shared** (`ReplicatedStorage.Net`)
|
|
37
40
|
|
|
38
41
|
```luau
|
|
39
42
|
local Lync = require(game.ReplicatedStorage.Lync)
|
|
40
43
|
|
|
41
44
|
local Net = {}
|
|
42
45
|
|
|
43
|
-
Net.State = Lync.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
headshot = Lync.bool,
|
|
58
|
-
}),
|
|
59
|
-
rateLimit = { maxPerSecond = 30, burstAllowance = 5 },
|
|
46
|
+
Net.State = Lync.packet("State", Lync.deltaStruct({
|
|
47
|
+
position = Lync.vec3,
|
|
48
|
+
health = Lync.float(0, 100, 0.5),
|
|
49
|
+
shield = Lync.float(0, 100, 0.5),
|
|
50
|
+
status = Lync.enum("idle", "moving", "attacking", "dead"),
|
|
51
|
+
alive = Lync.bool,
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
Net.Hit = Lync.packet("Hit", Lync.struct({
|
|
55
|
+
targetId = Lync.int(0, 65535),
|
|
56
|
+
damage = Lync.float(0, 200, 0.1),
|
|
57
|
+
headshot = Lync.bool,
|
|
58
|
+
}), {
|
|
59
|
+
rateLimit = { maxPerSecond = 30, burst = 5 },
|
|
60
60
|
validate = function(data, player)
|
|
61
61
|
if data.damage > 200 then return false, "damage" end
|
|
62
62
|
return true
|
|
63
63
|
end,
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
Net.Chat = Lync.
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
Net.Chat = Lync.packet("Chat", Lync.struct({
|
|
67
|
+
msg = Lync.string(200),
|
|
68
|
+
channel = Lync.int(0, 255),
|
|
69
|
+
}))
|
|
69
70
|
|
|
70
|
-
Net.Ping = Lync.
|
|
71
|
-
request = Lync.nothing,
|
|
72
|
-
response = Lync.f64,
|
|
73
|
-
timeout = 3,
|
|
74
|
-
})
|
|
71
|
+
Net.Ping = Lync.query("Ping", Lync.nothing, Lync.f64, { timeout = 3 })
|
|
75
72
|
|
|
76
73
|
return table.freeze(Net)
|
|
77
74
|
```
|
|
@@ -83,22 +80,15 @@ local Lync = require(game.ReplicatedStorage.Lync)
|
|
|
83
80
|
local Net = require(game.ReplicatedStorage.Net)
|
|
84
81
|
local Players = game:GetService("Players")
|
|
85
82
|
|
|
86
|
-
local alive = Lync.
|
|
83
|
+
local alive = Lync.group("alive")
|
|
87
84
|
|
|
88
|
-
Lync.
|
|
89
|
-
print("[out]", name)
|
|
90
|
-
return data
|
|
91
|
-
end)
|
|
92
|
-
|
|
93
|
-
Lync.onDrop(function(player, reason, name, data)
|
|
85
|
+
Lync.onDrop(function(player, reason, name)
|
|
94
86
|
warn(player.Name, "dropped", name, reason)
|
|
95
87
|
end)
|
|
96
88
|
|
|
97
89
|
Lync.start()
|
|
98
90
|
|
|
99
|
-
Players.PlayerAdded:Connect(function(player)
|
|
100
|
-
alive:add(player)
|
|
101
|
-
end)
|
|
91
|
+
Players.PlayerAdded:Connect(function(player) alive:add(player) end)
|
|
102
92
|
|
|
103
93
|
game:GetService("RunService").Heartbeat:Connect(function()
|
|
104
94
|
Net.State:send({
|
|
@@ -110,24 +100,14 @@ game:GetService("RunService").Heartbeat:Connect(function()
|
|
|
110
100
|
}, alive)
|
|
111
101
|
end)
|
|
112
102
|
|
|
113
|
-
Net.Hit:
|
|
103
|
+
Net.Hit:on(function(data, player)
|
|
114
104
|
local target = Players:GetPlayerByUserId(data.targetId)
|
|
115
105
|
if not target then return end
|
|
116
|
-
|
|
117
106
|
alive:remove(target)
|
|
118
107
|
Net.Chat:send({ msg = player.Name .. " eliminated " .. target.Name, channel = 0 }, Lync.all)
|
|
119
|
-
Net.State:send({
|
|
120
|
-
position = Vector3.zero,
|
|
121
|
-
health = 0,
|
|
122
|
-
shield = 0,
|
|
123
|
-
status = "dead",
|
|
124
|
-
alive = false,
|
|
125
|
-
}, Lync.except(target))
|
|
126
108
|
end)
|
|
127
109
|
|
|
128
|
-
Net.Ping:
|
|
129
|
-
return os.clock()
|
|
130
|
-
end)
|
|
110
|
+
Net.Ping:handle(function(_, player) return os.clock() end)
|
|
131
111
|
```
|
|
132
112
|
|
|
133
113
|
**Client**
|
|
@@ -140,474 +120,408 @@ Lync.start()
|
|
|
140
120
|
|
|
141
121
|
local scope = Lync.scope()
|
|
142
122
|
|
|
143
|
-
scope:
|
|
123
|
+
scope:on(Net.State, function(state)
|
|
144
124
|
local character = game.Players.LocalPlayer.Character
|
|
145
125
|
if not character then return end
|
|
146
126
|
character:PivotTo(CFrame.new(state.position))
|
|
147
127
|
end)
|
|
148
128
|
|
|
149
|
-
scope:
|
|
150
|
-
print("[chat]", data.msg)
|
|
151
|
-
end)
|
|
129
|
+
scope:on(Net.Chat, function(data) print("[chat]", data.msg) end)
|
|
152
130
|
|
|
153
131
|
Net.Hit:send({ targetId = 123, damage = 45.5, headshot = true })
|
|
154
132
|
|
|
155
133
|
local serverTime = Net.Ping:request(nil)
|
|
156
|
-
if serverTime then
|
|
157
|
-
print("server clock:", serverTime)
|
|
158
|
-
end
|
|
134
|
+
if serverTime then print("server clock:", serverTime) end
|
|
159
135
|
```
|
|
160
136
|
|
|
161
|
-
## Lifecycle
|
|
162
|
-
|
|
163
|
-
| | What it does |
|
|
164
|
-
|:---------|:------------|
|
|
165
|
-
| `Lync.start()` | Sets up transport. Server creates remotes, client connects. Call once after all definitions. |
|
|
166
|
-
| `Lync.VERSION` | `"2.0.0"` |
|
|
167
|
-
|
|
168
137
|
## Packets
|
|
169
138
|
|
|
170
|
-
`Lync.
|
|
171
|
-
|
|
172
|
-
| Config | Type | Required | What it does |
|
|
173
|
-
|:-------|:-----|:--------:|:-------------|
|
|
174
|
-
| `value` | Codec | Yes | How to serialize the payload. |
|
|
175
|
-
| `unreliable` | boolean | No | Sends over UnreliableRemoteEvent. Default `false`. Cant use with delta codecs. |
|
|
176
|
-
| `rateLimit` | `{ maxPerSecond, burstAllowance? }` | No | Server-side token bucket. Burst defaults to maxPerSecond if you dont set it. |
|
|
177
|
-
| `validate` | `(data, player) → (bool, string?)` | No | Server-side. Return `false, "reason"` to drop. Runs after NaN scan. |
|
|
178
|
-
| `maxPayloadBytes` | number | No | Server-side. Max bytes a single batch of this packet can consume. Fires `onDrop` with reason `"size"` if exceeded. |
|
|
179
|
-
| `timestamp` | `"frame" \| "offset" \| "full"` | No | Prepends a timestamp to each item. `"frame"` = u8 wrapping counter (1B), `"offset"` = u16 ms (2B), `"full"` = f64 clock (8B). Listeners receive it as a third argument. |
|
|
139
|
+
`Lync.packet(name, codec, options?)`
|
|
180
140
|
|
|
181
|
-
|
|
141
|
+
### Options
|
|
182
142
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
```
|
|
143
|
+
| Field | Type | Default | Description |
|
|
144
|
+
|:------|:-----|:--------|:------------|
|
|
145
|
+
| `unreliable` | `boolean` | `false` | Send over `UnreliableRemoteEvent`. Cannot use with delta codecs. |
|
|
146
|
+
| `rateLimit` | `RateLimitConfig` | none | Server-side rate limiting. |
|
|
147
|
+
| `validate` | `(data, player) → (bool, string?)` | none | Server-side validation. Return `false, "reason"` to drop. |
|
|
148
|
+
| `maxPayloadBytes` | `number` | none | Max bytes per payload. |
|
|
149
|
+
| `timestamp` | `"frame"`, `"offset"`, or `"full"` | none | Appends a timestamp. `"frame"` = 1B counter. `"offset"` = 2B ms. `"full"` = 8B clock. Received as third argument. |
|
|
191
150
|
|
|
192
|
-
|
|
151
|
+
### Sending
|
|
193
152
|
|
|
194
153
|
```luau
|
|
195
|
-
|
|
154
|
+
-- Server
|
|
155
|
+
packet:send(data, player)
|
|
156
|
+
packet:send(data, Lync.all)
|
|
157
|
+
packet:send(data, Lync.except(p1, p2))
|
|
158
|
+
packet:send(data, { p1, p2, p3 })
|
|
159
|
+
packet:send(data, group)
|
|
160
|
+
|
|
161
|
+
-- Client
|
|
162
|
+
packet:send(data)
|
|
196
163
|
```
|
|
197
164
|
|
|
198
|
-
|
|
165
|
+
### Receiving
|
|
199
166
|
|
|
200
|
-
| Method |
|
|
167
|
+
| Method | Description |
|
|
201
168
|
|:-------|:------------|
|
|
202
|
-
| `packet:
|
|
203
|
-
| `packet:once(fn
|
|
204
|
-
| `packet:wait()` | Returns `
|
|
205
|
-
| `packet:
|
|
169
|
+
| `packet:on(fn)` | `fn(data, sender, timestamp?)`. Returns a Connection. |
|
|
170
|
+
| `packet:once(fn)` | Fires once, then disconnects. |
|
|
171
|
+
| `packet:wait()` | Yields until next fire. Returns `data, sender, timestamp?`. |
|
|
172
|
+
| `packet:name()` | Returns the packet name. |
|
|
173
|
+
| `packet:stats()` | Returns `{ bytesSent, bytesReceived, fires, recvFires, drops }`. Requires stats enabled. |
|
|
206
174
|
|
|
207
175
|
## Queries
|
|
208
176
|
|
|
209
|
-
`Lync.
|
|
177
|
+
`Lync.query(name, requestCodec, responseCodec, options?)`
|
|
210
178
|
|
|
211
|
-
|
|
212
|
-
|:-------|:-----|:--------:|:-------------|
|
|
213
|
-
| `request` | Codec | Yes | How to serialize the request. |
|
|
214
|
-
| `response` | Codec | Yes | How to serialize the response. |
|
|
215
|
-
| `timeout` | number | No | Seconds before giving up. Default `5`. |
|
|
216
|
-
| `rateLimit` | `{ maxPerSecond, burstAllowance? }` | No | Server-side token bucket on incoming requests. |
|
|
217
|
-
| `validate` | `(data, player) → (bool, string?)` | No | Server-side validation on incoming requests. |
|
|
179
|
+
Request-response built on packets. Returns `nil` on timeout.
|
|
218
180
|
|
|
219
|
-
|
|
220
|
-
|:-------|:------|:-------------|
|
|
221
|
-
| `query:listen(fn)` | Both | Register a handler. Server gets `fn(request, player) → response`. Client gets `fn(request) → response`. |
|
|
222
|
-
| `query:request(data)` | Client | Send request to server, yield until response or timeout. |
|
|
223
|
-
| `query:requestFrom(player, data)` | Server | Send request to a specific client, yield until response or timeout. |
|
|
224
|
-
| `query:requestAll(data)` | Server | Send request to all players. Returns `{ [Player]: response? }`. |
|
|
225
|
-
| `query:requestList(players, data)` | Server | Send request to a list of players. Returns `{ [Player]: response? }`. |
|
|
226
|
-
| `query:requestGroup(group, data)` | Server | Send request to all players in a group. Returns `{ [Player]: response? }`. |
|
|
181
|
+
### Options
|
|
227
182
|
|
|
228
|
-
|
|
183
|
+
| Field | Type | Default | Description |
|
|
184
|
+
|:------|:-----|:--------|:------------|
|
|
185
|
+
| `timeout` | `number` | 5 | Seconds before yielding `nil`. |
|
|
186
|
+
| `rateLimit` | `RateLimitConfig` | `{ maxPerSecond = 30 }` | Server-side rate limiting. |
|
|
187
|
+
| `validate` | `(data, player) → (bool, string?)` | none | Server-side validation. |
|
|
229
188
|
|
|
230
|
-
|
|
189
|
+
### Methods
|
|
231
190
|
|
|
232
|
-
|
|
191
|
+
| Method | Context | Description |
|
|
192
|
+
|:-------|:--------|:------------|
|
|
193
|
+
| `query:handle(fn)` | Both | Register handler. Server: `fn(request, player) → response`. Client: `fn(request) → response`. |
|
|
194
|
+
| `query:request(data)` | Client | Send to server, yield for response. |
|
|
195
|
+
| `query:request(data, player)` | Server | Send to one client. |
|
|
196
|
+
| `query:request(data, target)` | Server | Send to multiple. Returns `{ [Player]: response? }`. |
|
|
197
|
+
| `query:name()` | Both | Returns the query name. |
|
|
198
|
+
| `query:stats()` | Both | Combined stats for request and response channels. |
|
|
233
199
|
|
|
234
|
-
|
|
235
|
-
local Combat = Lync.defineNamespace("Combat", {
|
|
236
|
-
packets = {
|
|
237
|
-
Hit = {
|
|
238
|
-
value = Lync.struct({ targetId = Lync.u16, damage = Lync.f32, headshot = Lync.bool }),
|
|
239
|
-
rateLimit = { maxPerSecond = 30, burstAllowance = 5 },
|
|
240
|
-
validate = function(data, player)
|
|
241
|
-
if data.damage > 200 then return false, "damage" end
|
|
242
|
-
return true
|
|
243
|
-
end,
|
|
244
|
-
},
|
|
245
|
-
Death = { value = Lync.u16 },
|
|
246
|
-
},
|
|
247
|
-
queries = {
|
|
248
|
-
Stats = {
|
|
249
|
-
request = Lync.nothing,
|
|
250
|
-
response = Lync.struct({ kills = Lync.u16, deaths = Lync.u16 }),
|
|
251
|
-
timeout = 3,
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
-- Access by short name directly on the namespace
|
|
257
|
-
Combat.Hit:send(data, player)
|
|
258
|
-
Combat.Death:listen(function(targetId, sender) end)
|
|
259
|
-
Combat.Stats:listen(function(request, player) return { kills = 10, deaths = 2 } end)
|
|
260
|
-
|
|
261
|
-
-- Or via the typed sub-tables
|
|
262
|
-
Combat.packets.Hit:send(data, player)
|
|
263
|
-
Combat.queries.Stats:request(nil)
|
|
264
|
-
```
|
|
200
|
+
Each query consumes two packet IDs internally.
|
|
265
201
|
|
|
266
|
-
|
|
267
|
-
|:-------------|:-----|:-------------|
|
|
268
|
-
| `packets` | `{ [string]: PacketConfig }?` | Map of short name → packet config. Each entry becomes a Packet on the namespace. |
|
|
269
|
-
| `queries` | `{ [string]: QueryConfig }?` | Map of short name → query config. Each entry becomes a Query on the namespace. |
|
|
270
|
-
|
|
271
|
-
Access packets and queries by their short name on the returned object: `ns.PacketName`, `ns.QueryName`. Or use the typed sub-tables: `ns.packets.PacketName`, `ns.queries.QueryName`.
|
|
272
|
-
|
|
273
|
-
| Method | What it does |
|
|
274
|
-
|:-------|:------------|
|
|
275
|
-
| `ns:listenAll(fn(name, data, sender, timestamp?))` | Listens to every packet in the namespace. `name` is the short name without prefix. Returns a Connection. |
|
|
276
|
-
| `ns:onSend(fn(data, name, player) → data?)` | Send middleware that only runs for this namespace. Returns a remover. |
|
|
277
|
-
| `ns:onReceive(fn(data, name, player) → data?)` | Receive middleware that only runs for this namespace. Returns a remover. |
|
|
278
|
-
| `ns:disconnectAll()` | Kills all listeners made through `listenAll`. |
|
|
279
|
-
| `ns:destroy()` | Kills listeners and removes scoped middleware. Full cleanup. |
|
|
280
|
-
| `ns:packetNames()` | Sorted list of packet short names. |
|
|
281
|
-
| `ns:queryNames()` | Sorted list of query short names. |
|
|
282
|
-
| `ns.packets` | Frozen table mapping short name → Packet object. |
|
|
283
|
-
| `ns.queries` | Frozen table mapping short name → Query object. |
|
|
202
|
+
## Groups
|
|
284
203
|
|
|
285
|
-
|
|
204
|
+
`Lync.group(name)`
|
|
286
205
|
|
|
287
|
-
|
|
206
|
+
Named player sets. Members auto-removed on `PlayerRemoving`. Iterable with `for player in group do`.
|
|
288
207
|
|
|
289
|
-
| |
|
|
290
|
-
|
|
291
|
-
| `
|
|
292
|
-
| `
|
|
208
|
+
| Method | Returns | Description |
|
|
209
|
+
|:-------|:--------|:------------|
|
|
210
|
+
| `group:add(player)` | `boolean` | `true` if added. |
|
|
211
|
+
| `group:remove(player)` | `boolean` | `true` if removed. |
|
|
212
|
+
| `group:has(player)` | `boolean` | Membership check. |
|
|
213
|
+
| `group:count()` | `number` | Member count. |
|
|
214
|
+
| `group:destroy()` | — | Clears members, frees name. |
|
|
293
215
|
|
|
294
216
|
## Scope
|
|
295
217
|
|
|
296
|
-
|
|
218
|
+
`Lync.scope()`
|
|
219
|
+
|
|
220
|
+
Batches connections for cleanup.
|
|
297
221
|
|
|
298
222
|
```luau
|
|
299
223
|
local scope = Lync.scope()
|
|
300
|
-
|
|
301
|
-
scope:
|
|
302
|
-
scope:
|
|
303
|
-
scope:listenAll(namespace, fnC)
|
|
304
|
-
|
|
224
|
+
scope:on(packetA, fnA)
|
|
225
|
+
scope:on(packetB, fnB)
|
|
226
|
+
scope:add(someRBXScriptConnection)
|
|
305
227
|
scope:destroy() -- disconnects everything
|
|
306
228
|
```
|
|
307
229
|
|
|
308
|
-
| Method |
|
|
230
|
+
| Method | Description |
|
|
309
231
|
|:-------|:------------|
|
|
310
|
-
| `scope:
|
|
311
|
-
| `scope:once(source, fn)` |
|
|
312
|
-
| `scope:
|
|
313
|
-
| `scope:
|
|
314
|
-
| `scope:destroy()` | Safe to call multiple times. |
|
|
315
|
-
|
|
316
|
-
## Groups
|
|
317
|
-
|
|
318
|
-
Named player sets. Members get removed automatically on `PlayerRemoving`. `Lync.createGroup(name)` returns a Group object.
|
|
319
|
-
|
|
320
|
-
```luau
|
|
321
|
-
local vips = Lync.createGroup("vips")
|
|
232
|
+
| `scope:on(source, fn)` | Connect and track. |
|
|
233
|
+
| `scope:once(source, fn)` | Connect once and track. |
|
|
234
|
+
| `scope:add(connection)` | Track an existing connection. |
|
|
235
|
+
| `scope:destroy()` | Disconnect all. Safe to call multiple times. |
|
|
322
236
|
|
|
323
|
-
|
|
324
|
-
vips:remove(player)
|
|
325
|
-
vips:has(player)
|
|
237
|
+
## Connection
|
|
326
238
|
|
|
327
|
-
packet:
|
|
328
|
-
```
|
|
239
|
+
Returned by `packet:on()`, `packet:once()`, `query:handle()`, and middleware functions.
|
|
329
240
|
|
|
330
|
-
|
|
|
331
|
-
|
|
332
|
-
| `
|
|
333
|
-
| `
|
|
334
|
-
| `group:has(player)` | `boolean` | Whether the player is in the group. |
|
|
335
|
-
| `group:count()` | `number` | Number of members. |
|
|
336
|
-
| `group:getSet()` | `{ [Player]: true }` | Snapshot of the internal set. |
|
|
337
|
-
| `group:forEach(fn)` | `()` | Calls `fn(player)` for each member. |
|
|
338
|
-
| `group:destroy()` | `()` | Removes the group and all memberships. |
|
|
241
|
+
| Field / Method | Description |
|
|
242
|
+
|:---------------|:------------|
|
|
243
|
+
| `connection.connected` | `boolean` |
|
|
244
|
+
| `connection:disconnect()` | Stops the listener. Safe mid-fire, safe to call multiple times. |
|
|
339
245
|
|
|
340
246
|
## Middleware
|
|
341
247
|
|
|
342
|
-
Global intercept on all packets. Handlers run in the order you registered them. Return `Lync.DROP` from a handler to drop the packet. Return the data to pass it through.
|
|
343
|
-
|
|
344
248
|
```luau
|
|
345
249
|
Lync.onSend(function(data, name, player)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
250
|
+
return data -- or return Lync.DROP to discard
|
|
251
|
+
end)
|
|
252
|
+
|
|
253
|
+
Lync.onReceive(function(data, name, player)
|
|
350
254
|
return data
|
|
351
255
|
end)
|
|
352
|
-
```
|
|
353
256
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
| `Lync.onDrop(fn(player, reason, name, data))` | Fires when a packet gets rejected. Returns a remover function. Supports multiple handlers. Reason is `"nan"`, `"rate"`, `"validate"`, `"size"`, or whatever string your validate function returned. |
|
|
359
|
-
| `Lync.DROP` | Frozen sentinel. Return from middleware to drop the packet. |
|
|
257
|
+
Lync.onDrop(function(player, reason, name, data)
|
|
258
|
+
warn(player.Name, "dropped", name, reason)
|
|
259
|
+
end)
|
|
260
|
+
```
|
|
360
261
|
|
|
361
|
-
|
|
262
|
+
All three return a Connection.
|
|
362
263
|
|
|
363
|
-
##
|
|
264
|
+
## Targets
|
|
364
265
|
|
|
365
|
-
|
|
266
|
+
Server-side second argument to `packet:send()`.
|
|
366
267
|
|
|
367
|
-
| Target |
|
|
268
|
+
| Target | Description |
|
|
368
269
|
|:-------|:------------|
|
|
369
|
-
| `player` |
|
|
370
|
-
| `Lync.all` |
|
|
371
|
-
| `Lync.except(
|
|
372
|
-
| `{ p1, p2, ... }` |
|
|
373
|
-
| `group` |
|
|
374
|
-
|
|
375
|
-
##
|
|
376
|
-
|
|
377
|
-
###
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
|
382
|
-
|
|
383
|
-
| `Lync.
|
|
384
|
-
| `Lync.
|
|
385
|
-
| `Lync.
|
|
386
|
-
| `Lync.
|
|
387
|
-
| `Lync.
|
|
388
|
-
| `Lync.
|
|
389
|
-
| `Lync.
|
|
390
|
-
| `Lync.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
|
398
|
-
|
|
399
|
-
| `Lync.
|
|
400
|
-
| `Lync.
|
|
401
|
-
| `Lync.
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
|
406
|
-
|
|
407
|
-
| `Lync.
|
|
408
|
-
| `Lync.
|
|
409
|
-
| `Lync.
|
|
410
|
-
| `Lync.
|
|
411
|
-
| `Lync.
|
|
412
|
-
| `Lync.
|
|
413
|
-
| `Lync.
|
|
414
|
-
| `Lync.
|
|
270
|
+
| `player` | Single player. |
|
|
271
|
+
| `Lync.all` | All connected players. |
|
|
272
|
+
| `Lync.except(...)` | Everyone except specified players or groups. |
|
|
273
|
+
| `{ p1, p2, ... }` | Array of players. |
|
|
274
|
+
| `group` | All members of a group. |
|
|
275
|
+
|
|
276
|
+
## Codecs
|
|
277
|
+
|
|
278
|
+
### Numbers
|
|
279
|
+
|
|
280
|
+
`Lync.int(min, max)` picks the smallest wire type for your range.
|
|
281
|
+
|
|
282
|
+
| Codec | Bytes | Description |
|
|
283
|
+
|:------|------:|:------------|
|
|
284
|
+
| `Lync.int(0, 255)` | 1 | u8 |
|
|
285
|
+
| `Lync.int(0, 65535)` | 2 | u16 |
|
|
286
|
+
| `Lync.int(0, 4294967295)` | 4 | u32 |
|
|
287
|
+
| `Lync.int(-128, 127)` | 1 | i8 |
|
|
288
|
+
| `Lync.int(-32768, 32767)` | 2 | i16 |
|
|
289
|
+
| `Lync.int(-2147483648, 2147483647)` | 4 | i32 |
|
|
290
|
+
| `Lync.f16` | 2 | Half-precision float. ~3 digits. ±65504. |
|
|
291
|
+
| `Lync.f32` | 4 | Single-precision float. |
|
|
292
|
+
| `Lync.f64` | 8 | Double-precision float. |
|
|
293
|
+
| `Lync.bool` | 1 | Bitpacked inside structs and arrays (8 per byte). |
|
|
294
|
+
| `Lync.float(min, max, precision)` | 1–4 | Quantized float. Clamped to range. |
|
|
295
|
+
|
|
296
|
+
### Strings & Buffers
|
|
297
|
+
|
|
298
|
+
| Codec | Description |
|
|
299
|
+
|:------|:------------|
|
|
300
|
+
| `Lync.string` | Variable length. Binary-safe. |
|
|
301
|
+
| `Lync.string(maxLength)` | Same, but rejects on read if length exceeds `maxLength`. |
|
|
302
|
+
| `Lync.buff` | Variable-length buffer. |
|
|
303
|
+
|
|
304
|
+
### Roblox Types
|
|
305
|
+
|
|
306
|
+
| Codec | Bytes |
|
|
307
|
+
|:------|------:|
|
|
308
|
+
| `Lync.vec2` | 8 |
|
|
309
|
+
| `Lync.vec3` | 12 |
|
|
310
|
+
| `Lync.cframe` | 24 |
|
|
311
|
+
| `Lync.color3` | 3 |
|
|
312
|
+
| `Lync.inst` | 2 |
|
|
313
|
+
| `Lync.udim` | 8 |
|
|
314
|
+
| `Lync.udim2` | 16 |
|
|
315
|
+
| `Lync.numberRange` | 8 |
|
|
316
|
+
| `Lync.rect` | 16 |
|
|
317
|
+
| `Lync.ray` | 24 |
|
|
318
|
+
| `Lync.vec2int16` | 4 |
|
|
319
|
+
| `Lync.vec3int16` | 6 |
|
|
320
|
+
| `Lync.region3` | 24 |
|
|
321
|
+
| `Lync.region3int16` | 12 |
|
|
322
|
+
| `Lync.numberSequence` | variable |
|
|
323
|
+
| `Lync.colorSequence` | variable |
|
|
324
|
+
|
|
325
|
+
### Quantized Variants
|
|
326
|
+
|
|
327
|
+
Call the codec to get a quantized version.
|
|
328
|
+
|
|
329
|
+
| Codec | Bytes | Description |
|
|
330
|
+
|:------|------:|:------------|
|
|
331
|
+
| `Lync.vec2(min, max, precision)` | 2–8 | Per-component quantization. |
|
|
332
|
+
| `Lync.vec3(min, max, precision)` | 3–12 | Per-component quantization. |
|
|
333
|
+
| `Lync.cframe()` | 16 | Compressed rotation. ≤0.16° angular error. Saves 8B vs lossless. |
|
|
415
334
|
|
|
416
335
|
### Composites
|
|
417
336
|
|
|
418
|
-
|
|
|
419
|
-
|
|
420
|
-
| `Lync.struct({ key = codec })` | Named fields. Bools
|
|
421
|
-
| `Lync.array(codec, maxCount?)` | Variable
|
|
422
|
-
| `Lync.map(keyCodec, valueCodec, maxCount?)` | Key-value pairs
|
|
423
|
-
| `Lync.optional(codec)` | 1
|
|
424
|
-
| `Lync.tuple(
|
|
425
|
-
| `Lync.tagged(tagField, { name = codec })` | Discriminated union with
|
|
337
|
+
| Codec | Description |
|
|
338
|
+
|:------|:------------|
|
|
339
|
+
| `Lync.struct({ key = codec })` | Named fields. Bools are automatically bitpacked. |
|
|
340
|
+
| `Lync.array(codec, maxCount?)` | Variable-length list. Bool arrays are bitpacked. |
|
|
341
|
+
| `Lync.map(keyCodec, valueCodec, maxCount?)` | Key-value pairs. |
|
|
342
|
+
| `Lync.optional(codec)` | 1-byte nil flag + value if present. |
|
|
343
|
+
| `Lync.tuple(...)` | Ordered positional values. |
|
|
344
|
+
| `Lync.tagged(tagField, { name = codec })` | Discriminated union with 1-byte tag. |
|
|
426
345
|
|
|
427
346
|
### Delta
|
|
428
347
|
|
|
429
|
-
|
|
348
|
+
Only works with reliable transport. Sends 1 byte when data hasn't changed.
|
|
430
349
|
|
|
431
|
-
|
|
|
432
|
-
|
|
433
|
-
| `Lync.deltaStruct(
|
|
434
|
-
| `Lync.deltaArray(codec, maxCount?)` |
|
|
435
|
-
| `Lync.deltaMap(keyCodec, valueCodec, maxCount?)` | Delta
|
|
350
|
+
| Codec | Description |
|
|
351
|
+
|:------|:------------|
|
|
352
|
+
| `Lync.deltaStruct(schema)` | Delta-compressed struct. |
|
|
353
|
+
| `Lync.deltaArray(codec, maxCount?)` | Delta-compressed array. |
|
|
354
|
+
| `Lync.deltaMap(keyCodec, valueCodec, maxCount?)` | Delta-compressed map. |
|
|
436
355
|
|
|
437
356
|
### Meta
|
|
438
357
|
|
|
439
|
-
|
|
|
440
|
-
|
|
441
|
-
| `Lync.enum(
|
|
442
|
-
| `Lync.
|
|
443
|
-
| `Lync.
|
|
444
|
-
| `Lync.
|
|
445
|
-
| `Lync.
|
|
446
|
-
| `Lync.
|
|
447
|
-
| `Lync.unknown` | Skips serialization entirely, goes through Roblox's sidecar. Requires refs array on read (same as `Lync.inst`). Use when you dont have a codec for the value. |
|
|
448
|
-
| `Lync.auto` | Self-describing. Writes a u8 type tag then the value. Handles nil, bool, all number types, string, vec2, vec3, color3, cframe, buffer, udim, udim2, numberRange, rect, vec2int16, vec3int16, region3, region3int16, ray, numberSequence, colorSequence. |
|
|
449
|
-
|
|
450
|
-
## Benchmarks
|
|
451
|
-
|
|
452
|
-
### Lync Tests
|
|
453
|
-
|
|
454
|
-
1,000 packets/frame, 10 seconds, one player.
|
|
455
|
-
|
|
456
|
-
| Scenario | Without Lync | With Lync | FPS |
|
|
457
|
-
|:---------|------------:|---------:|----:|
|
|
458
|
-
| Static booleans (1B) | 480 Kbps | **2.34 Kbps** | 60.00 |
|
|
459
|
-
| Static entities (34B) | 16,320 Kbps | **2.62 Kbps** | 60.00 |
|
|
460
|
-
| Moving entities | 16,320 Kbps | **3.14 Kbps** | 60.00 |
|
|
461
|
-
| Chaotic entities | 16,320 Kbps | **4.76 Kbps** | 59.99 |
|
|
462
|
-
|
|
463
|
-
### Cross-Library Comparison
|
|
464
|
-
|
|
465
|
-
Same data shapes and methodology as [Blink's benchmark suite](https://github.com/1Axen/blink/blob/main/benchmark/Benchmarks.md). 1,000 fires/frame, 10 seconds, same data every frame. Kbps scaled by 60/FPS.
|
|
466
|
-
|
|
467
|
-
**Entities** (100x struct of 6x u8, fired 1000 times/frame)
|
|
468
|
-
|
|
469
|
-
| Tool (FPS) | Median | P0 | P80 | P90 | P95 | P100 |
|
|
470
|
-
|:-----------|-------:|---:|----:|----:|----:|-----:|
|
|
471
|
-
| roblox | 16.00 | 16.00 | 15.00 | 15.00 | 15.00 | 15.00 |
|
|
472
|
-
| **lync** | **60.00** | 61.00 | 60.00 | 60.00 | 60.00 | 59.00 |
|
|
473
|
-
| blink | 42.00 | 45.00 | 42.00 | 42.00 | 42.00 | 42.00 |
|
|
474
|
-
| zap | 39.00 | 40.00 | 38.00 | 38.00 | 38.00 | 38.00 |
|
|
475
|
-
| bytenet | 32.00 | 34.00 | 32.00 | 32.00 | 32.00 | 31.00 |
|
|
476
|
-
|
|
477
|
-
| Tool (Kbps) | Median | P0 | P80 | P90 | P95 | P100 |
|
|
478
|
-
|:------------|-------:|---:|----:|----:|----:|-----:|
|
|
479
|
-
| roblox | 559,364 | 559,364 | 676,715 | 676,715 | 676,715 | 784,081 |
|
|
480
|
-
| **lync** | **3.61** | 3.53 | 3.63 | 3.64 | 3.64 | 4.64 |
|
|
481
|
-
| blink | 41.81 | 26.30 | 42.40 | 42.48 | 42.48 | 42.62 |
|
|
482
|
-
| zap | 41.71 | 25.46 | 42.19 | 42.32 | 42.32 | 42.93 |
|
|
483
|
-
| bytenet | 41.64 | 22.84 | 42.36 | 42.82 | 42.82 | 43.24 |
|
|
484
|
-
|
|
485
|
-
**Booleans** (1000x bool, fired 1000 times/frame)
|
|
486
|
-
|
|
487
|
-
| Tool (FPS) | Median | P0 | P80 | P90 | P95 | P100 |
|
|
488
|
-
|:-----------|-------:|---:|----:|----:|----:|-----:|
|
|
489
|
-
| roblox | 21.00 | 22.00 | 20.00 | 19.00 | 19.00 | 19.00 |
|
|
490
|
-
| **lync** | **60.00** | 61.00 | 60.00 | 60.00 | 60.00 | 59.00 |
|
|
491
|
-
| blink | 97.00 | 98.00 | 97.00 | 96.00 | 96.00 | 96.00 |
|
|
492
|
-
| zap | 52.00 | 53.00 | 51.00 | 51.00 | 51.00 | 49.00 |
|
|
493
|
-
| bytenet | 35.00 | 37.00 | 35.00 | 35.00 | 35.00 | 34.00 |
|
|
494
|
-
|
|
495
|
-
| Tool (Kbps) | Median | P0 | P80 | P90 | P95 | P100 |
|
|
496
|
-
|:------------|-------:|---:|----:|----:|----:|-----:|
|
|
497
|
-
| roblox | 353,107 | 196,826 | 690,747 | 842,240 | 842,240 | 1,124,176 |
|
|
498
|
-
| **lync** | **4.31** | 3.85 | 4.36 | 4.38 | 4.38 | 4.44 |
|
|
499
|
-
| blink | 7.91 | 7.41 | 7.93 | 7.99 | 7.99 | 8.00 |
|
|
500
|
-
| zap | 8.10 | 5.75 | 8.17 | 8.22 | 8.22 | 8.27 |
|
|
501
|
-
| bytenet | 8.11 | 5.07 | 8.35 | 8.46 | 8.46 | 8.47 |
|
|
358
|
+
| Codec | Description |
|
|
359
|
+
|:------|:------------|
|
|
360
|
+
| `Lync.enum(...)` | String enum. Up to 256 variants. 1 byte. |
|
|
361
|
+
| `Lync.bitfield(schema)` | Sub-byte packing. 1–32 bits. |
|
|
362
|
+
| `Lync.custom(size, write, read)` | User-defined fixed-size codec. |
|
|
363
|
+
| `Lync.nothing` | Zero bytes. Reads `nil`. |
|
|
364
|
+
| `Lync.unknown` | Bypasses serialization entirely. Use with `validate`. |
|
|
365
|
+
| `Lync.auto` | Self-describing. Supports nil, bool, numbers, strings, buffers, and Roblox types. |
|
|
502
366
|
|
|
503
|
-
|
|
504
|
-
> Lync benchmarks run on Ryzen 7 7800X3D, 32GB DDR5-4800. Other tool numbers are from [Blink's published benchmarks](https://github.com/1Axen/blink/blob/main/benchmark/Benchmarks.md) (v0.17.1, Ryzen 9 7900X, 34GB DDR5-4800). Different CPUs so FPS numbers arent directly comparable but bandwidth numbers are since Kbps is scaled by 60/FPS. Lync hits the 60 FPS frame cap in both tests.
|
|
505
|
-
|
|
506
|
-
## Stats
|
|
507
|
-
|
|
508
|
-
Off by default. Call `Lync.enableStats()` before `Lync.start()` to activate. When disabled, zero overhead on send and receive paths.
|
|
367
|
+
## Rate Limiting
|
|
509
368
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
```luau
|
|
513
|
-
Lync.enableStats()
|
|
514
|
-
Lync.start()
|
|
369
|
+
Two modes (pick one per packet):
|
|
515
370
|
|
|
516
|
-
|
|
517
|
-
print(Net.State:getBytesSent(), Net.State:getFires(), Net.State:getDrops())
|
|
371
|
+
**Token bucket:** `{ maxPerSecond = N, burst = M }`
|
|
518
372
|
|
|
519
|
-
|
|
520
|
-
local stats = Lync.getPlayerStats(player)
|
|
521
|
-
if stats then
|
|
522
|
-
print(stats.bytesSent, stats.bytesReceived)
|
|
523
|
-
end
|
|
373
|
+
**Cooldown:** `{ cooldown = seconds }`
|
|
524
374
|
|
|
525
|
-
Lync.
|
|
526
|
-
```
|
|
375
|
+
Global limit across all packets: `Lync.configure({ globalRateLimit = { maxPerSecond = N } })`
|
|
527
376
|
|
|
528
|
-
|
|
529
|
-
|:-------|:----------------|
|
|
530
|
-
| `Lync.enableStats()` | Activates stat counters. Call before `start()`. |
|
|
531
|
-
| `packet:getBytesSent()` | Wire bytes produced (includes batch header overhead). |
|
|
532
|
-
| `packet:getBytesReceived()` | Wire bytes consumed on receive. |
|
|
533
|
-
| `packet:getFires()` | Send fire count. |
|
|
534
|
-
| `packet:getRecvFires()` | Receive fire count. |
|
|
535
|
-
| `packet:getDrops()` | Gate rejections (rate limit, NaN, validate). |
|
|
536
|
-
| `Lync.getPlayerStats(player)` | Returns `{ bytesSent, bytesReceived }` or nil. Server only. |
|
|
537
|
-
| `Lync.resetStats()` | Zeros all counters in-place. |
|
|
377
|
+
## Configuration
|
|
538
378
|
|
|
539
|
-
|
|
379
|
+
`Lync.configure(options)` — call before `Lync.start()`.
|
|
540
380
|
|
|
541
|
-
|
|
381
|
+
| Option | Default | Description |
|
|
382
|
+
|:-------|--------:|:------------|
|
|
383
|
+
| `channelMaxSize` | 262,144 | Max buffer bytes per frame (4,096–1,048,576). |
|
|
384
|
+
| `validationDepth` | 16 | Max recursion depth for input validation (4–32). |
|
|
385
|
+
| `poolSize` | 16 | Buffer pool size (2–128). |
|
|
386
|
+
| `bandwidthLimit` | none | `{ softLimit, maxStrikes }`. Per-player bandwidth throttle. |
|
|
387
|
+
| `globalRateLimit` | none | `{ maxPerSecond }`. Global per-player rate limit. |
|
|
388
|
+
| `stats` | `false` | Enables `packet:stats()` and `Lync.stats.player()`. |
|
|
542
389
|
|
|
543
|
-
|
|
544
|
-
Lync.setFlushRate(30) -- flush every ~33ms instead of ~16ms
|
|
545
|
-
Lync.flush() -- force an immediate flush, resets the accumulator
|
|
546
|
-
```
|
|
390
|
+
### Lifecycle
|
|
547
391
|
|
|
548
|
-
| Function |
|
|
392
|
+
| Function | Description |
|
|
549
393
|
|:---------|:------------|
|
|
550
|
-
| `Lync.
|
|
551
|
-
| `Lync.
|
|
552
|
-
|
|
553
|
-
|
|
394
|
+
| `Lync.configure(options)` | Set options before start. |
|
|
395
|
+
| `Lync.start()` | Initialize transport. Call once after all definitions. |
|
|
396
|
+
| `Lync.started` | Read-only boolean. `true` after `start()`. |
|
|
397
|
+
| `Lync.flush()` | Force an immediate send. |
|
|
398
|
+
| `Lync.flushRate(hz)` | Set flush rate. 1–60. Default 60. |
|
|
554
399
|
|
|
555
|
-
###
|
|
400
|
+
### Stats
|
|
556
401
|
|
|
557
|
-
|
|
402
|
+
Enable with `Lync.configure({ stats = true })`.
|
|
558
403
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
404
|
+
| Function | Description |
|
|
405
|
+
|:---------|:------------|
|
|
406
|
+
| `packet:stats()` | `{ bytesSent, bytesReceived, fires, recvFires, drops }` |
|
|
407
|
+
| `Lync.stats.player(player)` | `{ bytesSent, bytesReceived }` — server only. |
|
|
408
|
+
| `Lync.stats.reset()` | Zeros all counters. |
|
|
562
409
|
|
|
563
|
-
|
|
410
|
+
### Debug
|
|
564
411
|
|
|
565
|
-
|
|
412
|
+
| Function | Description |
|
|
413
|
+
|:---------|:------------|
|
|
414
|
+
| `Lync.debug.pending()` | Number of in-flight query requests. Useful for detecting leaks. |
|
|
415
|
+
| `Lync.debug.registrations()` | Frozen array of `{ name, id, kind, isUnreliable }` for all registered packets and queries. |
|
|
566
416
|
|
|
567
|
-
|
|
417
|
+
## Limits
|
|
568
418
|
|
|
569
|
-
|
|
419
|
+
| Constraint | Limit |
|
|
420
|
+
|:-----------|------:|
|
|
421
|
+
| Packet + query registrations | 127 |
|
|
422
|
+
| Buffer per frame | 256 KB default, 1 MB max |
|
|
423
|
+
| Concurrent query requests | 65,536 |
|
|
424
|
+
| Enum variants | 256 |
|
|
425
|
+
| Bitfield bits | 32 |
|
|
426
|
+
| Tagged variants | 256 |
|
|
570
427
|
|
|
571
|
-
|
|
428
|
+
## Benchmarks
|
|
572
429
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
430
|
+
Run `rojo serve bench.project.json` with one server + one client.
|
|
431
|
+
|
|
432
|
+
### Wire Sizes
|
|
433
|
+
|
|
434
|
+
| Codec | Bytes |
|
|
435
|
+
|:------|------:|
|
|
436
|
+
| `bool` | 1 |
|
|
437
|
+
| `int(0, 255)` | 1 |
|
|
438
|
+
| `int(0, 65535)` | 2 |
|
|
439
|
+
| `f16` | 2 |
|
|
440
|
+
| `f32` | 4 |
|
|
441
|
+
| `f64` | 8 |
|
|
442
|
+
| `string` (5 chars) | 6 |
|
|
443
|
+
| `string` (1000 chars) | 1002 |
|
|
444
|
+
| `vec3` | 12 |
|
|
445
|
+
| `vec3(0, 100, 1)` | 3 |
|
|
446
|
+
| `cframe` | 24 |
|
|
447
|
+
| `cframe()` | 16 |
|
|
448
|
+
| `color3` | 3 |
|
|
449
|
+
| entity struct (6 fields) | 34 |
|
|
450
|
+
| entity compact (quantized) | 13 |
|
|
451
|
+
| bitfield | 2 |
|
|
452
|
+
| 100× entities | 601 |
|
|
453
|
+
| 1000× bools (bitpacked) | 127 |
|
|
454
|
+
|
|
455
|
+
### Codec Throughput
|
|
456
|
+
|
|
457
|
+
100k iterations, isolated CPU. No networking.
|
|
458
|
+
|
|
459
|
+
| Codec | Encode | Decode | Round-trips/sec |
|
|
460
|
+
|:------|-------:|-------:|----------------:|
|
|
461
|
+
| `bool` | 44ns | 29ns | 13.9M |
|
|
462
|
+
| `int(0, 255)` | 42ns | 28ns | 14.4M |
|
|
463
|
+
| `f32` | 41ns | 25ns | 15.0M |
|
|
464
|
+
| `f64` | 41ns | 26ns | 14.8M |
|
|
465
|
+
| `string` (10 chars) | 46ns | 60ns | 9.4M |
|
|
466
|
+
| `string` (1000 chars) | 76ns | 238ns | 3.2M |
|
|
467
|
+
| `vec3` | 53ns | 27ns | 12.4M |
|
|
468
|
+
| `cframe` | 92ns | 144ns | 4.2M |
|
|
469
|
+
| `cframe()` | 123ns | 170ns | 3.4M |
|
|
470
|
+
| entity struct | 239ns | 395ns | 1.6M |
|
|
471
|
+
| 100× entities | 15.2µs | 34.1µs | 20K |
|
|
472
|
+
| 1000× bools | 4.3µs | 5.1µs | 107K |
|
|
473
|
+
|
|
474
|
+
### Delta Savings
|
|
475
|
+
|
|
476
|
+
| Codec | Full | Unchanged | Savings |
|
|
477
|
+
|:------|-----:|----------:|--------:|
|
|
478
|
+
| `deltaStruct` (entity) | 35B | 1B | 97% |
|
|
479
|
+
| `deltaStruct` (compact) | 14B | 1B | 93% |
|
|
480
|
+
| `deltaArray` (100× entity) | 602B | 1B | 100% |
|
|
481
|
+
| `deltaArray` (1000× bool) | 128B | 1B | 99% |
|
|
482
|
+
| `deltaMap` (string → u8) | 19B | 1B | 95% |
|
|
483
|
+
|
|
484
|
+
### Network Throughput
|
|
485
|
+
|
|
486
|
+
1000 fires/frame, 8 seconds, one player.
|
|
487
|
+
|
|
488
|
+
| Packet | FPS | Kbps |
|
|
489
|
+
|:-------|----:|-----:|
|
|
490
|
+
| booleans | 60 | 2.5 |
|
|
491
|
+
| entity struct | 60 | 2.3 |
|
|
492
|
+
| entity compact | 60 | 2.4 |
|
|
493
|
+
| bitfield flags | 60 | 2.4 |
|
|
494
|
+
| cframe lossless | 60 | 2.5 |
|
|
495
|
+
| cframe compressed | 60 | 2.3 |
|
|
578
496
|
|
|
579
|
-
|
|
580
|
-
-- fire packets...
|
|
581
|
-
Lync.flush()
|
|
582
|
-
Lync.stopCapture()
|
|
497
|
+
### Cross-Library Comparison
|
|
583
498
|
|
|
584
|
-
|
|
585
|
-
```
|
|
499
|
+
Same methodology as [Blink's benchmarks](https://github.com/1Axen/blink/blob/main/benchmark/Benchmarks.md): 1,000 fires/frame, same data every frame, 10 seconds.
|
|
586
500
|
|
|
587
|
-
|
|
501
|
+
Other tool numbers from [Blink v0.17.1](https://github.com/1Axen/blink/blob/main/benchmark/Benchmarks.md) (2025-04-30).
|
|
588
502
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
|
605
|
-
|
|
606
|
-
|
|
|
607
|
-
|
|
|
608
|
-
|
|
|
609
|
-
|
|
|
610
|
-
|
|
|
503
|
+
> [!NOTE]
|
|
504
|
+
> Lync batches all sends into one buffer per frame. Other tools fire one RemoteEvent per send. Lync also includes server-side validation and bool bitpacking (1000 bools = 127B vs ~1002B). Delta compression is not exercised here — see [Delta Savings](#delta-savings).
|
|
505
|
+
|
|
506
|
+
#### Entities — 100× struct(6× u8)
|
|
507
|
+
|
|
508
|
+
| Tool | FPS | Kbps |
|
|
509
|
+
|:-----|----:|-----:|
|
|
510
|
+
| roblox | 16 | 559,364 |
|
|
511
|
+
| **lync** | **60** | **3.68** |
|
|
512
|
+
| blink | 42 | 41.81 |
|
|
513
|
+
| zap | 39 | 41.71 |
|
|
514
|
+
| bytenet | 32 | 41.64 |
|
|
515
|
+
|
|
516
|
+
#### Booleans — 1000× bool
|
|
517
|
+
|
|
518
|
+
| Tool | FPS | Kbps |
|
|
519
|
+
|:-----|----:|-----:|
|
|
520
|
+
| roblox | 21 | 353,107 |
|
|
521
|
+
| **lync** | **60** | **2.49** |
|
|
522
|
+
| blink | 97 | 7.91 |
|
|
523
|
+
| zap | 52 | 8.10 |
|
|
524
|
+
| bytenet | 35 | 8.11 |
|
|
611
525
|
|
|
612
526
|
## License
|
|
613
527
|
|