@axpecter/lync 1.5.1 → 2.0.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 +105 -12
- package/package.json +2 -2
- package/src/Types.luau +10 -0
- package/src/api/Namespace.luau +8 -3
- package/src/api/Packet.luau +76 -43
- package/src/api/Query.luau +35 -14
- package/src/api/Signal.luau +4 -0
- package/src/codec/composite/Array.luau +85 -6
- package/src/codec/composite/Map.luau +7 -1
- package/src/codec/composite/Optional.luau +1 -0
- package/src/codec/composite/Struct.luau +23 -1
- package/src/codec/composite/Tagged.luau +11 -0
- package/src/codec/composite/Tuple.luau +10 -0
- package/src/codec/datatype/Instance.luau +3 -0
- package/src/codec/meta/Unknown.luau +1 -0
- package/src/index.d.ts +36 -7
- package/src/init.luau +49 -2
- package/src/internal/Channel.luau +172 -42
- package/src/internal/Registry.luau +20 -3
- package/src/transport/Client.luau +65 -11
- package/src/transport/Gate.luau +9 -0
- package/src/transport/Reader.luau +75 -24
- package/src/transport/Server.luau +273 -68
|
@@ -12,20 +12,29 @@ local Gate = require(script.Parent.Gate)
|
|
|
12
12
|
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
13
13
|
local Pool = require(script.Parent.Parent.internal.Pool)
|
|
14
14
|
local Reader = require(script.Parent.Reader)
|
|
15
|
+
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
15
16
|
local Types = require(script.Parent.Parent.Types)
|
|
16
17
|
|
|
17
18
|
type ChannelState = Types.ChannelState
|
|
18
19
|
type Codec<T> = Types.Codec<T>
|
|
20
|
+
type Registration = Types.Registration
|
|
19
21
|
|
|
20
22
|
local channelWriteBatch = Channel.writeBatch
|
|
21
23
|
local channelWriteBatchRaw = Channel.writeBatchRaw
|
|
22
|
-
local
|
|
24
|
+
local channelUpdateTimestamp = Channel.updateTimestamp
|
|
25
|
+
local channelStatsEnabled = Channel.statsEnabled
|
|
23
26
|
|
|
24
27
|
-- State ---------------------------------------------------------------
|
|
25
28
|
|
|
29
|
+
type PlayerStats = {
|
|
30
|
+
bytesSent: number,
|
|
31
|
+
bytesReceived: number,
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
type PlayerState = {
|
|
27
35
|
reliable: ChannelState,
|
|
28
36
|
unreliable: ChannelState,
|
|
37
|
+
stats: PlayerStats,
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
local _players = {} :: { [Player]: PlayerState }
|
|
@@ -33,13 +42,47 @@ local _prevRecv = {} :: { [Player]: buffer }
|
|
|
33
42
|
local _isStarted = false
|
|
34
43
|
local _broadScratch = Channel.create()
|
|
35
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
|
|
76
|
+
|
|
36
77
|
-- Private -------------------------------------------------------------
|
|
37
78
|
|
|
38
79
|
local function onPlayerAdded(player: Player): ()
|
|
39
80
|
_players[player] = {
|
|
40
81
|
reliable = Pool.acquire(),
|
|
41
82
|
unreliable = Pool.acquire(),
|
|
83
|
+
stats = { bytesSent = 0, bytesReceived = 0 },
|
|
42
84
|
}
|
|
85
|
+
_strikes[player] = 0
|
|
43
86
|
Gate.onPlayerAdded(player)
|
|
44
87
|
end
|
|
45
88
|
|
|
@@ -53,6 +96,7 @@ local function onPlayerRemoving(player: Player): ()
|
|
|
53
96
|
Pool.release(state.unreliable)
|
|
54
97
|
_players[player] = nil
|
|
55
98
|
_prevRecv[player] = nil
|
|
99
|
+
_strikes[player] = nil
|
|
56
100
|
|
|
57
101
|
Gate.onPlayerRemoved(player)
|
|
58
102
|
Baseline.clearSource(player)
|
|
@@ -64,6 +108,19 @@ local function receive(player: Player, data: any, refs: any, applyXor: boolean):
|
|
|
64
108
|
return
|
|
65
109
|
end
|
|
66
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
|
+
|
|
67
124
|
local raw: buffer
|
|
68
125
|
if applyXor then
|
|
69
126
|
raw = Channel.xorApply(buf, _prevRecv[player])
|
|
@@ -76,6 +133,15 @@ local function receive(player: Player, data: any, refs: any, applyXor: boolean):
|
|
|
76
133
|
local ok, err = pcall(Reader.process, raw, safeRefs, player, 0)
|
|
77
134
|
if not ok then
|
|
78
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
|
|
79
145
|
end
|
|
80
146
|
end
|
|
81
147
|
|
|
@@ -86,18 +152,44 @@ local function flushChannel(remote: any, ch: ChannelState, target: Player, apply
|
|
|
86
152
|
|
|
87
153
|
local raw, refs = Channel.sealAndDump(ch)
|
|
88
154
|
|
|
155
|
+
local xored: buffer? = nil
|
|
89
156
|
if applyXor then
|
|
90
|
-
|
|
157
|
+
xored = Channel.xorApply(raw, ch.prevDump)
|
|
91
158
|
ch.prevDump = raw
|
|
92
159
|
remote:FireClient(target, xored, refs)
|
|
93
160
|
else
|
|
94
161
|
remote:FireClient(target, raw, refs)
|
|
95
162
|
end
|
|
96
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
|
|
176
|
+
|
|
177
|
+
if channelStatsEnabled() then
|
|
178
|
+
local state = _players[target]
|
|
179
|
+
if state then
|
|
180
|
+
state.stats.bytesSent += buffer.len(raw)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
97
184
|
Channel.reset(ch)
|
|
98
185
|
end
|
|
99
186
|
|
|
100
187
|
local function flush(): ()
|
|
188
|
+
if Channel.hasTimestamps() then
|
|
189
|
+
_frameCounter = (_frameCounter + 1) % 256
|
|
190
|
+
channelUpdateTimestamp(_frameCounter, 0, os.clock())
|
|
191
|
+
end
|
|
192
|
+
|
|
101
193
|
local reliable = Bridge.reliable
|
|
102
194
|
local unreliable = Bridge.unreliable
|
|
103
195
|
|
|
@@ -116,31 +208,22 @@ local function flush(): ()
|
|
|
116
208
|
end
|
|
117
209
|
end
|
|
118
210
|
|
|
119
|
-
--
|
|
120
|
-
local function serializeBroadcast(
|
|
121
|
-
name: string,
|
|
122
|
-
codec: Codec<any>,
|
|
123
|
-
data: any
|
|
124
|
-
): (number, { Instance }?)
|
|
211
|
+
-- No timestamp here: writeBatchRaw handles it in the batch header.
|
|
212
|
+
local function serializeBroadcast(reg: Registration, data: any): (number, { Instance }?)
|
|
125
213
|
local scratch = _broadScratch
|
|
126
214
|
scratch.cursor = 0
|
|
127
215
|
table.clear(scratch.refs)
|
|
128
|
-
|
|
129
|
-
codec.write(scratch, data)
|
|
216
|
+
Channel.setCurrentPacket(reg.name)
|
|
217
|
+
reg.codec.write(scratch, data)
|
|
130
218
|
|
|
131
219
|
local snapRefs = if #scratch.refs > 0 then table.clone(scratch.refs) else nil
|
|
132
220
|
return scratch.cursor, snapRefs
|
|
133
221
|
end
|
|
134
222
|
|
|
135
|
-
|
|
136
|
-
local function prepareBroadcast(
|
|
137
|
-
name: string,
|
|
138
|
-
codec: Codec<any>,
|
|
139
|
-
data: any
|
|
140
|
-
): (any, number, { Instance }?)
|
|
223
|
+
local function prepareBroadcast(reg: Registration, data: any): (any, number, { Instance }?)
|
|
141
224
|
local final: any
|
|
142
225
|
if Middleware.hasSend then
|
|
143
|
-
final = Middleware.runSend(data, name, nil)
|
|
226
|
+
final = Middleware.runSend(data, reg.name, nil)
|
|
144
227
|
if final == nil then
|
|
145
228
|
return nil, 0, nil
|
|
146
229
|
end
|
|
@@ -148,81 +231,107 @@ local function prepareBroadcast(
|
|
|
148
231
|
final = data
|
|
149
232
|
end
|
|
150
233
|
|
|
151
|
-
local snapLen, snapRefs = serializeBroadcast(
|
|
234
|
+
local snapLen, snapRefs = serializeBroadcast(reg, final)
|
|
152
235
|
return final, snapLen, snapRefs
|
|
153
236
|
end
|
|
154
237
|
|
|
155
|
-
--
|
|
238
|
+
-- Broadcast fan-out: stats computed once from snapLen, not per-player (#7)
|
|
156
239
|
local function broadcastToAll(
|
|
157
|
-
|
|
158
|
-
name: string,
|
|
159
|
-
codec: Codec<any>,
|
|
240
|
+
reg: Registration,
|
|
160
241
|
data: any,
|
|
161
242
|
isUnreliable: boolean,
|
|
162
243
|
exceptSet: { [Player]: true }?
|
|
163
244
|
): ()
|
|
164
|
-
local final, snapLen, snapRefs = prepareBroadcast(
|
|
245
|
+
local final, snapLen, snapRefs = prepareBroadcast(reg, data)
|
|
165
246
|
if final == nil and data ~= nil then
|
|
166
247
|
return
|
|
167
248
|
end
|
|
168
249
|
|
|
169
250
|
local scratchBuff = _broadScratch.buff
|
|
251
|
+
local count = 0
|
|
170
252
|
for player, state in _players do
|
|
171
253
|
if exceptSet and exceptSet[player] then
|
|
172
254
|
continue
|
|
173
255
|
end
|
|
174
256
|
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
175
|
-
channelWriteBatchRaw(ch,
|
|
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
|
|
176
269
|
end
|
|
177
270
|
end
|
|
178
271
|
|
|
179
|
-
-- Fan out to a specific player list.
|
|
180
272
|
local function broadcastToList(
|
|
181
273
|
players: { Player },
|
|
182
|
-
|
|
183
|
-
name: string,
|
|
184
|
-
codec: Codec<any>,
|
|
274
|
+
reg: Registration,
|
|
185
275
|
data: any,
|
|
186
276
|
isUnreliable: boolean
|
|
187
277
|
): ()
|
|
188
|
-
local final, snapLen, snapRefs = prepareBroadcast(
|
|
278
|
+
local final, snapLen, snapRefs = prepareBroadcast(reg, data)
|
|
189
279
|
if final == nil and data ~= nil then
|
|
190
280
|
return
|
|
191
281
|
end
|
|
192
282
|
|
|
193
283
|
local scratchBuff = _broadScratch.buff
|
|
284
|
+
local count = 0
|
|
194
285
|
for _, player in players do
|
|
195
286
|
local state = _players[player]
|
|
196
287
|
if not state then
|
|
197
288
|
continue
|
|
198
289
|
end
|
|
199
290
|
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
200
|
-
channelWriteBatchRaw(ch,
|
|
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
|
|
201
302
|
end
|
|
202
303
|
end
|
|
203
304
|
|
|
204
|
-
-- Fan out to a group set { [Player]: true }.
|
|
205
305
|
local function broadcastToSet(
|
|
206
306
|
set: { [Player]: true },
|
|
207
|
-
|
|
208
|
-
name: string,
|
|
209
|
-
codec: Codec<any>,
|
|
307
|
+
reg: Registration,
|
|
210
308
|
data: any,
|
|
211
309
|
isUnreliable: boolean
|
|
212
310
|
): ()
|
|
213
|
-
local final, snapLen, snapRefs = prepareBroadcast(
|
|
311
|
+
local final, snapLen, snapRefs = prepareBroadcast(reg, data)
|
|
214
312
|
if final == nil and data ~= nil then
|
|
215
313
|
return
|
|
216
314
|
end
|
|
217
315
|
|
|
218
316
|
local scratchBuff = _broadScratch.buff
|
|
317
|
+
local count = 0
|
|
219
318
|
for player in set do
|
|
220
319
|
local state = _players[player]
|
|
221
320
|
if not state then
|
|
222
321
|
continue
|
|
223
322
|
end
|
|
224
323
|
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
225
|
-
channelWriteBatchRaw(ch,
|
|
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
|
|
226
335
|
end
|
|
227
336
|
end
|
|
228
337
|
|
|
@@ -250,17 +359,32 @@ function Server.start(): ()
|
|
|
250
359
|
Bridge.unreliable.OnServerEvent:Connect(function(player: Player, data: any, refs: any): ()
|
|
251
360
|
receive(player, data, refs, false)
|
|
252
361
|
end)
|
|
253
|
-
|
|
362
|
+
|
|
363
|
+
if _useAccumulator then
|
|
364
|
+
RunService.Heartbeat:Connect(function(dt: number)
|
|
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
|
|
254
385
|
end
|
|
255
386
|
|
|
256
|
-
function Server.writeTo(
|
|
257
|
-
player: Player,
|
|
258
|
-
id: number,
|
|
259
|
-
name: string,
|
|
260
|
-
codec: Codec<any>,
|
|
261
|
-
data: any,
|
|
262
|
-
isUnreliable: boolean
|
|
263
|
-
): ()
|
|
387
|
+
function Server.writeTo(player: Player, reg: Registration, data: any, isUnreliable: boolean): ()
|
|
264
388
|
local state = _players[player]
|
|
265
389
|
if not state then
|
|
266
390
|
return
|
|
@@ -268,7 +392,7 @@ function Server.writeTo(
|
|
|
268
392
|
|
|
269
393
|
local final: any
|
|
270
394
|
if Middleware.hasSend then
|
|
271
|
-
final = Middleware.runSend(data, name, player)
|
|
395
|
+
final = Middleware.runSend(data, reg.name, player)
|
|
272
396
|
if final == nil then
|
|
273
397
|
return
|
|
274
398
|
end
|
|
@@ -277,53 +401,47 @@ function Server.writeTo(
|
|
|
277
401
|
end
|
|
278
402
|
|
|
279
403
|
local ch = if isUnreliable then state.unreliable else state.reliable
|
|
280
|
-
|
|
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
|
|
281
412
|
end
|
|
282
413
|
|
|
283
|
-
function Server.writeToAll(
|
|
284
|
-
|
|
285
|
-
name: string,
|
|
286
|
-
codec: Codec<any>,
|
|
287
|
-
data: any,
|
|
288
|
-
isUnreliable: boolean
|
|
289
|
-
): ()
|
|
290
|
-
broadcastToAll(id, name, codec, data, isUnreliable, nil)
|
|
414
|
+
function Server.writeToAll(reg: Registration, data: any, isUnreliable: boolean): ()
|
|
415
|
+
broadcastToAll(reg, data, isUnreliable, nil)
|
|
291
416
|
end
|
|
292
417
|
|
|
293
418
|
function Server.writeToList(
|
|
294
419
|
players: { Player },
|
|
295
|
-
|
|
296
|
-
name: string,
|
|
297
|
-
codec: Codec<any>,
|
|
420
|
+
reg: Registration,
|
|
298
421
|
data: any,
|
|
299
422
|
isUnreliable: boolean
|
|
300
423
|
): ()
|
|
301
|
-
broadcastToList(players,
|
|
424
|
+
broadcastToList(players, reg, data, isUnreliable)
|
|
302
425
|
end
|
|
303
426
|
|
|
304
427
|
function Server.writeToAllExcept(
|
|
305
428
|
exceptSet: { [Player]: true },
|
|
306
|
-
|
|
307
|
-
name: string,
|
|
308
|
-
codec: Codec<any>,
|
|
429
|
+
reg: Registration,
|
|
309
430
|
data: any,
|
|
310
431
|
isUnreliable: boolean
|
|
311
432
|
): ()
|
|
312
|
-
broadcastToAll(
|
|
433
|
+
broadcastToAll(reg, data, isUnreliable, exceptSet)
|
|
313
434
|
end
|
|
314
435
|
|
|
315
436
|
function Server.writeToSet(
|
|
316
437
|
set: { [Player]: true },
|
|
317
|
-
|
|
318
|
-
name: string,
|
|
319
|
-
codec: Codec<any>,
|
|
438
|
+
reg: Registration,
|
|
320
439
|
data: any,
|
|
321
440
|
isUnreliable: boolean
|
|
322
441
|
): ()
|
|
323
|
-
broadcastToSet(set,
|
|
442
|
+
broadcastToSet(set, reg, data, isUnreliable)
|
|
324
443
|
end
|
|
325
444
|
|
|
326
|
-
-- Shared for both query requests and responses(identical wire format).
|
|
327
445
|
function Server.writeQuery(
|
|
328
446
|
player: Player,
|
|
329
447
|
id: number,
|
|
@@ -359,4 +477,91 @@ function Server.writeQueryNil(player: Player, id: number, name: string, correlat
|
|
|
359
477
|
Channel.writeQuery(state.reliable, id, name, correlationId, nil, nil)
|
|
360
478
|
end
|
|
361
479
|
|
|
480
|
+
function Server.flush(): ()
|
|
481
|
+
_flushAccum = 0
|
|
482
|
+
_flushedThisFrame = true
|
|
483
|
+
flush()
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
function Server.setFlushRate(hz: number): ()
|
|
487
|
+
_flushRate = math.clamp(hz, 1, 60)
|
|
488
|
+
_flushInterval = 1 / _flushRate
|
|
489
|
+
_useAccumulator = _flushRate < 60
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
function Server.getFlushRate(): number
|
|
493
|
+
return _flushRate
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
function Server.setBandwidthLimit(softLimit: number, maxStrikes: number): ()
|
|
497
|
+
_softLimit = softLimit
|
|
498
|
+
_maxStrikes = maxStrikes
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
function Server.getPlayerStats(player: Player): PlayerStats?
|
|
502
|
+
local state = _players[player]
|
|
503
|
+
return if state then state.stats else nil
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
function Server.resetStats(): ()
|
|
507
|
+
for _, state in _players do
|
|
508
|
+
state.stats.bytesSent = 0
|
|
509
|
+
state.stats.bytesReceived = 0
|
|
510
|
+
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
|
+
end
|
|
519
|
+
|
|
520
|
+
function Server.startCapture(label: string?): ()
|
|
521
|
+
_captureEnabled = true
|
|
522
|
+
_captureLabel = label or "unknown"
|
|
523
|
+
_captureFrame = 0
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
function Server.stopCapture(): ()
|
|
527
|
+
_captureEnabled = false
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
function Server.dumpCaptures(): ()
|
|
531
|
+
local json = game:GetService("HttpService"):JSONEncode(_captures)
|
|
532
|
+
local storage = game:GetService("ServerStorage")
|
|
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
|
|
558
|
+
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
|
+
end
|
|
566
|
+
|
|
362
567
|
return table.freeze(Server)
|