@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,98 +1,81 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
--
|
|
3
|
+
-- array() and deltaArray(). Bool elements are bit-packed.
|
|
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
|
|
|
12
|
+
-- Constants --------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
-- deltaArray wire flags. Distinct from Shared's struct/map flag spaces.
|
|
15
|
+
local DA_UNCHANGED = 0
|
|
16
|
+
local DA_FULL = 1
|
|
17
|
+
local DA_PATCH = 2
|
|
18
|
+
|
|
11
19
|
-- Private ----------------------------------------------------------------
|
|
12
20
|
|
|
13
21
|
local alloc = Base.alloc
|
|
14
|
-
local
|
|
15
|
-
local
|
|
16
|
-
local readu8 = buffer.readu8
|
|
17
|
-
local band = bit32.band
|
|
18
|
-
local bor = bit32.bor
|
|
19
|
-
local lshift = bit32.lshift
|
|
20
|
-
|
|
22
|
+
local ensure = Base.ensure
|
|
23
|
+
local allocDeltaId = Shared.allocDeltaId
|
|
21
24
|
local acquireScratch = Shared.acquireScratch
|
|
22
25
|
local releaseScratch = Shared.releaseScratch
|
|
23
26
|
local rangeEqual = Shared.rangeEqual
|
|
24
|
-
local
|
|
25
|
-
local
|
|
26
|
-
local
|
|
27
|
-
local
|
|
27
|
+
local copyToBuf = Shared.copyToBuf
|
|
28
|
+
local containsDelta = Shared.containsDelta
|
|
29
|
+
local enforceMaxCount = Shared.enforceMaxCount
|
|
30
|
+
local boolBytes = Shared.boolBytes
|
|
31
|
+
local packBoolArray = Shared.packBoolArray
|
|
32
|
+
local unpackBoolArray = Shared.unpackBoolArray
|
|
33
|
+
local varintRead = Varint.read
|
|
34
|
+
local varintWrite = Varint.write
|
|
35
|
+
local varintLength = Varint.length
|
|
36
|
+
local writeu8 = buffer.writeu8
|
|
37
|
+
local readu8 = buffer.readu8
|
|
38
|
+
local bufCopy = buffer.copy
|
|
39
|
+
local bufLen = buffer.len
|
|
40
|
+
|
|
41
|
+
type DeltaArrayEntry = { eBuf: buffer, eLen: number }
|
|
28
42
|
|
|
43
|
+
-- Bit-packed bool element fast path: 1 bit per entry instead of 1 byte.
|
|
29
44
|
local function makeBoolArray(
|
|
30
45
|
element: Types.InternalCodec<any>,
|
|
31
46
|
maxCount: number?
|
|
32
47
|
): Types.InternalCodec<any>
|
|
33
|
-
return
|
|
48
|
+
return {
|
|
34
49
|
_isArray = true,
|
|
35
50
|
_element = element,
|
|
36
51
|
_maxCount = maxCount,
|
|
37
52
|
|
|
38
53
|
write = function(ch: Types.ChannelState, value: { boolean }): ()
|
|
39
54
|
local len = #value
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
local byteCount = (len + 7) // 8
|
|
46
|
-
local cursor = ch.cursor
|
|
47
|
-
if cursor + byteCount > ch.size then
|
|
48
|
-
alloc(ch, byteCount)
|
|
49
|
-
end
|
|
50
|
-
local b = ch.buff
|
|
51
|
-
|
|
52
|
-
local idx = 1
|
|
53
|
-
for byteIdx = 0, byteCount - 1 do
|
|
54
|
-
local packed = 0
|
|
55
|
-
local remaining = len - idx + 1
|
|
56
|
-
local limit = if remaining >= 8 then 8 else remaining
|
|
57
|
-
for bit = 0, limit - 1 do
|
|
58
|
-
if value[idx] then
|
|
59
|
-
packed = bor(packed, lshift(1, bit))
|
|
60
|
-
end
|
|
61
|
-
idx += 1
|
|
62
|
-
end
|
|
63
|
-
writeu8(b, cursor + byteIdx, packed)
|
|
64
|
-
end
|
|
65
|
-
ch.cursor = cursor + byteCount
|
|
55
|
+
varintWrite(ch, len)
|
|
56
|
+
packBoolArray(ch, value, len)
|
|
66
57
|
end,
|
|
67
58
|
|
|
68
59
|
read = function(src: buffer, pos: number, _refs: { Instance }?): ({ boolean }, number)
|
|
69
|
-
local len, lenBytes =
|
|
70
|
-
|
|
71
|
-
error(`[Lync] Array.read: count {len} exceeds max {maxCount}`)
|
|
72
|
-
end
|
|
60
|
+
local len, lenBytes = varintRead(src, pos)
|
|
61
|
+
enforceMaxCount(len, maxCount)
|
|
73
62
|
if len == 0 then
|
|
74
63
|
return {}, lenBytes
|
|
75
64
|
end
|
|
76
65
|
|
|
77
|
-
local byteCount = (len
|
|
66
|
+
local byteCount = boolBytes(len)
|
|
78
67
|
local dataStart = pos + lenBytes
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
local idx = 1
|
|
82
|
-
for byteIdx = 0, byteCount - 1 do
|
|
83
|
-
local byte = readu8(src, dataStart + byteIdx)
|
|
84
|
-
local remaining = len - idx + 1
|
|
85
|
-
local limit = if remaining >= 8 then 8 else remaining
|
|
86
|
-
for bit = 0, limit - 1 do
|
|
87
|
-
result[idx] = band(byte, lshift(1, bit)) ~= 0
|
|
88
|
-
idx += 1
|
|
89
|
-
end
|
|
68
|
+
if dataStart + byteCount > bufLen(src) then
|
|
69
|
+
Log.error(`payload {byteCount}B exceeds remaining buffer`)
|
|
90
70
|
end
|
|
71
|
+
local result = table.create(len, false)
|
|
72
|
+
unpackBoolArray(src, dataStart, len, result)
|
|
91
73
|
return result, lenBytes + byteCount
|
|
92
74
|
end,
|
|
93
|
-
}
|
|
75
|
+
}
|
|
94
76
|
end
|
|
95
77
|
|
|
78
|
+
-- Fixed-size element fast path: single alloc + tight loop calling _direct fns.
|
|
96
79
|
local function makeDirectArray(
|
|
97
80
|
element: Types.InternalCodec<any>,
|
|
98
81
|
elemSize: number,
|
|
@@ -100,14 +83,14 @@ local function makeDirectArray(
|
|
|
100
83
|
elemDR: (b: buffer, off: number) -> any,
|
|
101
84
|
maxCount: number?
|
|
102
85
|
): Types.InternalCodec<any>
|
|
103
|
-
return
|
|
86
|
+
return {
|
|
104
87
|
_isArray = true,
|
|
105
88
|
_element = element,
|
|
106
89
|
_maxCount = maxCount,
|
|
107
90
|
|
|
108
91
|
write = function(ch: Types.ChannelState, value: { any }): ()
|
|
109
92
|
local len = #value
|
|
110
|
-
|
|
93
|
+
varintWrite(ch, len)
|
|
111
94
|
if len == 0 then
|
|
112
95
|
return
|
|
113
96
|
end
|
|
@@ -118,7 +101,6 @@ local function makeDirectArray(
|
|
|
118
101
|
alloc(ch, payloadBytes)
|
|
119
102
|
end
|
|
120
103
|
local b = ch.buff
|
|
121
|
-
|
|
122
104
|
local off = cursor
|
|
123
105
|
for i = 1, len do
|
|
124
106
|
elemDW(b, off, value[i])
|
|
@@ -128,20 +110,16 @@ local function makeDirectArray(
|
|
|
128
110
|
end,
|
|
129
111
|
|
|
130
112
|
read = function(src: buffer, pos: number, _refs: { Instance }?): ({ any }, number)
|
|
131
|
-
local len, lenBytes =
|
|
132
|
-
|
|
133
|
-
error(`[Lync] Array.read: count {len} exceeds max {maxCount}`)
|
|
134
|
-
end
|
|
113
|
+
local len, lenBytes = varintRead(src, pos)
|
|
114
|
+
enforceMaxCount(len, maxCount)
|
|
135
115
|
if len == 0 then
|
|
136
116
|
return {}, lenBytes
|
|
137
117
|
end
|
|
138
118
|
|
|
139
119
|
local payloadBytes = len * elemSize
|
|
140
120
|
local dataStart = pos + lenBytes
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
len = remaining // elemSize
|
|
144
|
-
payloadBytes = len * elemSize
|
|
121
|
+
if payloadBytes > bufLen(src) - dataStart then
|
|
122
|
+
Log.error(`payload {payloadBytes}B exceeds remaining buffer`)
|
|
145
123
|
end
|
|
146
124
|
|
|
147
125
|
local result = table.create(len)
|
|
@@ -152,31 +130,30 @@ local function makeDirectArray(
|
|
|
152
130
|
end
|
|
153
131
|
return result, lenBytes + payloadBytes
|
|
154
132
|
end,
|
|
155
|
-
}
|
|
133
|
+
}
|
|
156
134
|
end
|
|
157
135
|
|
|
136
|
+
-- Generic fallback: variable-size or composite element codec.
|
|
158
137
|
local function makeGenericArray(
|
|
159
138
|
element: Types.InternalCodec<any>,
|
|
160
139
|
maxCount: number?
|
|
161
140
|
): Types.InternalCodec<any>
|
|
162
|
-
return
|
|
141
|
+
return {
|
|
163
142
|
_isArray = true,
|
|
164
143
|
_element = element,
|
|
165
144
|
_maxCount = maxCount,
|
|
166
145
|
|
|
167
146
|
write = function(ch: Types.ChannelState, value: { any }): ()
|
|
168
147
|
local len = #value
|
|
169
|
-
|
|
148
|
+
varintWrite(ch, len)
|
|
170
149
|
for i = 1, len do
|
|
171
150
|
element.write(ch, value[i])
|
|
172
151
|
end
|
|
173
152
|
end,
|
|
174
153
|
|
|
175
154
|
read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
|
|
176
|
-
local len, lenBytes =
|
|
177
|
-
|
|
178
|
-
error(`[Lync] Array.read: count {len} exceeds max {maxCount}`)
|
|
179
|
-
end
|
|
155
|
+
local len, lenBytes = varintRead(src, pos)
|
|
156
|
+
enforceMaxCount(len, maxCount)
|
|
180
157
|
if len == 0 then
|
|
181
158
|
return {}, lenBytes
|
|
182
159
|
end
|
|
@@ -186,14 +163,14 @@ local function makeGenericArray(
|
|
|
186
163
|
for i = 1, len do
|
|
187
164
|
local v, consumed = element.read(src, pos + total, refs)
|
|
188
165
|
if consumed == 0 then
|
|
189
|
-
error(`
|
|
166
|
+
Log.error(`element codec consumed 0 bytes at index {i}`)
|
|
190
167
|
end
|
|
191
168
|
result[i] = v
|
|
192
169
|
total += consumed
|
|
193
170
|
end
|
|
194
171
|
return result, total
|
|
195
172
|
end,
|
|
196
|
-
}
|
|
173
|
+
}
|
|
197
174
|
end
|
|
198
175
|
|
|
199
176
|
-- Public -----------------------------------------------------------------
|
|
@@ -201,75 +178,279 @@ end
|
|
|
201
178
|
local Array = {}
|
|
202
179
|
|
|
203
180
|
function Array.array(element: Types.InternalCodec<any>, maxCount: number?): Types.InternalCodec<any>
|
|
181
|
+
local codec: Types.InternalCodec<any>
|
|
204
182
|
if element._isBool then
|
|
205
|
-
|
|
183
|
+
codec = makeBoolArray(element, maxCount)
|
|
184
|
+
else
|
|
185
|
+
local elemSize = element._size
|
|
186
|
+
local elemDW = element._directWrite
|
|
187
|
+
local elemDR = element._directRead
|
|
188
|
+
if elemSize and elemDW and elemDR then
|
|
189
|
+
codec = makeDirectArray(element, elemSize, elemDW, elemDR, maxCount)
|
|
190
|
+
else
|
|
191
|
+
codec = makeGenericArray(element, maxCount)
|
|
192
|
+
end
|
|
206
193
|
end
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
local elemDW = element._directWrite
|
|
210
|
-
local elemDR = element._directRead
|
|
211
|
-
if elemSize and elemDW and elemDR then
|
|
212
|
-
return makeDirectArray(element, elemSize, elemDW, elemDR, maxCount)
|
|
194
|
+
if element._hasUnknown then
|
|
195
|
+
codec._hasUnknown = true
|
|
213
196
|
end
|
|
214
|
-
|
|
215
|
-
|
|
197
|
+
if containsDelta(element) then
|
|
198
|
+
codec._hasDelta = true
|
|
199
|
+
end
|
|
200
|
+
return table.freeze(codec)
|
|
216
201
|
end
|
|
217
202
|
|
|
203
|
+
--[[
|
|
204
|
+
Per-index deltaArray. Wire:
|
|
205
|
+
[0] UNCHANGED (1 byte)
|
|
206
|
+
[1] FULL <count varint> <element bytes ...>
|
|
207
|
+
[2] PATCH <newLen varint> <changeCount varint>
|
|
208
|
+
<(idx varint, element bytes) ...>
|
|
209
|
+
|
|
210
|
+
Length shrinkage is implicit in newLen — entries past newLen drop from
|
|
211
|
+
the cached map. Length growth: appended indices show up as changes.
|
|
212
|
+
Reordered lists tend to mark every index changed; the writer falls back
|
|
213
|
+
to FULL when changeCount * 2 >= newLen since PATCH index varints eat
|
|
214
|
+
any savings past that ratio.
|
|
215
|
+
]]
|
|
218
216
|
function Array.deltaArray(
|
|
219
217
|
element: Types.InternalCodec<any>,
|
|
220
218
|
maxCount: number?
|
|
221
219
|
): Types.InternalCodec<any>
|
|
220
|
+
-- Inner delta would chain-diff across elements within a frame, not across frames.
|
|
221
|
+
if containsDelta(element) then
|
|
222
|
+
Log.error("deltaArray element cannot contain delta state")
|
|
223
|
+
end
|
|
224
|
+
|
|
222
225
|
local deltaId = allocDeltaId()
|
|
223
|
-
local
|
|
226
|
+
local inner = Array.array(element, maxCount)
|
|
227
|
+
|
|
228
|
+
--[[
|
|
229
|
+
Encode each element into `scratch` and capture per-index byte ranges.
|
|
230
|
+
Returns the array length plus parallel arrays of offset/length so the
|
|
231
|
+
diff path can compare and emit individual entries without re-encoding.
|
|
232
|
+
]]
|
|
233
|
+
local function encodePerIdx(
|
|
234
|
+
scratch: Types.ChannelState,
|
|
235
|
+
value: { any }
|
|
236
|
+
): (number, { number }, { number })
|
|
237
|
+
local len = #value
|
|
238
|
+
enforceMaxCount(len, maxCount)
|
|
239
|
+
local off = table.create(len)
|
|
240
|
+
local elen = table.create(len)
|
|
241
|
+
for i = 1, len do
|
|
242
|
+
local before = scratch.cursor
|
|
243
|
+
element.write(scratch, value[i])
|
|
244
|
+
off[i] = before
|
|
245
|
+
elen[i] = scratch.cursor - before
|
|
246
|
+
end
|
|
247
|
+
return len, off, elen
|
|
248
|
+
end
|
|
224
249
|
|
|
225
250
|
return table.freeze({
|
|
226
251
|
_isDelta = true,
|
|
252
|
+
_isArray = true,
|
|
253
|
+
_element = element,
|
|
254
|
+
_maxCount = maxCount,
|
|
227
255
|
|
|
228
256
|
write = function(ch: Types.ChannelState, value: { any }): ()
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
writeByte(ch, DELTA_FULL)
|
|
257
|
+
if typeof(value) ~= "table" then
|
|
258
|
+
Log.error(`expected table, got {typeof(value)}`)
|
|
259
|
+
end
|
|
233
260
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
261
|
+
local cache = ch.deltas[deltaId] :: any
|
|
262
|
+
local scratch = acquireScratch()
|
|
263
|
+
local len, off, elen = encodePerIdx(scratch, value)
|
|
264
|
+
local scratchBuf = scratch.buff
|
|
237
265
|
|
|
238
|
-
|
|
239
|
-
|
|
266
|
+
-- First frame: emit FULL using the per-index scratch bytes.
|
|
267
|
+
if not cache then
|
|
268
|
+
ensure(ch, 1)
|
|
269
|
+
writeu8(ch.buff, ch.cursor, DA_FULL)
|
|
270
|
+
ch.cursor += 1
|
|
271
|
+
varintWrite(ch, len)
|
|
272
|
+
local payloadLen = scratch.cursor
|
|
273
|
+
ensure(ch, payloadLen)
|
|
274
|
+
bufCopy(ch.buff, ch.cursor, scratchBuf, 0, payloadLen)
|
|
275
|
+
ch.cursor += payloadLen
|
|
276
|
+
|
|
277
|
+
local perIdx: { [number]: DeltaArrayEntry } = {}
|
|
278
|
+
for i = 1, len do
|
|
279
|
+
local n = elen[i]
|
|
280
|
+
perIdx[i] = { eBuf = copyToBuf(nil, scratchBuf, off[i], n), eLen = n }
|
|
281
|
+
end
|
|
282
|
+
ch.deltas[deltaId] = { perIdx = perIdx, length = len } :: any
|
|
240
283
|
releaseScratch()
|
|
241
284
|
return
|
|
242
285
|
end
|
|
243
286
|
|
|
244
|
-
local
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
287
|
+
local cachedPerIdx = cache.perIdx :: { [number]: DeltaArrayEntry }
|
|
288
|
+
local cachedLen = cache.length :: number
|
|
289
|
+
|
|
290
|
+
-- Find changed indices in the overlap; appended indices count as changed.
|
|
291
|
+
local changed: { number } = {}
|
|
292
|
+
local changedCount = 0
|
|
293
|
+
local overlap = if len < cachedLen then len else cachedLen
|
|
294
|
+
for i = 1, overlap do
|
|
295
|
+
local entry = cachedPerIdx[i]
|
|
296
|
+
local n = elen[i]
|
|
297
|
+
if
|
|
298
|
+
not entry
|
|
299
|
+
or n ~= entry.eLen
|
|
300
|
+
or not rangeEqual(scratchBuf, off[i], entry.eBuf, 0, n)
|
|
301
|
+
then
|
|
302
|
+
changedCount += 1
|
|
303
|
+
changed[changedCount] = i
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
for i = overlap + 1, len do
|
|
307
|
+
changedCount += 1
|
|
308
|
+
changed[changedCount] = i
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
if changedCount == 0 and len == cachedLen then
|
|
312
|
+
ensure(ch, 1)
|
|
313
|
+
writeu8(ch.buff, ch.cursor, DA_UNCHANGED)
|
|
314
|
+
ch.cursor += 1
|
|
253
315
|
releaseScratch()
|
|
254
316
|
return
|
|
255
317
|
end
|
|
256
318
|
|
|
257
|
-
|
|
258
|
-
|
|
319
|
+
-- Pick FULL vs PATCH on actual byte cost. Both share `1 + varintLength(len)`
|
|
320
|
+
-- so it cancels: compare PATCH's index-header overhead + changed payload
|
|
321
|
+
-- against FULL's full payload.
|
|
322
|
+
local fullPayload = scratch.cursor
|
|
323
|
+
local patchPayload = varintLength(changedCount)
|
|
324
|
+
for i = 1, changedCount do
|
|
325
|
+
local idx = changed[i]
|
|
326
|
+
patchPayload += varintLength(idx) + elen[idx]
|
|
327
|
+
end
|
|
328
|
+
|
|
329
|
+
if patchPayload < fullPayload then
|
|
330
|
+
ensure(ch, 1)
|
|
331
|
+
writeu8(ch.buff, ch.cursor, DA_PATCH)
|
|
332
|
+
ch.cursor += 1
|
|
333
|
+
varintWrite(ch, len)
|
|
334
|
+
varintWrite(ch, changedCount)
|
|
335
|
+
for i = 1, changedCount do
|
|
336
|
+
local idx = changed[i]
|
|
337
|
+
varintWrite(ch, idx)
|
|
338
|
+
local n = elen[idx]
|
|
339
|
+
ensure(ch, n)
|
|
340
|
+
bufCopy(ch.buff, ch.cursor, scratchBuf, off[idx], n)
|
|
341
|
+
ch.cursor += n
|
|
342
|
+
end
|
|
343
|
+
else
|
|
344
|
+
ensure(ch, 1)
|
|
345
|
+
writeu8(ch.buff, ch.cursor, DA_FULL)
|
|
346
|
+
ch.cursor += 1
|
|
347
|
+
varintWrite(ch, len)
|
|
348
|
+
ensure(ch, fullPayload)
|
|
349
|
+
bufCopy(ch.buff, ch.cursor, scratchBuf, 0, fullPayload)
|
|
350
|
+
ch.cursor += fullPayload
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
-- Update cache: refresh changed (and appended), drop truncated.
|
|
354
|
+
for i = 1, changedCount do
|
|
355
|
+
local idx = changed[i]
|
|
356
|
+
local n = elen[idx]
|
|
357
|
+
local entry = cachedPerIdx[idx]
|
|
358
|
+
if entry then
|
|
359
|
+
entry.eBuf = copyToBuf(entry.eBuf, scratchBuf, off[idx], n)
|
|
360
|
+
entry.eLen = n
|
|
361
|
+
else
|
|
362
|
+
cachedPerIdx[idx] = { eBuf = copyToBuf(nil, scratchBuf, off[idx], n), eLen = n }
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
for i = len + 1, cachedLen do
|
|
366
|
+
cachedPerIdx[i] = nil
|
|
367
|
+
end
|
|
368
|
+
cache.length = len
|
|
259
369
|
|
|
260
|
-
cache.raw = snapshot(scratch.buff, scratchLen)
|
|
261
|
-
cache.len = scratchLen
|
|
262
370
|
releaseScratch()
|
|
263
371
|
end,
|
|
264
372
|
|
|
265
373
|
read = function(src: buffer, pos: number, refs: { Instance }?): ({ any }, number)
|
|
266
|
-
if
|
|
267
|
-
|
|
374
|
+
if pos >= bufLen(src) then
|
|
375
|
+
Log.error("truncated deltaArray header")
|
|
376
|
+
end
|
|
377
|
+
local flag = readu8(src, pos)
|
|
378
|
+
|
|
379
|
+
if flag == DA_UNCHANGED then
|
|
380
|
+
local cached = Baseline.getCache(deltaId)
|
|
381
|
+
if cached == nil then
|
|
382
|
+
Log.error("UNCHANGED flag before any FULL frame; baseline missing")
|
|
383
|
+
end
|
|
384
|
+
return cached, 1
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
if flag == DA_FULL then
|
|
388
|
+
local value, consumed = inner.read(src, pos + 1, refs)
|
|
389
|
+
Baseline.setCache(deltaId, value)
|
|
390
|
+
return value, 1 + consumed
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
if flag == DA_PATCH then
|
|
394
|
+
local cached = Baseline.getCache(deltaId)
|
|
395
|
+
if cached == nil then
|
|
396
|
+
Log.error("deltaArray PATCH before any FULL frame; baseline missing")
|
|
397
|
+
end
|
|
398
|
+
--[[
|
|
399
|
+
Capture the cached length BEFORE clone+mutate; #result
|
|
400
|
+
becomes unreliable once we set entries at appended indices
|
|
401
|
+
past the original boundary.
|
|
402
|
+
]]
|
|
403
|
+
local cachedLen = #cached
|
|
404
|
+
local result: { any } = table.clone(cached)
|
|
405
|
+
local total = 1
|
|
406
|
+
|
|
407
|
+
local newLen, lLen = varintRead(src, pos + total)
|
|
408
|
+
if lLen == 0 then
|
|
409
|
+
Log.error("truncated deltaArray length")
|
|
410
|
+
end
|
|
411
|
+
enforceMaxCount(newLen, maxCount, "newLen")
|
|
412
|
+
total += lLen
|
|
413
|
+
|
|
414
|
+
local changeCount, cLen = varintRead(src, pos + total)
|
|
415
|
+
if cLen == 0 then
|
|
416
|
+
Log.error("truncated deltaArray change count")
|
|
417
|
+
end
|
|
418
|
+
-- changeCount is also bounded by newLen: every index appears at most once.
|
|
419
|
+
if changeCount > newLen then
|
|
420
|
+
Log.error(`deltaArray changeCount {changeCount} exceeds newLen {newLen}`)
|
|
421
|
+
end
|
|
422
|
+
total += cLen
|
|
423
|
+
|
|
424
|
+
for _ = 1, changeCount do
|
|
425
|
+
local idx, iLen = varintRead(src, pos + total)
|
|
426
|
+
if iLen == 0 then
|
|
427
|
+
Log.error("truncated deltaArray index")
|
|
428
|
+
end
|
|
429
|
+
-- Reject out-of-range idx so the wire can't poison the cache with
|
|
430
|
+
-- arbitrary keys that survive truncation.
|
|
431
|
+
if idx < 1 or idx > newLen then
|
|
432
|
+
Log.error(`deltaArray index {idx} out of range [1, {newLen}]`)
|
|
433
|
+
end
|
|
434
|
+
total += iLen
|
|
435
|
+
local v, vc = element.read(src, pos + total, refs)
|
|
436
|
+
if vc == 0 then
|
|
437
|
+
Log.error("truncated deltaArray element")
|
|
438
|
+
end
|
|
439
|
+
total += vc
|
|
440
|
+
result[idx] = v
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
for i = newLen + 1, cachedLen do
|
|
444
|
+
result[i] = nil
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
Baseline.setCache(deltaId, result)
|
|
448
|
+
return result, total
|
|
268
449
|
end
|
|
269
|
-
|
|
270
|
-
|
|
450
|
+
|
|
451
|
+
Log.error(`invalid deltaArray flag {flag}`)
|
|
271
452
|
end,
|
|
272
|
-
})
|
|
453
|
+
}) :: Types.InternalCodec<any>
|
|
273
454
|
end
|
|
274
455
|
|
|
275
456
|
return table.freeze(Array)
|