@axpecter/lync 2.1.0 → 2.1.2
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 +279 -414
- package/package.json +1 -1
- package/src/index.d.ts +34 -32
package/README.md
CHANGED
|
@@ -5,13 +5,10 @@
|
|
|
5
5
|
<a href="#install">Install</a> ·
|
|
6
6
|
<a href="#example">Example</a> ·
|
|
7
7
|
<a href="#codecs">Codecs</a> ·
|
|
8
|
-
<a href="#wire-protocol">Wire Protocol</a> ·
|
|
9
8
|
<a href="#benchmarks">Benchmarks</a>
|
|
10
9
|
</p>
|
|
11
10
|
|
|
12
|
-
Lync
|
|
13
|
-
|
|
14
|
-
All codecs are defined at runtime. No code generation, no build step, no external CLI. Packets, queries, groups, and middleware are configured in shared modules and resolved at `Lync.start()`.
|
|
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.
|
|
15
12
|
|
|
16
13
|
## Install
|
|
17
14
|
|
|
@@ -19,7 +16,7 @@ All codecs are defined at runtime. No code generation, no build step, no externa
|
|
|
19
16
|
|
|
20
17
|
```toml
|
|
21
18
|
[dependencies]
|
|
22
|
-
Lync = "axp3cter/lync@2.1.
|
|
19
|
+
Lync = "axp3cter/lync@2.1.2"
|
|
23
20
|
```
|
|
24
21
|
|
|
25
22
|
**npm (roblox-ts)**
|
|
@@ -32,10 +29,10 @@ npm install @axpecter/lync
|
|
|
32
29
|
import Lync from "@axpecter/lync";
|
|
33
30
|
```
|
|
34
31
|
|
|
35
|
-
Or grab the `.rbxm` from [Releases](https://github.com/Axp3cter/Lync/releases/latest)
|
|
32
|
+
Or grab the `.rbxm` from [Releases](https://github.com/Axp3cter/Lync/releases/latest).
|
|
36
33
|
|
|
37
34
|
> [!IMPORTANT]
|
|
38
|
-
>
|
|
35
|
+
> Define all packets, queries, and groups before calling `Lync.start()`.
|
|
39
36
|
|
|
40
37
|
## Example
|
|
41
38
|
|
|
@@ -137,526 +134,394 @@ local serverTime = Net.Ping:request(nil)
|
|
|
137
134
|
if serverTime then print("server clock:", serverTime) end
|
|
138
135
|
```
|
|
139
136
|
|
|
140
|
-
## Lifecycle
|
|
141
|
-
|
|
142
|
-
| Function | Behavior |
|
|
143
|
-
|:---------|:---------|
|
|
144
|
-
| `Lync.configure(options)` | Sets limits and enables stats. Must be called before `start()`. See [Configuration](#configuration). |
|
|
145
|
-
| `Lync.start()` | Server creates remotes under `ReplicatedStorage.LyncRemotes`. Client waits for them. Connects the Heartbeat flush loop. Errors if called twice. |
|
|
146
|
-
| `Lync.started` | Read-only boolean. `true` after `start()` returns. |
|
|
147
|
-
| `Lync.flush()` | Forces an immediate buffer flush. Resets the accumulator to prevent double-sending on the next Heartbeat. Errors if not started. |
|
|
148
|
-
| `Lync.flushRate(hz)` | 1–60. Default 60. At 60, flushes every Heartbeat directly. Below 60, uses an elapsed-time accumulator with drift correction. Callable at runtime. |
|
|
149
|
-
|
|
150
137
|
## Packets
|
|
151
138
|
|
|
152
|
-
`Lync.packet(name, codec, options?)`
|
|
153
|
-
|
|
154
|
-
### Packet Options
|
|
139
|
+
`Lync.packet(name, codec, options?)`
|
|
155
140
|
|
|
156
|
-
|
|
157
|
-
|:------|:-----|:--------|:---------|
|
|
158
|
-
| `unreliable` | `boolean` | `false` | Routes through `UnreliableRemoteEvent`. Incompatible with delta codecs (errors at define time). |
|
|
159
|
-
| `rateLimit` | `RateLimitConfig` | none | Server-side rate limiting on incoming fires. See [Rate Limiting](#rate-limiting). |
|
|
160
|
-
| `validate` | `(data, player) → (bool, string?)` | none | Server-side callback after schema validation. Return `false, "reason"` to drop. Fires `onDrop`. |
|
|
161
|
-
| `maxPayloadBytes` | `number` | none | Maximum bytes a single payload can consume. |
|
|
162
|
-
| `timestamp` | `"frame"`, `"offset"`, or `"full"` | none | Prepends a timestamp to each item. `"frame"` = u8 wrapping counter (1B). `"offset"` = u16 milliseconds into the current second (2B). `"full"` = f64 `os.clock()` (8B). Listeners receive it as a third argument after `sender`. |
|
|
141
|
+
### Options
|
|
163
142
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
packet:send(data, Lync.except(p1, p2)) -- all except specified
|
|
172
|
-
packet:send(data, { p1, p2, p3 }) -- array of players
|
|
173
|
-
packet:send(data, group) -- group members
|
|
174
|
-
```
|
|
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. |
|
|
175
150
|
|
|
176
|
-
|
|
151
|
+
### Sending
|
|
177
152
|
|
|
178
153
|
```luau
|
|
179
|
-
|
|
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)
|
|
180
163
|
```
|
|
181
164
|
|
|
182
|
-
|
|
165
|
+
### Receiving
|
|
183
166
|
|
|
184
|
-
| Method |
|
|
185
|
-
|
|
186
|
-
| `packet:on(fn)` |
|
|
187
|
-
| `packet:once(fn)` |
|
|
188
|
-
| `packet:wait()` | Yields until
|
|
189
|
-
| `packet:name()` | Returns the
|
|
190
|
-
| `packet:stats()` | Returns `{ bytesSent, bytesReceived, fires, recvFires, drops }`.
|
|
167
|
+
| Method | Description |
|
|
168
|
+
|:-------|:------------|
|
|
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. |
|
|
191
174
|
|
|
192
175
|
## Queries
|
|
193
176
|
|
|
194
|
-
`Lync.query(name, requestCodec, responseCodec, options?)`
|
|
177
|
+
`Lync.query(name, requestCodec, responseCodec, options?)`
|
|
178
|
+
|
|
179
|
+
Request-response built on packets. Returns `nil` on timeout.
|
|
195
180
|
|
|
196
|
-
###
|
|
181
|
+
### Options
|
|
197
182
|
|
|
198
|
-
| Field | Type | Default |
|
|
199
|
-
|
|
200
|
-
| `timeout` | `number` | 5 | Seconds before
|
|
201
|
-
| `rateLimit` | `RateLimitConfig` | `{ maxPerSecond = 30 }` | Server-side rate limiting
|
|
202
|
-
| `validate` | `(data, player) → (bool, string?)` | none | Server-side validation
|
|
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. |
|
|
203
188
|
|
|
204
|
-
###
|
|
189
|
+
### Methods
|
|
205
190
|
|
|
206
|
-
| Method | Context |
|
|
207
|
-
|
|
208
|
-
| `query:handle(fn)` | Both |
|
|
209
|
-
| `query:request(data)` | Client |
|
|
210
|
-
| `query:request(data, player)` | Server |
|
|
211
|
-
| `query:request(data, target)` | Server |
|
|
212
|
-
| `query:name()` | Both | Returns the
|
|
213
|
-
| `query:stats()` | Both |
|
|
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. |
|
|
214
199
|
|
|
215
|
-
Each query consumes two packet IDs internally
|
|
200
|
+
Each query consumes two packet IDs internally.
|
|
216
201
|
|
|
217
202
|
## Groups
|
|
218
203
|
|
|
219
|
-
`Lync.group(name)`
|
|
204
|
+
`Lync.group(name)`
|
|
220
205
|
|
|
221
|
-
|
|
206
|
+
Named player sets. Members auto-removed on `PlayerRemoving`. Iterable with `for player in group do`.
|
|
222
207
|
|
|
223
|
-
| Method | Returns |
|
|
224
|
-
|
|
225
|
-
| `group:add(player)` | `boolean` | `true` if added
|
|
226
|
-
| `group:remove(player)` | `boolean` | `true` if removed
|
|
208
|
+
| Method | Returns | Description |
|
|
209
|
+
|:-------|:--------|:------------|
|
|
210
|
+
| `group:add(player)` | `boolean` | `true` if added. |
|
|
211
|
+
| `group:remove(player)` | `boolean` | `true` if removed. |
|
|
227
212
|
| `group:has(player)` | `boolean` | Membership check. |
|
|
228
|
-
| `group:count()` | `number` |
|
|
229
|
-
| `group:destroy()` | — | Clears
|
|
213
|
+
| `group:count()` | `number` | Member count. |
|
|
214
|
+
| `group:destroy()` | — | Clears members, frees name. |
|
|
230
215
|
|
|
231
216
|
## Scope
|
|
232
217
|
|
|
233
|
-
`Lync.scope()`
|
|
218
|
+
`Lync.scope()`
|
|
219
|
+
|
|
220
|
+
Batches connections for cleanup.
|
|
234
221
|
|
|
235
222
|
```luau
|
|
236
223
|
local scope = Lync.scope()
|
|
237
224
|
scope:on(packetA, fnA)
|
|
238
225
|
scope:on(packetB, fnB)
|
|
239
226
|
scope:add(someRBXScriptConnection)
|
|
240
|
-
scope:destroy()
|
|
227
|
+
scope:destroy() -- disconnects everything
|
|
241
228
|
```
|
|
242
229
|
|
|
243
|
-
| Method |
|
|
244
|
-
|
|
245
|
-
| `scope:on(source, fn)` |
|
|
246
|
-
| `scope:once(source, fn)` |
|
|
247
|
-
| `scope:add(connection)` |
|
|
248
|
-
| `scope:destroy()` |
|
|
230
|
+
| Method | Description |
|
|
231
|
+
|:-------|:------------|
|
|
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. |
|
|
249
236
|
|
|
250
237
|
## Connection
|
|
251
238
|
|
|
252
|
-
Returned by `packet:on()`, `packet:once()`, `query:handle()`,
|
|
239
|
+
Returned by `packet:on()`, `packet:once()`, `query:handle()`, and middleware functions.
|
|
253
240
|
|
|
254
|
-
| Field/Method |
|
|
255
|
-
|
|
256
|
-
| `connection.connected` | `boolean
|
|
257
|
-
| `connection:disconnect()` | Stops the listener.
|
|
241
|
+
| Field / Method | Description |
|
|
242
|
+
|:---------------|:------------|
|
|
243
|
+
| `connection.connected` | `boolean` |
|
|
244
|
+
| `connection:disconnect()` | Stops the listener. Safe mid-fire, safe to call multiple times. |
|
|
258
245
|
|
|
259
246
|
## Middleware
|
|
260
247
|
|
|
261
|
-
|
|
248
|
+
```luau
|
|
249
|
+
Lync.onSend(function(data, name, player)
|
|
250
|
+
return data -- or return Lync.DROP to discard
|
|
251
|
+
end)
|
|
252
|
+
|
|
253
|
+
Lync.onReceive(function(data, name, player)
|
|
254
|
+
return data
|
|
255
|
+
end)
|
|
262
256
|
|
|
263
|
-
|
|
257
|
+
Lync.onDrop(function(player, reason, name, data)
|
|
258
|
+
warn(player.Name, "dropped", name, reason)
|
|
259
|
+
end)
|
|
260
|
+
```
|
|
264
261
|
|
|
265
|
-
|
|
266
|
-
|:---------|:---------|
|
|
267
|
-
| `Lync.onSend(fn)` | `fn(data, name, player?) → data?`. Runs before serialization. |
|
|
268
|
-
| `Lync.onReceive(fn)` | `fn(data, name, player?) → data?`. Runs after deserialization and validation. |
|
|
269
|
-
| `Lync.onDrop(fn)` | `fn(player, reason, name, data?)`. Fires when a packet is rejected. Reason is `"rate"`, `"validation"`, or the string returned by the `validate` callback. |
|
|
270
|
-
| `Lync.DROP` | Frozen sentinel. Return from `onSend` to silently drop the packet. |
|
|
262
|
+
All three return a Connection.
|
|
271
263
|
|
|
272
264
|
## Targets
|
|
273
265
|
|
|
274
|
-
Server-side second argument to `packet:send()
|
|
266
|
+
Server-side second argument to `packet:send()`.
|
|
275
267
|
|
|
276
|
-
| Target |
|
|
277
|
-
|
|
278
|
-
| `player` | Single
|
|
279
|
-
| `Lync.all` | All connected players
|
|
280
|
-
| `Lync.except(...)` |
|
|
281
|
-
| `{ p1, p2, ... }` |
|
|
282
|
-
| `group` | All
|
|
268
|
+
| Target | Description |
|
|
269
|
+
|:-------|:------------|
|
|
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. |
|
|
283
275
|
|
|
284
276
|
## Codecs
|
|
285
277
|
|
|
286
278
|
### Numbers
|
|
287
279
|
|
|
288
|
-
`Lync.int(min, max)`
|
|
289
|
-
|
|
290
|
-
| Range | Wire | Bytes |
|
|
291
|
-
|:------|:-----|------:|
|
|
292
|
-
| `[0, 255]` | u8 | 1 |
|
|
293
|
-
| `[0, 65535]` | u16 | 2 |
|
|
294
|
-
| `[0, 4294967295]` | u32 | 4 |
|
|
295
|
-
| `[-128, 127]` | i8 | 1 |
|
|
296
|
-
| `[-32768, 32767]` | i16 | 2 |
|
|
297
|
-
| `[-2147483648, 2147483647]` | i32 | 4 |
|
|
298
|
-
|
|
299
|
-
Signed integers use unsigned buffer writes (`writeu8`/`writeu16`/`writeu32`) with two's complement conversion because `writei8`/`writei16`/`writei32` are not FASTCALL-optimized in Luau.
|
|
300
|
-
|
|
301
|
-
| Codec | Bytes | Behavior |
|
|
302
|
-
|:------|------:|:---------|
|
|
303
|
-
| `Lync.f16` | 2 | Half-precision IEEE 754. ~3 decimal digits. ±65504 normal range. Overflow clamps to ±inf. NaN preserved. |
|
|
304
|
-
| `Lync.f32` | 4 | IEEE 754 single-precision. |
|
|
305
|
-
| `Lync.f64` | 8 | IEEE 754 double-precision. |
|
|
306
|
-
| `Lync.bool` | 1 | `true`/`false`. Inside structs, bools are separated and bitpacked (8 per byte). Inside arrays, bools are bitpacked. Standalone uses 1 byte. |
|
|
307
|
-
|
|
308
|
-
`Lync.float(min, max, precision)` quantizes a float range to an integer range. Wire type is selected by `ceil((max - min) / precision)`: u8 if ≤ 255, u16 if ≤ 65535, u32 otherwise. Values outside `[min, max]` are clamped.
|
|
280
|
+
`Lync.int(min, max)` picks the smallest wire type for your range.
|
|
309
281
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
|
313
|
-
|
|
314
|
-
| `Lync.
|
|
315
|
-
| `Lync.
|
|
316
|
-
| `Lync.
|
|
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. |
|
|
317
303
|
|
|
318
304
|
### Roblox Types
|
|
319
305
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
|
323
|
-
|
|
324
|
-
| `Lync.
|
|
325
|
-
| `Lync.
|
|
326
|
-
| `Lync.
|
|
327
|
-
| `Lync.
|
|
328
|
-
| `Lync.
|
|
329
|
-
| `Lync.
|
|
330
|
-
| `Lync.
|
|
331
|
-
| `Lync.
|
|
332
|
-
| `Lync.
|
|
333
|
-
| `Lync.
|
|
334
|
-
| `Lync.
|
|
335
|
-
| `Lync.
|
|
336
|
-
| `Lync.
|
|
337
|
-
| `Lync.
|
|
338
|
-
| `Lync.numberSequence` | varint + N×12 | f32 time + f32 value + f32 envelope per keypoint |
|
|
339
|
-
| `Lync.colorSequence` | varint + N×7 | f32 time + u8 R + u8 G + u8 B per keypoint |
|
|
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 |
|
|
340
324
|
|
|
341
325
|
### Quantized Variants
|
|
342
326
|
|
|
343
|
-
|
|
327
|
+
Call the codec to get a quantized version.
|
|
344
328
|
|
|
345
|
-
| Codec | Bytes |
|
|
346
|
-
|
|
347
|
-
| `Lync.vec2(min, max, precision)` | 2–8 | Per-component quantization.
|
|
348
|
-
| `Lync.vec3(min, max, precision)` | 3–12 | Per-component quantization.
|
|
349
|
-
| `Lync.cframe()` | 16 |
|
|
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. |
|
|
350
334
|
|
|
351
335
|
### Composites
|
|
352
336
|
|
|
353
|
-
|
|
|
354
|
-
|
|
355
|
-
| `Lync.struct(
|
|
356
|
-
| `Lync.array(
|
|
357
|
-
| `Lync.map(keyCodec, valueCodec, maxCount?)` |
|
|
358
|
-
| `Lync.optional(codec)` | 1-byte
|
|
359
|
-
| `Lync.tuple(...)` |
|
|
360
|
-
| `Lync.tagged(tagField,
|
|
361
|
-
|
|
362
|
-
### Delta Codecs
|
|
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. |
|
|
363
345
|
|
|
364
|
-
|
|
346
|
+
### Delta
|
|
365
347
|
|
|
366
|
-
|
|
348
|
+
Only works with reliable transport. Sends 1 byte when data hasn't changed.
|
|
367
349
|
|
|
368
|
-
|
|
|
369
|
-
|
|
370
|
-
| `Lync.deltaStruct(schema)` |
|
|
371
|
-
| `Lync.deltaArray(
|
|
372
|
-
| `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. |
|
|
373
355
|
|
|
374
|
-
### Meta
|
|
356
|
+
### Meta
|
|
375
357
|
|
|
376
|
-
|
|
|
377
|
-
|
|
378
|
-
| `Lync.enum(...)` | String enum.
|
|
379
|
-
| `Lync.bitfield(schema)` | Sub-byte packing
|
|
380
|
-
| `Lync.custom(size, write, read)` | User-defined fixed-size codec.
|
|
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. |
|
|
381
363
|
| `Lync.nothing` | Zero bytes. Reads `nil`. |
|
|
382
|
-
| `Lync.unknown` | Bypasses
|
|
383
|
-
| `Lync.auto` | Self-describing.
|
|
384
|
-
|
|
385
|
-
## Wire Protocol
|
|
364
|
+
| `Lync.unknown` | Bypasses serialization entirely. Use with `validate`. |
|
|
365
|
+
| `Lync.auto` | Self-describing. Supports nil, bool, numbers, strings, buffers, and Roblox types. |
|
|
386
366
|
|
|
387
|
-
|
|
367
|
+
## Rate Limiting
|
|
388
368
|
|
|
389
|
-
|
|
369
|
+
Two modes (pick one per packet):
|
|
390
370
|
|
|
391
|
-
|
|
392
|
-
|:------|------:|:---------|
|
|
393
|
-
| 0–191 | 1 | Direct value |
|
|
394
|
-
| 192–8,383 | 2 | `0xC0 + high5`, `low8` |
|
|
395
|
-
| 8,384–1,056,959 | 3 | `0xE0 + high4`, `low16 LE` |
|
|
396
|
-
| 1,056,960–4,294,967,295 | 5 | `0xF0`, `u32 LE` |
|
|
371
|
+
**Token bucket:** `{ maxPerSecond = N, burst = M }`
|
|
397
372
|
|
|
398
|
-
|
|
373
|
+
**Cooldown:** `{ cooldown = seconds }`
|
|
399
374
|
|
|
400
|
-
|
|
375
|
+
Global limit across all packets: `Lync.configure({ globalRateLimit = { maxPerSecond = N } })`
|
|
401
376
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
**Multi-item:** `[0IIIIIII] [u16 count] [payload₁] ...` — MSB clear, u16 item count follows. Used when ≥2 sends to the same packet occur in one frame.
|
|
405
|
-
|
|
406
|
-
The single-item path saves 2 bytes per packet per frame vs always writing a count. Maximum 127 packet IDs (7 bits).
|
|
407
|
-
|
|
408
|
-
### XOR Framing
|
|
409
|
-
|
|
410
|
-
Reliable channels XOR the current frame against the previous before sending. The receiver XOR's against its previous decoded frame to recover the original. Produces long zero runs that compress well under Roblox's internal deflate.
|
|
411
|
-
|
|
412
|
-
XOR operates in u32-aligned chunks with u8 remainder. Mismatched frame sizes are handled: excess bytes in a longer frame are copied directly.
|
|
413
|
-
|
|
414
|
-
Unreliable channels skip XOR (no guaranteed frame ordering).
|
|
415
|
-
|
|
416
|
-
## Security
|
|
417
|
-
|
|
418
|
-
### Schema Validation
|
|
419
|
-
|
|
420
|
-
Every incoming packet on the server passes through Gate before listeners fire:
|
|
421
|
-
|
|
422
|
-
- **`_typeCheck`**: Rejects wrong `typeof`.
|
|
423
|
-
- **`_isInteger` + `_min`/`_max`**: Rejects non-integers, NaN, inf, out-of-range.
|
|
424
|
-
- **`_schema`** (struct codecs): Recursive per-field validation.
|
|
425
|
-
- **Fallback**: NaN/inf scan up to `validationDepth` levels for codecs without metadata.
|
|
426
|
-
|
|
427
|
-
Rejected packets fire `onDrop` and are silently discarded. Other packets in the same frame from the same player are unaffected.
|
|
428
|
-
|
|
429
|
-
### Rate Limiting
|
|
430
|
-
|
|
431
|
-
Two modes (mutually exclusive):
|
|
432
|
-
|
|
433
|
-
**Token bucket:** `{ maxPerSecond = N, burst = M }`. Tokens refill at N/sec. Burst defaults to 1. Each fire costs one token.
|
|
377
|
+
## Configuration
|
|
434
378
|
|
|
435
|
-
|
|
379
|
+
`Lync.configure(options)` — call before `Lync.start()`.
|
|
436
380
|
|
|
437
|
-
|
|
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()`. |
|
|
438
389
|
|
|
439
|
-
###
|
|
390
|
+
### Lifecycle
|
|
440
391
|
|
|
441
|
-
|
|
392
|
+
| Function | Description |
|
|
393
|
+
|:---------|:------------|
|
|
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. |
|
|
442
399
|
|
|
443
|
-
|
|
400
|
+
### Stats
|
|
444
401
|
|
|
445
|
-
|
|
402
|
+
Enable with `Lync.configure({ stats = true })`.
|
|
446
403
|
|
|
447
|
-
| Function |
|
|
448
|
-
|
|
404
|
+
| Function | Description |
|
|
405
|
+
|:---------|:------------|
|
|
449
406
|
| `packet:stats()` | `{ bytesSent, bytesReceived, fires, recvFires, drops }` |
|
|
450
|
-
| `Lync.stats.player(player)` | `{ bytesSent, bytesReceived }`
|
|
407
|
+
| `Lync.stats.player(player)` | `{ bytesSent, bytesReceived }` — server only. |
|
|
451
408
|
| `Lync.stats.reset()` | Zeros all counters. |
|
|
452
409
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
| Function | Behavior |
|
|
456
|
-
|:---------|:---------|
|
|
457
|
-
| `Lync.debug.pending()` | In-flight query request count. |
|
|
458
|
-
| `Lync.debug.registrations()` | Frozen array of `{ name, id, kind, isUnreliable }`. |
|
|
459
|
-
|
|
460
|
-
## Configuration
|
|
461
|
-
|
|
462
|
-
`Lync.configure(options)` — call before `Lync.start()`.
|
|
410
|
+
### Debug
|
|
463
411
|
|
|
464
|
-
|
|
|
465
|
-
|
|
466
|
-
| `
|
|
467
|
-
| `
|
|
468
|
-
| `poolSize` | 16 | 2–128 | ChannelState reuse pool size. |
|
|
469
|
-
| `bandwidthLimit` | none | — | `{ softLimit, maxStrikes }`. Per-player. |
|
|
470
|
-
| `globalRateLimit` | none | — | `{ maxPerSecond }`. Per-player across all packets. |
|
|
471
|
-
| `stats` | `false` | — | Enables stat counters. |
|
|
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. |
|
|
472
416
|
|
|
473
417
|
## Limits
|
|
474
418
|
|
|
475
|
-
| Constraint |
|
|
419
|
+
| Constraint | Limit |
|
|
476
420
|
|:-----------|------:|
|
|
477
|
-
|
|
|
478
|
-
|
|
|
479
|
-
|
|
|
480
|
-
|
|
|
481
|
-
|
|
|
482
|
-
|
|
|
483
|
-
| Bool packing density | 8 per byte |
|
|
484
|
-
| String inline varint threshold | 191 bytes (1B prefix), 192+ uses multi-byte |
|
|
485
|
-
| Delta + unreliable | Not allowed (define-time error) |
|
|
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 |
|
|
486
427
|
|
|
487
428
|
## Benchmarks
|
|
488
429
|
|
|
489
|
-
Run `rojo serve bench.project.json
|
|
490
|
-
|
|
491
|
-
See `bench/Run.server.luau` for full configuration and methodology.
|
|
430
|
+
Run `rojo serve bench.project.json` with one server + one client.
|
|
492
431
|
|
|
493
432
|
### Wire Sizes
|
|
494
433
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
|
498
|
-
|
|
499
|
-
| `
|
|
500
|
-
| `
|
|
501
|
-
| `
|
|
502
|
-
| `
|
|
503
|
-
| `
|
|
504
|
-
| `
|
|
505
|
-
| `
|
|
506
|
-
| `
|
|
507
|
-
| `
|
|
508
|
-
| `
|
|
509
|
-
| `
|
|
510
|
-
|
|
|
511
|
-
|
|
|
512
|
-
|
|
|
513
|
-
|
|
|
514
|
-
|
|
|
515
|
-
| `vec3` | lossless | 12 |
|
|
516
|
-
| `vec3(0, 100, 1)` | u8 quantized | 3 |
|
|
517
|
-
| `vec3(-1000, 1000, 0.1)` | u16 quantized | 6 |
|
|
518
|
-
| `cframe` | lossless | 24 |
|
|
519
|
-
| `cframe()` | smallest-three | 16 |
|
|
520
|
-
| `color3` | RGB | 3 |
|
|
521
|
-
| `ray` | origin + direction | 24 |
|
|
522
|
-
| entity struct | 6 fields + bool (lossless) | 34 |
|
|
523
|
-
| entity struct | quantized fields (compact) | 13 |
|
|
524
|
-
| bitfield | bool + uint packed | 2 |
|
|
525
|
-
| `array` × 100 entities | 100× struct(6× u8) | 601 |
|
|
526
|
-
| `array` × 1000 bools | bitpacked | 127 |
|
|
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 |
|
|
527
454
|
|
|
528
455
|
### Codec Throughput
|
|
529
456
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
| Codec |
|
|
533
|
-
|
|
534
|
-
| `bool` |
|
|
535
|
-
| `int(0, 255)` |
|
|
536
|
-
| `
|
|
537
|
-
| `
|
|
538
|
-
| `
|
|
539
|
-
| `
|
|
540
|
-
| `
|
|
541
|
-
| `
|
|
542
|
-
| `
|
|
543
|
-
|
|
|
544
|
-
|
|
|
545
|
-
|
|
|
546
|
-
| `vec3` (quantized) | 3 | 130ns | 85ns | 4,636,348 |
|
|
547
|
-
| `cframe` (lossless) | 24 | 92ns | 144ns | 4,232,266 |
|
|
548
|
-
| `cframe()` (compressed) | 16 | 123ns | 170ns | 3,413,621 |
|
|
549
|
-
| `color3` | 3 | 125ns | 58ns | 5,482,756 |
|
|
550
|
-
| `udim2` | 16 | 235ns | 112ns | 2,880,482 |
|
|
551
|
-
| entity struct | 34 | 239ns | 395ns | 1,578,183 |
|
|
552
|
-
| entity compact | 13 | 377ns | 490ns | 1,153,064 |
|
|
553
|
-
| bitfield flags | 2 | 142ns | 332ns | 2,107,486 |
|
|
554
|
-
| 100× entity array | 601 | 15.2µs | 34.1µs | 20,306 |
|
|
555
|
-
| 1000× bool array | 127 | 4.3µs | 5.1µs | 106,806 |
|
|
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 |
|
|
556
473
|
|
|
557
474
|
### Delta Savings
|
|
558
475
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
|
562
|
-
|
|
563
|
-
| `
|
|
564
|
-
| `
|
|
565
|
-
| `
|
|
566
|
-
| `deltaArray` (1000× bool) | 128B | 1B | 1B | 99% |
|
|
567
|
-
| `deltaMap` (string → u8) | 19B | 1B | 19B | 95% |
|
|
568
|
-
|
|
569
|
-
### Batch Framing
|
|
570
|
-
|
|
571
|
-
MSB single-item batches use a 1-byte header. Multi-item batches add a u16 count after the header.
|
|
572
|
-
|
|
573
|
-
| Scenario | Total bytes | Per-item overhead |
|
|
574
|
-
|:---------|----------:|------------------:|
|
|
575
|
-
| 1 × u8 (single-item) | 2B | 1B |
|
|
576
|
-
| 10 × u8 (multi-item) | 13B | 0.3B |
|
|
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% |
|
|
577
483
|
|
|
578
484
|
### Network Throughput
|
|
579
485
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
| Packet | Fires/frame | FPS median | FPS p1 | Kbps median | Kbps p95 | Kbps p99 |
|
|
583
|
-
|:-------|:---:|----:|----:|-----:|-----:|-----:|
|
|
584
|
-
| booleans | 1000 | 60 | 59.9 | 2.5 | 6.2 | 6.2 |
|
|
585
|
-
| entity struct | 1000 | 60 | 59.9 | 2.3 | 2.4 | 2.4 |
|
|
586
|
-
| entity compact | 1000 | 60 | 59.9 | 2.4 | 2.5 | 2.5 |
|
|
587
|
-
| 100× entities | 100 | 60 | 59.9 | 2.3 | 3.1 | 3.1 |
|
|
588
|
-
| 1000× bools | 100 | 60 | 59.9 | 2.3 | 2.3 | 2.3 |
|
|
589
|
-
| bitfield flags | 1000 | 60 | 59.9 | 2.4 | 2.5 | 2.5 |
|
|
590
|
-
| cframe lossless | 1000 | 60 | 59.9 | 2.5 | 2.5 | 2.5 |
|
|
591
|
-
| cframe compressed | 1000 | 60 | 59.8 | 2.3 | 2.3 | 2.3 |
|
|
486
|
+
1000 fires/frame, 8 seconds, one player.
|
|
592
487
|
|
|
593
|
-
|
|
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 |
|
|
594
496
|
|
|
595
497
|
### Cross-Library Comparison
|
|
596
498
|
|
|
597
|
-
|
|
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.
|
|
598
500
|
|
|
599
|
-
|
|
501
|
+
Other tool numbers from [Blink v0.17.1](https://github.com/1Axen/blink/blob/main/benchmark/Benchmarks.md) (2025-04-30).
|
|
600
502
|
|
|
601
503
|
> [!NOTE]
|
|
602
|
-
>
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
####
|
|
615
|
-
|
|
616
|
-
| Tool |
|
|
617
|
-
|
|
618
|
-
| roblox |
|
|
619
|
-
| **lync** | **60
|
|
620
|
-
| blink |
|
|
621
|
-
| zap |
|
|
622
|
-
| bytenet |
|
|
623
|
-
|
|
624
|
-
#### Entities — Kbps
|
|
625
|
-
|
|
626
|
-
| Tool | Median | P0 | P80 | P90 | P95 | P100 | Loss |
|
|
627
|
-
|:-----|-------:|---:|----:|----:|----:|-----:|-----:|
|
|
628
|
-
| roblox | 559,364 | 559,364 | 676,716 | 676,716 | 676,716 | 784,082 | 0% |
|
|
629
|
-
| **lync** | **3.68** | **3.61** | **3.72** | **3.75** | **3.75** | **4.18** | **0%** |
|
|
630
|
-
| blink | 41.81 | 26.30 | 42.40 | 42.48 | 42.48 | 42.62 | 0% |
|
|
631
|
-
| zap | 41.71 | 25.46 | 42.19 | 42.32 | 42.32 | 42.93 | 0% |
|
|
632
|
-
| bytenet | 41.64 | 22.84 | 42.36 | 42.82 | 42.82 | 43.24 | 0% |
|
|
633
|
-
|
|
634
|
-
#### Booleans — FPS
|
|
635
|
-
|
|
636
|
-
| Tool | Median | P0 | P80 | P90 | P95 | P100 | Loss |
|
|
637
|
-
|:-----|-------:|---:|----:|----:|----:|-----:|-----:|
|
|
638
|
-
| roblox | 21.00 | 22.00 | 20.00 | 19.00 | 19.00 | 19.00 | 0% |
|
|
639
|
-
| **lync** | **60.00** | **61.00** | **60.00** | **60.00** | **60.00** | **59.00** | **0%** |
|
|
640
|
-
| blink | 97.00 | 98.00 | 97.00 | 96.00 | 96.00 | 96.00 | 0% |
|
|
641
|
-
| zap | 52.00 | 53.00 | 51.00 | 51.00 | 51.00 | 49.00 | 0% |
|
|
642
|
-
| bytenet | 35.00 | 37.00 | 35.00 | 35.00 | 35.00 | 34.00 | 0% |
|
|
643
|
-
|
|
644
|
-
#### Booleans — Kbps
|
|
645
|
-
|
|
646
|
-
| Tool | Median | P0 | P80 | P90 | P95 | P100 | Loss |
|
|
647
|
-
|:-----|-------:|---:|----:|----:|----:|-----:|-----:|
|
|
648
|
-
| roblox | 353,107 | 196,827 | 690,748 | 842,240 | 842,240 | 1,124,176 | 0% |
|
|
649
|
-
| **lync** | **2.49** | **2.44** | **2.50** | **2.52** | **2.52** | **2.54** | **0%** |
|
|
650
|
-
| blink | 7.91 | 7.41 | 7.93 | 7.99 | 7.99 | 8.00 | 0% |
|
|
651
|
-
| zap | 8.10 | 5.75 | 8.17 | 8.22 | 8.22 | 8.27 | 0% |
|
|
652
|
-
| bytenet | 8.11 | 5.07 | 8.35 | 8.46 | 8.46 | 8.47 | 0% |
|
|
653
|
-
|
|
654
|
-
#### Wire Size Comparison
|
|
655
|
-
|
|
656
|
-
| Data | Lync | Other tools | Difference |
|
|
657
|
-
|:-----|-----:|------------:|-----------:|
|
|
658
|
-
| 100× entities | 601B | ~602B | -1B |
|
|
659
|
-
| 1000× bools | 127B | ~1002B | -875B (87% smaller) |
|
|
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 |
|
|
660
525
|
|
|
661
526
|
## License
|
|
662
527
|
|
package/package.json
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -139,56 +139,57 @@ declare namespace Lync {
|
|
|
139
139
|
interface LyncModule {
|
|
140
140
|
// ── Lifecycle ────────────────────────────────────────────────────
|
|
141
141
|
|
|
142
|
-
configure(options: Lync.ConfigureOptions): void;
|
|
143
|
-
start(): void;
|
|
142
|
+
configure(this: void, options: Lync.ConfigureOptions): void;
|
|
143
|
+
start(this: void): void;
|
|
144
144
|
readonly started: boolean;
|
|
145
145
|
|
|
146
146
|
// ── Definitions ─────────────────────────────────────────────────
|
|
147
147
|
|
|
148
|
-
packet<T>(name: string, codec: Lync.Codec<T>, options?: Lync.PacketOptions<T>): Lync.Packet<T>;
|
|
148
|
+
packet<T>(this: void, name: string, codec: Lync.Codec<T>, options?: Lync.PacketOptions<T>): Lync.Packet<T>;
|
|
149
149
|
|
|
150
150
|
query<Req, Resp>(
|
|
151
|
+
this: void,
|
|
151
152
|
name: string,
|
|
152
153
|
requestCodec: Lync.Codec<Req>,
|
|
153
154
|
responseCodec: Lync.Codec<Resp>,
|
|
154
155
|
options?: Lync.QueryOptions<Req>,
|
|
155
156
|
): Lync.Query<Req, Resp>;
|
|
156
157
|
|
|
157
|
-
group(name: string): Lync.Group;
|
|
158
|
-
scope(): Lync.Scope;
|
|
158
|
+
group(this: void, name: string): Lync.Group;
|
|
159
|
+
scope(this: void): Lync.Scope;
|
|
159
160
|
|
|
160
161
|
// ── Targeting ───────────────────────────────────────────────────
|
|
161
162
|
|
|
162
163
|
readonly all: Lync.AllTarget;
|
|
163
|
-
except(...args: Array<Player | Lync.Group>): Lync.ExceptTarget;
|
|
164
|
+
except(this: void, ...args: Array<Player | Lync.Group>): Lync.ExceptTarget;
|
|
164
165
|
readonly DROP: Lync.DropSentinel;
|
|
165
166
|
|
|
166
167
|
// ── Middleware ───────────────────────────────────────────────────
|
|
167
168
|
|
|
168
|
-
onSend(fn: (data: unknown, name: string, player?: Player) => unknown): Lync.Connection;
|
|
169
|
-
onReceive(fn: (data: unknown, name: string, player?: Player) => unknown): Lync.Connection;
|
|
170
|
-
onDrop(fn: (player: Player, reason: string, name: string, data?: unknown) => void): Lync.Connection;
|
|
169
|
+
onSend(this: void, fn: (data: unknown, name: string, player?: Player) => unknown): Lync.Connection;
|
|
170
|
+
onReceive(this: void, fn: (data: unknown, name: string, player?: Player) => unknown): Lync.Connection;
|
|
171
|
+
onDrop(this: void, fn: (player: Player, reason: string, name: string, data?: unknown) => void): Lync.Connection;
|
|
171
172
|
|
|
172
173
|
// ── Runtime Control ─────────────────────────────────────────────
|
|
173
174
|
|
|
174
|
-
flush(): void;
|
|
175
|
-
flushRate(hz: number): void;
|
|
175
|
+
flush(this: void): void;
|
|
176
|
+
flushRate(this: void, hz: number): void;
|
|
176
177
|
|
|
177
178
|
// ── Stats ───────────────────────────────────────────────────────
|
|
178
179
|
|
|
179
180
|
readonly stats: {
|
|
180
|
-
player(player: Player): Lync.PlayerStats | undefined;
|
|
181
|
-
reset(): void;
|
|
181
|
+
player(this: void, player: Player): Lync.PlayerStats | undefined;
|
|
182
|
+
reset(this: void): void;
|
|
182
183
|
};
|
|
183
184
|
|
|
184
185
|
// ── Debug ───────────────────────────────────────────────────────
|
|
185
186
|
|
|
186
187
|
readonly debug: {
|
|
187
|
-
capture(label?: string): void;
|
|
188
|
-
stop(): void;
|
|
189
|
-
dump(): void;
|
|
190
|
-
pending(): number;
|
|
191
|
-
registrations(): ReadonlyArray<{
|
|
188
|
+
capture(this: void, label?: string): void;
|
|
189
|
+
stop(this: void): void;
|
|
190
|
+
dump(this: void): void;
|
|
191
|
+
pending(this: void): number;
|
|
192
|
+
registrations(this: void): ReadonlyArray<{
|
|
192
193
|
name: string;
|
|
193
194
|
id: number;
|
|
194
195
|
kind: number;
|
|
@@ -198,8 +199,8 @@ interface LyncModule {
|
|
|
198
199
|
|
|
199
200
|
// ── Number Codecs ───────────────────────────────────────────────
|
|
200
201
|
|
|
201
|
-
int(min: number, max: number): Lync.Codec<number>;
|
|
202
|
-
float(min: number, max: number, precision: number): Lync.Codec<number>;
|
|
202
|
+
int(this: void, min: number, max: number): Lync.Codec<number>;
|
|
203
|
+
float(this: void, min: number, max: number, precision: number): Lync.Codec<number>;
|
|
203
204
|
readonly f16: Lync.Codec<number>;
|
|
204
205
|
readonly f32: Lync.Codec<number>;
|
|
205
206
|
readonly f64: Lync.Codec<number>;
|
|
@@ -234,24 +235,25 @@ interface LyncModule {
|
|
|
234
235
|
|
|
235
236
|
// ── Composites ──────────────────────────────────────────────────
|
|
236
237
|
|
|
237
|
-
struct<S extends Record<string, Lync.Codec<unknown>>>(schema: S): Lync.Codec<Lync.InferSchema<S>>;
|
|
238
|
-
deltaStruct<S extends Record<string, Lync.Codec<unknown>>>(schema: S): Lync.Codec<Lync.InferSchema<S>>;
|
|
239
|
-
array<T>(element: Lync.Codec<T>, maxCount?: number): Lync.Codec<T[]>;
|
|
240
|
-
deltaArray<T>(element: Lync.Codec<T>, maxCount?: number): Lync.Codec<T[]>;
|
|
241
|
-
map<K, V>(keyCodec: Lync.Codec<K>, valueCodec: Lync.Codec<V>, maxCount?: number): Lync.Codec<Map<K, V>>;
|
|
242
|
-
deltaMap<K, V>(keyCodec: Lync.Codec<K>, valueCodec: Lync.Codec<V>, maxCount?: number): Lync.Codec<Map<K, V>>;
|
|
243
|
-
optional<T>(codec: Lync.Codec<T>): Lync.Codec<T | undefined>;
|
|
244
|
-
tuple<T extends Lync.Codec<unknown>[]>(...codecs: T): Lync.Codec<{ [K in keyof T]: Lync.InferCodec<T[K]> }>;
|
|
238
|
+
struct<S extends Record<string, Lync.Codec<unknown>>>(this: void, schema: S): Lync.Codec<Lync.InferSchema<S>>;
|
|
239
|
+
deltaStruct<S extends Record<string, Lync.Codec<unknown>>>(this: void, schema: S): Lync.Codec<Lync.InferSchema<S>>;
|
|
240
|
+
array<T>(this: void, element: Lync.Codec<T>, maxCount?: number): Lync.Codec<T[]>;
|
|
241
|
+
deltaArray<T>(this: void, element: Lync.Codec<T>, maxCount?: number): Lync.Codec<T[]>;
|
|
242
|
+
map<K, V>(this: void, keyCodec: Lync.Codec<K>, valueCodec: Lync.Codec<V>, maxCount?: number): Lync.Codec<Map<K, V>>;
|
|
243
|
+
deltaMap<K, V>(this: void, keyCodec: Lync.Codec<K>, valueCodec: Lync.Codec<V>, maxCount?: number): Lync.Codec<Map<K, V>>;
|
|
244
|
+
optional<T>(this: void, codec: Lync.Codec<T>): Lync.Codec<T | undefined>;
|
|
245
|
+
tuple<T extends Lync.Codec<unknown>[]>(this: void, ...codecs: T): Lync.Codec<{ [K in keyof T]: Lync.InferCodec<T[K]> }>;
|
|
245
246
|
tagged<Tag extends string, V extends Record<string, Lync.Codec<unknown>>>(
|
|
247
|
+
this: void,
|
|
246
248
|
tagField: Tag,
|
|
247
249
|
variants: V,
|
|
248
|
-
): Lync.Codec<{ [K in keyof V]:
|
|
250
|
+
): Lync.Codec<{ [K in keyof V & string]: { [F in Tag]: K } & Lync.InferCodec<V[K]> }[keyof V & string]>;
|
|
249
251
|
|
|
250
252
|
// ── Meta ────────────────────────────────────────────────────────
|
|
251
253
|
|
|
252
|
-
enum<T extends string[]>(...values: T): Lync.Codec<T[number]>;
|
|
253
|
-
bitfield(schema: Record<string, { type: "bool" } | { type: "uint"; width: number } | { type: "int"; width: number }>): Lync.Codec<Record<string, boolean | number>>;
|
|
254
|
-
custom<T>(size: number, write: (b: buffer, offset: number, value: T) => void, read: (b: buffer, offset: number) => T): Lync.Codec<T>;
|
|
254
|
+
enum<T extends string[]>(this: void, ...values: T): Lync.Codec<T[number]>;
|
|
255
|
+
bitfield(this: void, schema: Record<string, { type: "bool" } | { type: "uint"; width: number } | { type: "int"; width: number }>): Lync.Codec<Record<string, boolean | number>>;
|
|
256
|
+
custom<T>(this: void, size: number, write: (b: buffer, offset: number, value: T) => void, read: (b: buffer, offset: number) => T): Lync.Codec<T>;
|
|
255
257
|
readonly nothing: Lync.Codec<undefined>;
|
|
256
258
|
readonly unknown: Lync.Codec<unknown>;
|
|
257
259
|
readonly auto: Lync.Codec<unknown>;
|