@axpecter/lync 1.5.2 → 2.1.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 (55) hide show
  1. package/README.md +499 -357
  2. package/package.json +2 -2
  3. package/src/Types.luau +69 -27
  4. package/src/api/Group.luau +101 -106
  5. package/src/api/Packet.luau +191 -157
  6. package/src/api/Query.luau +223 -282
  7. package/src/api/Scope.luau +46 -57
  8. package/src/api/Signal.luau +104 -137
  9. package/src/codec/Base.luau +69 -20
  10. package/src/codec/composite/Array.luau +189 -201
  11. package/src/codec/composite/Map.luau +97 -341
  12. package/src/codec/composite/Optional.luau +27 -23
  13. package/src/codec/composite/Shared.luau +96 -65
  14. package/src/codec/composite/Struct.luau +201 -339
  15. package/src/codec/composite/Tagged.luau +64 -178
  16. package/src/codec/composite/Tuple.luau +81 -95
  17. package/src/codec/datatype/Buffer.luau +49 -21
  18. package/src/codec/datatype/CFrame.luau +207 -30
  19. package/src/codec/datatype/Color.luau +21 -11
  20. package/src/codec/datatype/Instance.luau +29 -19
  21. package/src/codec/datatype/IntVector.luau +33 -21
  22. package/src/codec/datatype/NumberRange.luau +19 -10
  23. package/src/codec/datatype/Ray.luau +26 -22
  24. package/src/codec/datatype/Rect.luau +20 -16
  25. package/src/codec/datatype/Region.luau +51 -45
  26. package/src/codec/datatype/Sequence.luau +64 -56
  27. package/src/codec/datatype/String.luau +89 -56
  28. package/src/codec/datatype/UDim.luau +40 -22
  29. package/src/codec/datatype/Vector.luau +181 -17
  30. package/src/codec/meta/Auto.luau +295 -253
  31. package/src/codec/meta/Bitfield.luau +104 -148
  32. package/src/codec/meta/Custom.luau +8 -4
  33. package/src/codec/meta/Enum.luau +29 -57
  34. package/src/codec/meta/Float.luau +64 -0
  35. package/src/codec/meta/Nothing.luau +9 -5
  36. package/src/codec/meta/Unknown.luau +44 -35
  37. package/src/codec/primitive/Bool.luau +16 -26
  38. package/src/codec/primitive/Float16.luau +58 -64
  39. package/src/codec/primitive/Int.luau +68 -0
  40. package/src/codec/primitive/Number.luau +21 -43
  41. package/src/codec/primitive/Varint.luau +67 -46
  42. package/src/index.d.ts +249 -306
  43. package/src/init.luau +332 -173
  44. package/src/internal/Baseline.luau +29 -24
  45. package/src/internal/Channel.luau +346 -161
  46. package/src/internal/Middleware.luau +60 -85
  47. package/src/internal/Pool.luau +19 -30
  48. package/src/internal/Registry.luau +61 -101
  49. package/src/transport/Bridge.luau +64 -43
  50. package/src/transport/Client.luau +60 -117
  51. package/src/transport/Gate.luau +282 -133
  52. package/src/transport/Reader.luau +159 -100
  53. package/src/transport/Server.luau +147 -292
  54. package/src/api/Namespace.luau +0 -251
  55. package/src/codec/meta/Quantized.luau +0 -174
@@ -1,362 +1,217 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Server-side transport with batching, delta state, frame XOR.
3
+ -- Server-side transport: per-player channels, broadcast, flush.
4
4
 
5
5
  local Players = game:GetService("Players")
6
- local RunService = game:GetService("RunService")
7
6
 
8
7
  local Baseline = require(script.Parent.Parent.internal.Baseline)
9
- local Bridge = require(script.Parent.Bridge)
8
+ local Bridge = require(script.Parent.Parent.transport.Bridge)
10
9
  local Channel = require(script.Parent.Parent.internal.Channel)
11
- local Gate = require(script.Parent.Gate)
12
- local Middleware = require(script.Parent.Parent.internal.Middleware)
10
+ local Gate = require(script.Parent.Parent.transport.Gate)
13
11
  local Pool = require(script.Parent.Parent.internal.Pool)
14
- local Reader = require(script.Parent.Reader)
12
+ local Reader = require(script.Parent.Parent.transport.Reader)
15
13
  local Types = require(script.Parent.Parent.Types)
