@axpecter/lync 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +475 -426
- package/package.json +1 -1
- package/src/Types.luau +63 -31
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +187 -186
- package/src/api/Query.luau +223 -284
- package/src/api/Scope.luau +46 -57
- package/src/api/Signal.luau +104 -137
- package/src/codec/Base.luau +69 -20
- package/src/codec/composite/Array.luau +179 -270
- package/src/codec/composite/Map.luau +97 -347
- package/src/codec/composite/Optional.luau +27 -24
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +200 -360
- package/src/codec/composite/Tagged.luau +62 -187
- package/src/codec/composite/Tuple.luau +79 -103
- package/src/codec/datatype/Buffer.luau +49 -21
- package/src/codec/datatype/CFrame.luau +207 -30
- package/src/codec/datatype/Color.luau +21 -11
- package/src/codec/datatype/Instance.luau +28 -21
- package/src/codec/datatype/IntVector.luau +33 -21
- package/src/codec/datatype/NumberRange.luau +19 -10
- package/src/codec/datatype/Ray.luau +26 -22
- package/src/codec/datatype/Rect.luau +20 -16
- package/src/codec/datatype/Region.luau +51 -45
- package/src/codec/datatype/Sequence.luau +64 -56
- package/src/codec/datatype/String.luau +89 -56
- package/src/codec/datatype/UDim.luau +40 -22
- package/src/codec/datatype/Vector.luau +181 -17
- package/src/codec/meta/Auto.luau +295 -253
- package/src/codec/meta/Bitfield.luau +104 -148
- package/src/codec/meta/Custom.luau +8 -4
- package/src/codec/meta/Enum.luau +29 -57
- package/src/codec/meta/Float.luau +64 -0
- package/src/codec/meta/Nothing.luau +9 -5
- package/src/codec/meta/Unknown.luau +44 -36
- package/src/codec/primitive/Bool.luau +16 -26
- package/src/codec/primitive/Float16.luau +58 -64
- package/src/codec/primitive/Int.luau +68 -0
- package/src/codec/primitive/Number.luau +21 -43
- package/src/codec/primitive/Varint.luau +67 -46
- package/src/index.d.ts +249 -335
- package/src/init.luau +319 -207
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +333 -278
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +56 -113
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +59 -170
- package/src/transport/Gate.luau +282 -142
- package/src/transport/Reader.luau +148 -140
- package/src/transport/Server.luau +138 -488
- package/src/api/Namespace.luau +0 -256
- package/src/codec/meta/Quantized.luau +0 -174
|
@@ -1,224 +1,232 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Deserializes incoming buffers
|
|
3
|
+
-- Deserializes incoming buffers with MSB batch dispatch.
|
|
4
4
|
|
|
5
5
|
local Baseline = require(script.Parent.Parent.internal.Baseline)
|
|
6
6
|
local Channel = require(script.Parent.Parent.internal.Channel)
|
|
7
|
-
local Gate = require(script.Parent.Gate)
|
|
7
|
+
local Gate = require(script.Parent.Parent.transport.Gate)
|
|
8
8
|
local Middleware = require(script.Parent.Parent.internal.Middleware)
|
|
9
9
|
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
10
|
+
local Types = require(script.Parent.Parent.Types)
|
|
10
11
|
local Varint = require(script.Parent.Parent.codec.primitive.Varint)
|
|
11
12
|
|
|
12
|
-
local registryGet = Registry.get
|
|
13
|
-
local gateCheck = Gate.check
|
|
14
|
-
local gateReportDrop = Gate.reportDrop
|
|
15
|
-
|
|
16
13
|
-- Constants -----------------------------------------------------------
|
|
17
14
|
|
|
18
15
|
local KIND_PACKET = Registry.KIND_PACKET
|
|
19
16
|
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
17
|
+
local KIND_RESPONSE = Registry.KIND_RESPONSE
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
local TS_OFFSET = Channel.TS_OFFSET
|
|
23
|
-
local TS_FULL = Channel.TS_FULL
|
|
24
|
-
|
|
25
|
-
local MAX_INCOMING = 65536
|
|
19
|
+
-- Private -------------------------------------------------------------
|
|
26
20
|
|
|
27
|
-
local
|
|
21
|
+
local band = bit32.band
|
|
22
|
+
local readu8 = buffer.readu8
|
|
28
23
|
|
|
29
24
|
-- Public --------------------------------------------------------------
|
|
30
25
|
|
|
31
26
|
local Reader = {}
|
|
32
27
|
|
|
33
|
-
-- Shared incoming-data validation used by both Client and Server receive.
|
|
34
28
|
function Reader.decodeIncoming(data: any): buffer?
|
|
35
29
|
if typeof(data) ~= "buffer" then
|
|
36
30
|
return nil
|
|
37
31
|
end
|
|
38
|
-
|
|
32
|
+
local b = data :: buffer
|
|
33
|
+
local len = buffer.len(b)
|
|
34
|
+
if len == 0 or len > 65536 then
|
|
39
35
|
return nil
|
|
40
36
|
end
|
|
41
|
-
return
|
|
37
|
+
return b
|
|
42
38
|
end
|
|
43
39
|
|
|
44
|
-
-- Callers wrap this in pcall. Any throw aborts the entire buffer from this source.
|
|
45
40
|
function Reader.process(
|
|
46
41
|
incoming: buffer,
|
|
42
|
+
incomingLen: number,
|
|
47
43
|
refs: { Instance }?,
|
|
48
44
|
player: Player?,
|
|
49
|
-
|
|
45
|
+
isServer: boolean
|
|
50
46
|
): ()
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if Baseline.hasDelta then
|
|
47
|
+
-- Set baseline read key for delta codecs
|
|
48
|
+
if Baseline.hasDelta() then
|
|
55
49
|
Baseline.setReadKey(player or false)
|
|
56
50
|
end
|
|
57
51
|
|
|
58
|
-
local
|
|
59
|
-
local
|
|
60
|
-
local
|
|
52
|
+
local statsEnabled = Channel.statsEnabled()
|
|
53
|
+
local hasRecvHooks = Middleware.hasReceiveHooks()
|
|
54
|
+
local pos = 0
|
|
61
55
|
|
|
62
|
-
while pos <
|
|
63
|
-
local
|
|
56
|
+
while pos < incomingLen do
|
|
57
|
+
local raw = readu8(incoming, pos)
|
|
64
58
|
pos += 1
|
|
65
59
|
|
|
66
|
-
local
|
|
60
|
+
local msb = band(raw, 0x80) ~= 0
|
|
61
|
+
local id = band(raw, 0x7F)
|
|
62
|
+
|
|
63
|
+
local reg = Registry.get(id)
|
|
67
64
|
if not reg then
|
|
68
|
-
warn(`[Lync]
|
|
65
|
+
warn(`[Lync] Reader: unknown packet ID {id} at byte {pos - 1}`)
|
|
69
66
|
break
|
|
70
67
|
end
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if kind == KIND_PACKET then
|
|
75
|
-
if pos + 2 > length then
|
|
76
|
-
break
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
local count = buffer.readu16(incoming, pos)
|
|
80
|
-
pos += 2
|
|
69
|
+
if reg.kind == KIND_PACKET then
|
|
70
|
+
local count: number
|
|
81
71
|
|
|
82
|
-
if
|
|
83
|
-
|
|
72
|
+
if msb then
|
|
73
|
+
count = 1
|
|
74
|
+
else
|
|
75
|
+
count = buffer.readu16(incoming, pos)
|
|
76
|
+
pos += 2
|
|
84
77
|
end
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
local signal = reg.signal
|
|
88
|
-
local batchStart = pos
|
|
89
|
-
local maxBytes = reg.maxPayloadBytes
|
|
90
|
-
|
|
91
|
-
-- Timestamp is per-batch, read once after count
|
|
92
|
-
local timestamp: number? = nil
|
|
79
|
+
-- Read timestamp if this packet has one configured
|
|
93
80
|
local tsMode = reg.timestampMode
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
local tsValue: any = nil
|
|
82
|
+
|
|
83
|
+
if tsMode == Channel.TS_FRAME then
|
|
84
|
+
tsValue = readu8(incoming, pos)
|
|
96
85
|
pos += 1
|
|
97
|
-
elseif tsMode == TS_OFFSET then
|
|
98
|
-
|
|
86
|
+
elseif tsMode == Channel.TS_OFFSET then
|
|
87
|
+
tsValue = buffer.readu16(incoming, pos)
|
|
99
88
|
pos += 2
|
|
100
|
-
elseif tsMode == TS_FULL then
|
|
101
|
-
|
|
89
|
+
elseif tsMode == Channel.TS_FULL then
|
|
90
|
+
tsValue = buffer.readf64(incoming, pos)
|
|
102
91
|
pos += 8
|
|
103
92
|
end
|
|
104
93
|
|
|
105
|
-
--
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
local maxItems = floor((length - pos) / fixedSize)
|
|
94
|
+
-- Prevent a malicious count from reading past the buffer
|
|
95
|
+
if reg.codec._size then
|
|
96
|
+
local maxItems = (incomingLen - pos) // (reg.codec._size :: number)
|
|
109
97
|
if count > maxItems then
|
|
110
98
|
count = maxItems
|
|
111
99
|
end
|
|
112
100
|
end
|
|
113
101
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
local batchBytes = 0
|
|
118
|
-
for _ = 1, count do
|
|
119
|
-
if pos >= length then
|
|
120
|
-
break
|
|
121
|
-
end
|
|
102
|
+
for _ = 1, count do
|
|
103
|
+
local value, consumed = reg.codec.read(incoming, pos, refs)
|
|
104
|
+
pos += consumed
|
|
122
105
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
end
|
|
128
|
-
reg.bytesReceived += batchBytes
|
|
129
|
-
reg.recvFires += count
|
|
130
|
-
else
|
|
131
|
-
for _ = 1, count do
|
|
132
|
-
if pos >= length then
|
|
133
|
-
break
|
|
106
|
+
if isServer and reg.needsGate then
|
|
107
|
+
if player and not Gate.checkGlobalRateLimit(player) then
|
|
108
|
+
if statsEnabled then
|
|
109
|
+
reg.drops += 1
|
|
134
110
|
end
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
pos += consumed
|
|
138
|
-
signal:fire(value, player, timestamp)
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
else
|
|
142
|
-
-- Cold path: gate and/or middleware and/or byte budget active
|
|
143
|
-
local baseName = reg.baseName
|
|
144
|
-
local needsGate = isServer and reg.needsGate
|
|
145
|
-
local batchBytes = if statsOn then 0 else nil
|
|
146
|
-
local recvCount = if statsOn then 0 else nil
|
|
147
|
-
|
|
148
|
-
for _ = 1, count do
|
|
149
|
-
if pos >= length then
|
|
150
|
-
break
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
if maxBytes and (pos - batchStart) > maxBytes then
|
|
154
|
-
gateReportDrop(player, "size", reg.name, nil)
|
|
155
|
-
break
|
|
111
|
+
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
112
|
+
continue
|
|
156
113
|
end
|
|
157
114
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
115
|
+
if player and not Gate.checkRateLimit(reg, player) then
|
|
116
|
+
if statsEnabled then
|
|
117
|
+
reg.drops += 1
|
|
118
|
+
end
|
|
119
|
+
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
120
|
+
continue
|
|
164
121
|
end
|
|
165
122
|
|
|
166
|
-
|
|
123
|
+
local ok, reason = Gate.validate(value, reg.codec)
|
|
124
|
+
if not ok then
|
|
125
|
+
if statsEnabled then
|
|
126
|
+
reg.drops += 1
|
|
127
|
+
end
|
|
128
|
+
if player then
|
|
129
|
+
Middleware.fireDrop(player, "validation", reg.name, value)
|
|
130
|
+
end
|
|
167
131
|
continue
|
|
168
132
|
end
|
|
169
133
|
|
|
170
|
-
if
|
|
171
|
-
local
|
|
172
|
-
if
|
|
134
|
+
if reg.validate then
|
|
135
|
+
local valid, validReason = reg.validate(value, player :: Player)
|
|
136
|
+
if not valid then
|
|
137
|
+
if statsEnabled then
|
|
138
|
+
reg.drops += 1
|
|
139
|
+
end
|
|
140
|
+
Middleware.fireDrop(
|
|
141
|
+
player :: Player,
|
|
142
|
+
validReason or "validate",
|
|
143
|
+
reg.name,
|
|
144
|
+
value
|
|
145
|
+
)
|
|
173
146
|
continue
|
|
174
147
|
end
|
|
175
|
-
signal:fire(final, player, timestamp)
|
|
176
|
-
else
|
|
177
|
-
signal:fire(value, player, timestamp)
|
|
178
148
|
end
|
|
179
149
|
end
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
150
|
+
|
|
151
|
+
if hasRecvHooks then
|
|
152
|
+
value = Middleware.runReceive(value, reg.name, player)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
if statsEnabled then
|
|
156
|
+
reg.recvFires += 1
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
if tsValue ~= nil then
|
|
160
|
+
reg.signal:fire(value, player, tsValue)
|
|
161
|
+
else
|
|
162
|
+
reg.signal:fire(value, player)
|
|
183
163
|
end
|
|
184
164
|
end
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
165
|
+
|
|
166
|
+
if statsEnabled then
|
|
167
|
+
-- Approximate: counts all bytes consumed in this frame, not just payload
|
|
168
|
+
reg.bytesReceived += pos
|
|
189
169
|
end
|
|
170
|
+
elseif reg.kind == KIND_REQUEST or reg.kind == KIND_RESPONSE then
|
|
171
|
+
-- MSB distinguishes nil responses (no payload) from data responses
|
|
172
|
+
local corrId, corrBytes = Varint.read(incoming, pos)
|
|
173
|
+
pos += corrBytes
|
|
190
174
|
|
|
191
|
-
|
|
192
|
-
|
|
175
|
+
if msb then
|
|
176
|
+
if statsEnabled then
|
|
177
|
+
reg.recvFires += 1
|
|
178
|
+
end
|
|
179
|
+
reg.signal:fire(nil, player, corrId)
|
|
180
|
+
else
|
|
181
|
+
local value, consumed = reg.codec.read(incoming, pos, refs)
|
|
182
|
+
pos += consumed
|
|
193
183
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
184
|
+
if isServer and reg.needsGate and reg.kind == KIND_REQUEST then
|
|
185
|
+
if player and not Gate.checkRateLimit(reg, player) then
|
|
186
|
+
if statsEnabled then
|
|
187
|
+
reg.drops += 1
|
|
188
|
+
end
|
|
189
|
+
Middleware.fireDrop(player :: Player, "rate", reg.name, value)
|
|
190
|
+
continue
|
|
191
|
+
end
|
|
198
192
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
193
|
+
local ok, reason = Gate.validate(value, reg.codec)
|
|
194
|
+
if not ok then
|
|
195
|
+
if statsEnabled then
|
|
196
|
+
reg.drops += 1
|
|
197
|
+
end
|
|
198
|
+
if player then
|
|
199
|
+
Middleware.fireDrop(player, "validation", reg.name, value)
|
|
200
|
+
end
|
|
201
|
+
continue
|
|
202
|
+
end
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
if reg.validate then
|
|
205
|
+
local valid, validReason = reg.validate(value, player :: Player)
|
|
206
|
+
if not valid then
|
|
207
|
+
if statsEnabled then
|
|
208
|
+
reg.drops += 1
|
|
209
|
+
end
|
|
210
|
+
Middleware.fireDrop(
|
|
211
|
+
player :: Player,
|
|
212
|
+
validReason or "validate",
|
|
213
|
+
reg.name,
|
|
214
|
+
value
|
|
215
|
+
)
|
|
216
|
+
continue
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
206
220
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
if hasRecvHooks then
|
|
222
|
+
value = Middleware.runReceive(value, reg.name, player)
|
|
223
|
+
end
|
|
210
224
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
final = Middleware.runReceive(value, reg.baseName, player)
|
|
214
|
-
if final == nil then
|
|
215
|
-
continue
|
|
225
|
+
if statsEnabled then
|
|
226
|
+
reg.recvFires += 1
|
|
216
227
|
end
|
|
217
|
-
|
|
218
|
-
final = value
|
|
228
|
+
reg.signal:fire(value, player, corrId)
|
|
219
229
|
end
|
|
220
|
-
|
|
221
|
-
querySignal:fire(final, player, correlationId)
|
|
222
230
|
end
|
|
223
231
|
end
|
|
224
232
|
end
|