@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.
- package/README.md +499 -357
- package/package.json +2 -2
- package/src/Types.luau +69 -27
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +191 -157
- package/src/api/Query.luau +223 -282
- 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 +189 -201
- package/src/codec/composite/Map.luau +97 -341
- package/src/codec/composite/Optional.luau +27 -23
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +201 -339
- package/src/codec/composite/Tagged.luau +64 -178
- package/src/codec/composite/Tuple.luau +81 -95
- 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 +29 -19
- 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 -35
- 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 -306
- package/src/init.luau +332 -173
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +346 -161
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +61 -101
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +60 -117
- package/src/transport/Gate.luau +282 -133
- package/src/transport/Reader.luau +159 -100
- package/src/transport/Server.luau +147 -292
- package/src/api/Namespace.luau +0 -251
- package/src/codec/meta/Quantized.luau +0 -174
|
@@ -1,362 +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)
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
62
|
-
local
|
|
63
|
-
if not
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
101
|
-
local
|
|
102
|
-
|
|
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
|
-
|
|
105
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
--
|
|
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
|
-
|
|
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
|
-
|
|
152
|
-
return
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
-
|
|
170
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
109
|
+
function Server.start(): ()
|
|
110
|
+
local container = Instance.new("Folder")
|
|
111
|
+
container.Name = "LyncRemotes"
|
|
112
|
+
container.Parent = game:GetService("ReplicatedStorage")
|
|
230
113
|
|
|
231
|
-
|
|
114
|
+
Bridge.setup(container)
|
|
232
115
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return
|
|
236
|
-
end
|
|
237
|
-
_isStarted = true
|
|
116
|
+
local reliable = Bridge.reliable()
|
|
117
|
+
local unreliable = Bridge.unreliable()
|
|
238
118
|
|
|
239
|
-
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
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
|
|
273
151
|
return
|
|
274
152
|
end
|
|
275
|
-
else
|
|
276
|
-
final = data
|
|
277
|
-
end
|
|
278
153
|
|
|
279
|
-
|
|
280
|
-
channelWriteBatch(ch, id, name, codec, final)
|
|
281
|
-
end
|
|
154
|
+
local incomingLen = buffer.len(incoming)
|
|
282
155
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
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
|
-
|
|
202
|
+
function Server.enableStats(): ()
|
|
203
|
+
_statsEnabled = true
|
|
351
204
|
end
|
|
352
205
|
|
|
353
|
-
function Server.
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
return
|
|
357
|
-
end
|
|
206
|
+
function Server.getPlayerStats(player: Player): Types.PlayerStats?
|
|
207
|
+
return _playerStats[player]
|
|
208
|
+
end
|
|
358
209
|
|
|
359
|
-
|
|
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)
|