16
14
 
17
- type ChannelState = Types.ChannelState
18
- type Codec<T> = Types.Codec<T>
19
-
20
- local channelWriteBatch = Channel.writeBatch
21
- local channelWriteBatchRaw = Channel.writeBatchRaw
22
- local channelSetPacket = Channel.setCurrentPacket
23
-
24
15
  -- State ---------------------------------------------------------------
25
16
 
26
- type PlayerState = {
27
- reliable: ChannelState,
28
- unreliable: ChannelState,
29
- }
30
-
31
- local _players = {} :: { [Player]: PlayerState }
32
- local _prevRecv = {} :: { [Player]: buffer }
33
- local _isStarted = false
34
- local _broadScratch = Channel.create()
17
+ local _reliableChannels: { [Player]: Types.ChannelState } = {}
18
+ local _unreliableChannels: { [Player]: Types.ChannelState } = {}
19
+ local _playerStats: { [Player]: Types.PlayerStats } = {}
20
+ local _statsEnabled = false
35
21
 
36
22
  -- Private -------------------------------------------------------------
37
23
 
38
- local function onPlayerAdded(player: Player): ()
39
- _players[player] = {
40
- reliable = Pool.acquire(),
41
- unreliable = Pool.acquire(),
42
- }
43
- Gate.onPlayerAdded(player)
44
- end
45
-
46
- local function onPlayerRemoving(player: Player): ()
47
- local state = _players[player]
48
- if not state then
49
- return
24
+ local function getReliable(player: Player): Types.ChannelState
25
+ local ch = _reliableChannels[player]
26
+ if not ch then
27
+ ch = Pool.acquire()
28
+ _reliableChannels[player] = ch
50
29
  end
51
-
52
- Pool.release(state.reliable)
53
- Pool.release(state.unreliable)
54
- _players[player] = nil
55
- _prevRecv[player] = nil
56
-
57
- Gate.onPlayerRemoved(player)
58
- Baseline.clearSource(player)
30
+ return ch
59
31
  end
60
32
 
61
- local function receive(player: Player, data: any, refs: any, applyXor: boolean): ()
62
- local buf = Reader.decodeIncoming(data)
63
- if not buf then
64
- return
65
- end
66
-
67
- local raw: buffer
68
- if applyXor then
69
- raw = Channel.xorApply(buf, _prevRecv[player])
70
- _prevRecv[player] = raw
71
- else
72
- raw = buf
73
- end
74
-
75
- local safeRefs: { Instance }? = if typeof(refs) == "table" then refs else nil
76
- local ok, err = pcall(Reader.process, raw, safeRefs, player, 0)
77
- if not ok then
78
- warn(`[Lync] Read failed from {player.Name} ({player.UserId}): {err}`)
33
+ local function getUnreliable(player: Player): Types.ChannelState
34
+ local ch = _unreliableChannels[player]
35
+ if not ch then
36
+ ch = Pool.acquire()
37
+ _unreliableChannels[player] = ch
79
38
  end
39
+ return ch
80
40
  end
81
41
 
82
- local function flushChannel(remote: any, ch: ChannelState, target: Player, applyXor: boolean): ()
83
- if ch.cursor == 0 then
84
- return
85
- end
42
+ local function flushPlayer(player: Player): ()
43
+ -- Reliable channel
44
+ local rCh = _reliableChannels[player]
45
+ if rCh and rCh.cursor > 0 then
46
+ local snapshot, refs, byteLen, refCount = Channel.sealAndDump(rCh)
86
47
 
87
- local raw, refs = Channel.sealAndDump(ch)
48
+ -- XOR against previous frame
49
+ local xored = Channel.xorApply(snapshot, byteLen, rCh.prevDump, rCh.prevDumpLen)
50
+ rCh.prevDump = snapshot
51
+ rCh.prevDumpLen = byteLen
88
52
 
89
- if applyXor then
90
- local xored = Channel.xorApply(raw, ch.prevDump)
91
- ch.prevDump = raw
92
- remote:FireClient(target, xored, refs)
93
- else
94
- remote:FireClient(target, raw, refs)
95
- end
53
+ Bridge.fireClient(player, xored, refs)
54
+ Channel.reset(rCh)
96
55
 
