@axpecter/lync 2.3.2 → 3.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/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- package/src/util/Quat.luau +0 -124
|
@@ -1,331 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!native
|
|
3
|
-
-- Inbound buffer parser. Returns false on malformed wire so the transport can drop its XOR baseline.
|
|
4
|
-
|
|
5
|
-
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
6
|
-
local Channel = require(script.Parent.Parent.internal.Channel)
|
|
7
|
-
local Constants = require(script.Parent.Parent.util.Constants)
|
|
8
|
-
local Gate = require(script.Parent.Parent.transport.Gate)
|
|
9
|
-
local Log = require(script.Parent.Parent.util.Log)
|
|
10
|
-
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
11
|
-
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
12
|
-
local Types = require(script.Parent.Parent.Types)
|
|
13
|
-
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
14
|
-
|
|
15
|
-
-- Constants --------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
local KIND_PACKET = Registry.KIND_PACKET
|
|
18
|
-
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
19
|
-
local KIND_RESPONSE = Registry.KIND_RESPONSE
|
|
20
|
-
|
|
21
|
-
local TS_FRAME = Channel.TS_FRAME
|
|
22
|
-
local TS_OFFSET = Channel.TS_OFFSET
|
|
23
|
-
local TS_FULL = Channel.TS_FULL
|
|
24
|
-
|
|
25
|
-
local FRAME_MSB = Constants.FRAME_MSB
|
|
26
|
-
local FRAME_ID_MASK = Constants.FRAME_ID_MASK
|
|
27
|
-
|
|
28
|
-
-- Private ----------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
local band = bit32.band
|
|
31
|
-
local readu8 = buffer.readu8
|
|
32
|
-
local readu16 = buffer.readu16
|
|
33
|
-
local readf64 = buffer.readf64
|
|
34
|
-
local registryGet = Registry.get
|
|
35
|
-
local checkGlobal = Gate.checkGlobalRateLimit
|
|
36
|
-
local checkRate = Gate.checkRateLimit
|
|
37
|
-
local validateGate = Gate.validate
|
|
38
|
-
local fireDrop = Middleware.fireDrop
|
|
39
|
-
local runReceive = Middleware.runReceive
|
|
40
|
-
local varintRead = Varint.read
|
|
41
|
-
|
|
42
|
-
local function recordDrop(
|
|
43
|
-
reg: Types.Registration,
|
|
44
|
-
statsEnabled: boolean,
|
|
45
|
-
player: Player?,
|
|
46
|
-
reason: string,
|
|
47
|
-
value: any
|
|
48
|
-
): ()
|
|
49
|
-
if statsEnabled then
|
|
50
|
-
reg.drops += 1
|
|
51
|
-
end
|
|
52
|
-
if player then
|
|
53
|
-
fireDrop(player, reason, reg.name, value)
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
--[[
|
|
58
|
-
Server gate pipeline. Returns true to deliver, false on rate-limit,
|
|
59
|
-
schema-validate, or custom-validate rejection. `runGlobal` lets the
|
|
60
|
-
response path skip the global per-player limit.
|
|
61
|
-
]]
|
|
62
|
-
local function gateValue(
|
|
63
|
-
reg: Types.Registration,
|
|
64
|
-
regCodec: Types.InternalCodec<any>,
|
|
65
|
-
value: any,
|
|
66
|
-
player: Player,
|
|
67
|
-
statsEnabled: boolean,
|
|
68
|
-
runGlobal: boolean
|
|
69
|
-
): boolean
|
|
70
|
-
-- Both global and per-packet drops use the "rate" reason; collapsed into one branch.
|
|
71
|
-
if (runGlobal and not checkGlobal(player)) or not checkRate(reg, player) then
|
|
72
|
-
recordDrop(reg, statsEnabled, player, "rate", value)
|
|
73
|
-
return false
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
local validateReason = validateGate(value, regCodec)
|
|
77
|
-
if validateReason then
|
|
78
|
-
recordDrop(reg, statsEnabled, player, `validation: {validateReason}`, value)
|
|
79
|
-
return false
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
if reg.validate then
|
|
83
|
-
local valid, validReason = reg.validate(value, player)
|
|
84
|
-
if not valid then
|
|
85
|
-
recordDrop(
|
|
86
|
-
reg,
|
|
87
|
-
statsEnabled,
|
|
88
|
-
player,
|
|
89
|
-
if validReason then `validate: {validReason}` else "validate",
|
|
90
|
-
value
|
|
91
|
-
)
|
|
92
|
-
return false
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
return true
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
-- Public -----------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
local Reader = {}
|
|
101
|
-
|
|
102
|
-
--[[
|
|
103
|
-
Sanity-check an inbound payload. Size cap is delegated to Roblox's ~1MB
|
|
104
|
-
RemoteEvent ceiling: the receiver may not have called Lync.configure
|
|
105
|
-
with the same channelMaxSize as the sender (independent VMs, independent
|
|
106
|
-
module state), so a local cap here would reject legitimate frames.
|
|
107
|
-
]]
|
|
108
|
-
function Reader.decodeIncoming(data: any): buffer?
|
|
109
|
-
if typeof(data) ~= "buffer" then
|
|
110
|
-
return nil
|
|
111
|
-
end
|
|
112
|
-
local b = data :: buffer
|
|
113
|
-
if buffer.len(b) == 0 then
|
|
114
|
-
return nil
|
|
115
|
-
end
|
|
116
|
-
return b
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
--[[
|
|
120
|
-
Returns true on full success, false if a malformed frame aborted the
|
|
121
|
-
parse. Transport callers use this to decide whether to keep the per-peer
|
|
122
|
-
XOR baseline: false means the buffer is suspect and would permanently
|
|
123
|
-
desync the channel if reused as the next XOR mask.
|
|
124
|
-
]]
|
|
125
|
-
function Reader.process(
|
|
126
|
-
incoming: buffer,
|
|
127
|
-
incomingLen: number,
|
|
128
|
-
refs: { Instance }?,
|
|
129
|
-
player: Player?,
|
|
130
|
-
isServer: boolean
|
|
131
|
-
): boolean
|
|
132
|
-
if Baseline.hasDelta() then
|
|
133
|
-
Baseline.setReadKey(player or false)
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
local statsEnabled = Channel.statsEnabled()
|
|
137
|
-
local hasRecvHooks = Middleware.hasReceiveHooks()
|
|
138
|
-
local pos = 0
|
|
139
|
-
|
|
140
|
-
while pos < incomingLen do
|
|
141
|
-
local frameStart = pos
|
|
142
|
-
local raw = readu8(incoming, pos)
|
|
143
|
-
pos += 1
|
|
144
|
-
|
|
145
|
-
local msb = band(raw, FRAME_MSB) ~= 0
|
|
146
|
-
local id = band(raw, FRAME_ID_MASK)
|
|
147
|
-
|
|
148
|
-
local reg = registryGet(id)
|
|
149
|
-
if not reg then
|
|
150
|
-
Log.warn(`unknown packet ID {id} at byte {pos - 1}`)
|
|
151
|
-
return false
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
local kind = reg.kind
|
|
155
|
-
if kind == KIND_PACKET then
|
|
156
|
-
local count: number
|
|
157
|
-
if msb then
|
|
158
|
-
count = 1
|
|
159
|
-
else
|
|
160
|
-
if pos + 2 > incomingLen then
|
|
161
|
-
Log.warn("truncated multi-frame header")
|
|
162
|
-
return false
|
|
163
|
-
end
|
|
164
|
-
count = readu16(incoming, pos)
|
|
165
|
-
pos += 2
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
local tsMode = reg.timestampMode
|
|
169
|
-
local tsValue: number? = nil
|
|
170
|
-
if tsMode == TS_FRAME then
|
|
171
|
-
if pos + 1 > incomingLen then
|
|
172
|
-
Log.warn("truncated timestamp")
|
|
173
|
-
return false
|
|
174
|
-
end
|
|
175
|
-
tsValue = readu8(incoming, pos)
|
|
176
|
-
pos += 1
|
|
177
|
-
elseif tsMode == TS_OFFSET then
|
|
178
|
-
if pos + 2 > incomingLen then
|
|
179
|
-
Log.warn("truncated timestamp")
|
|
180
|
-
return false
|
|
181
|
-
end
|
|
182
|
-
tsValue = readu16(incoming, pos)
|
|
183
|
-
pos += 2
|
|
184
|
-
elseif tsMode == TS_FULL then
|
|
185
|
-
if pos + 8 > incomingLen then
|
|
186
|
-
Log.warn("truncated timestamp")
|
|
187
|
-
return false
|
|
188
|
-
end
|
|
189
|
-
tsValue = readf64(incoming, pos)
|
|
190
|
-
pos += 8
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
--[[
|
|
194
|
-
Fixed-size codec: a count exceeding remaining bytes is
|
|
195
|
-
unambiguously corrupt. Abort to keep the cursor on a frame
|
|
196
|
-
boundary so the transport can reset cleanly.
|
|
197
|
-
]]
|
|
198
|
-
local regCodec = reg.codec
|
|
199
|
-
local sizeMeta = regCodec._size
|
|
200
|
-
if sizeMeta and count * sizeMeta > incomingLen - pos then
|
|
201
|
-
Log.warn(`"{reg.name}" count {count} exceeds remaining bytes; aborting`)
|
|
202
|
-
return false
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
local maxPayload = reg.maxPayloadBytes
|
|
206
|
-
local payloadStart = pos
|
|
207
|
-
local doGate = isServer and reg.needsGate and player ~= nil
|
|
208
|
-
local regSignal = reg.signal
|
|
209
|
-
local regName = reg.name
|
|
210
|
-
-- Lync.nothing reads zero bytes; bypass the pos/consumed gates that assume forward progress.
|
|
211
|
-
local isZeroSize = sizeMeta == 0
|
|
212
|
-
|
|
213
|
-
for _ = 1, count do
|
|
214
|
-
if not isZeroSize and pos >= incomingLen then
|
|
215
|
-
Log.warn(`codec for "{regName}" ran past frame end; aborting`)
|
|
216
|
-
return false
|
|
217
|
-
end
|
|
218
|
-
local value, consumed = regCodec.read(incoming, pos, refs)
|
|
219
|
-
if not isZeroSize and consumed == 0 then
|
|
220
|
-
Log.warn(`codec for "{regName}" reported 0 bytes; aborting`)
|
|
221
|
-
return false
|
|
222
|
-
end
|
|
223
|
-
-- Fixed-size codecs MUST report consumed == _size or `pos` desyncs silently.
|
|
224
|
-
if sizeMeta and consumed ~= sizeMeta then
|
|
225
|
-
Log.warn(
|
|
226
|
-
`codec for "{regName}" consumed {consumed} bytes, expected {sizeMeta}; aborting`
|
|
227
|
-
)
|
|
228
|
-
return false
|
|
229
|
-
end
|
|
230
|
-
pos += consumed
|
|
231
|
-
if pos > incomingLen then
|
|
232
|
-
Log.warn(`codec for "{regName}" overran frame end; aborting`)
|
|
233
|
-
return false
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
if maxPayload and (pos - payloadStart) > maxPayload then
|
|
237
|
-
recordDrop(reg, statsEnabled, player, "maxPayloadBytes", value)
|
|
238
|
-
return false
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
if
|
|
242
|
-
doGate
|
|
243
|
-
and not gateValue(reg, regCodec, value, player :: Player, statsEnabled, true)
|
|
244
|
-
then
|
|
245
|
-
continue
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
if hasRecvHooks then
|
|
249
|
-
value = runReceive(value, regName, player)
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
if statsEnabled then
|
|
253
|
-
reg.recvFires += 1
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
-- tsValue is nil when mode == TS_NONE; pass through to skip a per-item branch.
|
|
257
|
-
regSignal:fire(value, player, tsValue)
|
|
258
|
-
end
|
|
259
|
-
|
|
260
|
-
if statsEnabled then
|
|
261
|
-
reg.bytesReceived += (pos - frameStart)
|
|
262
|
-
end
|
|
263
|
-
elseif kind == KIND_REQUEST or kind == KIND_RESPONSE then
|
|
264
|
-
local corrId, corrBytes = varintRead(incoming, pos)
|
|
265
|
-
if corrBytes == 0 then
|
|
266
|
-
Log.warn(`truncated correlation id for "{reg.name}"; aborting`)
|
|
267
|
-
return false
|
|
268
|
-
end
|
|
269
|
-
pos += corrBytes
|
|
270
|
-
|
|
271
|
-
if msb then
|
|
272
|
-
if statsEnabled then
|
|
273
|
-
reg.recvFires += 1
|
|
274
|
-
reg.bytesReceived += (pos - frameStart)
|
|
275
|
-
end
|
|
276
|
-
reg.signal:fire(nil, player, corrId)
|
|
277
|
-
else
|
|
278
|
-
local regCodec = reg.codec
|
|
279
|
-
-- Mirror the packet path: 0-byte response codecs (Lync.nothing) bypass the gates.
|
|
280
|
-
local qIsZeroSize = regCodec._size == 0
|
|
281
|
-
if not qIsZeroSize and pos >= incomingLen then
|
|
282
|
-
Log.warn(`query "{reg.name}" body missing; aborting`)
|
|
283
|
-
return false
|
|
284
|
-
end
|
|
285
|
-
local value, consumed = regCodec.read(incoming, pos, refs)
|
|
286
|
-
if not qIsZeroSize and consumed == 0 then
|
|
287
|
-
Log.warn(`query codec for "{reg.name}" reported 0 bytes; aborting`)
|
|
288
|
-
return false
|
|
289
|
-
end
|
|
290
|
-
pos += consumed
|
|
291
|
-
if pos > incomingLen then
|
|
292
|
-
Log.warn(`query "{reg.name}" overran frame end; aborting`)
|
|
293
|
-
return false
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
--[[
|
|
297
|
-
Server gates REQUEST and RESPONSE frames opted into
|
|
298
|
-
needsGate so a malicious client can't bypass via the
|
|
299
|
-
response channel.
|
|
300
|
-
]]
|
|
301
|
-
if
|
|
302
|
-
isServer
|
|
303
|
-
and reg.needsGate
|
|
304
|
-
and player
|
|
305
|
-
and not gateValue(reg, regCodec, value, player, statsEnabled, false)
|
|
306
|
-
then
|
|
307
|
-
if statsEnabled then
|
|
308
|
-
reg.bytesReceived += (pos - frameStart)
|
|
309
|
-
end
|
|
310
|
-
continue
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
if hasRecvHooks then
|
|
314
|
-
value = runReceive(value, reg.name, player)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
if statsEnabled then
|
|
318
|
-
reg.recvFires += 1
|
|
319
|
-
reg.bytesReceived += (pos - frameStart)
|
|
320
|
-
end
|
|
321
|
-
reg.signal:fire(value, player, corrId)
|
|
322
|
-
end
|
|
323
|
-
end
|
|
324
|
-
end
|
|
325
|
-
return true
|
|
326
|
-
end
|
|
327
|
-
|
|
328
|
-
Reader.setSilent = Log.setSilent
|
|
329
|
-
Reader.reset = Log.reset
|
|
330
|
-
|
|
331
|
-
return table.freeze(Reader)
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Server transport: per-player channels, broadcast, flush.
|
|
4
|
-
|
|
5
|
-
local Players = game:GetService("Players")
|
|
6
|
-
local ReplicatedStorage = game:GetService("ReplicatedStorage")
|
|
7
|
-
|
|
8
|
-
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
9
|
-
local Bridge = require(script.Parent.Parent.transport.Bridge)
|
|
10
|
-
local Channel = require(script.Parent.Parent.internal.Channel)
|
|
11
|
-
local Gate = require(script.Parent.Parent.transport.Gate)
|
|
12
|
-
local Pool = require(script.Parent.Parent.internal.Pool)
|
|
13
|
-
local Reader = require(script.Parent.Parent.transport.Reader)
|
|
14
|
-
local Types = require(script.Parent.Parent.Types)
|
|
15
|
-
|
|
16
|
-
-- Constants --------------------------------------------------------------
|
|
17
|
-
|
|
18
|
-
local CONTAINER_NAME = "LyncRemotes"
|
|
19
|
-
|
|
20
|
-
-- State ------------------------------------------------------------------
|
|
21
|
-
|
|
22
|
-
local _reliableChannels: { [Player]: Types.ChannelState } = {}
|
|
23
|
-
local _unreliableChannels: { [Player]: Types.ChannelState } = {}
|
|
24
|
-
local _playerStats: { [Player]: Types.PlayerStats } = {}
|
|
25
|
-
local _prevIncoming: { [Player]: buffer } = {}
|
|
26
|
-
local _prevIncomingLen: { [Player]: number } = {}
|
|
27
|
-
|
|
28
|
-
-- Private ----------------------------------------------------------------
|
|
29
|
-
|
|
30
|
-
local statsEnabled = Channel.statsEnabled
|
|
31
|
-
local xorApply = Channel.xorApply
|
|
32
|
-
local sealAndDump = Channel.sealAndDump
|
|
33
|
-
local resetCh = Channel.reset
|
|
34
|
-
|
|
35
|
-
type StatsField = "bytesSent" | "bytesReceived"
|
|
36
|
-
|
|
37
|
-
local function getOrCreateChannel(
|
|
38
|
-
map: { [Player]: Types.ChannelState },
|
|
39
|
-
player: Player
|
|
40
|
-
): Types.ChannelState
|
|
41
|
-
local ch = map[player]
|
|
42
|
-
if not ch then
|
|
43
|
-
ch = Pool.acquire()
|
|
44
|
-
map[player] = ch
|
|
45
|
-
end
|
|
46
|
-
return ch
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
-- Skip the table read when stats are off; the hot path needs no branch.
|
|
50
|
-
local function addStat(player: Player, field: StatsField, byteLen: number): ()
|
|
51
|
-
if statsEnabled() then
|
|
52
|
-
local ps = _playerStats[player]
|
|
53
|
-
if ps then
|
|
54
|
-
ps[field] += byteLen
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
local function flushReliable(player: Player): ()
|
|
60
|
-
local rCh = _reliableChannels[player]
|
|
61
|
-
if not rCh or rCh.cursor == 0 then
|
|
62
|
-
return
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
local snapshot, refs, byteLen = sealAndDump(rCh)
|
|
66
|
-
local xored = xorApply(snapshot, byteLen, rCh.prevDump, rCh.prevDumpLen)
|
|
67
|
-
rCh.prevDump = snapshot
|
|
68
|
-
rCh.prevDumpLen = byteLen
|
|
69
|
-
|
|
70
|
-
Bridge.fireClient(player, xored, refs)
|
|
71
|
-
resetCh(rCh)
|
|
72
|
-
addStat(player, "bytesSent", byteLen)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
local function flushUnreliable(player: Player): ()
|
|
76
|
-
local uCh = _unreliableChannels[player]
|
|
77
|
-
if not uCh or uCh.cursor == 0 then
|
|
78
|
-
return
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
local snapshot, refs, byteLen = sealAndDump(uCh)
|
|
82
|
-
Bridge.fireClientUnreliable(player, snapshot, refs)
|
|
83
|
-
resetCh(uCh)
|
|
84
|
-
addStat(player, "bytesSent", byteLen)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
local function flushPlayer(player: Player): ()
|
|
88
|
-
flushReliable(player)
|
|
89
|
-
flushUnreliable(player)
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
local function handleIncomingReliable(player: Player, data: any, refs: any?): ()
|
|
93
|
-
local incoming = Reader.decodeIncoming(data)
|
|
94
|
-
if not incoming then
|
|
95
|
-
return
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
local incomingLen = buffer.len(incoming)
|
|
99
|
-
if not Gate.checkBandwidth(player, incomingLen) then
|
|
100
|
-
return
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
local prev = _prevIncoming[player]
|
|
104
|
-
local decoded = xorApply(incoming, incomingLen, prev, _prevIncomingLen[player])
|
|
105
|
-
|
|
106
|
-
--[[
|
|
107
|
-
Only adopt `decoded` as the next XOR baseline on success. A failed
|
|
108
|
-
parse means the wire is suspect; reusing it would poison every
|
|
109
|
-
subsequent packet.
|
|
110
|
-
]]
|
|
111
|
-
if Reader.process(decoded, incomingLen, refs, player, true) then
|
|
112
|
-
_prevIncoming[player] = decoded
|
|
113
|
-
_prevIncomingLen[player] = incomingLen
|
|
114
|
-
else
|
|
115
|
-
_prevIncoming[player] = nil
|
|
116
|
-
_prevIncomingLen[player] = nil
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
addStat(player, "bytesReceived", incomingLen)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
local function handleIncomingUnreliable(player: Player, data: any, refs: any?): ()
|
|
123
|
-
local incoming = Reader.decodeIncoming(data)
|
|
124
|
-
if not incoming then
|
|
125
|
-
return
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
local incomingLen = buffer.len(incoming)
|
|
129
|
-
Reader.process(incoming, incomingLen, refs, player, true)
|
|
130
|
-
addStat(player, "bytesReceived", incomingLen)
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
local function clearPlayerState(player: Player): ()
|
|
134
|
-
local rCh = _reliableChannels[player]
|
|
135
|
-
if rCh then
|
|
136
|
-
_reliableChannels[player] = nil
|
|
137
|
-
Pool.release(rCh)
|
|
138
|
-
end
|
|
139
|
-
local uCh = _unreliableChannels[player]
|
|
140
|
-
if uCh then
|
|
141
|
-
_unreliableChannels[player] = nil
|
|
142
|
-
Pool.release(uCh)
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
_playerStats[player] = nil
|
|
146
|
-
_prevIncoming[player] = nil
|
|
147
|
-
_prevIncomingLen[player] = nil
|
|
148
|
-
|
|
149
|
-
Gate.clearPlayer(player)
|
|
150
|
-
Baseline.clearPlayer(player)
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
-- Public -----------------------------------------------------------------
|
|
154
|
-
|
|
155
|
-
local Server = {}
|
|
156
|
-
|
|
157
|
-
function Server.getChannel(player: Player, isUnreliable: boolean): Types.ChannelState
|
|
158
|
-
return getOrCreateChannel(
|
|
159
|
-
if isUnreliable then _unreliableChannels else _reliableChannels,
|
|
160
|
-
player
|
|
161
|
-
)
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
function Server.flush(): ()
|
|
165
|
-
if Channel.hasTimestamps() then
|
|
166
|
-
Channel.updateTimestamp()
|
|
167
|
-
end
|
|
168
|
-
for _, player in Players:GetPlayers() do
|
|
169
|
-
flushPlayer(player)
|
|
170
|
-
end
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
Server.flushPlayer = flushPlayer
|
|
174
|
-
|
|
175
|
-
function Server.start(): ()
|
|
176
|
-
local container = Instance.new("Folder")
|
|
177
|
-
container.Name = CONTAINER_NAME
|
|
178
|
-
container.Parent = ReplicatedStorage
|
|
179
|
-
|
|
180
|
-
Bridge.setup(container)
|
|
181
|
-
|
|
182
|
-
Bridge.reliable().OnServerEvent:Connect(handleIncomingReliable)
|
|
183
|
-
Bridge.unreliable().OnServerEvent:Connect(handleIncomingUnreliable)
|
|
184
|
-
|
|
185
|
-
Players.PlayerAdded:Connect(function(player: Player)
|
|
186
|
-
if statsEnabled() then
|
|
187
|
-
_playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
|
|
188
|
-
end
|
|
189
|
-
end)
|
|
190
|
-
Players.PlayerRemoving:Connect(clearPlayerState)
|
|
191
|
-
|
|
192
|
-
-- Catch players who joined before Lync.start.
|
|
193
|
-
if statsEnabled() then
|
|
194
|
-
for _, player in Players:GetPlayers() do
|
|
195
|
-
_playerStats[player] = { bytesSent = 0, bytesReceived = 0 }
|
|
196
|
-
end
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
|
|
200
|
-
function Server.getPlayerStats(player: Player): Types.PlayerStats?
|
|
201
|
-
return _playerStats[player]
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
function Server.resetStats(): ()
|
|
205
|
-
for _, ps in _playerStats do
|
|
206
|
-
ps.bytesSent = 0
|
|
207
|
-
ps.bytesReceived = 0
|
|
208
|
-
end
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
return table.freeze(Server)
|
package/src/util/Array.luau
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- O(1) array swap-remove. Breaks insertion order.
|
|
4
|
-
|
|
5
|
-
-- Public -----------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
local Array = {}
|
|
8
|
-
|
|
9
|
-
-- Reorders. Caller-tracked counts must decrement separately.
|
|
10
|
-
function Array.swapRemove<T>(arr: { T }, idx: number): ()
|
|
11
|
-
local last = #arr
|
|
12
|
-
if idx ~= last then
|
|
13
|
-
arr[idx] = arr[last]
|
|
14
|
-
end
|
|
15
|
-
arr[last] = nil
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
return table.freeze(Array)
|
package/src/util/Buffer.luau
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!native
|
|
3
|
-
-- Word-aligned XOR and equality helpers for Channel and composite deltas.
|
|
4
|
-
|
|
5
|
-
-- Private ----------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
local band = bit32.band
|
|
8
|
-
local bxor = bit32.bxor
|
|
9
|
-
local readu8 = buffer.readu8
|
|
10
|
-
local readu32 = buffer.readu32
|
|
11
|
-
local writeu8 = buffer.writeu8
|
|
12
|
-
local writeu32 = buffer.writeu32
|
|
13
|
-
local bufCopy = buffer.copy
|
|
14
|
-
local bufCreate = buffer.create
|
|
15
|
-
local minN = math.min
|
|
16
|
-
|
|
17
|
-
-- Public -----------------------------------------------------------------
|
|
18
|
-
|
|
19
|
-
local Buf = {}
|
|
20
|
-
|
|
21
|
-
-- 8-byte unrolled compare; 4-byte step then byte tail for remainder.
|
|
22
|
-
function Buf.rangeEqual(a: buffer, offA: number, b: buffer, offB: number, len: number): boolean
|
|
23
|
-
local aligned8 = band(len, -8)
|
|
24
|
-
local i = 0
|
|
25
|
-
while i < aligned8 do
|
|
26
|
-
if
|
|
27
|
-
readu32(a, offA + i) ~= readu32(b, offB + i)
|
|
28
|
-
or readu32(a, offA + i + 4) ~= readu32(b, offB + i + 4)
|
|
29
|
-
then
|
|
30
|
-
return false
|
|
31
|
-
end
|
|
32
|
-
i += 8
|
|
33
|
-
end
|
|
34
|
-
if i + 4 <= len then
|
|
35
|
-
if readu32(a, offA + i) ~= readu32(b, offB + i) then
|
|
36
|
-
return false
|
|
37
|
-
end
|
|
38
|
-
i += 4
|
|
39
|
-
end
|
|
40
|
-
while i < len do
|
|
41
|
-
if readu8(a, offA + i) ~= readu8(b, offB + i) then
|
|
42
|
-
return false
|
|
43
|
-
end
|
|
44
|
-
i += 1
|
|
45
|
-
end
|
|
46
|
-
return true
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
function Buf.snapshot(src: buffer, len: number, srcOff: number?): buffer
|
|
50
|
-
local out = bufCreate(len)
|
|
51
|
-
bufCopy(out, 0, src, srcOff or 0, len)
|
|
52
|
-
return out
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
--[[
|
|
56
|
-
XOR `current` against `previous` into a fresh curLen buffer. Returns
|
|
57
|
-
`current` unchanged when previous is nil so first-frame callers don't
|
|
58
|
-
branch. Overlap is 8-byte unrolled; tail is bulk-copied.
|
|
59
|
-
]]
|
|
60
|
-
function Buf.xorApply(current: buffer, curLen: number, previous: buffer?, prevLen: number?): buffer
|
|
61
|
-
if not previous then
|
|
62
|
-
return current
|
|
63
|
-
end
|
|
64
|
-
local pLen = prevLen or 0
|
|
65
|
-
|
|
66
|
-
local result = bufCreate(curLen)
|
|
67
|
-
local overlap = minN(curLen, pLen)
|
|
68
|
-
|
|
69
|
-
local aligned8 = band(overlap, -8)
|
|
70
|
-
local i = 0
|
|
71
|
-
while i < aligned8 do
|
|
72
|
-
writeu32(result, i, bxor(readu32(current, i), readu32(previous, i)))
|
|
73
|
-
writeu32(result, i + 4, bxor(readu32(current, i + 4), readu32(previous, i + 4)))
|
|
74
|
-
i += 8
|
|
75
|
-
end
|
|
76
|
-
if i + 4 <= overlap then
|
|
77
|
-
writeu32(result, i, bxor(readu32(current, i), readu32(previous, i)))
|
|
78
|
-
i += 4
|
|
79
|
-
end
|
|
80
|
-
while i < overlap do
|
|
81
|
-
writeu8(result, i, bxor(readu8(current, i), readu8(previous, i)))
|
|
82
|
-
i += 1
|
|
83
|
-
end
|
|
84
|
-
if curLen > overlap then
|
|
85
|
-
bufCopy(result, overlap, current, overlap, curLen - overlap)
|
|
86
|
-
end
|
|
87
|
-
return result
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
return table.freeze(Buf)
|
package/src/util/Constants.luau
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Wire-format constants and integer limits shared across codecs.
|
|
4
|
-
|
|
5
|
-
-- Public -----------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
local Constants = {}
|
|
8
|
-
|
|
9
|
-
Constants.U8_MAX = 0xFF
|
|
10
|
-
Constants.U16_MAX = 0xFFFF
|
|
11
|
-
Constants.U32_MAX = 0xFFFFFFFF
|
|
12
|
-
|
|
13
|
-
Constants.I8_MIN = -0x80
|
|
14
|
-
Constants.I8_MAX = 0x7F
|
|
15
|
-
Constants.I16_MIN = -0x8000
|
|
16
|
-
Constants.I16_MAX = 0x7FFF
|
|
17
|
-
Constants.I32_MIN = -0x80000000
|
|
18
|
-
Constants.I32_MAX = 0x7FFFFFFF
|
|
19
|
-
|
|
20
|
-
--[[
|
|
21
|
-
Frame header byte: MSB = single-item (no count cell); cleared = multi-item
|
|
22
|
-
(u16 count follows). Low 7 bits = registration id (caps registry at 128).
|
|
23
|
-
]]
|
|
24
|
-
Constants.FRAME_MSB = 0x80
|
|
25
|
-
Constants.FRAME_ID_MASK = 0x7F
|
|
26
|
-
|
|
27
|
-
-- u16 count cell.
|
|
28
|
-
Constants.MAX_BATCH_ITEMS = 0xFFFF
|
|
29
|
-
|
|
30
|
-
return table.freeze(Constants)
|