@axpecter/lync 2.1.1 → 2.2.0
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 +241 -349
- package/package.json +1 -1
- package/src/Types.luau +53 -13
- package/src/api/Group.luau +41 -51
- package/src/api/Packet.luau +145 -143
- package/src/api/Query.luau +204 -202
- package/src/api/Scope.luau +36 -38
- package/src/api/Signal.luau +68 -94
- package/src/codec/Base.luau +43 -23
- package/src/codec/composite/Array.luau +161 -152
- package/src/codec/composite/Map.luau +37 -53
- package/src/codec/composite/Optional.luau +15 -21
- package/src/codec/composite/Shared.luau +78 -41
- package/src/codec/composite/Struct.luau +64 -125
- package/src/codec/composite/Tagged.luau +15 -25
- package/src/codec/composite/Tuple.luau +15 -20
- package/src/codec/datatype/Buffer.luau +44 -51
- package/src/codec/datatype/CFrame.luau +72 -104
- package/src/codec/datatype/Color.luau +14 -10
- package/src/codec/datatype/Instance.luau +32 -42
- package/src/codec/datatype/IntVector.luau +24 -22
- package/src/codec/datatype/NumberRange.luau +21 -12
- package/src/codec/datatype/Ray.luau +13 -7
- package/src/codec/datatype/Rect.luau +13 -7
- package/src/codec/datatype/Region.luau +25 -22
- package/src/codec/datatype/Sequence.luau +144 -118
- package/src/codec/datatype/String.luau +29 -59
- package/src/codec/datatype/UDim.luau +29 -30
- package/src/codec/datatype/Vector.luau +89 -105
- package/src/codec/meta/Auto.luau +194 -246
- package/src/codec/meta/Bitfield.luau +37 -36
- package/src/codec/meta/Custom.luau +10 -10
- package/src/codec/meta/Enum.luau +8 -11
- package/src/codec/meta/Float.luau +16 -20
- package/src/codec/meta/Nothing.luau +2 -2
- package/src/codec/meta/Unknown.luau +22 -32
- package/src/codec/primitive/Bool.luau +9 -5
- package/src/codec/primitive/Float16.luau +13 -23
- package/src/codec/primitive/Int.luau +20 -28
- package/src/codec/primitive/Number.luau +6 -10
- package/src/codec/primitive/Signed.luau +32 -0
- package/src/codec/primitive/Varint.luau +59 -28
- package/src/index.d.ts +9 -3
- package/src/init.luau +118 -143
- package/src/internal/Baseline.luau +17 -18
- package/src/internal/Channel.luau +143 -212
- package/src/internal/Middleware.luau +37 -47
- package/src/internal/Pool.luau +10 -10
- package/src/internal/Registry.luau +38 -34
- package/src/internal/Transport.luau +28 -0
- package/src/internal/Util.luau +26 -0
- package/src/transport/Bridge.luau +32 -24
- package/src/transport/Client.luau +51 -53
- package/src/transport/Gate.luau +183 -113
- package/src/transport/Reader.luau +95 -66
- package/src/transport/Server.luau +130 -125
package/README.md
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
<a href="https://github.com/Axp3cter/Lync/releases/latest">Releases</a> ·
|
|
5
5
|
<a href="#install">Install</a> ·
|
|
6
6
|
<a href="#example">Example</a> ·
|
|
7
|
+
<a href="#api">API</a> ·
|
|
7
8
|
<a href="#codecs">Codecs</a> ·
|
|
8
9
|
<a href="#benchmarks">Benchmarks</a>
|
|
9
10
|
</p>
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Packets, queries, groups, validation, and rate limiting — all batched into one buffer per player per frame. No code generation.
|
|
12
13
|
|
|
13
14
|
## Install
|
|
14
15
|
|
|
@@ -16,7 +17,7 @@ Lync batches all sends into a single buffer per player per frame, applies XOR co
|
|
|
16
17
|
|
|
17
18
|
```toml
|
|
18
19
|
[dependencies]
|
|
19
|
-
Lync = "axp3cter/lync@2.
|
|
20
|
+
Lync = "axp3cter/lync@2.2.0"
|
|
20
21
|
```
|
|
21
22
|
|
|
22
23
|
**npm (roblox-ts)**
|
|
@@ -36,41 +37,29 @@ Or grab the `.rbxm` from [Releases](https://github.com/Axp3cter/Lync/releases/la
|
|
|
36
37
|
|
|
37
38
|
## Example
|
|
38
39
|
|
|
39
|
-
**Shared**
|
|
40
|
+
**Shared** — `ReplicatedStorage.Net`
|
|
40
41
|
|
|
41
42
|
```luau
|
|
42
43
|
local Lync = require(game.ReplicatedStorage.Lync)
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}),
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
if data.damage > 200 then return false, "damage" end
|
|
62
|
-
return true
|
|
63
|
-
end,
|
|
45
|
+
return table.freeze({
|
|
46
|
+
State = Lync.packet("State", Lync.deltaStruct({
|
|
47
|
+
position = Lync.vec3,
|
|
48
|
+
health = Lync.float(0, 100, 0.5),
|
|
49
|
+
status = Lync.enum("idle", "moving", "attacking", "dead"),
|
|
50
|
+
alive = Lync.bool,
|
|
51
|
+
})),
|
|
52
|
+
|
|
53
|
+
Hit = Lync.packet("Hit", Lync.struct({
|
|
54
|
+
targetId = Lync.int(0, 65535),
|
|
55
|
+
damage = Lync.float(0, 200, 0.1),
|
|
56
|
+
}), {
|
|
57
|
+
rateLimit = { maxPerSecond = 30, burst = 5 },
|
|
58
|
+
validate = function(data) return data.damage <= 200, "damage" end,
|
|
59
|
+
}),
|
|
60
|
+
|
|
61
|
+
Ping = Lync.query("Ping", Lync.nothing, Lync.f64, { timeout = 3 }),
|
|
64
62
|
})
|
|
65
|
-
|
|
66
|
-
Net.Chat = Lync.packet("Chat", Lync.struct({
|
|
67
|
-
msg = Lync.string(200),
|
|
68
|
-
channel = Lync.int(0, 255),
|
|
69
|
-
}))
|
|
70
|
-
|
|
71
|
-
Net.Ping = Lync.query("Ping", Lync.nothing, Lync.f64, { timeout = 3 })
|
|
72
|
-
|
|
73
|
-
return table.freeze(Net)
|
|
74
63
|
```
|
|
75
64
|
|
|
76
65
|
**Server**
|
|
@@ -81,33 +70,16 @@ local Net = require(game.ReplicatedStorage.Net)
|
|
|
81
70
|
local Players = game:GetService("Players")
|
|
82
71
|
|
|
83
72
|
local alive = Lync.group("alive")
|
|
73
|
+
Players.PlayerAdded:Connect(function(p) alive:add(p) end)
|
|
84
74
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
end)
|
|
75
|
+
Net.Hit:on(function(data, sender) -- ... end)
|
|
76
|
+
Net.Ping:handle(function() return os.clock() end)
|
|
88
77
|
|
|
89
78
|
Lync.start()
|
|
90
79
|
|
|
91
|
-
Players.PlayerAdded:Connect(function(player) alive:add(player) end)
|
|
92
|
-
|
|
93
80
|
game:GetService("RunService").Heartbeat:Connect(function()
|
|
94
|
-
Net.State:send(
|
|
95
|
-
position = Vector3.new(0, 5, 0),
|
|
96
|
-
health = 100,
|
|
97
|
-
shield = 50,
|
|
98
|
-
status = "idle",
|
|
99
|
-
alive = true,
|
|
100
|
-
}, alive)
|
|
81
|
+
Net.State:send(getState(), alive)
|
|
101
82
|
end)
|
|
102
|
-
|
|
103
|
-
Net.Hit:on(function(data, player)
|
|
104
|
-
local target = Players:GetPlayerByUserId(data.targetId)
|
|
105
|
-
if not target then return end
|
|
106
|
-
alive:remove(target)
|
|
107
|
-
Net.Chat:send({ msg = player.Name .. " eliminated " .. target.Name, channel = 0 }, Lync.all)
|
|
108
|
-
end)
|
|
109
|
-
|
|
110
|
-
Net.Ping:handle(function(_, player) return os.clock() end)
|
|
111
83
|
```
|
|
112
84
|
|
|
113
85
|
**Client**
|
|
@@ -119,36 +91,38 @@ local Net = require(game.ReplicatedStorage.Net)
|
|
|
119
91
|
Lync.start()
|
|
120
92
|
|
|
121
93
|
local scope = Lync.scope()
|
|
94
|
+
scope:on(Net.State, function(state) -- ... end)
|
|
122
95
|
|
|
123
|
-
|
|
124
|
-
local character = game.Players.LocalPlayer.Character
|
|
125
|
-
if not character then return end
|
|
126
|
-
character:PivotTo(CFrame.new(state.position))
|
|
127
|
-
end)
|
|
128
|
-
|
|
129
|
-
scope:on(Net.Chat, function(data) print("[chat]", data.msg) end)
|
|
130
|
-
|
|
131
|
-
Net.Hit:send({ targetId = 123, damage = 45.5, headshot = true })
|
|
132
|
-
|
|
96
|
+
Net.Hit:send({ targetId = 123, damage = 45 })
|
|
133
97
|
local serverTime = Net.Ping:request(nil)
|
|
134
|
-
if serverTime then print("server clock:", serverTime) end
|
|
135
98
|
```
|
|
136
99
|
|
|
137
|
-
##
|
|
100
|
+
## API
|
|
138
101
|
|
|
139
|
-
|
|
102
|
+
### Lifecycle
|
|
103
|
+
|
|
104
|
+
| Function | Description |
|
|
105
|
+
|:---|:---|
|
|
106
|
+
| `Lync.configure(opts)` | Set options. Must precede `start()`. |
|
|
107
|
+
| `Lync.start()` | Initialize transport. Call once. |
|
|
108
|
+
| `Lync.isStarted()` | `true` after `start()`. |
|
|
109
|
+
| `Lync.flush()` | Force an immediate send. |
|
|
110
|
+
| `Lync.flushRate(hz)` | 1–60. Default 60. |
|
|
111
|
+
|
|
112
|
+
### Configure options
|
|
140
113
|
|
|
141
|
-
|
|
114
|
+
| Option | Default | Range | Description |
|
|
115
|
+
|:---|---:|:---|:---|
|
|
116
|
+
| `channelMaxSize` | 262144 | 4 KB – 1 MB | Max buffer bytes per frame. |
|
|
117
|
+
| `validationDepth` | 16 | 4–32 | Max recursion depth for input validation. |
|
|
118
|
+
| `poolSize` | 16 | 2–128 | Reusable channel-state pool. |
|
|
119
|
+
| `bandwidthLimit` | none | — | `{ softLimit, maxStrikes }` per-player throttle. |
|
|
120
|
+
| `globalRateLimit` | none | — | `{ maxPerSecond }` across all packets per player. |
|
|
121
|
+
| `stats` | `false` | — | Enables `:stats()` and `Lync.stats.player()`. |
|
|
142
122
|
|
|
143
|
-
|
|
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. |
|
|
123
|
+
### Packets
|
|
150
124
|
|
|
151
|
-
|
|
125
|
+
`Lync.packet(name, codec, options?)`
|
|
152
126
|
|
|
153
127
|
```luau
|
|
154
128
|
-- Server
|
|
@@ -160,365 +134,283 @@ packet:send(data, group)
|
|
|
160
134
|
|
|
161
135
|
-- Client
|
|
162
136
|
packet:send(data)
|
|
163
|
-
```
|
|
164
137
|
|
|
165
|
-
|
|
138
|
+
-- Both
|
|
139
|
+
packet:on(function(data, sender, timestamp?) end) -- returns Connection
|
|
140
|
+
packet:once(fn)
|
|
141
|
+
packet:wait() -- yields, returns data, sender, timestamp?
|
|
142
|
+
packet:name()
|
|
143
|
+
packet:stats() -- requires stats=true
|
|
144
|
+
```
|
|
166
145
|
|
|
167
|
-
|
|
|
168
|
-
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
| `
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
146
|
+
| Option | Type | Description |
|
|
147
|
+
|:---|:---|:---|
|
|
148
|
+
| `unreliable` | boolean | Use `UnreliableRemoteEvent`. Disallowed with delta codecs. |
|
|
149
|
+
| `rateLimit` | `RateLimitConfig` | Server-side per-player limit. |
|
|
150
|
+
| `validate` | `(data, player) → (bool, string?)` | Drop on `false`. |
|
|
151
|
+
| `maxPayloadBytes` | number | Reject oversize incoming payloads. |
|
|
152
|
+
| `timestamp` | `"frame"` / `"offset"` / `"full"` | Append 1B / 2B / 8B timestamp. Read as third arg. |
|
|
174
153
|
|
|
175
|
-
|
|
154
|
+
### Queries
|
|
176
155
|
|
|
177
156
|
`Lync.query(name, requestCodec, responseCodec, options?)`
|
|
178
157
|
|
|
179
|
-
Request-response
|
|
158
|
+
Request-response on top of two packet IDs.
|
|
180
159
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| `rateLimit` | `RateLimitConfig` | `{ maxPerSecond = 30 }` | Server-side rate limiting. |
|
|
187
|
-
| `validate` | `(data, player) → (bool, string?)` | none | Server-side validation. |
|
|
188
|
-
|
|
189
|
-
### Methods
|
|
190
|
-
|
|
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. |
|
|
160
|
+
```luau
|
|
161
|
+
-- Server
|
|
162
|
+
query:handle(function(data, player) return response end)
|
|
163
|
+
query:request(data, player) -- → response?
|
|
164
|
+
query:request(data, target) -- → { [Player]: response? }
|
|
199
165
|
|
|
200
|
-
|
|
166
|
+
-- Client
|
|
167
|
+
query:handle(function(data) return response end)
|
|
168
|
+
query:request(data) -- yields, → response? (nil on timeout)
|
|
169
|
+
```
|
|
201
170
|
|
|
202
|
-
|
|
171
|
+
| Option | Default | Description |
|
|
172
|
+
|:---|:---|:---|
|
|
173
|
+
| `timeout` | 5 | Seconds before yielding `nil`. |
|
|
174
|
+
| `rateLimit` | `{ maxPerSecond = 30 }` | Server-side. |
|
|
175
|
+
| `validate` | none | `(data, player) → (bool, string?)` |
|
|
203
176
|
|
|
204
|
-
|
|
177
|
+
### Groups
|
|
205
178
|
|
|
206
|
-
|
|
179
|
+
`Lync.group(name)` — named player set. Members auto-removed on `PlayerRemoving`. Iterable: `for player in group do`.
|
|
207
180
|
|
|
208
181
|
| Method | Returns | Description |
|
|
209
|
-
|
|
210
|
-
| `group:add(
|
|
211
|
-
| `group:
|
|
212
|
-
| `group:
|
|
213
|
-
| `group:
|
|
214
|
-
| `group:destroy()` | — | Clears members, frees name. |
|
|
182
|
+
|:---|:---|:---|
|
|
183
|
+
| `group:add(p)` / `:remove(p)` | boolean | `true` if changed. |
|
|
184
|
+
| `group:has(p)` | boolean | Membership. |
|
|
185
|
+
| `group:count()` | number | |
|
|
186
|
+
| `group:destroy()` | — | Clear and free name. |
|
|
215
187
|
|
|
216
|
-
|
|
188
|
+
### Scope
|
|
217
189
|
|
|
218
|
-
`Lync.scope()`
|
|
219
|
-
|
|
220
|
-
Batches connections for cleanup.
|
|
190
|
+
`Lync.scope()` — batches connections for cleanup.
|
|
221
191
|
|
|
222
192
|
```luau
|
|
223
193
|
local scope = Lync.scope()
|
|
224
|
-
scope:on(
|
|
225
|
-
scope:
|
|
226
|
-
scope:add(
|
|
227
|
-
scope:destroy()
|
|
194
|
+
scope:on(packet, fn)
|
|
195
|
+
scope:once(packet, fn)
|
|
196
|
+
scope:add(rbxConnection)
|
|
197
|
+
scope:destroy()
|
|
228
198
|
```
|
|
229
199
|
|
|
230
|
-
|
|
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. |
|
|
236
|
-
|
|
237
|
-
## Connection
|
|
200
|
+
### Targets
|
|
238
201
|
|
|
239
|
-
|
|
202
|
+
Server-side `:send` second arg.
|
|
240
203
|
|
|
241
|
-
|
|
|
242
|
-
|
|
243
|
-
| `
|
|
244
|
-
| `
|
|
204
|
+
| Target | Description |
|
|
205
|
+
|:---|:---|
|
|
206
|
+
| `Player` | One player. |
|
|
207
|
+
| `Lync.all` | All connected. |
|
|
208
|
+
| `Lync.except(...)` | Everyone except given players or groups. |
|
|
209
|
+
| `{ p1, p2 }` | Array of players. |
|
|
210
|
+
| `group` | All members. |
|
|
245
211
|
|
|
246
|
-
|
|
212
|
+
### Middleware
|
|
247
213
|
|
|
248
214
|
```luau
|
|
249
|
-
Lync.onSend(function(data, name, player)
|
|
250
|
-
|
|
251
|
-
end)
|
|
252
|
-
|
|
253
|
-
Lync.onReceive(function(data, name, player)
|
|
254
|
-
return data
|
|
255
|
-
end)
|
|
256
|
-
|
|
257
|
-
Lync.onDrop(function(player, reason, name, data)
|
|
258
|
-
warn(player.Name, "dropped", name, reason)
|
|
259
|
-
end)
|
|
215
|
+
Lync.onSend(function(data, name, player) return data end) -- return Lync.DROP to discard
|
|
216
|
+
Lync.onReceive(function(data, name, player) return data end)
|
|
217
|
+
Lync.onDrop(function(player, reason, name, data) end)
|
|
260
218
|
```
|
|
261
219
|
|
|
262
|
-
All
|
|
263
|
-
|
|
264
|
-
## Targets
|
|
220
|
+
All return a `Connection`.
|
|
265
221
|
|
|
266
|
-
|
|
222
|
+
### Connection
|
|
267
223
|
|
|
268
|
-
|
|
|
269
|
-
|
|
270
|
-
| `
|
|
271
|
-
| `
|
|
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
|
|
224
|
+
| | |
|
|
225
|
+
|:---|:---|
|
|
226
|
+
| `c.connected` | boolean |
|
|
227
|
+
| `c:disconnect()` | Idempotent. |
|
|
277
228
|
|
|
278
|
-
###
|
|
229
|
+
### Stats
|
|
279
230
|
|
|
280
|
-
`Lync.
|
|
231
|
+
`Lync.configure({ stats = true })`.
|
|
281
232
|
|
|
282
|
-
|
|
|
283
|
-
|
|
284
|
-
| `Lync.
|
|
285
|
-
| `Lync.
|
|
286
|
-
| `
|
|
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. |
|
|
233
|
+
| Function | Description |
|
|
234
|
+
|:---|:---|
|
|
235
|
+
| `Lync.stats.player(p)` | `{ bytesSent, bytesReceived }`. Server only. |
|
|
236
|
+
| `Lync.stats.reset()` | Zero all counters. |
|
|
237
|
+
| `packet:stats()` | `{ bytesSent, bytesReceived, fires, recvFires, drops }` |
|
|
295
238
|
|
|
296
|
-
###
|
|
239
|
+
### Debug
|
|
297
240
|
|
|
298
|
-
|
|
|
299
|
-
|
|
300
|
-
| `Lync.
|
|
301
|
-
| `Lync.
|
|
302
|
-
| `Lync.buff` | Variable-length buffer. |
|
|
241
|
+
| Function | Description |
|
|
242
|
+
|:---|:---|
|
|
243
|
+
| `Lync.debug.pending()` | In-flight query requests. |
|
|
244
|
+
| `Lync.debug.registrations()` | Frozen array of `{ name, id, kind, isUnreliable }`. |
|
|
303
245
|
|
|
304
|
-
|
|
246
|
+
## Codecs
|
|
305
247
|
|
|
306
|
-
|
|
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. |
|
|
248
|
+
### Numbers
|
|
334
249
|
|
|
335
|
-
|
|
250
|
+
| Codec | Bytes | Notes |
|
|
251
|
+
|:---|---:|:---|
|
|
252
|
+
| `int(min, max)` | 1 / 2 / 4 | Picks smallest u8/u16/u32/i8/i16/i32. |
|
|
253
|
+
| `f16` / `f32` / `f64` | 2 / 4 / 8 | f16: ±65504, ~3 digits. |
|
|
254
|
+
| `float(min, max, precision)` | 1–4 | Quantized. Clamped. |
|
|
255
|
+
| `bool` | 1 | Auto-bitpacked inside `struct` and `array`. |
|
|
336
256
|
|
|
337
|
-
|
|
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. |
|
|
257
|
+
### Strings & buffers
|
|
345
258
|
|
|
346
|
-
|
|
259
|
+
| Codec | Notes |
|
|
260
|
+
|:---|:---|
|
|
261
|
+
| `string` | Variable length. Binary-safe. |
|
|
262
|
+
| `string(maxLength)` | Bounded. Rejects on read if exceeded. |
|
|
263
|
+
| `buff` | Variable-length buffer. |
|
|
347
264
|
|
|
348
|
-
|
|
265
|
+
### Roblox types
|
|
349
266
|
|
|
350
|
-
| Codec |
|
|
351
|
-
|
|
352
|
-
| `
|
|
353
|
-
| `
|
|
354
|
-
| `
|
|
267
|
+
| Codec | Bytes |
|
|
268
|
+
|:---|---:|
|
|
269
|
+
| `vec2` / `vec3` | 8 / 12 |
|
|
270
|
+
| `cframe` | 24 |
|
|
271
|
+
| `color3` | 3 |
|
|
272
|
+
| `inst` | 2 |
|
|
273
|
+
| `udim` / `udim2` | 8 / 16 |
|
|
274
|
+
| `numberRange` | 8 |
|
|
275
|
+
| `rect` | 16 |
|
|
276
|
+
| `ray` | 24 |
|
|
277
|
+
| `vec2int16` / `vec3int16` | 4 / 6 |
|
|
278
|
+
| `region3` / `region3int16` | 24 / 12 |
|
|
279
|
+
| `numberSequence` / `colorSequence` | variable |
|
|
355
280
|
|
|
356
|
-
###
|
|
281
|
+
### Quantized variants
|
|
357
282
|
|
|
358
|
-
|
|
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. |
|
|
283
|
+
Call as a function for compression.
|
|
366
284
|
|
|
367
|
-
|
|
285
|
+
| Codec | Bytes | Notes |
|
|
286
|
+
|:---|---:|:---|
|
|
287
|
+
| `vec2(min, max, precision)` | 2–8 | Per-component. |
|
|
288
|
+
| `vec3(min, max, precision)` | 3–12 | Per-component. |
|
|
289
|
+
| `cframe()` | 16 | Smallest-three quaternion. ≤0.16° rotation error. |
|
|
368
290
|
|
|
369
|
-
|
|
291
|
+
### Composites
|
|
370
292
|
|
|
371
|
-
|
|
293
|
+
| Codec | Notes |
|
|
294
|
+
|:---|:---|
|
|
295
|
+
| `struct({k = c})` | Named fields. Bools auto-bitpacked. |
|
|
296
|
+
| `array(c, max?)` | List. Bool arrays bitpacked. |
|
|
297
|
+
| `map(k, v, max?)` | Key-value pairs. |
|
|
298
|
+
| `optional(c)` | 1B nil flag + value. |
|
|
299
|
+
| `tuple(...)` | Positional. |
|
|
300
|
+
| `tagged(field, {name = c})` | Discriminated union. 1B tag. ≤256 variants. |
|
|
372
301
|
|
|
373
|
-
|
|
302
|
+
### Delta — reliable only
|
|
374
303
|
|
|
375
|
-
|
|
304
|
+
Sends 1 byte when unchanged.
|
|
376
305
|
|
|
377
|
-
|
|
306
|
+
| Codec |
|
|
307
|
+
|:---|
|
|
308
|
+
| `deltaStruct(schema)` |
|
|
309
|
+
| `deltaArray(c, max?)` |
|
|
310
|
+
| `deltaMap(k, v, max?)` |
|
|
378
311
|
|
|
379
|
-
|
|
312
|
+
### Meta
|
|
380
313
|
|
|
381
|
-
|
|
|
382
|
-
|
|
383
|
-
| `
|
|
384
|
-
| `
|
|
385
|
-
| `
|
|
386
|
-
| `
|
|
387
|
-
| `
|
|
388
|
-
| `
|
|
314
|
+
| Codec | Notes |
|
|
315
|
+
|:---|:---|
|
|
316
|
+
| `enum(...)` | String enum. ≤256 variants. 1B. |
|
|
317
|
+
| `bitfield(schema)` | 1–32 bits. Sub-byte packing. |
|
|
318
|
+
| `custom(size, write, read, typeCheck?)` | User-defined fixed-size. |
|
|
319
|
+
| `nothing` | 0 bytes. Reads `nil`. |
|
|
320
|
+
| `unknown` | Bypass serialization. Use with `validate`. |
|
|
321
|
+
| `auto` | Self-describing. nil/bool/numbers/strings/buffers/Roblox types. |
|
|
389
322
|
|
|
390
|
-
|
|
323
|
+
## Rate limiting
|
|
391
324
|
|
|
392
|
-
|
|
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. |
|
|
325
|
+
Per-packet, pick one mode:
|
|
399
326
|
|
|
400
|
-
|
|
327
|
+
```luau
|
|
328
|
+
{ maxPerSecond = N, burst = M } -- token bucket
|
|
329
|
+
{ cooldown = seconds } -- cooldown
|
|
330
|
+
```
|
|
401
331
|
|
|
402
|
-
|
|
332
|
+
Global per-player: `Lync.configure({ globalRateLimit = { maxPerSecond = N } })`.
|
|
403
333
|
|
|
404
|
-
|
|
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. |
|
|
334
|
+
## Limits
|
|
409
335
|
|
|
410
|
-
|
|
336
|
+
| | |
|
|
337
|
+
|:---|---:|
|
|
338
|
+
| Packet + query IDs | 127 |
|
|
339
|
+
| Buffer per frame | 1 MB max |
|
|
340
|
+
| In-flight queries | 65,536 |
|
|
341
|
+
| Enum / tagged variants | 256 |
|
|
342
|
+
| Bitfield total bits | 32 |
|
|
411
343
|
|
|
412
|
-
|
|
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. |
|
|
344
|
+
## Benchmarks
|
|
416
345
|
|
|
417
|
-
|
|
346
|
+
`rojo serve bench.project.json` with one server + one client.
|
|
418
347
|
|
|
419
|
-
|
|
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 |
|
|
348
|
+
CPU benches run a fixed 1000 iterations per case.
|
|
427
349
|
|
|
428
|
-
|
|
350
|
+
### Codec throughput
|
|
429
351
|
|
|
430
|
-
|
|
352
|
+
| Codec | Encode | Decode | RT/s |
|
|
353
|
+
|:---|---:|---:|---:|
|
|
354
|
+
| `bool` | 43 ns | 28 ns | 14.1 M |
|
|
355
|
+
| `int(0, 255)` | 41 ns | 25 ns | 15.2 M |
|
|
356
|
+
| `int(0, 65535)` | 40 ns | 25 ns | 15.3 M |
|
|
357
|
+
| `f16` | 60 ns | 42 ns | 9.7 M |
|
|
358
|
+
| `f32` | 41 ns | 26 ns | 14.9 M |
|
|
359
|
+
| `f64` | 41 ns | 25 ns | 15.3 M |
|
|
360
|
+
| `string` (10 chars) | 45 ns | 73 ns | 8.4 M |
|
|
361
|
+
| `string` (1000 chars) | 74 ns | 250 ns | 3.1 M |
|
|
362
|
+
| `vec3` | 56 ns | 27 ns | 12.1 M |
|
|
363
|
+
| `vec3` quantized | 121 ns | 85 ns | 4.9 M |
|
|
364
|
+
| `cframe` | 88 ns | 186 ns | 3.6 M |
|
|
365
|
+
| `cframe()` | 118 ns | 214 ns | 3.0 M |
|
|
366
|
+
| entity struct (6 fields) | 234 ns | 476 ns | 1.4 M |
|
|
367
|
+
| 100× entities | 15.3 µs | 34.6 µs | 20 K |
|
|
368
|
+
| 1000× bools (bitpacked) | 4.3 µs | 5.3 µs | 104 K |
|
|
431
369
|
|
|
432
|
-
### Wire
|
|
370
|
+
### Wire sizes
|
|
433
371
|
|
|
434
372
|
| Codec | Bytes |
|
|
435
|
-
|
|
436
|
-
|
|
|
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 |
|
|
373
|
+
|:---|---:|
|
|
374
|
+
| entity struct (6 fields, lossless) | 34 |
|
|
450
375
|
| entity compact (quantized) | 13 |
|
|
451
|
-
| bitfield | 2 |
|
|
452
376
|
| 100× entities | 601 |
|
|
453
377
|
| 1000× bools (bitpacked) | 127 |
|
|
378
|
+
| bitfield flags | 2 |
|
|
379
|
+
| `tuple(u8, vec3, bool)` | 14 |
|
|
454
380
|
|
|
455
|
-
###
|
|
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 |
|
|
381
|
+
### Delta savings
|
|
496
382
|
|
|
497
|
-
|
|
383
|
+
| Codec | Full | Unchanged |
|
|
384
|
+
|:---|---:|---:|
|
|
385
|
+
| `deltaStruct` (entity) | 35 B | 1 B |
|
|
386
|
+
| `deltaStruct` (compact) | 14 B | 1 B |
|
|
387
|
+
| `deltaArray` (100× entity) | 602 B | 1 B |
|
|
388
|
+
| `deltaArray` (1000× bool) | 128 B | 1 B |
|
|
389
|
+
| `deltaMap` (string → u8) | 19 B | 1 B |
|
|
498
390
|
|
|
499
|
-
|
|
391
|
+
### Cross-library comparison
|
|
500
392
|
|
|
501
|
-
|
|
393
|
+
Same methodology as [Blink](https://github.com/1Axen/blink/blob/main/benchmark/Benchmarks.md): 1000 fires/frame, identical data, 10 s. Other-tool numbers from Blink v0.17.1.
|
|
502
394
|
|
|
503
395
|
> [!NOTE]
|
|
504
|
-
> Lync batches all sends into one buffer per frame
|
|
396
|
+
> Lync batches all sends into one buffer per frame and bitpacks bools (1000 = 127 B vs ~1002 B). Delta compression isn't exercised here.
|
|
505
397
|
|
|
506
|
-
|
|
398
|
+
**100× struct(6× u8) entities**
|
|
507
399
|
|
|
508
400
|
| Tool | FPS | Kbps |
|
|
509
|
-
|
|
401
|
+
|:---|---:|---:|
|
|
510
402
|
| roblox | 16 | 559,364 |
|
|
511
|
-
| **lync** | **60** | **3.
|
|
403
|
+
| **lync** | **60** | **3.47** |
|
|
512
404
|
| blink | 42 | 41.81 |
|
|
513
405
|
| zap | 39 | 41.71 |
|
|
514
406
|
| bytenet | 32 | 41.64 |
|
|
515
407
|
|
|
516
|
-
|
|
408
|
+
**1000× bool**
|
|
517
409
|
|
|
518
410
|
| Tool | FPS | Kbps |
|
|
519
|
-
|
|
411
|
+
|:---|---:|---:|
|
|
520
412
|
| roblox | 21 | 353,107 |
|
|
521
|
-
| **lync** | **60** | **2.
|
|
413
|
+
| **lync** | **60** | **2.33** |
|
|
522
414
|
| blink | 97 | 7.91 |
|
|
523
415
|
| zap | 52 | 8.10 |
|
|
524
416
|
| bytenet | 35 | 8.11 |
|