@axpecter/lync 2.1.2 → 2.2.1
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 +241 -349
- package/package.json +1 -1
- package/src/Types.luau +53 -13
- package/src/api/Group.luau +41 -51
- package/src/api/Packet.luau +163 -152
- package/src/api/Query.luau +224 -211
- package/src/api/Scope.luau +36 -38
- package/src/api/Signal.luau +68 -94
- package/src/codec/Base.luau +43 -23
- package/src/codec/composite/Array.luau +161 -152
- package/src/codec/composite/Map.luau +37 -53
- package/src/codec/composite/Optional.luau +15 -21
- package/src/codec/composite/Shared.luau +78 -41
- package/src/codec/composite/Struct.luau +64 -125
- package/src/codec/composite/Tagged.luau +15 -25
- package/src/codec/composite/Tuple.luau +15 -20
- package/src/codec/datatype/Buffer.luau +44 -51
- package/src/codec/datatype/CFrame.luau +72 -104
- package/src/codec/datatype/Color.luau +14 -10
- package/src/codec/datatype/Instance.luau +32 -42
- package/src/codec/datatype/IntVector.luau +24 -22
- package/src/codec/datatype/NumberRange.luau +21 -12
- package/src/codec/datatype/Ray.luau +13 -7
- package/src/codec/datatype/Rect.luau +13 -7
- package/src/codec/datatype/Region.luau +25 -22
- package/src/codec/datatype/Sequence.luau +144 -118
- package/src/codec/datatype/String.luau +29 -59
- package/src/codec/datatype/UDim.luau +29 -30
- package/src/codec/datatype/Vector.luau +89 -105
- package/src/codec/meta/Auto.luau +194 -246
- package/src/codec/meta/Bitfield.luau +37 -36
- package/src/codec/meta/Custom.luau +10 -10
- package/src/codec/meta/Enum.luau +8 -11
- package/src/codec/meta/Float.luau +16 -20
- package/src/codec/meta/Nothing.luau +2 -2
- package/src/codec/meta/Unknown.luau +22 -32
- package/src/codec/primitive/Bool.luau +9 -5
- package/src/codec/primitive/Float16.luau +13 -23
- package/src/codec/primitive/Int.luau +20 -28
- package/src/codec/primitive/Number.luau +6 -10
- package/src/codec/primitive/Signed.luau +32 -0
- package/src/codec/primitive/Varint.luau +59 -28
- package/src/index.d.ts +8 -2
- package/src/init.luau +133 -143
- package/src/internal/Baseline.luau +17 -18
- package/src/internal/Channel.luau +143 -212
- package/src/internal/Middleware.luau +37 -47
- package/src/internal/Pool.luau +10 -10
- package/src/internal/Registry.luau +38 -34
- package/src/internal/Transport.luau +28 -0
- package/src/internal/Util.luau +26 -0
- package/src/transport/Bridge.luau +32 -24
- package/src/transport/Client.luau +51 -53
- package/src/transport/Gate.luau +183 -113
- package/src/transport/Reader.luau +95 -66
- package/src/transport/Server.luau +130 -125
|
@@ -10,28 +10,60 @@ local Registry = require(script.Parent.Parent.internal.Registry)
|
|
|
10
10
|
local Types = require(script.Parent.Parent.Types)
|
|
11
11
|
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
12
12
|
|
|
13
|
-
-- Constants
|
|
13
|
+
-- Constants --------------------------------------------------------------
|
|
14
14
|
|
|
15
15
|
local KIND_PACKET = Registry.KIND_PACKET
|
|
16
16
|
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
17
17
|
local KIND_RESPONSE = Registry.KIND_RESPONSE
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
local TS_FRAME = Channel.TS_FRAME
|
|
20
|
+
local TS_OFFSET = Channel.TS_OFFSET
|
|
21
|
+
local TS_FULL = Channel.TS_FULL
|
|
22
|
+
|
|
23
|
+
local MAX_ITEMS_PER_FRAME = 0xFFFF
|
|
24
|
+
|
|
25
|
+
-- Private ----------------------------------------------------------------
|
|
20
26
|
|
|
21
27
|
local band = bit32.band
|
|
22
28
|
local readu8 = buffer.readu8
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
local function recordDrop(
|
|
31
|
+
reg: Types.Registration,
|
|
32
|
+
statsEnabled: boolean,
|
|
33
|
+
player: Player?,
|
|
34
|
+
reason: string,
|
|
35
|
+
value: any
|
|
36
|
+
): ()
|
|
37
|
+
if statsEnabled then
|
|
38
|
+
reg.drops += 1
|
|
39
|
+
end
|
|
40
|
+
if player then
|
|
41
|
+
Middleware.fireDrop(player, reason, reg.name, value)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
-- Public -----------------------------------------------------------------
|
|
25
46
|
|
|
26
47
|
local Reader = {}
|
|
27
48
|
|
|
49
|
+
--[[
|
|
50
|
+
Sanity-check an inbound RemoteEvent payload. The size cap MUST NOT be
|
|
51
|
+
tied to the local `Channel.maxSize()` — sender and receiver run in
|
|
52
|
+
separate Lua VMs with independent module state, so the receiver may
|
|
53
|
+
not have called Lync.configure with the same channelMaxSize. Coupling
|
|
54
|
+
them caused server frames larger than the receiver's local default to
|
|
55
|
+
be silently dropped, which permanently desynced the XOR baseline.
|
|
56
|
+
|
|
57
|
+
Roblox enforces a per-RemoteEvent payload ceiling around 1MB; if the
|
|
58
|
+
payload reached us at all, it's already within Roblox's limit. Trust
|
|
59
|
+
that and accept anything non-empty.
|
|
60
|
+
]]
|
|
28
61
|
function Reader.decodeIncoming(data: any): buffer?
|
|
29
62
|
if typeof(data) ~= "buffer" then
|
|
30
63
|
return nil
|
|
31
64
|
end
|
|
32
65
|
local b = data :: buffer
|
|
33
|
-
|
|
34
|
-
if len == 0 or len > 65536 then
|
|
66
|
+
if buffer.len(b) == 0 then
|
|
35
67
|
return nil
|
|
36
68
|
end
|
|
37
69
|
return b
|
|
@@ -44,7 +76,6 @@ function Reader.process(
|
|
|
44
76
|
player: Player?,
|
|
45
77
|
isServer: boolean
|
|
46
78
|
): ()
|
|
47
|
-
-- Set baseline read key for delta codecs
|
|
48
79
|
if Baseline.hasDelta() then
|
|
49
80
|
Baseline.setReadKey(player or false)
|
|
50
81
|
end
|
|
@@ -54,6 +85,7 @@ function Reader.process(
|
|
|
54
85
|
local pos = 0
|
|
55
86
|
|
|
56
87
|
while pos < incomingLen do
|
|
88
|
+
local frameStart = pos
|
|
57
89
|
local raw = readu8(incoming, pos)
|
|
58
90
|
pos += 1
|
|
59
91
|
|
|
@@ -62,87 +94,85 @@ function Reader.process(
|
|
|
62
94
|
|
|
63
95
|
local reg = Registry.get(id)
|
|
64
96
|
if not reg then
|
|
65
|
-
warn(`[Lync] Reader: unknown packet ID {id} at byte {pos - 1}`)
|
|
66
|
-
|
|
97
|
+
warn(`[Lync] Reader.process: unknown packet ID {id} at byte {pos - 1}`)
|
|
98
|
+
return
|
|
67
99
|
end
|
|
68
100
|
|
|
69
101
|
if reg.kind == KIND_PACKET then
|
|
70
102
|
local count: number
|
|
71
|
-
|
|
72
103
|
if msb then
|
|
73
104
|
count = 1
|
|
74
105
|
else
|
|
106
|
+
if pos + 2 > incomingLen then
|
|
107
|
+
warn("[Lync] Reader.process: truncated multi-frame header")
|
|
108
|
+
return
|
|
109
|
+
end
|
|
75
110
|
count = buffer.readu16(incoming, pos)
|
|
76
111
|
pos += 2
|
|
77
112
|
end
|
|
78
113
|
|
|
79
|
-
-- Read timestamp if this packet has one configured
|
|
80
114
|
local tsMode = reg.timestampMode
|
|
81
|
-
local tsValue:
|
|
82
|
-
|
|
83
|
-
if tsMode == Channel.TS_FRAME then
|
|
115
|
+
local tsValue: number? = nil
|
|
116
|
+
if tsMode == TS_FRAME then
|
|
84
117
|
tsValue = readu8(incoming, pos)
|
|
85
118
|
pos += 1
|
|
86
|
-
elseif tsMode ==
|
|
119
|
+
elseif tsMode == TS_OFFSET then
|
|
87
120
|
tsValue = buffer.readu16(incoming, pos)
|
|
88
121
|
pos += 2
|
|
89
|
-
elseif tsMode ==
|
|
122
|
+
elseif tsMode == TS_FULL then
|
|
90
123
|
tsValue = buffer.readf64(incoming, pos)
|
|
91
124
|
pos += 8
|
|
92
125
|
end
|
|
93
126
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
127
|
+
if count > MAX_ITEMS_PER_FRAME then
|
|
128
|
+
count = MAX_ITEMS_PER_FRAME
|
|
129
|
+
end
|
|
130
|
+
local sizeMeta = reg.codec._size
|
|
131
|
+
if sizeMeta then
|
|
132
|
+
local maxBySize = (incomingLen - pos) // sizeMeta
|
|
133
|
+
if count > maxBySize then
|
|
134
|
+
count = maxBySize
|
|
99
135
|
end
|
|
100
136
|
end
|
|
101
137
|
|
|
138
|
+
local maxPayload = reg.maxPayloadBytes
|
|
139
|
+
local payloadStart = pos
|
|
140
|
+
|
|
102
141
|
for _ = 1, count do
|
|
103
142
|
local value, consumed = reg.codec.read(incoming, pos, refs)
|
|
143
|
+
if consumed == 0 then
|
|
144
|
+
warn(
|
|
145
|
+
`[Lync] Reader.process: codec for "{reg.name}" reported 0 bytes; aborting frame`
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
end
|
|
104
149
|
pos += consumed
|
|
105
150
|
|
|
151
|
+
if maxPayload and (pos - payloadStart) > maxPayload then
|
|
152
|
+
recordDrop(reg, statsEnabled, player, "maxPayloadBytes", value)
|
|
153
|
+
return
|
|
154
|
+
end
|
|
155
|
+
|
|
106
156
|
if isServer and reg.needsGate then
|
|
107
157
|
if player and not Gate.checkGlobalRateLimit(player) then
|
|
108
|
-
|
|
109
|
-
reg.drops += 1
|
|
110
|
-
end
|
|
111
|
-
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
158
|
+
recordDrop(reg, statsEnabled, player, "rate", value)
|
|
112
159
|
continue
|
|
113
160
|
end
|
|
114
|
-
|
|
115
161
|
if player and not Gate.checkRateLimit(reg, player) then
|
|
116
|
-
|
|
117
|
-
reg.drops += 1
|
|
118
|
-
end
|
|
119
|
-
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
162
|
+
recordDrop(reg, statsEnabled, player, "rate", value)
|
|
120
163
|
continue
|
|
121
164
|
end
|
|
122
165
|
|
|
123
|
-
local ok
|
|
166
|
+
local ok = Gate.validate(value, reg.codec)
|
|
124
167
|
if not ok then
|
|
125
|
-
|
|
126
|
-
reg.drops += 1
|
|
127
|
-
end
|
|
128
|
-
if player then
|
|
129
|
-
Middleware.fireDrop(player, "validation", reg.name, value)
|
|
130
|
-
end
|
|
168
|
+
recordDrop(reg, statsEnabled, player, "validation", value)
|
|
131
169
|
continue
|
|
132
170
|
end
|
|
133
171
|
|
|
134
|
-
if reg.validate then
|
|
135
|
-
local valid, validReason = reg.validate(value, player
|
|
172
|
+
if reg.validate and player then
|
|
173
|
+
local valid, validReason = reg.validate(value, player)
|
|
136
174
|
if not valid then
|
|
137
|
-
|
|
138
|
-
reg.drops += 1
|
|
139
|
-
end
|
|
140
|
-
Middleware.fireDrop(
|
|
141
|
-
player :: Player,
|
|
142
|
-
validReason or "validate",
|
|
143
|
-
reg.name,
|
|
144
|
-
value
|
|
145
|
-
)
|
|
175
|
+
recordDrop(reg, statsEnabled, player, validReason or "validate", value)
|
|
146
176
|
continue
|
|
147
177
|
end
|
|
148
178
|
end
|
|
@@ -164,55 +194,53 @@ function Reader.process(
|
|
|
164
194
|
end
|
|
165
195
|
|
|
166
196
|
if statsEnabled then
|
|
167
|
-
|
|
168
|
-
reg.bytesReceived += pos
|
|
197
|
+
reg.bytesReceived += (pos - frameStart)
|
|
169
198
|
end
|
|
170
199
|
elseif reg.kind == KIND_REQUEST or reg.kind == KIND_RESPONSE then
|
|
171
|
-
-- MSB distinguishes nil responses (no payload) from data responses
|
|
172
200
|
local corrId, corrBytes = Varint.read(incoming, pos)
|
|
173
201
|
pos += corrBytes
|
|
174
202
|
|
|
175
203
|
if msb then
|
|
176
204
|
if statsEnabled then
|
|
177
205
|
reg.recvFires += 1
|
|
206
|
+
reg.bytesReceived += (pos - frameStart)
|
|
178
207
|
end
|
|
179
208
|
reg.signal:fire(nil, player, corrId)
|
|
180
209
|
else
|
|
181
210
|
local value, consumed = reg.codec.read(incoming, pos, refs)
|
|
211
|
+
if consumed == 0 then
|
|
212
|
+
warn(
|
|
213
|
+
`[Lync] Reader.process: query codec for "{reg.name}" reported 0 bytes; aborting frame`
|
|
214
|
+
)
|
|
215
|
+
return
|
|
216
|
+
end
|
|
182
217
|
pos += consumed
|
|
183
218
|
|
|
184
219
|
if isServer and reg.needsGate and reg.kind == KIND_REQUEST then
|
|
185
220
|
if player and not Gate.checkRateLimit(reg, player) then
|
|
221
|
+
recordDrop(reg, statsEnabled, player, "rate", value)
|
|
186
222
|
if statsEnabled then
|
|
187
|
-
reg.
|
|
223
|
+
reg.bytesReceived += (pos - frameStart)
|
|
188
224
|
end
|
|
189
|
-
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
190
225
|
continue
|
|
191
226
|
end
|
|
192
227
|
|
|
193
|
-
local ok
|
|
228
|
+
local ok = Gate.validate(value, reg.codec)
|
|
194
229
|
if not ok then
|
|
230
|
+
recordDrop(reg, statsEnabled, player, "validation", value)
|
|
195
231
|
if statsEnabled then
|
|
196
|
-
reg.
|
|
197
|
-
end
|
|
198
|
-
if player then
|
|
199
|
-
Middleware.fireDrop(player, "validation", reg.name, value)
|
|
232
|
+
reg.bytesReceived += (pos - frameStart)
|
|
200
233
|
end
|
|
201
234
|
continue
|
|
202
235
|
end
|
|
203
236
|
|
|
204
|
-
if reg.validate then
|
|
205
|
-
local valid, validReason = reg.validate(value, player
|
|
237
|
+
if reg.validate and player then
|
|
238
|
+
local valid, validReason = reg.validate(value, player)
|
|
206
239
|
if not valid then
|
|
240
|
+
recordDrop(reg, statsEnabled, player, validReason or "validate", value)
|
|
207
241
|
if statsEnabled then
|
|
208
|
-
reg.
|
|
242
|
+
reg.bytesReceived += (pos - frameStart)
|
|
209
243
|
end
|
|
210
|
-
Middleware.fireDrop(
|
|
211
|
-
player :: Player,
|
|
212
|
-
validReason or "validate",
|
|
213
|
-
reg.name,
|
|
214
|
-
value
|
|
215
|
-
)
|
|
216
244
|
continue
|
|
217
245
|
end
|
|
218
246
|
end
|
|
@@ -224,6 +252,7 @@ function Reader.process(
|
|
|
224
252
|
|
|
225
253
|
if statsEnabled then
|
|
226
254
|
reg.recvFires += 1
|
|
255
|
+
reg.bytesReceived += (pos - frameStart)
|
|
227
256
|
end
|
|
228
257
|
reg.signal:fire(value, player, corrId)
|
|
229
258
|
end
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
3
|
-- Server-side transport: per-player channels, broadcast, flush.
|
|
4
|
+
-- Decodes inbound XOR delta against a per-player baseline.
|
|
4
5
|
|
|
5
6
|
local Players = game:GetService("Players")
|
|
7
|
+
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
6
8
|
|
|
7
9
|
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
8
10
|
local Bridge = require(script.Parent.Parent.transport.Bridge)
|
|
@@ -12,186 +14,189 @@ local Pool = require(script.Parent.Parent.internal.Pool)
|
|
|
12
14
|
local Reader = require(script.Parent.Parent.transport.Reader)
|
|
13
15
|
local Types = require(script.Parent.Parent.Types)
|
|
14
16
|
|
|
15
|
-
--
|
|
17
|
+
-- Constants --------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
local CONTAINER_NAME = "LyncRemotes"
|
|
20
|
+
|
|
21
|
+
-- State ------------------------------------------------------------------
|
|
16
22
|
|
|
17
23
|
local _reliableChannels: { [Player]: Types.ChannelState } = {}
|
|
18
24
|
local _unreliableChannels: { [Player]: Types.ChannelState } = {}
|
|
19
25
|
local _playerStats: { [Player]: Types.PlayerStats } = {}
|
|
20
|
-
local
|
|
26
|
+
local _prevIncoming: { [Player]: buffer } = {}
|
|
27
|
+
local _prevIncomingLen: { [Player]: number } = {}
|
|
21
28
|
|
|
22
|
-
|
|
29
|
+
local _statsEnabled = false
|
|
23
30
|
|
|
24
|
-
|
|
25
|
-
local ch = _reliableChannels[player]
|
|
26
|
-
if not ch then
|
|
27
|
-
ch = Pool.acquire()
|
|
28
|
-
_reliableChannels[player] = ch
|
|
29
|
-
end
|
|
30
|
-
return ch
|
|
31
|
-
end
|
|
31
|
+
-- Private ----------------------------------------------------------------
|
|
32
32
|
|
|
33
|
-
local function
|
|
34
|
-
|
|
33
|
+
local function getOrCreateChannel(
|
|
34
|
+
map: { [Player]: Types.ChannelState },
|
|
35
|
+
player: Player
|
|
36
|
+
): Types.ChannelState
|
|
37
|
+
local ch = map[player]
|
|
35
38
|
if not ch then
|
|
36
39
|
ch = Pool.acquire()
|
|
37
|
-
|
|
40
|
+
map[player] = ch
|
|
38
41
|
end
|
|
39
42
|
return ch
|
|
40
43
|
end
|
|
41
44
|
|
|
42
|
-
local function
|
|
43
|
-
-- Reliable channel
|
|
45
|
+
local function flushReliable(player: Player): ()
|
|
44
46
|
local rCh = _reliableChannels[player]
|
|
45
|
-
if rCh
|
|
46
|
-
|
|
47
|
+
if not rCh or rCh.cursor == 0 then
|
|
48
|
+
return
|
|
49
|
+
end
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
local snapshot, refs, byteLen = Channel.sealAndDump(rCh)
|
|
52
|
+
local xored = Channel.xorApply(snapshot, byteLen, rCh.prevDump, rCh.prevDumpLen)
|
|
53
|
+
rCh.prevDump = snapshot
|
|
54
|
+
rCh.prevDumpLen = byteLen
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
Bridge.fireClient(player, xored, refs)
|
|
57
|
+
Channel.reset(rCh)
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
59
|
+
if _statsEnabled then
|
|
60
|
+
local ps = _playerStats[player]
|
|
61
|
+
if ps then
|
|
62
|
+
ps.bytesSent += byteLen
|
|
61
63
|
end
|
|
62
64
|
end
|
|
65
|
+
end
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
local function flushUnreliable(player: Player): ()
|
|
65
68
|
local uCh = _unreliableChannels[player]
|
|
66
|
-
if uCh
|
|
67
|
-
|
|
69
|
+
if not uCh or uCh.cursor == 0 then
|
|
70
|
+
return
|
|
71
|
+
end
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
|
|
73
|
+
local snapshot, refs, byteLen = Channel.sealAndDump(uCh)
|
|
74
|
+
Bridge.fireClientUnreliable(player, snapshot, refs)
|
|
75
|
+
Channel.reset(uCh)
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
end
|
|
77
|
+
if _statsEnabled then
|
|
78
|
+
local ps = _playerStats[player]
|
|
79
|
+
if ps then
|
|
80
|
+
ps.bytesSent += byteLen
|
|
77
81
|
end
|
|
78
82
|
end
|
|
79
83
|
end
|
|
80
84
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
function Server.getChannel(player: Player, isUnreliable: boolean): Types.ChannelState
|
|
86
|
-
return if isUnreliable then getUnreliable(player) else getReliable(player)
|
|
85
|
+
local function flushPlayer(player: Player): ()
|
|
86
|
+
flushReliable(player)
|
|
87
|
+
flushUnreliable(player)
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
local function handleIncomingReliable(player: Player, data: any, refs: any?): ()
|
|
91
|
+
local incoming = Reader.decodeIncoming(data)
|
|
92
|
+
if not incoming then
|
|
93
|
+
return
|
|
92
94
|
end
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
local incomingLen = buffer.len(incoming)
|
|
97
|
+
if not Gate.checkBandwidth(player, incomingLen) then
|
|
98
|
+
return
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
local prev = _prevIncoming[player]
|
|
102
|
+
local decoded = if prev
|
|
103
|
+
then Channel.xorApply(incoming, incomingLen, prev, _prevIncomingLen[player])
|
|
104
|
+
else incoming
|
|
105
|
+
_prevIncoming[player] = decoded
|
|
106
|
+
_prevIncomingLen[player] = incomingLen
|
|
107
|
+
|
|
108
|
+
local ok, err = pcall(Reader.process, decoded, incomingLen, refs, player, true)
|
|
109
|
+
if not ok then
|
|
110
|
+
warn(`[Lync] Server.receive: {player.Name}: {err}`)
|
|
96
111
|
end
|
|
97
112
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
113
|
+
if _statsEnabled then
|
|
114
|
+
local ps = _playerStats[player]
|
|
115
|
+
if ps then
|
|
116
|
+
ps.bytesReceived += incomingLen
|
|
101
117
|
end
|
|
102
118
|
end
|
|
103
119
|
end
|
|
104
120
|
|
|
105
|
-
function
|
|
106
|
-
|
|
107
|
-
|
|
121
|
+
local function handleIncomingUnreliable(player: Player, data: any, refs: any?): ()
|
|
122
|
+
local incoming = Reader.decodeIncoming(data)
|
|
123
|
+
if not incoming then
|
|
124
|
+
return
|
|
125
|
+
end
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
local
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
local incomingLen = buffer.len(incoming)
|
|
128
|
+
local ok, err = pcall(Reader.process, incoming, incomingLen, refs, player, true)
|
|
129
|
+
if not ok then
|
|
130
|
+
warn(`[Lync] Server.receiveUnreliable: {player.Name}: {err}`)
|
|
131
|
+
end
|
|
113
132
|
|
|
114
|
-
|
|
133
|
+
if _statsEnabled then
|
|
134
|
+
local ps = _playerStats[player]
|
|
135
|
+
if ps then
|
|
136
|
+
ps.bytesReceived += incomingLen
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
115
140
|
|
|
116
|
-
|
|
117
|
-
local
|
|
141
|
+
local function clearPlayerState(player: Player): ()
|
|
142
|
+
local rCh = _reliableChannels[player]
|
|
143
|
+
if rCh then
|
|
144
|
+
_reliableChannels[player] = nil
|
|
145
|
+
Pool.release(rCh)
|
|
146
|
+
end
|
|
147
|
+
local uCh = _unreliableChannels[player]
|
|
148
|
+
if uCh then
|
|
149
|
+
_unreliableChannels[player] = nil
|
|
150
|
+
Pool.release(uCh)
|
|
151
|
+
end
|
|
118
152
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if not incoming then
|
|
123
|
-
return
|
|
124
|
-
end
|
|
153
|
+
_playerStats[player] = nil
|
|
154
|
+
_prevIncoming[player] = nil
|
|
155
|
+
_prevIncomingLen[player] = nil
|
|
125
156
|
|
|
126
|
-
|
|
157
|
+
Gate.clearPlayer(player)
|
|
158
|
+
Baseline.clearPlayer(player)
|
|
159
|
+
end
|
|
127
160
|
|
|
128
|
-
|
|
129
|
-
if not Gate.checkBandwidth(player, incomingLen) then
|
|
130
|
-
return
|
|
131
|
-
end
|
|
161
|
+
-- Public -----------------------------------------------------------------
|
|
132
162
|
|
|
133
|
-
|
|
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
|
|
163
|
+
local Server = {}
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
end)
|
|
165
|
+
function Server.getChannel(player: Player, isUnreliable: boolean): Types.ChannelState
|
|
166
|
+
return getOrCreateChannel(
|
|
167
|
+
if isUnreliable then _unreliableChannels else _reliableChannels,
|
|
168
|
+
player
|
|
169
|
+
)
|
|
170
|
+
end
|
|
146
171
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
172
|
+
function Server.flush(): ()
|
|
173
|
+
if Channel.hasTimestamps() then
|
|
174
|
+
Channel.updateTimestamp()
|
|
175
|
+
end
|
|
176
|
+
for _, player in Players:GetPlayers() do
|
|
177
|
+
flushPlayer(player)
|
|
178
|
+
end
|
|
179
|
+
end
|
|
153
180
|
|
|
154
|
-
|
|
181
|
+
Server.flushPlayer = flushPlayer
|
|
155
182
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
183
|
+
function Server.start(): ()
|
|
184
|
+
local container = Instance.new("Folder")
|
|
185
|
+
container.Name = CONTAINER_NAME
|
|
186
|
+
container.Parent = ReplicatedStorage
|
|
160
187
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end)
|
|
188
|
+
Bridge.setup(container)
|
|
189
|
+
|
|
190
|
+
Bridge.reliable().OnServerEvent:Connect(handleIncomingReliable)
|
|
191
|
+
Bridge.unreliable().OnServerEvent:Connect(handleIncomingUnreliable)
|
|
168
192
|
|
|
169
|
-
-- Player lifecycle
|
|
170
193
|
Players.PlayerAdded:Connect(function(player: Player)
|
|
171
194
|
if _statsEnabled then
|
|
172
195
|
_playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
|
|
173
196
|
end
|
|
174
197
|
end)
|
|
198
|
+
Players.PlayerRemoving:Connect(clearPlayerState)
|
|
175
199
|
|
|
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
|
|
182
|
-
|
|
183
|
-
local uCh = _unreliableChannels[player]
|
|
184
|
-
if uCh then
|
|
185
|
-
_unreliableChannels[player] = nil
|
|
186
|
-
Pool.release(uCh)
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
_playerStats[player] = nil
|
|
190
|
-
Gate.clearPlayer(player)
|
|
191
|
-
Baseline.clearPlayer(player)
|
|
192
|
-
end)
|
|
193
|
-
|
|
194
|
-
-- Initialize stats for existing players
|
|
195
200
|
if _statsEnabled then
|
|
196
201
|
for _, player in Players:GetPlayers() do
|
|
197
202
|
_playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
|
|
@@ -208,7 +213,7 @@ function Server.getPlayerStats(player: Player): Types.PlayerStats?
|
|
|
208
213
|
end
|
|
209
214
|
|
|
210
215
|
function Server.resetStats(): ()
|
|
211
|
-
for
|
|
216
|
+
for _, ps in _playerStats do
|
|
212
217
|
ps.bytesSent = 0
|
|
213
218
|
ps.bytesReceived = 0
|
|
214
219
|
end
|