@axpecter/lync 1.3.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.
Files changed (52) hide show
  1. package/README.md +300 -0
  2. package/package.json +38 -0
  3. package/src/Types.luau +63 -0
  4. package/src/api/Group.luau +126 -0
  5. package/src/api/Namespace.luau +226 -0
  6. package/src/api/Packet.luau +147 -0
  7. package/src/api/Query.luau +295 -0
  8. package/src/api/Signal.luau +224 -0
  9. package/src/codec/Base.luau +49 -0
  10. package/src/codec/composite/Array.luau +275 -0
  11. package/src/codec/composite/Map.luau +395 -0
  12. package/src/codec/composite/Optional.luau +47 -0
  13. package/src/codec/composite/Shared.luau +151 -0
  14. package/src/codec/composite/Struct.luau +440 -0
  15. package/src/codec/composite/Tagged.luau +222 -0
  16. package/src/codec/composite/Tuple.luau +143 -0
  17. package/src/codec/datatype/Buffer.luau +44 -0
  18. package/src/codec/datatype/CFrame.luau +51 -0
  19. package/src/codec/datatype/Color.luau +22 -0
  20. package/src/codec/datatype/Instance.luau +48 -0
  21. package/src/codec/datatype/IntVector.luau +25 -0
  22. package/src/codec/datatype/NumberRange.luau +14 -0
  23. package/src/codec/datatype/Ray.luau +27 -0
  24. package/src/codec/datatype/Rect.luau +21 -0
  25. package/src/codec/datatype/Region.luau +58 -0
  26. package/src/codec/datatype/Sequence.luau +129 -0
  27. package/src/codec/datatype/String.luau +87 -0
  28. package/src/codec/datatype/UDim.luau +27 -0
  29. package/src/codec/datatype/Vector.luau +25 -0
  30. package/src/codec/meta/Auto.luau +353 -0
  31. package/src/codec/meta/Bitfield.luau +191 -0
  32. package/src/codec/meta/Custom.luau +27 -0
  33. package/src/codec/meta/Enum.luau +80 -0
  34. package/src/codec/meta/Nothing.luau +9 -0
  35. package/src/codec/meta/Quantized.luau +170 -0
  36. package/src/codec/meta/Unknown.luau +35 -0
  37. package/src/codec/primitive/Bool.luau +30 -0
  38. package/src/codec/primitive/Float16.luau +111 -0
  39. package/src/codec/primitive/Number.luau +48 -0
  40. package/src/codec/primitive/Varint.luau +76 -0
  41. package/src/index.d.ts +279 -0
  42. package/src/init.luau +161 -0
  43. package/src/internal/Baseline.luau +41 -0
  44. package/src/internal/Channel.luau +235 -0
  45. package/src/internal/Middleware.luau +109 -0
  46. package/src/internal/Pool.luau +68 -0
  47. package/src/internal/Registry.luau +146 -0
  48. package/src/transport/Bridge.luau +66 -0
  49. package/src/transport/Client.luau +151 -0
  50. package/src/transport/Gate.luau +222 -0
  51. package/src/transport/Reader.luau +175 -0
  52. package/src/transport/Server.luau +364 -0
