@axpecter/lync 1.3.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 +300 -0
- package/package.json +38 -0
- package/src/Types.luau +63 -0
- package/src/api/Group.luau +126 -0
- package/src/api/Namespace.luau +226 -0
- package/src/api/Packet.luau +147 -0
- package/src/api/Query.luau +295 -0
- package/src/api/Signal.luau +224 -0
- package/src/codec/Base.luau +49 -0
- package/src/codec/composite/Array.luau +275 -0
- package/src/codec/composite/Map.luau +395 -0
- package/src/codec/composite/Optional.luau +47 -0
- package/src/codec/composite/Shared.luau +151 -0
- package/src/codec/composite/Struct.luau +440 -0
- package/src/codec/composite/Tagged.luau +222 -0
- package/src/codec/composite/Tuple.luau +143 -0
- package/src/codec/datatype/Buffer.luau +44 -0
- package/src/codec/datatype/CFrame.luau +51 -0
- package/src/codec/datatype/Color.luau +22 -0
- package/src/codec/datatype/Instance.luau +48 -0
- package/src/codec/datatype/IntVector.luau +25 -0
- package/src/codec/datatype/NumberRange.luau +14 -0
- package/src/codec/datatype/Ray.luau +27 -0
- package/src/codec/datatype/Rect.luau +21 -0
- package/src/codec/datatype/Region.luau +58 -0
- package/src/codec/datatype/Sequence.luau +129 -0
- package/src/codec/datatype/String.luau +87 -0
- package/src/codec/datatype/UDim.luau +27 -0
- package/src/codec/datatype/Vector.luau +25 -0
- package/src/codec/meta/Auto.luau +353 -0
- package/src/codec/meta/Bitfield.luau +191 -0
- package/src/codec/meta/Custom.luau +27 -0
- package/src/codec/meta/Enum.luau +80 -0
- package/src/codec/meta/Nothing.luau +9 -0
- package/src/codec/meta/Quantized.luau +170 -0
- package/src/codec/meta/Unknown.luau +35 -0
- package/src/codec/primitive/Bool.luau +30 -0
- package/src/codec/primitive/Float16.luau +111 -0
- package/src/codec/primitive/Number.luau +48 -0
- package/src/codec/primitive/Varint.luau +76 -0
- package/src/index.d.ts +279 -0
- package/src/init.luau +161 -0
- package/src/internal/Baseline.luau +41 -0
- package/src/internal/Channel.luau +235 -0
- package/src/internal/Middleware.luau +109 -0
- package/src/internal/Pool.luau +68 -0
- package/src/internal/Registry.luau +146 -0
- package/src/transport/Bridge.luau +66 -0
- package/src/transport/Client.luau +151 -0
- package/src/transport/Gate.luau +222 -0
- package/src/transport/Reader.luau +175 -0
- package/src/transport/Server.luau +364 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Client-side transport with batching, frame XOR on reliable.
|
|
4
|
+
|
|
5
|
+
local RunService = game:GetService ("RunService")
|
|
6
|
+
|
|
7
|
+
local Bridge = require (script.Parent.Bridge)
|
|
8
|
+
local Channel = require (script.Parent.Parent.internal.Channel)
|
|
9
|
+
local Middleware = require (script.Parent.Parent.internal.Middleware)
|
|
10
|
+
local Reader = require (script.Parent.Reader)
|
|
11
|
+
local Types = require (script.Parent.Parent.Types)
|
|
12
|
+
|
|
13
|
+
type ChannelState = Types.ChannelState
|
|
14
|
+
type Codec<T> = Types.Codec<T>
|
|
15
|
+
|
|
16
|
+
-- State ------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
local NOT_STARTED = "[Lync] Client not started. Call Lync.start() before sending"
|
|
19
|
+
local _reliable = nil :: ChannelState?
|
|
20
|
+
local _unreliable = nil :: ChannelState?
|
|
21
|
+
local _prevRecv = nil :: buffer?
|
|
22
|
+
local _isStarted = false
|
|
23
|
+
|
|
24
|
+
-- Private ----------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
local function receive (data: any, refs: any, applyXor: boolean): ()
|
|
27
|
+
local buf = Reader.decodeIncoming (data)
|
|
28
|
+
if not buf then
|
|
29
|
+
return
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
local raw: buffer
|
|
33
|
+
if applyXor then
|
|
34
|
+
raw = Channel.xorApply (buf, _prevRecv)
|
|
35
|
+
_prevRecv = raw
|
|
36
|
+
else
|
|
37
|
+
raw = buf
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
local safeRefs: { Instance }? = if typeof (refs) == "table" then refs else nil
|
|
41
|
+
local ok, err = pcall (Reader.process, raw, safeRefs, nil, 0)
|
|
42
|
+
if not ok then
|
|
43
|
+
warn (`[Lync] Read failed: {err}`)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
local function flushChannel (remote: any, ch: ChannelState, applyXor: boolean): ()
|
|
48
|
+
if ch.cursor == 0 then
|
|
49
|
+
return
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
local raw, refs = Channel.sealAndDump (ch)
|
|
53
|
+
|
|
54
|
+
if applyXor then
|
|
55
|
+
local xored = Channel.xorApply (raw, ch.prevDump)
|
|
56
|
+
ch.prevDump = raw
|
|
57
|
+
remote:FireServer (xored, refs)
|
|
58
|
+
else
|
|
59
|
+
remote:FireServer (raw, refs)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
Channel.reset (ch)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
local function flush (): ()
|
|
66
|
+
flushChannel (Bridge.reliable, _reliable :: ChannelState, true)
|
|
67
|
+
flushChannel (Bridge.unreliable, _unreliable :: ChannelState, false)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
-- Public -----------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
local Client = {}
|
|
73
|
+
|
|
74
|
+
function Client.start (): ()
|
|
75
|
+
if _isStarted then
|
|
76
|
+
return
|
|
77
|
+
end
|
|
78
|
+
_isStarted = true
|
|
79
|
+
|
|
80
|
+
Bridge.setup ()
|
|
81
|
+
|
|
82
|
+
_reliable = Channel.create ()
|
|
83
|
+
_unreliable = Channel.create ()
|
|
84
|
+
|
|
85
|
+
Bridge.reliable.OnClientEvent:Connect (function (data: any, refs: any): ()
|
|
86
|
+
receive (data, refs, true)
|
|
87
|
+
end)
|
|
88
|
+
Bridge.unreliable.OnClientEvent:Connect (function (data: any, refs: any): ()
|
|
89
|
+
receive (data, refs, false)
|
|
90
|
+
end)
|
|
91
|
+
RunService.Heartbeat:Connect (flush)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
function Client.write (
|
|
95
|
+
id: number,
|
|
96
|
+
name: string,
|
|
97
|
+
codec: Codec<any>,
|
|
98
|
+
data: any,
|
|
99
|
+
isUnreliable: boolean
|
|
100
|
+
): ()
|
|
101
|
+
if not _isStarted then
|
|
102
|
+
error (NOT_STARTED)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
local final: any
|
|
106
|
+
if Middleware.hasSend then
|
|
107
|
+
final = Middleware.runSend (data, name, nil)
|
|
108
|
+
if final == nil then
|
|
109
|
+
return
|
|
110
|
+
end
|
|
111
|
+
else
|
|
112
|
+
final = data
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
local ch = if isUnreliable then _unreliable :: ChannelState else _reliable :: ChannelState
|
|
116
|
+
Channel.writeBatch (ch, id, name, codec, final)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
-- Shared for both query requests and responses (identical wire format).
|
|
120
|
+
function Client.writeQuery (
|
|
121
|
+
id: number,
|
|
122
|
+
name: string,
|
|
123
|
+
correlationId: number,
|
|
124
|
+
codec: Codec<any>,
|
|
125
|
+
data: any
|
|
126
|
+
): ()
|
|
127
|
+
if not _isStarted then
|
|
128
|
+
error (NOT_STARTED)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
local final: any
|
|
132
|
+
if Middleware.hasSend then
|
|
133
|
+
final = Middleware.runSend (data, name, nil)
|
|
134
|
+
if final == nil then
|
|
135
|
+
return
|
|
136
|
+
end
|
|
137
|
+
else
|
|
138
|
+
final = data
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
Channel.writeQuery (_reliable :: ChannelState, id, name, correlationId, codec, final)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
function Client.writeQueryNil (id: number, name: string, correlationId: number): ()
|
|
145
|
+
if not _isStarted then
|
|
146
|
+
error (NOT_STARTED)
|
|
147
|
+
end
|
|
148
|
+
Channel.writeQuery (_reliable :: ChannelState, id, name, correlationId, nil, nil)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
return table.freeze (Client)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Post-deserialization security: NaN/inf, rate limiting, custom validate.
|
|
4
|
+
|
|
5
|
+
local Types = require (script.Parent.Parent.Types)
|
|
6
|
+
|
|
7
|
+
type Registration = Types.Registration
|
|
8
|
+
type RateLimitConfig = Types.RateLimitConfig
|
|
9
|
+
|
|
10
|
+
-- Constants --------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
local DEFAULT_MAX_DEPTH = 16
|
|
13
|
+
|
|
14
|
+
local isfinite = math.isfinite
|
|
15
|
+
local min = math.min
|
|
16
|
+
local clock = os.clock
|
|
17
|
+
|
|
18
|
+
-- State ------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
type Bucket = {
|
|
21
|
+
tokens: number,
|
|
22
|
+
lastRef: number,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
local _maxDepth = DEFAULT_MAX_DEPTH
|
|
26
|
+
local _buckets = {} :: { [Player]: { [number]: Bucket } }
|
|
27
|
+
local _dropHandlers =
|
|
28
|
+
{} :: { (player: Player, reason: string, packetName: string, data: any?) -> () }
|
|
29
|
+
|
|
30
|
+
-- Private ----------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
local function fireDrop (player: Player, reason: string, packetName: string, data: any?): ()
|
|
33
|
+
for _, handler in _dropHandlers do
|
|
34
|
+
handler (player, reason, packetName, data)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@native
|
|
39
|
+
local function isClean (value: any, depth: number): boolean
|
|
40
|
+
if depth > _maxDepth then
|
|
41
|
+
return false
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
local t = type (value)
|
|
45
|
+
|
|
46
|
+
if t == "number" then
|
|
47
|
+
return isfinite (value)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
-- String, boolean, nil are always clean . Exit before table/vector/userdata checks
|
|
51
|
+
if t == "string" or t == "boolean" or t == "nil" then
|
|
52
|
+
return true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
if t == "table" then
|
|
56
|
+
local deeper = depth + 1
|
|
57
|
+
for k, v in value do
|
|
58
|
+
if not isClean (k, deeper) or not isClean (v, deeper) then
|
|
59
|
+
return false
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
return true
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
if t == "vector" then
|
|
66
|
+
return isfinite (value.X) and isfinite (value.Y) and isfinite (value.Z)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if t == "userdata" then
|
|
70
|
+
local ut = typeof (value)
|
|
71
|
+
|
|
72
|
+
if ut == "Vector2" then
|
|
73
|
+
return isfinite (value.X) and isfinite (value.Y)
|
|
74
|
+
elseif ut == "Color3" then
|
|
75
|
+
return isfinite (value.R) and isfinite (value.G) and isfinite (value.B)
|
|
76
|
+
elseif ut == "CFrame" then
|
|
77
|
+
local position = value.Position
|
|
78
|
+
if
|
|
79
|
+
not isfinite (position.X)
|
|
80
|
+
or not isfinite (position.Y)
|
|
81
|
+
or not isfinite (position.Z)
|
|
82
|
+
then
|
|
83
|
+
return false
|
|
84
|
+
end
|
|
85
|
+
local axis, angle = value:ToAxisAngle ()
|
|
86
|
+
return isfinite (angle)
|
|
87
|
+
and isfinite (axis.X)
|
|
88
|
+
and isfinite (axis.Y)
|
|
89
|
+
and isfinite (axis.Z)
|
|
90
|
+
elseif ut == "UDim" then
|
|
91
|
+
return isfinite (value.Scale)
|
|
92
|
+
elseif ut == "UDim2" then
|
|
93
|
+
return isfinite (value.X.Scale) and isfinite (value.Y.Scale)
|
|
94
|
+
elseif ut == "NumberRange" then
|
|
95
|
+
return isfinite (value.Min) and isfinite (value.Max)
|
|
96
|
+
elseif ut == "Rect" then
|
|
97
|
+
return isfinite (value.Min.X)
|
|
98
|
+
and isfinite (value.Min.Y)
|
|
99
|
+
and isfinite (value.Max.X)
|
|
100
|
+
and isfinite (value.Max.Y)
|
|
101
|
+
elseif ut == "Ray" then
|
|
102
|
+
local origin = value.Origin
|
|
103
|
+
local direction = value.Direction
|
|
104
|
+
return isfinite (origin.X)
|
|
105
|
+
and isfinite (origin.Y)
|
|
106
|
+
and isfinite (origin.Z)
|
|
107
|
+
and isfinite (direction.X)
|
|
108
|
+
and isfinite (direction.Y)
|
|
109
|
+
and isfinite (direction.Z)
|
|
110
|
+
elseif ut == "Region3" then
|
|
111
|
+
local position = value.CFrame.Position
|
|
112
|
+
return isfinite (position.X) and isfinite (position.Y) and isfinite (position.Z)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
return true
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
local function checkRate (player: Player, reg: Registration): boolean
|
|
120
|
+
local cfg = reg.rateLimit
|
|
121
|
+
if not cfg then
|
|
122
|
+
return true
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
local playerBuckets = _buckets[player]
|
|
126
|
+
if not playerBuckets then
|
|
127
|
+
return true
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
local id = reg.id
|
|
131
|
+
local now = clock ()
|
|
132
|
+
local cap = cfg.burstAllowance or cfg.maxPerSecond
|
|
133
|
+
local bucket = playerBuckets[id]
|
|
134
|
+
|
|
135
|
+
if not bucket then
|
|
136
|
+
playerBuckets[id] = {
|
|
137
|
+
tokens = cap - 1,
|
|
138
|
+
lastRef = now,
|
|
139
|
+
}
|
|
140
|
+
return true
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
local elapsed = now - bucket.lastRef
|
|
144
|
+
bucket.tokens = min (cap, bucket.tokens + elapsed * cfg.maxPerSecond)
|
|
145
|
+
bucket.lastRef = now
|
|
146
|
+
|
|
147
|
+
if bucket.tokens >= 1 then
|
|
148
|
+
bucket.tokens -= 1
|
|
149
|
+
return true
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
return false
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
-- Public -----------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
local Gate = {}
|
|
158
|
+
|
|
159
|
+
-- Override the max recursion depth for NaN/inf scanning. Default 16. Range: 4–32.
|
|
160
|
+
function Gate.setMaxDepth (depth: number): ()
|
|
161
|
+
if depth < 4 or depth > 32 then
|
|
162
|
+
error (`[Lync] Gate max depth must be 4–32, got {depth}`)
|
|
163
|
+
end
|
|
164
|
+
_maxDepth = depth
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
function Gate.getMaxDepth (): number
|
|
168
|
+
return _maxDepth
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
function Gate.check (value: any, reg: Registration, player: Player): boolean
|
|
172
|
+
if not checkRate (player, reg) then
|
|
173
|
+
fireDrop (player, "rate", reg.name, nil)
|
|
174
|
+
return false
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
if not isClean (value, 1) then
|
|
178
|
+
fireDrop (player, "nan", reg.name, value)
|
|
179
|
+
return false
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
local validate = reg.validate
|
|
183
|
+
if validate then
|
|
184
|
+
local ok, reason = validate (value, player)
|
|
185
|
+
if not ok then
|
|
186
|
+
fireDrop (player, reason or "validate", reg.name, value)
|
|
187
|
+
return false
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
return true
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
function Gate.onDrop (
|
|
195
|
+
callback: (player: Player, reason: string, packetName: string, data: any?) -> ()
|
|
196
|
+
): () -> ()
|
|
197
|
+
table.insert (_dropHandlers, callback)
|
|
198
|
+
|
|
199
|
+
return function (): ()
|
|
200
|
+
local idx = table.find (_dropHandlers, callback)
|
|
201
|
+
if idx then
|
|
202
|
+
table.remove (_dropHandlers, idx)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
-- Fires drop handlers without validation. Used by Reader for byte-budget violations.
|
|
208
|
+
function Gate.reportDrop (player: Player?, reason: string, packetName: string, data: any?): ()
|
|
209
|
+
if player and #_dropHandlers > 0 then
|
|
210
|
+
fireDrop (player, reason, packetName, data)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
function Gate.onPlayerAdded (player: Player): ()
|
|
215
|
+
_buckets[player] = {}
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
function Gate.onPlayerRemoved (player: Player): ()
|
|
219
|
+
_buckets[player] = nil
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
return table.freeze (Gate)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!optimize 2
|
|
3
|
+
-- Deserializes incoming buffers and routes by registration kind.
|
|
4
|
+
|
|
5
|
+
local Baseline = require (script.Parent.Parent.internal.Baseline)
|
|
6
|
+
local Gate = require (script.Parent.Gate)
|
|
7
|
+
local Middleware = require (script.Parent.Parent.internal.Middleware)
|
|
8
|
+
local Registry = require (script.Parent.Parent.internal.Registry)
|
|
9
|
+
|
|
10
|
+
local registryGet = Registry.get
|
|
11
|
+
local gateCheck = Gate.check
|
|
12
|
+
local gateReportDrop = Gate.reportDrop
|
|
13
|
+
|
|
14
|
+
-- Constants --------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
local KIND_PACKET = Registry.KIND_PACKET
|
|
17
|
+
local KIND_REQUEST = Registry.KIND_REQUEST
|
|
18
|
+
|
|
19
|
+
local MAX_INCOMING = 65536
|
|
20
|
+
|
|
21
|
+
-- Public -----------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
local Reader = {}
|
|
24
|
+
|
|
25
|
+
-- Shared incoming-data validation used by both Client and Server receive.
|
|
26
|
+
function Reader.decodeIncoming (data: any): buffer?
|
|
27
|
+
if typeof (data) ~= "buffer" then
|
|
28
|
+
return nil
|
|
29
|
+
end
|
|
30
|
+
if buffer.len (data) > MAX_INCOMING then
|
|
31
|
+
return nil
|
|
32
|
+
end
|
|
33
|
+
return data
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
-- Callers wrap this in pcall. Any throw aborts the entire buffer from this source.
|
|
37
|
+
function Reader.process (
|
|
38
|
+
incoming: buffer,
|
|
39
|
+
refs: { Instance }?,
|
|
40
|
+
player: Player?,
|
|
41
|
+
startOffset: number
|
|
42
|
+
): ()
|
|
43
|
+
local length = buffer.len (incoming)
|
|
44
|
+
local pos = startOffset
|
|
45
|
+
|
|
46
|
+
if Baseline.hasDelta then
|
|
47
|
+
Baseline.setReadKey (player or false)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
-- Cache flags once per frame-buffer, not per packet
|
|
51
|
+
local hasReceiveMiddleware = Middleware.hasReceive
|
|
52
|
+
local isServer = player ~= nil
|
|
53
|
+
|
|
54
|
+
while pos < length do
|
|
55
|
+
local id = buffer.readu8 (incoming, pos)
|
|
56
|
+
pos += 1
|
|
57
|
+
|
|
58
|
+
local reg = registryGet (id)
|
|
59
|
+
if not reg then
|
|
60
|
+
warn (`[Lync] Unknown packet ID {id}`)
|
|
61
|
+
break
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
local kind = reg.kind
|
|
65
|
+
|
|
66
|
+
if kind == KIND_PACKET then
|
|
67
|
+
if pos + 2 > length then
|
|
68
|
+
break
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
local count = buffer.readu16 (incoming, pos)
|
|
72
|
+
pos += 2
|
|
73
|
+
|
|
74
|
+
if count == 0 then
|
|
75
|
+
continue
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
local codec = reg.codec
|
|
79
|
+
local signal = reg.signal
|
|
80
|
+
local batchStart = pos
|
|
81
|
+
local maxBytes = reg.maxPayloadBytes
|
|
82
|
+
|
|
83
|
+
-- Hot path: no gate, no middleware, no byte budget
|
|
84
|
+
if not (isServer and reg.needsGate) and not hasReceiveMiddleware and not maxBytes then
|
|
85
|
+
for _ = 1, count do
|
|
86
|
+
if pos >= length then
|
|
87
|
+
break
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
local value, consumed = codec.read (incoming, pos, refs)
|
|
91
|
+
pos += consumed
|
|
92
|
+
signal:fire (value, player)
|
|
93
|
+
end
|
|
94
|
+
else
|
|
95
|
+
-- Cold path: gate and/or middleware and/or byte budget active
|
|
96
|
+
local baseName = reg.baseName
|
|
97
|
+
local needsGate = isServer and reg.needsGate
|
|
98
|
+
|
|
99
|
+
for _ = 1, count do
|
|
100
|
+
if pos >= length then
|
|
101
|
+
break
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
if maxBytes and (pos - batchStart) > maxBytes then
|
|
105
|
+
gateReportDrop (player, "size", reg.name, nil)
|
|
106
|
+
break
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
local value, consumed = codec.read (incoming, pos, refs)
|
|
110
|
+
pos += consumed
|
|
111
|
+
|
|
112
|
+
if needsGate and not gateCheck (value, reg, player :: Player) then
|
|
113
|
+
continue
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if hasReceiveMiddleware then
|
|
117
|
+
local final = Middleware.runReceive (value, baseName, player)
|
|
118
|
+
if final == nil then
|
|
119
|
+
continue
|
|
120
|
+
end
|
|
121
|
+
signal:fire (final, player)
|
|
122
|
+
else
|
|
123
|
+
signal:fire (value, player)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
-- Query frame: id(u8, already read) + correlationId(u16) + status(u8) = 3 more bytes
|
|
129
|
+
if pos + 2 >= length then
|
|
130
|
+
break
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
local queryCodec = reg.codec
|
|
134
|
+
local querySignal = reg.signal
|
|
135
|
+
|
|
136
|
+
local correlationId = buffer.readu16 (incoming, pos)
|
|
137
|
+
local status = buffer.readu8 (incoming, pos + 2)
|
|
138
|
+
pos += 3
|
|
139
|
+
|
|
140
|
+
if status == 1 then
|
|
141
|
+
if kind == KIND_REQUEST then
|
|
142
|
+
querySignal:fire (nil, player, correlationId)
|
|
143
|
+
else
|
|
144
|
+
querySignal:fire (nil, correlationId)
|
|
145
|
+
end
|
|
146
|
+
continue
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
local value, consumed = queryCodec.read (incoming, pos, refs)
|
|
150
|
+
pos += consumed
|
|
151
|
+
|
|
152
|
+
if isServer and reg.needsGate and not gateCheck (value, reg, player :: Player) then
|
|
153
|
+
continue
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
local final: any
|
|
157
|
+
if hasReceiveMiddleware then
|
|
158
|
+
final = Middleware.runReceive (value, reg.baseName, player)
|
|
159
|
+
if final == nil then
|
|
160
|
+
continue
|
|
161
|
+
end
|
|
162
|
+
else
|
|
163
|
+
final = value
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if kind == KIND_REQUEST then
|
|
167
|
+
querySignal:fire (final, player, correlationId)
|
|
168
|
+
else
|
|
169
|
+
querySignal:fire (final, correlationId)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
return table.freeze (Reader)
|