97
- Channel.reset(ch)
98
- end
56
+ if _statsEnabled then
57
+ local ps = _playerStats[player]
58
+ if ps then
59
+ ps.bytesSent += byteLen
60
+ end
61
+ end
62
+ end
99
63
 
100
- local function flush(): ()
101
- local reliable = Bridge.reliable
102
- local unreliable = Bridge.unreliable
64
+ -- Unreliable channel (no XOR, no delta)
65
+ local uCh = _unreliableChannels[player]
66
+ if uCh and uCh.cursor > 0 then
67
+ local snapshot, refs, byteLen, _ = Channel.sealAndDump(uCh)
103
68
 
104
- for player, state in _players do
105
- local ok, err = pcall(flushChannel, reliable, state.reliable, player, true)
106
- if not ok then
107
- warn(`[Lync] Flush failed for {player.Name} ({player.UserId}): {err}`)
108
- Channel.reset(state.reliable)
109
- end
69
+ Bridge.fireClientUnreliable(player, snapshot, refs)
70
+ Channel.reset(uCh)
110
71
 
111
- local ok2, err2 = pcall(flushChannel, unreliable, state.unreliable, player, false)
112
- if not ok2 then
113
- warn(`[Lync] Flush failed for {player.Name} ({player.UserId}): {err2}`)
114
- Channel.reset(state.unreliable)
72
+ if _statsEnabled then
73
+ local ps = _playerStats[player]
74
+ if ps then
75
+ ps.bytesSent += byteLen
76
+ end
115
77
  end
116
78
  end
117
79
  end
118
80
 
119
- -- Serialize data once into shared scratch, return raw bytes + refs snapshot.
120
- local function serializeBroadcast(
121
- name: string,
122
- codec: Codec<any>,
123
- data: any
124
- ): (number, { Instance }?)
125
- local scratch = _broadScratch
126
- scratch.cursor = 0
127
- table.clear(scratch.refs)
128
- channelSetPacket(name)
129
- codec.write(scratch, data)
130
-
131
- local snapRefs = if #scratch.refs > 0 then table.clone(scratch.refs) else nil
132
- return scratch.cursor, snapRefs
133
- end
81
+ -- Public --------------------------------------------------------------
134
82
 
135
- -- Run middleware and serialize once. Returns nil if middleware dropped.
136
- local function prepareBroadcast(
137
- name: string,
138
- codec: Codec<any>,
139
- data: any
140
- ): (any, number, { Instance }?)
141
- local final: any
142
- if Middleware.hasSend then
143
- final = Middleware.runSend(data, name, nil)
144
- if final == nil then
145
- return nil, 0, nil
146
- end
147
- else
148
- final = data
149
- end
83
+ local Server = {}
150
84
 
151
- local snapLen, snapRefs = serializeBroadcast(name, codec, final)
152
- return final, snapLen, snapRefs
85
+ function Server.getChannel(player: Player, isUnreliable: boolean): Types.ChannelState
86
+ return if isUnreliable then getUnreliable(player) else getReliable(player)
153
87
  end
154
88
 
155
- -- Fan out to all connected players via _players map.
156
- local function broadcastToAll(
157
- id: number,
158
- name: string,
159
- codec: Codec<any>,
160
- data: any,
161
- isUnreliable: boolean,
162
- exceptSet: { [Player]: true }?
163
- ): ()
164
- local final, snapLen, snapRefs = prepareBroadcast(name, codec, data)
165
- if final == nil and data ~= nil then
166
- return
89
+ function Server.flush(): ()
90
+ if Channel.hasTimestamps() then
91
+ Channel.updateTimestamp()
167
92
  end
168
93
 
169
- local scratchBuff = _broadScratch.buff
170
- for player, state in _players do
171
- if exceptSet and exceptSet[player] then
172
- continue
173
- end
174
- local ch = if isUnreliable then state.unreliable else state.reliable
175
- channelWriteBatchRaw(ch, id, scratchBuff, snapLen, snapRefs)
176
- end
177
- end
178
-
179
- -- Fan out to a specific player list.
180
- local function broadcastToList(
181
- players: { Player },
182
- id: number,
183
- name: string,
184
- codec: Codec<any>,
185
- data: any,
186
- isUnreliable: boolean
187
- ): ()
188
- local final, snapLen, snapRefs = prepareBroadcast(name, codec, data)
189
- if final == nil and data ~= nil then
190
- return
94
+ for player in _reliableChannels do
95
+ flushPlayer(player)
191
96
  end
