@axpecter/lync 2.0.0 → 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.
- package/README.md +475 -426
- package/package.json +1 -1
- package/src/Types.luau +63 -31
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +187 -186
- package/src/api/Query.luau +223 -284
- package/src/api/Scope.luau +46 -57
- package/src/api/Signal.luau +104 -137
- package/src/codec/Base.luau +69 -20
- package/src/codec/composite/Array.luau +179 -270
- package/src/codec/composite/Map.luau +97 -347
- package/src/codec/composite/Optional.luau +27 -24
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +200 -360
- package/src/codec/composite/Tagged.luau +62 -187
- package/src/codec/composite/Tuple.luau +79 -103
- package/src/codec/datatype/Buffer.luau +49 -21
- package/src/codec/datatype/CFrame.luau +207 -30
- package/src/codec/datatype/Color.luau +21 -11
- package/src/codec/datatype/Instance.luau +28 -21
- package/src/codec/datatype/IntVector.luau +33 -21
- package/src/codec/datatype/NumberRange.luau +19 -10
- package/src/codec/datatype/Ray.luau +26 -22
- package/src/codec/datatype/Rect.luau +20 -16
- package/src/codec/datatype/Region.luau +51 -45
- package/src/codec/datatype/Sequence.luau +64 -56
- package/src/codec/datatype/String.luau +89 -56
- package/src/codec/datatype/UDim.luau +40 -22
- package/src/codec/datatype/Vector.luau +181 -17
- package/src/codec/meta/Auto.luau +295 -253
- package/src/codec/meta/Bitfield.luau +104 -148
- package/src/codec/meta/Custom.luau +8 -4
- package/src/codec/meta/Enum.luau +29 -57
- package/src/codec/meta/Float.luau +64 -0
- package/src/codec/meta/Nothing.luau +9 -5
- package/src/codec/meta/Unknown.luau +44 -36
- package/src/codec/primitive/Bool.luau +16 -26
- package/src/codec/primitive/Float16.luau +58 -64
- package/src/codec/primitive/Int.luau +68 -0
- package/src/codec/primitive/Number.luau +21 -43
- package/src/codec/primitive/Varint.luau +67 -46
- package/src/index.d.ts +249 -335
- package/src/init.luau +319 -207
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +333 -278
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +56 -113
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +59 -170
- package/src/transport/Gate.luau +282 -142
- package/src/transport/Reader.luau +148 -140
- package/src/transport/Server.luau +138 -488
- package/src/api/Namespace.luau +0 -256
- package/src/codec/meta/Quantized.luau +0 -174
|
@@ -1,567 +1,217 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Server-side transport
|
|
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)
|
|
15
|
-
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
12
|
+
local Reader = require(script.Parent.Parent.transport.Reader)
|
|
16
13
|
local Types = require(script.Parent.Parent.Types)
|
|
17
14
|
|
|
18
|
-
type ChannelState = Types.ChannelState
|
|
19
|
-
type Codec<T> = Types.Codec<T>
|
|
20
|
-
type Registration = Types.Registration
|
|
21
|
-
|
|
22
|
-
local channelWriteBatch = Channel.writeBatch
|
|
23
|
-
local channelWriteBatchRaw = Channel.writeBatchRaw
|
|
24
|
-
local channelUpdateTimestamp = Channel.updateTimestamp
|
|
25
|
-
local channelStatsEnabled = Channel.statsEnabled
|
|
26
|
-
|
|
27
15
|
-- State ---------------------------------------------------------------
|
|
28
16
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
type PlayerState = {
|
|
35
|
-
reliable: ChannelState,
|
|
36
|
-
unreliable: ChannelState,
|
|
37
|
-
stats: PlayerStats,
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
local _players = {} :: { [Player]: PlayerState }
|
|
41
|
-
local _prevRecv = {} :: { [Player]: buffer }
|
|
42
|
-
local _isStarted = false
|
|
43
|
-
local _broadScratch = Channel.create()
|
|
44
|
-
|
|
45
|
-
-- Flush control
|
|
46
|
-
local _flushRate = 60
|
|
47
|
-
local _flushInterval = 1 / 60
|
|
48
|
-
local _useAccumulator = false
|
|
49
|
-
local _flushAccum = 0
|
|
50
|
-
local _flushedThisFrame = false
|
|
51
|
-
|
|
52
|
-
-- Timestamp
|
|
53
|
-
local _frameCounter: number = 0
|
|
54
|
-
|
|
55
|
-
-- Bandwidth strike counter
|
|
56
|
-
local _strikes = {} :: { [Player]: number }
|
|
57
|
-
local _softLimit = 16384
|
|
58
|
-
local _maxStrikes = 10
|
|
59
|
-
|
|
60
|
-
-- Packet capture
|
|
61
|
-
local _captureEnabled = false
|
|
62
|
-
local _captureLabel = ""
|
|
63
|
-
local _captureFrame = 0
|
|
64
|
-
local _captures = {} :: { any }
|
|
65
|
-
|
|
66
|
-
local MAX_HEX = 512 -- cap hex dump to first 512 bytes to keep capture size sane
|
|
67
|
-
|
|
68
|
-
local function bufToHex(buf: buffer, len: number): string
|
|
69
|
-
local cap = math.min(len, MAX_HEX)
|
|
70
|
-
local parts = table.create(cap)
|
|
71
|
-
for i = 0, cap - 1 do
|
|
72
|
-
parts[i + 1] = string.format("%02x", buffer.readu8(buf, i))
|
|
73
|
-
end
|
|
74
|
-
return table.concat(parts)
|
|
75
|
-
end
|
|
17
|
+
local _reliableChannels: { [Player]: Types.ChannelState } = {}
|
|
18
|
+
local _unreliableChannels: { [Player]: Types.ChannelState } = {}
|
|
19
|
+
local _playerStats: { [Player]: Types.PlayerStats } = {}
|
|
20
|
+
local _statsEnabled = false
|
|
76
21
|
|
|
77
22
|
-- Private -------------------------------------------------------------
|
|
78
23
|
|
|
79
|
-
local function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
_strikes[player] = 0
|
|
86
|
-
Gate.onPlayerAdded(player)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
local function onPlayerRemoving(player: Player): ()
|
|
90
|
-
local state = _players[player]
|
|
91
|
-
if not state then
|
|
92
|
-
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
|
|
93
29
|
end
|
|
94
|
-
|
|
95
|
-
Pool.release(state.reliable)
|
|
96
|
-
Pool.release(state.unreliable)
|
|
97
|
-
_players[player] = nil
|
|
98
|
-
_prevRecv[player] = nil
|
|
99
|
-
_strikes[player] = nil
|
|
100
|
-
|
|
101
|
-
Gate.onPlayerRemoved(player)
|
|
102
|
-
Baseline.clearSource(player)
|
|
30
|
+
return ch
|
|
103
31
|
end
|
|
104
32
|
|
|
105
|
-
local function
|
|
106
|
-
local
|
|
107
|
-
if not
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
local len = buffer.len(buf)
|
|
112
|
-
local strikes = _strikes[player] or 0
|
|
113
|
-
if len > _softLimit then
|
|
114
|
-
strikes += 1
|
|
115
|
-
else
|
|
116
|
-
strikes = math.max(0, strikes - 1)
|
|
117
|
-
end
|
|
118
|
-
_strikes[player] = strikes
|
|
119
|
-
if strikes > _maxStrikes then
|
|
120
|
-
Gate.reportDrop(player, "bandwidth", "frame", nil)
|
|
121
|
-
return
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
local raw: buffer
|
|
125
|
-
if applyXor then
|
|
126
|
-
raw = Channel.xorApply(buf, _prevRecv[player])
|
|
127
|
-
_prevRecv[player] = raw
|
|
128
|
-
else
|
|
129
|
-
raw = buf
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
local safeRefs: { Instance }? = if typeof(refs) == "table" then refs else nil
|
|
133
|
-
local ok, err = pcall(Reader.process, raw, safeRefs, player, 0)
|
|
134
|
-
if not ok then
|
|
135
|
-
warn(`[Lync] Read failed from {player.Name} ({player.UserId}): {err}`)
|
|
136
|
-
strikes = (_strikes[player] or 0) + 1
|
|
137
|
-
_strikes[player] = strikes
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
if channelStatsEnabled() then
|
|
141
|
-
local state = _players[player]
|
|
142
|
-
if state then
|
|
143
|
-
state.stats.bytesReceived += buffer.len(raw)
|
|
144
|
-
end
|
|
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
|
|
145
38
|
end
|
|
39
|
+
return ch
|
|
146
40
|
end
|
|
147
41
|
|
|
148
|
-
local function
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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)
|
|
152
47
|
|
|
153
|
-
|
|
48
|
+
-- XOR against previous frame
|
|
49
|
+
local xored = Channel.xorApply(snapshot, byteLen, rCh.prevDump, rCh.prevDumpLen)
|
|
50
|
+
rCh.prevDump = snapshot
|
|
51
|
+
rCh.prevDumpLen = byteLen
|
|
154
52
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
xored = Channel.xorApply(raw, ch.prevDump)
|
|
158
|
-
ch.prevDump = raw
|
|
159
|
-
remote:FireClient(target, xored, refs)
|
|
160
|
-
else
|
|
161
|
-
remote:FireClient(target, raw, refs)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
if _captureEnabled then
|
|
165
|
-
_captureFrame += 1
|
|
166
|
-
local rawLen = buffer.len(raw)
|
|
167
|
-
table.insert(_captures, {
|
|
168
|
-
label = _captureLabel,
|
|
169
|
-
frame = _captureFrame,
|
|
170
|
-
raw = bufToHex(raw, rawLen),
|
|
171
|
-
xored = if xored then bufToHex(xored :: buffer, buffer.len(xored :: buffer)) else nil,
|
|
172
|
-
bytes = rawLen,
|
|
173
|
-
refs = #refs,
|
|
174
|
-
})
|
|
175
|
-
end
|
|
53
|
+
Bridge.fireClient(player, xored, refs)
|
|
54
|
+
Channel.reset(rCh)
|
|
176
55
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
56
|
+
if _statsEnabled then
|
|
57
|
+
local ps = _playerStats[player]
|
|
58
|
+
if ps then
|
|
59
|
+
ps.bytesSent += byteLen
|
|
60
|
+
end
|
|
181
61
|
end
|
|
182
62
|
end
|
|
183
63
|
|
|
184
|
-
|
|
185
|
-
|
|
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)
|
|
186
68
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
_frameCounter = (_frameCounter + 1) % 256
|
|
190
|
-
channelUpdateTimestamp(_frameCounter, 0, os.clock())
|
|
191
|
-
end
|
|
69
|
+
Bridge.fireClientUnreliable(player, snapshot, refs)
|
|
70
|
+
Channel.reset(uCh)
|
|
192
71
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if not ok then
|
|
199
|
-
warn(`[Lync] Flush failed for {player.Name} ({player.UserId}): {err}`)
|
|
200
|
-
Channel.reset(state.reliable)
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
local ok2, err2 = pcall(flushChannel, unreliable, state.unreliable, player, false)
|
|
204
|
-
if not ok2 then
|
|
205
|
-
warn(`[Lync] Flush failed for {player.Name} ({player.UserId}): {err2}`)
|
|
206
|
-
Channel.reset(state.unreliable)
|
|
72
|
+
if _statsEnabled then
|
|
73
|
+
local ps = _playerStats[player]
|
|
74
|
+
if ps then
|
|
75
|
+
ps.bytesSent += byteLen
|
|
76
|
+
end
|
|
207
77
|
end
|
|
208
78
|
end
|
|
209
79
|
end
|
|
210
80
|
|
|
211
|
-
--
|
|
212
|
-
local function serializeBroadcast(reg: Registration, data: any): (number, { Instance }?)
|
|
213
|
-
local scratch = _broadScratch
|
|
214
|
-
scratch.cursor = 0
|
|
215
|
-
table.clear(scratch.refs)
|
|
216
|
-
Channel.setCurrentPacket(reg.name)
|
|
217
|
-
reg.codec.write(scratch, data)
|
|
218
|
-
|
|
219
|
-
local snapRefs = if #scratch.refs > 0 then table.clone(scratch.refs) else nil
|
|
220
|
-
return scratch.cursor, snapRefs
|
|
221
|
-
end
|
|
81
|
+
-- Public --------------------------------------------------------------
|
|
222
82
|
|
|
223
|
-
local
|
|
224
|
-
local final: any
|
|
225
|
-
if Middleware.hasSend then
|
|
226
|
-
final = Middleware.runSend(data, reg.name, nil)
|
|
227
|
-
if final == nil then
|
|
228
|
-
return nil, 0, nil
|
|
229
|
-
end
|
|
230
|
-
else
|
|
231
|
-
final = data
|
|
232
|
-
end
|
|
83
|
+
local Server = {}
|
|
233
84
|
|
|
234
|
-
|
|
235
|
-
return
|
|
85
|
+
function Server.getChannel(player: Player, isUnreliable: boolean): Types.ChannelState
|
|
86
|
+
return if isUnreliable then getUnreliable(player) else getReliable(player)
|
|
236
87
|
end
|
|
237
88
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
data: any,
|
|
242
|
-
isUnreliable: boolean,
|
|
243
|
-
exceptSet: { [Player]: true }?
|
|
244
|
-
): ()
|
|
245
|
-
local final, snapLen, snapRefs = prepareBroadcast(reg, data)
|
|
246
|
-
if final == nil and data ~= nil then
|
|
247
|
-
return
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
local scratchBuff = _broadScratch.buff
|
|
251
|
-
local count = 0
|
|
252
|
-
for player, state in _players do
|
|
253
|
-
if exceptSet and exceptSet[player] then
|
|
254
|
-
continue
|
|
255
|
-
end
|
|
256
|
-
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
257
|
-
channelWriteBatchRaw(ch, reg, scratchBuff, snapLen, snapRefs)
|
|
258
|
-
count += 1
|
|
259
|
-
end
|
|
260
|
-
if channelStatsEnabled() and count > 0 then
|
|
261
|
-
-- Header (3 bytes for id+count) + timestamp + payload per player
|
|
262
|
-
local tsBytes = if reg.timestampMode == 1
|
|
263
|
-
then 1
|
|
264
|
-
elseif reg.timestampMode == 2 then 2
|
|
265
|
-
elseif reg.timestampMode == 3 then 8
|
|
266
|
-
else 0
|
|
267
|
-
reg.bytesSent += (3 + tsBytes + snapLen) * count
|
|
268
|
-
reg.fires += count
|
|
89
|
+
function Server.flush(): ()
|
|
90
|
+
if Channel.hasTimestamps() then
|
|
91
|
+
Channel.updateTimestamp()
|
|
269
92
|
end
|
|
270
|
-
end
|
|
271
93
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
reg: Registration,
|
|
275
|
-
data: any,
|
|
276
|
-
isUnreliable: boolean
|
|
277
|
-
): ()
|
|
278
|
-
local final, snapLen, snapRefs = prepareBroadcast(reg, data)
|
|
279
|
-
if final == nil and data ~= nil then
|
|
280
|
-
return
|
|
94
|
+
for player in _reliableChannels do
|
|
95
|
+
flushPlayer(player)
|
|
281
96
|
end
|
|
282
97
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
local state = _players[player]
|
|
287
|
-
if not state then
|
|
288
|
-
continue
|
|
98
|
+
for player in _unreliableChannels do
|
|
99
|
+
if not _reliableChannels[player] then
|
|
100
|
+
flushPlayer(player)
|
|
289
101
|
end
|
|
290
|
-
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
291
|
-
channelWriteBatchRaw(ch, reg, scratchBuff, snapLen, snapRefs)
|
|
292
|
-
count += 1
|
|
293
|
-
end
|
|
294
|
-
if channelStatsEnabled() and count > 0 then
|
|
295
|
-
local tsBytes = if reg.timestampMode == 1
|
|
296
|
-
then 1
|
|
297
|
-
elseif reg.timestampMode == 2 then 2
|
|
298
|
-
elseif reg.timestampMode == 3 then 8
|
|
299
|
-
else 0
|
|
300
|
-
reg.bytesSent += (3 + tsBytes + snapLen) * count
|
|
301
|
-
reg.fires += count
|
|
302
102
|
end
|
|
303
103
|
end
|
|
304
104
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
reg: Registration,
|
|
308
|
-
data: any,
|
|
309
|
-
isUnreliable: boolean
|
|
310
|
-
): ()
|
|
311
|
-
local final, snapLen, snapRefs = prepareBroadcast(reg, data)
|
|
312
|
-
if final == nil and data ~= nil then
|
|
313
|
-
return
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
local scratchBuff = _broadScratch.buff
|
|
317
|
-
local count = 0
|
|
318
|
-
for player in set do
|
|
319
|
-
local state = _players[player]
|
|
320
|
-
if not state then
|
|
321
|
-
continue
|
|
322
|
-
end
|
|
323
|
-
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
324
|
-
channelWriteBatchRaw(ch, reg, scratchBuff, snapLen, snapRefs)
|
|
325
|
-
count += 1
|
|
326
|
-
end
|
|
327
|
-
if channelStatsEnabled() and count > 0 then
|
|
328
|
-
local tsBytes = if reg.timestampMode == 1
|
|
329
|
-
then 1
|
|
330
|
-
elseif reg.timestampMode == 2 then 2
|
|
331
|
-
elseif reg.timestampMode == 3 then 8
|
|
332
|
-
else 0
|
|
333
|
-
reg.bytesSent += (3 + tsBytes + snapLen) * count
|
|
334
|
-
reg.fires += count
|
|
335
|
-
end
|
|
105
|
+
function Server.flushPlayer(player: Player): ()
|
|
106
|
+
flushPlayer(player)
|
|
336
107
|
end
|
|
337
108
|
|
|
338
|
-
-- Public --------------------------------------------------------------
|
|
339
|
-
|
|
340
|
-
local Server = {}
|
|
341
|
-
|
|
342
109
|
function Server.start(): ()
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
_isStarted = true
|
|
347
|
-
|
|
348
|
-
Bridge.setup()
|
|
349
|
-
|
|
350
|
-
Players.PlayerAdded:Connect(onPlayerAdded)
|
|
351
|
-
Players.PlayerRemoving:Connect(onPlayerRemoving)
|
|
352
|
-
for _, player in Players:GetPlayers() do
|
|
353
|
-
onPlayerAdded(player)
|
|
354
|
-
end
|
|
110
|
+
local container = Instance.new("Folder")
|
|
111
|
+
container.Name = "LyncRemotes"
|
|
112
|
+
container.Parent = game:GetService("ReplicatedStorage")
|
|
355
113
|
|
|
356
|
-
Bridge.
|
|
357
|
-
receive(player, data, refs, true)
|
|
358
|
-
end)
|
|
359
|
-
Bridge.unreliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any): ()
|
|
360
|
-
receive(player, data, refs, false)
|
|
361
|
-
end)
|
|
114
|
+
Bridge.setup(container)
|
|
362
115
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
if _flushedThisFrame then
|
|
366
|
-
_flushedThisFrame = false
|
|
367
|
-
return
|
|
368
|
-
end
|
|
369
|
-
_flushAccum += dt
|
|
370
|
-
if _flushAccum < _flushInterval then
|
|
371
|
-
return
|
|
372
|
-
end
|
|
373
|
-
_flushAccum -= _flushInterval
|
|
374
|
-
flush()
|
|
375
|
-
end)
|
|
376
|
-
else
|
|
377
|
-
RunService.Heartbeat:Connect(function()
|
|
378
|
-
if _flushedThisFrame then
|
|
379
|
-
_flushedThisFrame = false
|
|
380
|
-
return
|
|
381
|
-
end
|
|
382
|
-
flush()
|
|
383
|
-
end)
|
|
384
|
-
end
|
|
385
|
-
end
|
|
116
|
+
local reliable = Bridge.reliable()
|
|
117
|
+
local unreliable = Bridge.unreliable()
|
|
386
118
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
local final: any
|
|
394
|
-
if Middleware.hasSend then
|
|
395
|
-
final = Middleware.runSend(data, reg.name, player)
|
|
396
|
-
if final == nil then
|
|
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
|
|
397
123
|
return
|
|
398
124
|
end
|
|
399
|
-
else
|
|
400
|
-
final = data
|
|
401
|
-
end
|
|
402
125
|
|
|
403
|
-
|
|
404
|
-
if channelStatsEnabled() then
|
|
405
|
-
local before = ch.cursor
|
|
406
|
-
channelWriteBatch(ch, reg, final)
|
|
407
|
-
reg.bytesSent += ch.cursor - before
|
|
408
|
-
reg.fires += 1
|
|
409
|
-
else
|
|
410
|
-
channelWriteBatch(ch, reg, final)
|
|
411
|
-
end
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
function Server.writeToAll(reg: Registration, data: any, isUnreliable: boolean): ()
|
|
415
|
-
broadcastToAll(reg, data, isUnreliable, nil)
|
|
416
|
-
end
|
|
126
|
+
local incomingLen = buffer.len(incoming)
|
|
417
127
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
isUnreliable: boolean
|
|
423
|
-
): ()
|
|
424
|
-
broadcastToList(players, reg, data, isUnreliable)
|
|
425
|
-
end
|
|
426
|
-
|
|
427
|
-
function Server.writeToAllExcept(
|
|
428
|
-
exceptSet: { [Player]: true },
|
|
429
|
-
reg: Registration,
|
|
430
|
-
data: any,
|
|
431
|
-
isUnreliable: boolean
|
|
432
|
-
): ()
|
|
433
|
-
broadcastToAll(reg, data, isUnreliable, exceptSet)
|
|
434
|
-
end
|
|
128
|
+
-- Bandwidth check
|
|
129
|
+
if not Gate.checkBandwidth(player, incomingLen) then
|
|
130
|
+
return
|
|
131
|
+
end
|
|
435
132
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
): ()
|
|
442
|
-
broadcastToSet(set, reg, data, isUnreliable)
|
|
443
|
-
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
|
|
444
138
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
): ()
|
|
453
|
-
local state = _players[player]
|
|
454
|
-
if not state then
|
|
455
|
-
return
|
|
456
|
-
end
|
|
139
|
+
if _statsEnabled then
|
|
140
|
+
local ps = _playerStats[player]
|
|
141
|
+
if ps then
|
|
142
|
+
ps.bytesReceived += incomingLen
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end)
|
|
457
146
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if
|
|
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
|
|
462
151
|
return
|
|
463
152
|
end
|
|
464
|
-
else
|
|
465
|
-
final = data
|
|
466
|
-
end
|
|
467
153
|
|
|
468
|
-
|
|
469
|
-
end
|
|
154
|
+
local incomingLen = buffer.len(incoming)
|
|
470
155
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
end
|
|
476
|
-
|
|
477
|
-
Channel.writeQuery(state.reliable, id, name, correlationId, nil, nil)
|
|
478
|
-
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
|
|
479
160
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
end
|
|
161
|
+
if _statsEnabled then
|
|
162
|
+
local ps = _playerStats[player]
|
|
163
|
+
if ps then
|
|
164
|
+
ps.bytesReceived += incomingLen
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end)
|
|
485
168
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
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)
|
|
491
175
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
|
495
182
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
183
|
+
local uCh = _unreliableChannels[player]
|
|
184
|
+
if uCh then
|
|
185
|
+
_unreliableChannels[player] = nil
|
|
186
|
+
Pool.release(uCh)
|
|
187
|
+
end
|
|
500
188
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
end
|
|
189
|
+
_playerStats[player] = nil
|
|
190
|
+
Gate.clearPlayer(player)
|
|
191
|
+
Baseline.clearPlayer(player)
|
|
192
|
+
end)
|
|
505
193
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
194
|
+
-- Initialize stats for existing players
|
|
195
|
+
if _statsEnabled then
|
|
196
|
+
for _, player in Players:GetPlayers() do
|
|
197
|
+
_playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
|
|
198
|
+
end
|
|
510
199
|
end
|
|
511
|
-
Registry.forEach(function(reg: Registration)
|
|
512
|
-
reg.bytesSent = 0
|
|
513
|
-
reg.bytesReceived = 0
|
|
514
|
-
reg.fires = 0
|
|
515
|
-
reg.recvFires = 0
|
|
516
|
-
reg.drops = 0
|
|
517
|
-
end)
|
|
518
200
|
end
|
|
519
201
|
|
|
520
|
-
function Server.
|
|
521
|
-
|
|
522
|
-
_captureLabel = label or "unknown"
|
|
523
|
-
_captureFrame = 0
|
|
202
|
+
function Server.enableStats(): ()
|
|
203
|
+
_statsEnabled = true
|
|
524
204
|
end
|
|
525
205
|
|
|
526
|
-
function Server.
|
|
527
|
-
|
|
206
|
+
function Server.getPlayerStats(player: Player): Types.PlayerStats?
|
|
207
|
+
return _playerStats[player]
|
|
528
208
|
end
|
|
529
209
|
|
|
530
|
-
function Server.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
-- Clean up previous captures
|
|
535
|
-
local old = storage:FindFirstChild("LyncCapture")
|
|
536
|
-
if old then
|
|
537
|
-
old:Destroy()
|
|
538
|
-
end
|
|
539
|
-
|
|
540
|
-
-- Split across StringValues if needed (200K limit per value)
|
|
541
|
-
local CHUNK = 199000
|
|
542
|
-
if #json <= CHUNK then
|
|
543
|
-
local sv = Instance.new("StringValue")
|
|
544
|
-
sv.Name = "LyncCapture"
|
|
545
|
-
sv.Value = json
|
|
546
|
-
sv.Parent = storage
|
|
547
|
-
else
|
|
548
|
-
local folder = Instance.new("Folder")
|
|
549
|
-
folder.Name = "LyncCapture"
|
|
550
|
-
local parts = math.ceil(#json / CHUNK)
|
|
551
|
-
for i = 1, parts do
|
|
552
|
-
local sv = Instance.new("StringValue")
|
|
553
|
-
sv.Name = `Part{i}`
|
|
554
|
-
sv.Value = string.sub(json, (i - 1) * CHUNK + 1, i * CHUNK)
|
|
555
|
-
sv.Parent = folder
|
|
556
|
-
end
|
|
557
|
-
folder.Parent = storage
|
|
210
|
+
function Server.resetStats(): ()
|
|
211
|
+
for player, ps in _playerStats do
|
|
212
|
+
ps.bytesSent = 0
|
|
213
|
+
ps.bytesReceived = 0
|
|
558
214
|
end
|
|
559
|
-
|
|
560
|
-
local count = #_captures
|
|
561
|
-
table.clear(_captures)
|
|
562
|
-
print(
|
|
563
|
-
`[Lync] Capture saved: {count} entries, {#json} chars -> ServerStorage.LyncCapture (open in script editor, copy contents)`
|
|
564
|
-
)
|
|
565
215
|
end
|
|
566
216
|
|
|
567
217
|
return table.freeze(Server)
|