package/README.md ADDED
@@ -0,0 +1,300 @@
1
+ <h1 align="center">Lync</h1>
2
+ <p align="center">Buffer networking for Roblox. Delta compression, XOR framing, built-in security.</p>
3
+ <p align="center">
4
+ <a href="https://github.com/Axp3cter/Lync/releases/latest">Releases</a> ·
5
+ <a href="#benchmarks">Benchmarks</a> ·
6
+ <a href="#limits--configuration">Limits</a>
7
+ </p>
8
+
9
+ ## Install
10
+
11
+ **Wally (Luau)**
12
+
13
+ ```toml
14
+ [dependencies]
15
+ Lync = "axp3cter/lync@1.3.0"
16
+ ```
17
+
18
+ **npm (roblox-ts)**
19
+
20
+ ```bash
21
+ npm install @rbxts/lync
22
+ ```
23
+
24
+ ```typescript
25
+ import Lync from "@rbxts/lync";
26
+ ```
27
+
28
+ Or grab the `.rbxm` from [releases](https://github.com/Axp3cter/Lync/releases/latest) and drop it in `ReplicatedStorage`.
29
+
30
+ > [!IMPORTANT]
31
+ > Define everything before calling `Lync.start()`. Packets, queries, namespaces, all of it.
32
+
33
+ ## Lifecycle
34
+
35
+ | | What it does |
36
+ |:---------|:------------|
37
+ | `Lync.start()` | Sets up transport. Server creates remotes, client connects. Call once after all definitions. |
38
+ | `Lync.version` | `"1.3.0"` |
39
+ | `Lync.VERSION` | `"1.3.0"` |
40
+
41
+ ## Packets
42
+
43
+ `Lync.definePacket(name, config)` returns a Packet.
44
+
45
+ | Config | Type | Required | What it does |
46
+ |:-------|:-----|:--------:|:-------------|
47
+ | `value` | Codec | Yes | How to serialize the payload. |
48
+ | `unreliable` | boolean | No | Sends over UnreliableRemoteEvent. Default `false`. Cant use with delta codecs. |
49
+ | `rateLimit` | `{ maxPerSecond, burstAllowance? }` | No | Server-side token bucket. Burst defaults to maxPerSecond if you dont set it. |
50
+ | `validate` | `(data, player) → (bool, string?)` | No | Server-side. Return `false, "reason"` to drop. Runs after NaN scan. |
51
+ | `maxPayloadBytes` | number | No | Server-side. Max bytes a single batch of this packet can consume. Fires `onDrop` with reason `"size"` if exceeded. |
52
+
53
+ **Server methods:**
54
+
55
+ | Method | What it does |
56
+ |:-------|:------------|
57
+ | `packet:sendTo(data, player)` | Send to one player. |
58
+ | `packet:sendToAll(data)` | Send to everyone. |
59
+ | `packet:sendToAllExcept(data, except)` | Send to everyone except one. |
60
+ | `packet:sendToList(data, players)` | Send to a list. |
61
+ | `packet:sendToGroup(data, groupName)` | Send to a named group. |
62
+
63
+ **Client methods:**
64
+
65
+ | Method | What it does |
66
+ |:-------|:------------|
67
+ | `packet:send(data)` | Send to server. |
68
+
69
+ **Shared methods:**
70
+
71
+ | Method | What it does |
72
+ |:-------|:------------|
73
+ | `packet:listen(fn(data, sender))` | Listen for incoming. Returns a Connection. Sender is `Player` on server, `nil` on client. |
74
+ | `packet:once(fn(data, sender))` | Same as listen but auto-disconnects after one fire. |
75
+ | `packet:wait()` | Yields until next fire. Returns `(data, sender)`. |
76
+ | `packet:disconnectAll()` | Kills all listeners on this packet. |
77
+
78
+ ## Queries
79
+
80
+ `Lync.defineQuery(name, config)` returns a Query. Basically RemoteFunctions but built on RemoteEvents. Returns `nil` if the other side times out or errors.
81
+
82
+ | Config | Type | Required | What it does |
83
+ |:-------|:-----|:--------:|:-------------|
84
+ | `request` | Codec | Yes | How to serialize the request. |
85
+ | `response` | Codec | Yes | How to serialize the response. |
86
+ | `timeout` | number | No | Seconds before giving up. Default `5`. |
87
+ | `rateLimit` | `{ maxPerSecond, burstAllowance? }` | No | Server-side token bucket on incoming requests. |
88
+ | `validate` | `(data, player) → (bool, string?)` | No | Server-side validation on incoming requests. |
89
+
90
+ | Method | Where | What it does |
91
+ |:-------|:------|:-------------|
92
+ | `query:listen(fn)` | Both | Register a handler. Server gets `fn(request, player) → response`. Client gets `fn(request) → response`. |
93
+ | `query:invoke(request)` | Client | Send request to server, yield until response comes back or timeout. |
94
+ | `query:invoke(request, player)` | Server | Send request to a specific client, yield until response or timeout. |
95
+ | `query:invokeAll(request)` | Server | Send request to all players, yield until all respond or timeout. Returns `{ [Player]: response? }`. |
96
+ | `query:invokeList(request, players)` | Server | Send request to a list of players, yield until all respond or timeout. Returns `{ [Player]: response? }`. |
97
+ | `query:invokeGroup(request, groupName)` | Server | Send request to all players in a named group. Returns `{ [Player]: response? }`. |
98
+
99
+ ## Namespaces
100
+
101
+ `Lync.defineNamespace(name, config)` returns a Namespace. Takes a `packets` table and/or a `queries` table. All names get auto-prefixed with `"YourNamespace."` so nothing collides.
102
+
103
+ Access packets and queries by their short name on the returned object: `ns.PacketName`, `ns.QueryName`.
104
+
105
+ | Method | What it does |
106
+ |:-------|:------------|
107
+ | `ns:listenAll(fn(name, data, sender))` | Listens to every packet in the namespace. `name` is the short name without prefix. Returns a Connection. |
108
+ | `ns:onSend(fn(data, name, player) → data?)` | Send middleware that only runs for this namespace. Returns a remover. |
109
+ | `ns:onReceive(fn(data, name, player) → data?)` | Receive middleware that only runs for this namespace. Returns a remover. |
110
+ | `ns:disconnectAll()` | Kills all listeners made through `listenAll`. |
111
+ | `ns:destroy()` | Kills listeners and removes scoped middleware. Full cleanup. |
112
+ | `ns:packetNames()` | Sorted list of packet short names. |
113
+ | `ns:queryNames()` | Sorted list of query short names. |
114
+
115
+ ## Connection
116
+
117
+ Returned by `packet:listen()`, `packet:once()`, `query:listen()`, and `ns:listenAll()`.
118
+
119
+ | | What it does |
120
+ |:-------|:------------|
121
+ | `connection.connected` | `true` if still connected, `false` after disconnect. |
122
+ | `connection:disconnect()` | Stops the listener. |
123
+
124
+ ## Types
125
+
126
+ ### Primitives
127
+
128
+ | Type | Bytes | Range |
129
+ |:-----|------:|:------|
130
+ | `Lync.u8` | 1 | 0 to 255 |
131
+ | `Lync.u16` | 2 | 0 to 65,535 |
132
+ | `Lync.u32` | 4 | 0 to 4,294,967,295 |
133
+ | `Lync.i8` | 1 | -128 to 127 |
134
+ | `Lync.i16` | 2 | -32,768 to 32,767 |
135
+ | `Lync.i32` | 4 | -2,147,483,648 to 2,147,483,647 |
136
+ | `Lync.f16` | 2 | ±65,504, roughly 3 digits of precision |
137
+ | `Lync.f32` | 4 | IEEE 754 single |
138
+ | `Lync.f64` | 8 | IEEE 754 double |
139
+ | `Lync.bool` | 1 | true/false. Gets packed into bitfields when inside structs. |
140
+
141
+ ### Complex
142
+
143
+ | Type | Bytes | What it is |
144
+ |:-----|------:|:-----------|
145
+ | `Lync.string` | varint + N | Varint length prefix then raw bytes. |
146
+ | `Lync.vec2` | 8 | 2x f32. |
147
+ | `Lync.vec3` | 12 | 3x f32. |
148
+ | `Lync.cframe` | 24 | Position as 3x f32, rotation as axis-angle 3x f32. |
149
+ | `Lync.color3` | 3 | RGB 0-255 per channel, clamped. |
150
+ | `Lync.inst` | 2 | Instance ref through sidecar array. Requires refs on read, throws without them. |
151
+ | `Lync.buff` | varint + N | Varint length prefix then raw bytes. |
152
+ | `Lync.udim` | 8 | Scale f32 + Offset i32. |
153
+ | `Lync.udim2` | 16 | 2x UDim (X then Y). |
154
+ | `Lync.numberRange` | 8 | Min f32 + Max f32. |
155
+ | `Lync.rect` | 16 | Min.X f32 + Min.Y f32 + Max.X f32 + Max.Y f32. |
156
+ | `Lync.vec2int16` | 4 | 2x i16. |
157
+ | `Lync.vec3int16` | 6 | 3x i16. |
158
+ | `Lync.region3` | 24 | Min Vec3 + Max Vec3 as 6x f32. |
159
+ | `Lync.region3int16` | 12 | Min Vec3int16 + Max Vec3int16 as 6x i16. |
160
+ | `Lync.ray` | 24 | Origin Vec3 + Direction Vec3 as 6x f32. |
161
+ | `Lync.numberSequence` | varint + N×12 | Varint count then (time f32 + value f32 + envelope f32) per keypoint. |
162
+ | `Lync.colorSequence` | varint + N×7 | Varint count then (time f32 + R u8 + G u8 + B u8) per keypoint. |
163
+
164
+ ### Composites
165
+
166
+ | Constructor | What it does |
167
+ |:------------|:------------|
168
+ | `Lync.struct({ key = codec })` | Named fields. Bools get packed into bitfields automatically. |
169
+ | `Lync.array(codec, maxCount?)` | Variable length list with varint count. Optional `maxCount` rejects on read if exceeded. |
170
+ | `Lync.map(keyCodec, valueCodec, maxCount?)` | Key-value pairs with varint count. Optional `maxCount` rejects on read if exceeded. |
171
+ | `Lync.optional(codec)` | 1 byte flag, value only if present. |
172
+ | `Lync.tuple(codec, codec, ...)` | Ordered positional values, no keys. |
173
+ | `Lync.boundedString(maxLength)` | Same wire format as `Lync.string` but rejects on read if length exceeds `maxLength`. |
174
+
175
+ ### Delta
176
+
177
+ Reliable only. Lync will error if you try to use these with `unreliable = true`.
178
+
179
+ | Constructor | What it does |
180
+ |:------------|:------------|
181
+ | `Lync.deltaStruct({ key = codec })` | First frame sends everything. After that only dirty fields get sent via bitmask. If nothing changed it costs 1 byte. |
182
+ | `Lync.deltaArray(codec, maxCount?)` | Same idea but for arrays. Dirty elements get sent with varint indices. Optional `maxCount` rejects on read if exceeded. |
183
+ | `Lync.deltaMap(keyCodec, valueCodec, maxCount?)` | Delta compression for key-value maps. Sends only upserted and removed entries after the first frame. Optional `maxCount` rejects on read if exceeded. |
184
+
185
+ ### Specialized
186
+
187
+ | Constructor | What it does |
188
+ |:------------|:------------|
189
+ | `Lync.enum(value, value, ...)` | u8 index, up to 256 variants. |
190
+ | `Lync.quantizedFloat(min, max, precision)` | Fixed-point compression. Picks u8/u16/u32 based on your range and precision. |
191
+ | `Lync.quantizedVec3(min, max, precision)` | Same thing but for all 3 components. |
192
+ | `Lync.bitfield({ key = spec })` | Sub-byte packing, 1 to 32 bits total. Spec is `{ type = "bool" }` or `{ type = "uint", width = N }` or `{ type = "int", width = N }`. |
193
+ | `Lync.tagged(tagField, { name = codec })` | Discriminated union with a u8 variant tag. Puts `tagField` into the decoded table so you know which variant it is. |
194
+ | `Lync.custom(size, write, read)` | User-defined fixed-size codec. `write` is `(b, offset, value) → ()`, `read` is `(b, offset) → value`. Plugs into struct/array/delta specialization automatically. |
195
+ | `Lync.nothing` | Zero bytes. Reads nil. Good for fire-and-forget signals. |
196
+ | `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. |
197
+ | `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. |
198
+
199
+ ## Groups
200
+
201
+ Named player sets. Members get removed automatically on `PlayerRemoving`.
202
+
203
+ | Function | Returns | What it does |
204
+ |:---------|:--------|:-------------|
205
+ | `Lync.createGroup(name)` | | Makes a new group. Errors if it already exists. |
206
+ | `Lync.destroyGroup(name)` | | Removes the group and all memberships. |
207
+ | `Lync.addToGroup(name, player)` | `boolean` | `true` if added, `false` if already in. |
208
+ | `Lync.removeFromGroup(name, player)` | `boolean` | `true` if removed, `false` if wasnt in there. |
209
+ | `Lync.hasInGroup(name, player)` | `boolean` | |
210
+ | `Lync.groupCount(name)` | `number` | |
211
+ | `Lync.getGroupSet(name)` | `{ [Player]: true }` | |
212
+ | `Lync.forEachInGroup(name, fn)` | | Calls `fn(player)` for each member. |
213
+
214
+ Send to a group with `packet:sendToGroup(data, groupName)`.
215
+
216
+ ## Middleware
217
+
218
+ Global intercept on all packets. Handlers run in the order you registered them. Return `nil` from a handler to drop the packet.
219
+
220
+ | Function | What it does |
221
+ |:---------|:------------|
222
+ | `Lync.onSend(fn(data, name, player) → data?)` | Runs before a packet goes out. Returns a remover function. |
223
+ | `Lync.onReceive(fn(data, name, player) → data?)` | Runs when a packet comes in. Returns a remover function. |
224
+ | `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. |
225
+
226
+ Packets that fail validation are dropped individually. Other packets in the same frame from the same player are unaffected.
227
+
228
+ ## Benchmarks
229
+
230
+ ### Lync Tests
231
+
232
+ 1,000 packets/frame, 10 seconds, one player.
233
+
234
+ | Scenario | Without Lync | With Lync | FPS |
235
+ |:---------|------------:|---------:|----:|
236
+ | Static booleans (1B) | 480 Kbps | **2.45 Kbps** | 59.98 |
237
+ | Static entities (34B) | 16,320 Kbps | **2.52 Kbps** | 60.00 |
238
+ | Moving entities | 16,320 Kbps | **3.51 Kbps** | 59.99 |
239
+ | Chaotic entities | 16,320 Kbps | **4.66 Kbps** | 59.99 |
240
+
241
+ ### Cross-Library Comparison
242
+
243
+ 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.
244
+
245
+ **Entities** (100x struct of 6x u8, fired 1000 times/frame)
246
+
247
+ | Tool (FPS) | Median | P0 | P80 | P90 | P95 | P100 |
248
+ |:-----------|-------:|---:|----:|----:|----:|-----:|
249
+ | roblox | 16.00 | 16.00 | 15.00 | 15.00 | 15.00 | 15.00 |
250
+ | **lync** | **60.00** | 61.00 | 60.00 | 60.00 | 60.00 | 59.00 |
251
+ | blink | 42.00 | 45.00 | 42.00 | 42.00 | 42.00 | 42.00 |
252
+ | zap | 39.00 | 40.00 | 38.00 | 38.00 | 38.00 | 38.00 |
253
+ | bytenet | 32.00 | 34.00 | 32.00 | 32.00 | 32.00 | 31.00 |
254
+
255
+ | Tool (Kbps) | Median | P0 | P80 | P90 | P95 | P100 |
256
+ |:------------|-------:|---:|----:|----:|----:|-----:|
257
+ | roblox | 559,364 | 559,364 | 676,715 | 676,715 | 676,715 | 784,081 |
258
+ | **lync** | **3.59** | 3.50 | 3.61 | 3.62 | 3.62 | 4.86 |
259
+ | blink | 41.81 | 26.30 | 42.40 | 42.48 | 42.48 | 42.62 |
260
+ | zap | 41.71 | 25.46 | 42.19 | 42.32 | 42.32 | 42.93 |
261
+ | bytenet | 41.64 | 22.84 | 42.36 | 42.82 | 42.82 | 43.24 |
262
+
263
+ **Booleans** (1000x bool, fired 1000 times/frame)
264
+
265
+ | Tool (FPS) | Median | P0 | P80 | P90 | P95 | P100 |
266
+ |:-----------|-------:|---:|----:|----:|----:|-----:|
267
+ | roblox | 21.00 | 22.00 | 20.00 | 19.00 | 19.00 | 19.00 |
268
+ | **lync** | **60.00** | 61.00 | 60.00 | 59.00 | 59.00 | 58.00 |
269
+ | blink | 97.00 | 98.00 | 97.00 | 96.00 | 96.00 | 96.00 |
270
+ | zap | 52.00 | 53.00 | 51.00 | 51.00 | 51.00 | 49.00 |
271
+ | bytenet | 35.00 | 37.00 | 35.00 | 35.00 | 35.00 | 34.00 |
272
+
273
+ | Tool (Kbps) | Median | P0 | P80 | P90 | P95 | P100 |
274
+ |:------------|-------:|---:|----:|----:|----:|-----:|
275
+ | roblox | 353,107 | 196,826 | 690,747 | 842,240 | 842,240 | 1,124,176 |
276
+ | **lync** | **4.31** | 3.77 | 4.33 | 4.34 | 4.34 | 4.43 |
277
+ | blink | 7.91 | 7.41 | 7.93 | 7.99 | 7.99 | 8.00 |
278
+ | zap | 8.10 | 5.75 | 8.17 | 8.22 | 8.22 | 8.27 |
279
+ | bytenet | 8.11 | 5.07 | 8.35 | 8.46 | 8.46 | 8.47 |
280
+
281
+ > [!NOTE]
282
+ > 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.
283
+
284
+ ## Limits & Configuration
285
+
286
+ Call these before `Lync.start()`.
287
+
288
+ | What | Default | How to change | Notes |
289
+ |:-----|--------:|:--------------|:------|
290
+ | Packet types | 255 | Cant change | u8 on the wire. Each query eats 2 IDs. |
291
+ | Buffer per channel per frame | 256 KB | `Lync.setChannelMaxSize(n)` | 4 KB to 1 MB. |
292
+ | Concurrent queries | 65,536 | Cant change | u16 correlation IDs. Freed on response or timeout. `Lync.queryPendingCount()` returns in-flight count. |
293
+ | NaN/inf scan depth | 16 | `Lync.setValidationDepth(n)` | 4 to 32. |
294
+ | Channel pool | 16 | `Lync.setPoolSize(n)` | 2 to 128. Extra gets GCd. |
295
+ | Namespaces | 64 | Cant change | |
296
+ | Delta + unreliable | Nope | Cant change | Errors at define time. |
297
+
298
+ ## License
299
+
300
+ MIT
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@axpecter/lync",
3
+ "version": "1.3.0",
4
+ "description": "Buffer networking for Roblox. Delta compression, XOR framing, built-in security.",
5
+ "main": "src/init.luau",
6
+ "types": "src/index.d.ts",
7
+ "module": "commonjs",
8
+ "author": "Axp3cter",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/Axp3cter/Lync.git"
13
+ },
14
+ "homepage": "https://github.com/Axp3cter/Lync",
15
+ "bugs": {
16
+ "url": "https://github.com/Axp3cter/Lync/issues"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "keywords": [
22
+ "roblox",
23
+ "networking",
24
+ "buffer",
25
+ "delta",
26
+ "compression",
27
+ "roblox-ts",
28
+ "luau"
29
+ ],
30
+ "files": [
31
+ "src"
32
+ ],
33
+ "devDependencies": {
34
+ "@rbxts/compiler-types": "^3.0.0-types.0",
35
+ "@rbxts/types": "^1.0.0",
36
+ "typescript": "^5.6.0"
37
+ }
38
+ }
package/src/Types.luau ADDED
@@ -0,0 +1,63 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Shared type definitions.
4
+
5
+ export type ChannelState = {
6
+ buff: buffer,
7
+ cursor: number,
8
+ size: number,
9
+ refs: { Instance },
10
+ lastId: number,
11
+ countPos: number,
12
+ itemCount: number,
13
+ deltas: { [number]: any },
14
+ prevDump: buffer?,
15
+ }
16
+
17
+ export type Codec<T> = {
18
+ write: (ch: ChannelState, value: T) -> (),
19
+ read: (src: buffer, pos: number, refs: { Instance }?) -> (T, number),
20
+ }
21
+
22
+ export type RateLimitConfig = {
23
+ maxPerSecond: number,
24
+ burstAllowance: number?,
25
+ }
26
+
27
+ export type PacketConfig<T> = {
28
+ value: Codec<T>,
29
+ unreliable: boolean?,
30
+ rateLimit: RateLimitConfig?,
31
+ validate: ((data: T, player: Player) -> (boolean, string?))?,
32
+ maxPayloadBytes: number?,
33
+ }
34
+
35
+ export type QueryConfig<Req, Resp> = {
36
+ request: Codec<Req>,
37
+ response: Codec<Resp>,
38
+ timeout: number?,
39
+ rateLimit: RateLimitConfig?,
40
+ validate: ((data: Req, player: Player) -> (boolean, string?))?,
41
+ }
42
+
43
+ export type Registration = {
44
+ id: number,
45
+ name: string,
46
+ baseName: string,
47
+ codec: Codec<any>,
48
+ isUnreliable: boolean,
49
+ kind: number,
50
+ partner: number?,
51
+ signal: any,
52
+ rateLimit: RateLimitConfig?,
53
+ validate: ((data: any, player: Player) -> (boolean, string?))?,
54
+ needsGate: boolean,
55
+ maxPayloadBytes: number?,
56
+ }
57
+
58
+ export type Connection = {
59
+ disconnect: (self: Connection) -> (),
60
+ connected: boolean,
61
+ }
62
+
63
+ return nil
@@ -0,0 +1,126 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Named player sets with auto-cleanup on PlayerRemoving.
4
+
5
+ local Players = game:GetService ("Players")
6
+
7
+ -- State ------------------------------------------------------------------
8
+
9
+ local _groups = {} :: { [string]: { [Player]: true } }
10
+ local _counts = {} :: { [string]: number }
11
+ local _playerGroups = {} :: { [Player]: { [string]: true } }
12
+
13
+ -- Private ----------------------------------------------------------------
14
+
15
+ local function onPlayerRemoving (player: Player): ()
16
+ local memberships = _playerGroups[player]
17
+ if not memberships then
18
+ return
19
+ end
20
+
21
+ for name in memberships do
22
+ local set = _groups[name]
23
+ if set and set[player] then
24
+ set[player] = nil
25
+ _counts[name] -= 1
26
+ end
27
+ end
28
+
29
+ _playerGroups[player] = nil
30
+ end
31
+
32
+ Players.PlayerRemoving:Connect (onPlayerRemoving)
33
+
34
+ local function getSetOrError (name: string): { [Player]: true }
35
+ local set = _groups[name]
36
+ if not set then
37
+ error (`[Lync] Group does not exist: \"{name}\"`)
38
+ end
39
+ return set
40
+ end
41
+
42
+ -- Public -----------------------------------------------------------------
43
+
44
+ local Group = {}
45
+
46
+ function Group.create (name: string): ()
47
+ if _groups[name] then
48
+ error (`[Lync] Group already exists: \"{name}\"`)
49
+ end
50
+ _groups[name] = {}
51
+ _counts[name] = 0
52
+ end
53
+
54
+ function Group.destroy (name: string): ()
55
+ local set = getSetOrError (name)
56
+
57
+ for player in set do
58
+ local memberships = _playerGroups[player]
59
+ if memberships then
60
+ memberships[name] = nil
61
+ end
62
+ end
63
+
64
+ _groups[name] = nil
65
+ _counts[name] = nil
66
+ end
67
+
68
+ function Group.add (name: string, player: Player): boolean
69
+ local set = getSetOrError (name)
70
+
71
+ if set[player] then
72
+ return false
73
+ end
74
+
75
+ set[player] = true
76
+ _counts[name] += 1
77
+
78
+ local memberships = _playerGroups[player]
79
+ if not memberships then
80
+ memberships = {}
81
+ _playerGroups[player] = memberships
82
+ end
83
+ memberships[name] = true
84
+
85
+ return true
86
+ end
87
+
88
+ function Group.remove (name: string, player: Player): boolean
89
+ local set = getSetOrError (name)
90
+
91
+ if not set[player] then
92
+ return false
93
+ end
94
+
95
+ set[player] = nil
96
+ _counts[name] -= 1
97
+
98
+ local memberships = _playerGroups[player]
99
+ if memberships then
100
+ memberships[name] = nil
101
+ end
102
+
103
+ return true
104
+ end
105
+
106
+ function Group.has (name: string, player: Player): boolean
107
+ local set = getSetOrError (name)
108
+ return set[player] == true
109
+ end
110
+
111
+ function Group.count (name: string): number
112
+ getSetOrError (name)
113
+ return _counts[name]
114
+ end
115
+
116
+ function Group.getSet (name: string): { [Player]: true }
117
+ return getSetOrError (name)
118
+ end
119
+
120
+ function Group.forEach (name: string, fn: (player: Player) -> ()): ()
121
+ for player in getSetOrError (name) do
122
+ fn (player)
123
+ end
124
+ end
125
+
126
+ return table.freeze (Group)