192
97
 
193
- local scratchBuff = _broadScratch.buff
194
- for _, player in players do
195
- local state = _players[player]
196
- if not state then
197
- continue
98
+ for player in _unreliableChannels do
99
+ if not _reliableChannels[player] then
100
+ flushPlayer(player)
198
101
  end
199
- local ch = if isUnreliable then state.unreliable else state.reliable
200
- channelWriteBatchRaw(ch, id, scratchBuff, snapLen, snapRefs)
201
102
  end
202
103
  end
203
104
 
204
- -- Fan out to a group set { [Player]: true }.
205
- local function broadcastToSet(
206
- set: { [Player]: true },
207
- id: number,
208
- name: string,
209
- codec: Codec<any>,
210
- data: any,
211
- isUnreliable: boolean
212
- ): ()
213
- local final, snapLen, snapRefs = prepareBroadcast(name, codec, data)
214
- if final == nil and data ~= nil then
215
- return
216
- end
217
-
218
- local scratchBuff = _broadScratch.buff
219
- for player in set do
220
- local state = _players[player]
221
- if not state then
222
- continue
223
- end
224
- local ch = if isUnreliable then state.unreliable else state.reliable
225
- channelWriteBatchRaw(ch, id, scratchBuff, snapLen, snapRefs)
226
- end
105
+ function Server.flushPlayer(player: Player): ()
106
+ flushPlayer(player)
227
107
  end
228
108
 
229
- -- Public --------------------------------------------------------------
109
+ function Server.start(): ()
110
+ local container = Instance.new("Folder")
111
+ container.Name = "LyncRemotes"
112
+ container.Parent = game:GetService("ReplicatedStorage")
230
113
 
231
- local Server = {}
114
+ Bridge.setup(container)
232
115
 
233
- function Server.start(): ()
234
- if _isStarted then
235
- return
236
- end
237
- _isStarted = true
116
+ local reliable = Bridge.reliable()
117
+ local unreliable = Bridge.unreliable()
238
118
 
239
- Bridge.setup()
119
+ -- Incoming reliable
120
+ reliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any?)
121
+ local incoming = Reader.decodeIncoming(data)
122
+ if not incoming then
123
+ return
124
+ end
240
125
 
241
- Players.PlayerAdded:Connect(onPlayerAdded)
242
- Players.PlayerRemoving:Connect(onPlayerRemoving)
243
- for _, player in Players:GetPlayers() do
244
- onPlayerAdded(player)
245
- end
126
+ local incomingLen = buffer.len(incoming)
246
127
 
247
- Bridge.reliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any): ()
248
- receive(player, data, refs, true)
249
- end)
250
- Bridge.unreliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any): ()
251
- receive(player, data, refs, false)
252
- end)
253
- RunService.Heartbeat:Connect(flush)
254
- end
128
+ -- Bandwidth check
129
+ if not Gate.checkBandwidth(player, incomingLen) then
130
+ return
131
+ end
255
132
 
256
- function Server.writeTo(
257
- player: Player,
258
- id: number,
259
- name: string,
260
- codec: Codec<any>,
261
- data: any,
262
- isUnreliable: boolean
263
- ): ()
264
- local state = _players[player]
265
- if not state then
266
- return
267
- end
133
+ -- XOR decode against player's previous outbound (client→server has no XOR)
134
+ local ok, err = pcall(Reader.process, incoming, incomingLen, refs :: any, player, true)
135
+ if not ok then
136
+ warn(`[Lync] Server: read error from {player.Name}: {err}`)
137
+ end
138
+
139
+ if _statsEnabled then
140
+ local ps = _playerStats[player]
141
+ if ps then
142
+ ps.bytesReceived += incomingLen
143
+ end
144
+ end
145
+ end)
268
146
 
269
- local final: any
270
- if Middleware.hasSend then
271
- final = Middleware.runSend(data, name, player)
272
- if final == nil then
147
+ -- Incoming unreliable
148
+ unreliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any?)
149
+ local incoming = Reader.decodeIncoming(data)
150
+ if not incoming then
273
151
  return
274
152
  end
275
- else
276
- final = data
277
- end
278
153
 
