@axpecter/lync 2.2.1 → 2.3.2
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 +109 -147
- package/package.json +1 -1
- package/src/Types.luau +34 -22
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +140 -66
- package/src/api/Query.luau +103 -65
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +28 -14
- package/src/codec/composite/Array.luau +296 -115
- package/src/codec/composite/Map.luau +377 -57
- package/src/codec/composite/Optional.luau +10 -2
- package/src/codec/composite/Shared.luau +134 -64
- package/src/codec/composite/Struct.luau +294 -43
- package/src/codec/composite/Tagged.luau +21 -16
- package/src/codec/composite/Tuple.luau +32 -15
- package/src/codec/datatype/Buffer.luau +7 -7
- package/src/codec/datatype/CFrame.luau +34 -122
- package/src/codec/datatype/Color.luau +10 -10
- package/src/codec/datatype/Instance.luau +15 -12
- package/src/codec/datatype/IntVector.luau +2 -2
- package/src/codec/datatype/NumberRange.luau +15 -8
- package/src/codec/datatype/Ray.luau +13 -14
- package/src/codec/datatype/Rect.luau +12 -12
- package/src/codec/datatype/Region.luau +13 -14
- package/src/codec/datatype/Sequence.luau +87 -65
- package/src/codec/datatype/String.luau +17 -10
- package/src/codec/datatype/UDim.luau +10 -8
- package/src/codec/datatype/Vector.luau +20 -59
- package/src/codec/meta/Auto.luau +94 -126
- package/src/codec/meta/Bitfield.luau +12 -14
- package/src/codec/meta/Custom.luau +3 -1
- package/src/codec/meta/DeltaScalar.luau +390 -0
- package/src/codec/meta/Enum.luau +9 -9
- package/src/codec/meta/Float.luau +6 -28
- package/src/codec/meta/Nothing.luau +1 -1
- package/src/codec/meta/Unknown.luau +10 -7
- package/src/codec/primitive/Bool.luau +6 -4
- package/src/codec/primitive/Float16.luau +5 -2
- package/src/codec/primitive/Int.luau +5 -6
- package/src/codec/primitive/Number.luau +6 -4
- package/src/codec/primitive/Signed.luau +2 -2
- package/src/codec/primitive/Varint.luau +76 -39
- package/src/codec/primitive/Zint.luau +99 -0
- package/src/index.d.ts +207 -53
- package/src/init.luau +116 -103
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +191 -157
- package/src/internal/Middleware.luau +22 -6
- package/src/internal/Pool.luau +12 -4
- package/src/internal/Registry.luau +25 -16
- package/src/internal/Transport.luau +1 -1
- package/src/transport/Bridge.luau +47 -33
- package/src/transport/Client.luau +25 -21
- package/src/transport/Gate.luau +227 -172
- package/src/transport/Reader.luau +174 -106
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +90 -0
- package/src/util/Constants.luau +30 -0
- package/src/util/Log.luau +68 -0
- package/src/util/Player.luau +14 -0
- package/src/util/Quantize.luau +84 -0
- package/src/util/Quat.luau +124 -0
- package/src/internal/Util.luau +0 -26
|
@@ -1,26 +1,97 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
--
|
|
3
|
+
-- map() and deltaMap(). Keys sorted at encode for stable wire bytes.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
-
local
|
|
7
|
-
local
|
|
6
|
+
local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
|
|
7
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
8
|
+
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
8
9
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
9
10
|
local Varint = require(script.Parent.Parent.primitive.Varint)
|
|
10
11
|
|
|
11
|
-
--
|
|
12
|
+
-- Constants --------------------------------------------------------------
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
local
|
|
15
|
-
local
|
|
14
|
+
-- deltaMap wire flags. Distinct from Shared's struct/array flag spaces.
|
|
15
|
+
local DM_UNCHANGED = 0
|
|
16
|
+
local DM_FULL = 1
|
|
17
|
+
local DM_DIFF = 2
|
|
18
|
+
|
|
19
|
+
-- State ------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
--[[
|
|
22
|
+
Depth-stacked key scratch. A nested Map.write (Map<K, Map<K2, V>>) would
|
|
23
|
+
clobber a single shared array mid-iteration; the stack gives each frame
|
|
24
|
+
its own slot. Mirrors Shared.acquireScratch / releaseScratch.
|
|
25
|
+
]]
|
|
26
|
+
local _keyStacks: { { any } } = {}
|
|
27
|
+
local _keyDepth = 0
|
|
28
|
+
|
|
29
|
+
-- Private ----------------------------------------------------------------
|
|
16
30
|
|
|
31
|
+
local ensure = Base.ensure
|
|
32
|
+
local allocDeltaId = Shared.allocDeltaId
|
|
17
33
|
local acquireScratch = Shared.acquireScratch
|
|
18
34
|
local releaseScratch = Shared.releaseScratch
|
|
19
35
|
local rangeEqual = Shared.rangeEqual
|
|
20
|
-
local
|
|
21
|
-
local
|
|
22
|
-
local
|
|
23
|
-
local
|
|
36
|
+
local copyToBuf = Shared.copyToBuf
|
|
37
|
+
local containsDelta = Shared.containsDelta
|
|
38
|
+
local enforceMaxCount = Shared.enforceMaxCount
|
|
39
|
+
local writeu8 = buffer.writeu8
|
|
40
|
+
local readu8 = buffer.readu8
|
|
41
|
+
local bufCopy = buffer.copy
|
|
42
|
+
local bufLen = buffer.len
|
|
43
|
+
local varintRead = Varint.read
|
|
44
|
+
local varintWrite = Varint.write
|
|
45
|
+
|
|
46
|
+
type DeltaMapEntry = { kBuf: buffer, kLen: number, vBuf: buffer, vLen: number }
|
|
47
|
+
|
|
48
|
+
local function acquireKeys(): { any }
|
|
49
|
+
_keyDepth += 1
|
|
50
|
+
local keys = _keyStacks[_keyDepth]
|
|
51
|
+
if keys then
|
|
52
|
+
return keys
|
|
53
|
+
end
|
|
54
|
+
keys = {}
|
|
55
|
+
_keyStacks[_keyDepth] = keys
|
|
56
|
+
return keys
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
local function releaseKeys(): ()
|
|
60
|
+
_keyDepth -= 1
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
-- Module-level so the mixed-type sort path doesn't allocate a closure per write.
|
|
64
|
+
local function tostringLess(a: any, b: any): boolean
|
|
65
|
+
return tostring(a) < tostring(b)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
local function sortKeys(keys: { any }, canSortDirect: boolean): ()
|
|
69
|
+
if canSortDirect then
|
|
70
|
+
table.sort(keys :: { any })
|
|
71
|
+
else
|
|
72
|
+
table.sort(keys, tostringLess)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
--[[
|
|
77
|
+
Acquire a scratch keys array, populate it from `value`, nil any leftover
|
|
78
|
+
tail entries from a prior reuse, then sort. Returns the keys array and
|
|
79
|
+
the populated count. Caller MUST releaseKeys() after consuming the keys.
|
|
80
|
+
]]
|
|
81
|
+
local function collectAndSortKeys(value: { [any]: any }, canSortDirect: boolean): ({ any }, number)
|
|
82
|
+
local keys = acquireKeys()
|
|
83
|
+
local count = 0
|
|
84
|
+
for k in value do
|
|
85
|
+
count += 1
|
|
86
|
+
keys[count] = k
|
|
87
|
+
end
|
|
88
|
+
local prevTail = #keys
|
|
89
|
+
for i = count + 1, prevTail do
|
|
90
|
+
keys[i] = nil
|
|
91
|
+
end
|
|
92
|
+
sortKeys(keys, canSortDirect)
|
|
93
|
+
return keys, count
|
|
94
|
+
end
|
|
24
95
|
|
|
25
96
|
-- Public -----------------------------------------------------------------
|
|
26
97
|
|
|
@@ -33,117 +104,366 @@ function Map.map(
|
|
|
33
104
|
): Types.InternalCodec<any>
|
|
34
105
|
local fixedKey = keyCodec._size
|
|
35
106
|
local fixedValue = valueCodec._size
|
|
107
|
+
local hasUnknown = keyCodec._hasUnknown or valueCodec._hasUnknown
|
|
108
|
+
local hasDelta = containsDelta(keyCodec) or containsDelta(valueCodec)
|
|
36
109
|
|
|
37
|
-
|
|
110
|
+
--[[
|
|
111
|
+
Numbers and strings sort by `<` directly; everything else (Instance,
|
|
112
|
+
vector, mixed-type via Auto) routes through tostring. Pinned at codec
|
|
113
|
+
creation so writes don't re-sniff the key type.
|
|
114
|
+
]]
|
|
115
|
+
local typeCheck = keyCodec._typeCheck
|
|
116
|
+
local canSortDirect = typeCheck == "number" or typeCheck == "string"
|
|
117
|
+
|
|
118
|
+
local codec: Types.InternalCodec<any> = {
|
|
38
119
|
_isMap = true,
|
|
39
120
|
_keyCodec = keyCodec,
|
|
40
121
|
_valueCodec = valueCodec,
|
|
41
122
|
_maxCount = maxCount,
|
|
42
123
|
|
|
124
|
+
--[[
|
|
125
|
+
Sort keys for deterministic wire layout. Without this, two
|
|
126
|
+
structurally-equal maps yield different byte sequences across
|
|
127
|
+
frames, which breaks every downstream byte-equality cache (the
|
|
128
|
+
XOR baseline and deltaMap).
|
|
129
|
+
]]
|
|
43
130
|
write = function(ch: Types.ChannelState, value: { [any]: any }): ()
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
count += 1
|
|
131
|
+
if typeof(value) ~= "table" then
|
|
132
|
+
Log.error(`expected table, got {typeof(value)}`)
|
|
47
133
|
end
|
|
48
134
|
|
|
49
|
-
|
|
135
|
+
local keys, count = collectAndSortKeys(value, canSortDirect)
|
|
136
|
+
|
|
137
|
+
varintWrite(ch, count)
|
|
50
138
|
if count == 0 then
|
|
139
|
+
releaseKeys()
|
|
51
140
|
return
|
|
52
141
|
end
|
|
53
142
|
|
|
143
|
+
-- Single alloc when both halves are fixed-size.
|
|
54
144
|
if fixedKey and fixedValue then
|
|
55
|
-
|
|
56
|
-
local cursor = ch.cursor
|
|
57
|
-
if cursor + count * entrySize > ch.size then
|
|
58
|
-
alloc(ch, count * entrySize)
|
|
59
|
-
end
|
|
145
|
+
ensure(ch, count * (fixedKey + fixedValue))
|
|
60
146
|
end
|
|
61
147
|
|
|
62
|
-
for
|
|
148
|
+
for i = 1, count do
|
|
149
|
+
local k = keys[i]
|
|
63
150
|
keyCodec.write(ch, k)
|
|
64
|
-
valueCodec.write(ch,
|
|
151
|
+
valueCodec.write(ch, value[k])
|
|
65
152
|
end
|
|
153
|
+
releaseKeys()
|
|
66
154
|
end,
|
|
67
155
|
|
|
68
156
|
read = function(src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
|
|
69
|
-
local count, lenBytes =
|
|
70
|
-
|
|
71
|
-
|
|
157
|
+
local count, lenBytes = varintRead(src, pos)
|
|
158
|
+
enforceMaxCount(count, maxCount)
|
|
159
|
+
|
|
160
|
+
-- Fixed-size: reject corrupt counts before per-entry decode.
|
|
161
|
+
if fixedKey and fixedValue and count > 0 then
|
|
162
|
+
local payloadBytes = count * (fixedKey + fixedValue)
|
|
163
|
+
if payloadBytes > bufLen(src) - (pos + lenBytes) then
|
|
164
|
+
Log.error(`payload {payloadBytes}B exceeds remaining buffer`)
|
|
165
|
+
end
|
|
72
166
|
end
|
|
73
167
|
|
|
74
168
|
local result: { [any]: any } = {}
|
|
75
169
|
local total = lenBytes
|
|
76
170
|
for i = 1, count do
|
|
77
171
|
local k, kc = keyCodec.read(src, pos + total, refs)
|
|
172
|
+
if kc == 0 then
|
|
173
|
+
Log.error(`truncated key at index {i}`)
|
|
174
|
+
end
|
|
78
175
|
total += kc
|
|
79
176
|
local v, vc = valueCodec.read(src, pos + total, refs)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
error(`[Lync] Map.read: zero-byte entry at index {i}`)
|
|
177
|
+
if vc == 0 then
|
|
178
|
+
Log.error(`truncated value at index {i}`)
|
|
83
179
|
end
|
|
180
|
+
total += vc
|
|
84
181
|
result[k] = v
|
|
85
182
|
end
|
|
86
183
|
return result, total
|
|
87
184
|
end,
|
|
88
|
-
}
|
|
185
|
+
}
|
|
186
|
+
if hasUnknown then
|
|
187
|
+
codec._hasUnknown = true
|
|
188
|
+
end
|
|
189
|
+
if hasDelta then
|
|
190
|
+
codec._hasDelta = true
|
|
191
|
+
end
|
|
192
|
+
return table.freeze(codec)
|
|
89
193
|
end
|
|
90
194
|
|
|
195
|
+
--[[
|
|
196
|
+
Per-key deltaMap. Wire:
|
|
197
|
+
[0] UNCHANGED (1 byte)
|
|
198
|
+
[1] FULL <count varint> <(k,v) pairs ...>
|
|
199
|
+
[2] DIFF <removeCount varint> <removed key bytes ...>
|
|
200
|
+
<changeCount varint> <(k,v) pair bytes ...>
|
|
201
|
+
|
|
202
|
+
Cache: per-key encoded value bytes for byte-equality diffing plus per-key
|
|
203
|
+
encoded key bytes so removed-key emission doesn't re-encode the key codec
|
|
204
|
+
for keys that no longer live in the map.
|
|
205
|
+
]]
|
|
91
206
|
function Map.deltaMap(
|
|
92
207
|
keyCodec: Types.InternalCodec<any>,
|
|
93
208
|
valueCodec: Types.InternalCodec<any>,
|
|
94
209
|
maxCount: number?
|
|
95
210
|
): Types.InternalCodec<any>
|
|
211
|
+
-- Inner delta would chain-diff across pairs within a frame, not across frames.
|
|
212
|
+
if containsDelta(keyCodec) then
|
|
213
|
+
Log.error("deltaMap key codec cannot contain delta state")
|
|
214
|
+
end
|
|
215
|
+
if containsDelta(valueCodec) then
|
|
216
|
+
Log.error("deltaMap value codec cannot contain delta state")
|
|
217
|
+
end
|
|
218
|
+
|
|
96
219
|
local deltaId = allocDeltaId()
|
|
97
|
-
local
|
|
220
|
+
local typeCheck = keyCodec._typeCheck
|
|
221
|
+
local canSortDirect = typeCheck == "number" or typeCheck == "string"
|
|
222
|
+
local inner = Map.map(keyCodec, valueCodec, maxCount)
|
|
223
|
+
|
|
224
|
+
--[[
|
|
225
|
+
Encode each (key, value) pair into `scratch` and capture per-pair byte
|
|
226
|
+
ranges. Returns the sorted-keys list plus four parallel arrays of
|
|
227
|
+
offset/length so the diff path can compare and emit individual entries
|
|
228
|
+
without re-encoding.
|
|
229
|
+
]]
|
|
230
|
+
local function encodePerKey(
|
|
231
|
+
scratch: Types.ChannelState,
|
|
232
|
+
value: { [any]: any }
|
|
233
|
+
): ({ any }, { number }, { number }, { number }, { number })
|
|
234
|
+
local keys, count = collectAndSortKeys(value, canSortDirect)
|
|
235
|
+
local kOff = table.create(count)
|
|
236
|
+
local kLen = table.create(count)
|
|
237
|
+
local vOff = table.create(count)
|
|
238
|
+
local vLen = table.create(count)
|
|
239
|
+
|
|
240
|
+
for i = 1, count do
|
|
241
|
+
local k = keys[i]
|
|
242
|
+
local before = scratch.cursor
|
|
243
|
+
keyCodec.write(scratch, k)
|
|
244
|
+
local mid = scratch.cursor
|
|
245
|
+
kOff[i] = before
|
|
246
|
+
kLen[i] = mid - before
|
|
247
|
+
valueCodec.write(scratch, value[k])
|
|
248
|
+
vOff[i] = mid
|
|
249
|
+
vLen[i] = scratch.cursor - mid
|
|
250
|
+
end
|
|
251
|
+
return keys, kOff, kLen, vOff, vLen
|
|
252
|
+
end
|
|
98
253
|
|
|
99
254
|
return table.freeze({
|
|
100
255
|
_isDelta = true,
|
|
256
|
+
_isMap = true,
|
|
257
|
+
_keyCodec = keyCodec,
|
|
258
|
+
_valueCodec = valueCodec,
|
|
259
|
+
_maxCount = maxCount,
|
|
101
260
|
|
|
102
261
|
write = function(ch: Types.ChannelState, value: { [any]: any }): ()
|
|
103
|
-
|
|
262
|
+
if typeof(value) ~= "table" then
|
|
263
|
+
Log.error(`expected table, got {typeof(value)}`)
|
|
264
|
+
end
|
|
104
265
|
|
|
105
|
-
|
|
106
|
-
|
|
266
|
+
local cache = ch.deltas[deltaId] :: any
|
|
267
|
+
local scratch = acquireScratch()
|
|
268
|
+
local keys, kOff, kLen, vOff, vLen = encodePerKey(scratch, value)
|
|
269
|
+
local count = #keys
|
|
270
|
+
local scratchBuf = scratch.buff
|
|
107
271
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
272
|
+
-- First frame: emit FULL. Per-key scratch bytes ARE the inner map payload.
|
|
273
|
+
if not cache then
|
|
274
|
+
ensure(ch, 1)
|
|
275
|
+
writeu8(ch.buff, ch.cursor, DM_FULL)
|
|
276
|
+
ch.cursor += 1
|
|
277
|
+
varintWrite(ch, count)
|
|
278
|
+
local payloadLen = scratch.cursor
|
|
279
|
+
ensure(ch, payloadLen)
|
|
280
|
+
bufCopy(ch.buff, ch.cursor, scratchBuf, 0, payloadLen)
|
|
281
|
+
ch.cursor += payloadLen
|
|
111
282
|
|
|
112
|
-
|
|
113
|
-
|
|
283
|
+
local perKey: { [any]: DeltaMapEntry } = {}
|
|
284
|
+
for i = 1, count do
|
|
285
|
+
local kbLen = kLen[i]
|
|
286
|
+
local vbLen = vLen[i]
|
|
287
|
+
perKey[keys[i]] = {
|
|
288
|
+
kBuf = copyToBuf(nil, scratchBuf, kOff[i], kbLen),
|
|
289
|
+
kLen = kbLen,
|
|
290
|
+
vBuf = copyToBuf(nil, scratchBuf, vOff[i], vbLen),
|
|
291
|
+
vLen = vbLen,
|
|
292
|
+
}
|
|
293
|
+
end
|
|
294
|
+
ch.deltas[deltaId] = { perKey = perKey } :: any
|
|
295
|
+
releaseKeys()
|
|
114
296
|
releaseScratch()
|
|
115
297
|
return
|
|
116
298
|
end
|
|
117
299
|
|
|
118
|
-
local
|
|
119
|
-
|
|
120
|
-
local
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
300
|
+
local cachedPerKey = cache.perKey :: { [any]: DeltaMapEntry }
|
|
301
|
+
|
|
302
|
+
local seen: { [any]: boolean } = {}
|
|
303
|
+
local changed: { number } = {}
|
|
304
|
+
local changedCount = 0
|
|
305
|
+
for i = 1, count do
|
|
306
|
+
local k = keys[i]
|
|
307
|
+
seen[k] = true
|
|
308
|
+
local entry = cachedPerKey[k]
|
|
309
|
+
local newLen = vLen[i]
|
|
310
|
+
if
|
|
311
|
+
not entry
|
|
312
|
+
or newLen ~= entry.vLen
|
|
313
|
+
or not rangeEqual(scratchBuf, vOff[i], entry.vBuf, 0, newLen)
|
|
314
|
+
then
|
|
315
|
+
changedCount += 1
|
|
316
|
+
changed[changedCount] = i
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
local removed: { any } = {}
|
|
321
|
+
local removedCount = 0
|
|
322
|
+
for k in cachedPerKey do
|
|
323
|
+
if not seen[k] then
|
|
324
|
+
removedCount += 1
|
|
325
|
+
removed[removedCount] = k
|
|
326
|
+
end
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
if removedCount == 0 and changedCount == 0 then
|
|
330
|
+
ensure(ch, 1)
|
|
331
|
+
writeu8(ch.buff, ch.cursor, DM_UNCHANGED)
|
|
332
|
+
ch.cursor += 1
|
|
333
|
+
releaseKeys()
|
|
127
334
|
releaseScratch()
|
|
128
335
|
return
|
|
129
336
|
end
|
|
130
337
|
|
|
131
|
-
|
|
132
|
-
|
|
338
|
+
-- Sort removed keys so the XOR baseline can collapse identical drops.
|
|
339
|
+
sortKeys(removed, canSortDirect)
|
|
340
|
+
|
|
341
|
+
ensure(ch, 1)
|
|
342
|
+
writeu8(ch.buff, ch.cursor, DM_DIFF)
|
|
343
|
+
ch.cursor += 1
|
|
344
|
+
|
|
345
|
+
varintWrite(ch, removedCount)
|
|
346
|
+
for i = 1, removedCount do
|
|
347
|
+
local entry = cachedPerKey[removed[i]]
|
|
348
|
+
local n = entry.kLen
|
|
349
|
+
ensure(ch, n)
|
|
350
|
+
bufCopy(ch.buff, ch.cursor, entry.kBuf, 0, n)
|
|
351
|
+
ch.cursor += n
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
varintWrite(ch, changedCount)
|
|
355
|
+
for i = 1, changedCount do
|
|
356
|
+
local idx = changed[i]
|
|
357
|
+
local kbLen = kLen[idx]
|
|
358
|
+
local vbLen = vLen[idx]
|
|
359
|
+
ensure(ch, kbLen + vbLen)
|
|
360
|
+
bufCopy(ch.buff, ch.cursor, scratchBuf, kOff[idx], kbLen)
|
|
361
|
+
bufCopy(ch.buff, ch.cursor + kbLen, scratchBuf, vOff[idx], vbLen)
|
|
362
|
+
ch.cursor += kbLen + vbLen
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
-- Update cache: drop removed, refresh changed, leave untouched in place.
|
|
366
|
+
for i = 1, removedCount do
|
|
367
|
+
cachedPerKey[removed[i]] = nil
|
|
368
|
+
end
|
|
369
|
+
for i = 1, changedCount do
|
|
370
|
+
local idx = changed[i]
|
|
371
|
+
local k = keys[idx]
|
|
372
|
+
local kbLen = kLen[idx]
|
|
373
|
+
local vbLen = vLen[idx]
|
|
374
|
+
local entry = cachedPerKey[k]
|
|
375
|
+
if entry then
|
|
376
|
+
entry.kBuf = copyToBuf(entry.kBuf, scratchBuf, kOff[idx], kbLen)
|
|
377
|
+
entry.kLen = kbLen
|
|
378
|
+
entry.vBuf = copyToBuf(entry.vBuf, scratchBuf, vOff[idx], vbLen)
|
|
379
|
+
entry.vLen = vbLen
|
|
380
|
+
else
|
|
381
|
+
cachedPerKey[k] = {
|
|
382
|
+
kBuf = copyToBuf(nil, scratchBuf, kOff[idx], kbLen),
|
|
383
|
+
kLen = kbLen,
|
|
384
|
+
vBuf = copyToBuf(nil, scratchBuf, vOff[idx], vbLen),
|
|
385
|
+
vLen = vbLen,
|
|
386
|
+
}
|
|
387
|
+
end
|
|
388
|
+
end
|
|
133
389
|
|
|
134
|
-
|
|
135
|
-
cache.len = scratchLen
|
|
390
|
+
releaseKeys()
|
|
136
391
|
releaseScratch()
|
|
137
392
|
end,
|
|
138
393
|
|
|
139
394
|
read = function(src: buffer, pos: number, refs: { Instance }?): ({ [any]: any }, number)
|
|
140
|
-
if
|
|
141
|
-
|
|
395
|
+
if pos >= bufLen(src) then
|
|
396
|
+
Log.error("truncated deltaMap header")
|
|
397
|
+
end
|
|
398
|
+
local flag = readu8(src, pos)
|
|
399
|
+
|
|
400
|
+
if flag == DM_UNCHANGED then
|
|
401
|
+
local cached = Baseline.getCache(deltaId)
|
|
402
|
+
if cached == nil then
|
|
403
|
+
Log.error("UNCHANGED flag before any FULL frame; baseline missing")
|
|
404
|
+
end
|
|
405
|
+
return cached, 1
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
if flag == DM_FULL then
|
|
409
|
+
local value, consumed = inner.read(src, pos + 1, refs)
|
|
410
|
+
Baseline.setCache(deltaId, value)
|
|
411
|
+
return value, 1 + consumed
|
|
142
412
|
end
|
|
143
|
-
|
|
144
|
-
|
|
413
|
+
|
|
414
|
+
if flag == DM_DIFF then
|
|
415
|
+
local cached = Baseline.getCache(deltaId)
|
|
416
|
+
if cached == nil then
|
|
417
|
+
Log.error("deltaMap DIFF before any FULL frame; baseline missing")
|
|
418
|
+
end
|
|
419
|
+
local result: { [any]: any } = table.clone(cached)
|
|
420
|
+
local total = 1
|
|
421
|
+
|
|
422
|
+
local removeCount, rcLen = varintRead(src, pos + total)
|
|
423
|
+
if rcLen == 0 then
|
|
424
|
+
Log.error("truncated deltaMap remove count")
|
|
425
|
+
end
|
|
426
|
+
-- Both counts bounded by maxCount; otherwise a malicious DIFF
|
|
427
|
+
-- could spin the loop arbitrarily long before erroring on
|
|
428
|
+
-- buffer overrun.
|
|
429
|
+
enforceMaxCount(removeCount, maxCount, "removeCount")
|
|
430
|
+
total += rcLen
|
|
431
|
+
for _ = 1, removeCount do
|
|
432
|
+
local k, kc = keyCodec.read(src, pos + total, refs)
|
|
433
|
+
if kc == 0 then
|
|
434
|
+
Log.error("truncated deltaMap removed key")
|
|
435
|
+
end
|
|
436
|
+
total += kc
|
|
437
|
+
result[k] = nil
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
local changeCount, ccLen = varintRead(src, pos + total)
|
|
441
|
+
if ccLen == 0 then
|
|
442
|
+
Log.error("truncated deltaMap change count")
|
|
443
|
+
end
|
|
444
|
+
enforceMaxCount(changeCount, maxCount, "changeCount")
|
|
445
|
+
total += ccLen
|
|
446
|
+
for _ = 1, changeCount do
|
|
447
|
+
local k, kc = keyCodec.read(src, pos + total, refs)
|
|
448
|
+
if kc == 0 then
|
|
449
|
+
Log.error("truncated deltaMap changed key")
|
|
450
|
+
end
|
|
451
|
+
total += kc
|
|
452
|
+
local v, vc = valueCodec.read(src, pos + total, refs)
|
|
453
|
+
if vc == 0 then
|
|
454
|
+
Log.error("truncated deltaMap changed value")
|
|
455
|
+
end
|
|
456
|
+
total += vc
|
|
457
|
+
result[k] = v
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
Baseline.setCache(deltaId, result)
|
|
461
|
+
return result, total
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
Log.error(`invalid deltaMap flag {flag}`)
|
|
145
465
|
end,
|
|
146
|
-
})
|
|
466
|
+
}) :: Types.InternalCodec<any>
|
|
147
467
|
end
|
|
148
468
|
|
|
149
469
|
return table.freeze(Map)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
3
|
-
-- Optional codec. 1-byte present/absent flag, value only
|
|
2
|
+
--!native
|
|
3
|
+
-- Optional codec. 1-byte present/absent flag, value only when present.
|
|
4
4
|
|
|
5
5
|
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
7
|
+
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
6
8
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
9
|
|
|
8
10
|
-- Private ----------------------------------------------------------------
|
|
9
11
|
|
|
10
12
|
local writeByte = Channel.writeByte
|
|
11
13
|
local readu8 = buffer.readu8
|
|
14
|
+
local bufferLen = buffer.len
|
|
12
15
|
|
|
13
16
|
-- Public -----------------------------------------------------------------
|
|
14
17
|
|
|
@@ -29,6 +32,7 @@ function Optional.optional(inner: Types.InternalCodec<any>): Types.InternalCodec
|
|
|
29
32
|
end,
|
|
30
33
|
|
|
31
34
|
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
35
|
+
Log.assert(pos < bufferLen(src), "presence byte missing at end of buffer")
|
|
32
36
|
if readu8(src, pos) == 0 then
|
|
33
37
|
return nil, 1
|
|
34
38
|
end
|
|
@@ -37,9 +41,13 @@ function Optional.optional(inner: Types.InternalCodec<any>): Types.InternalCodec
|
|
|
37
41
|
end,
|
|
38
42
|
}
|
|
39
43
|
|
|
44
|
+
-- Propagate _hasUnknown so Gate skips schema validation upstream.
|
|
40
45
|
if inner._hasUnknown then
|
|
41
46
|
codec._hasUnknown = true
|
|
42
47
|
end
|
|
48
|
+
if Shared.containsDelta(inner) then
|
|
49
|
+
codec._hasDelta = true
|
|
50
|
+
end
|
|
43
51
|
return table.freeze(codec)
|
|
44
52
|
end
|
|
45
53
|
|