279
- local ch = if isUnreliable then state.unreliable else state.reliable
280
- channelWriteBatch(ch, id, name, codec, final)
281
- end
154
+ local incomingLen = buffer.len(incoming)
282
155
 
283
- function Server.writeToAll(
284
- id: number,
285
- name: string,
286
- codec: Codec<any>,
287
- data: any,
288
- isUnreliable: boolean
289
- ): ()
290
- broadcastToAll(id, name, codec, data, isUnreliable, nil)
291
- end
156
+ local ok, err = pcall(Reader.process, incoming, incomingLen, refs :: any, player, true)
157
+ if not ok then
158
+ warn(`[Lync] Server: read error (unreliable) from {player.Name}: {err}`)
159
+ end
292
160
 
293
- function Server.writeToList(
294
- players: { Player },
295
- id: number,
296
- name: string,
297
- codec: Codec<any>,
298
- data: any,
299
- isUnreliable: boolean
300
- ): ()
301
- broadcastToList(players, id, name, codec, data, isUnreliable)
302
- end
161
+ if _statsEnabled then
162
+ local ps = _playerStats[player]
163
+ if ps then
164
+ ps.bytesReceived += incomingLen
165
+ end
166
+ end
167
+ end)
303
168
 
304
- function Server.writeToAllExcept(
305
- exceptSet: { [Player]: true },
306
- id: number,
307
- name: string,
308
- codec: Codec<any>,
309
- data: any,
310
- isUnreliable: boolean
311
- ): ()
312
- broadcastToAll(id, name, codec, data, isUnreliable, exceptSet)
313
- end
169
+ -- Player lifecycle
170
+ Players.PlayerAdded:Connect(function(player: Player)
171
+ if _statsEnabled then
172
+ _playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
173
+ end
174
+ end)
314
175
 
315
- function Server.writeToSet(
316
- set: { [Player]: true },
317
- id: number,
318
- name: string,
319
- codec: Codec<any>,
320
- data: any,
321
- isUnreliable: boolean
322
- ): ()
323
- broadcastToSet(set, id, name, codec, data, isUnreliable)
324
- end
176
+ Players.PlayerRemoving:Connect(function(player: Player)
177
+ local rCh = _reliableChannels[player]
178
+ if rCh then
179
+ _reliableChannels[player] = nil
180
+ Pool.release(rCh)
181
+ end
325
182
 
326
- -- Shared for both query requests and responses(identical wire format).
327
- function Server.writeQuery(
328
- player: Player,
329
- id: number,
330
- name: string,
331
- correlationId: number,
332
- codec: Codec<any>,
333
- data: any
334
- ): ()
335
- local state = _players[player]
336
- if not state then
337
- return
338
- end
183
+ local uCh = _unreliableChannels[player]
184
+ if uCh then
185
+ _unreliableChannels[player] = nil
186
+ Pool.release(uCh)
187
+ end
339
188
 
340
- local final: any
341
- if Middleware.hasSend then
342
- final = Middleware.runSend(data, name, player)
343
- if final == nil then
344
- return
189
+ _playerStats[player] = nil
190
+ Gate.clearPlayer(player)
191
+ Baseline.clearPlayer(player)
192
+ end)
193
+
194
+ -- Initialize stats for existing players
195
+ if _statsEnabled then
196
+ for _, player in Players:GetPlayers() do
197
+ _playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
345
198
  end
346
- else
347
- final = data
348
199
  end
200
+ end
349
201
 
350
- Channel.writeQuery(state.reliable, id, name, correlationId, codec, final)
202
+ function Server.enableStats(): ()
203
+ _statsEnabled = true
351
204
  end
352
205
 
353
- function Server.writeQueryNil(player: Player, id: number, name: string, correlationId: number): ()
354
- local state = _players[player]
355
- if not state then
356
- return
357
- end
206
+ function Server.getPlayerStats(player: Player): Types.PlayerStats?
207
+ return _playerStats[player]
208
+ end
358
209
 
359
- Channel.writeQuery(state.reliable, id, name, correlationId, nil, nil)
210
+ function Server.resetStats(): ()
211
+ for player, ps in _playerStats do
212
+ ps.bytesSent = 0
213
+ ps.bytesReceived = 0
214
+ end
360
215
  end
361
216
 
362
217
  return table.freeze(Server)