@axpecter/lync 2.2.0 → 2.3.1
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 +95 -143
- package/package.json +1 -1
- package/src/Types.luau +22 -16
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +95 -72
- package/src/api/Query.luau +128 -79
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +21 -14
- package/src/codec/composite/Array.luau +56 -134
- package/src/codec/composite/Map.luau +103 -72
- package/src/codec/composite/Optional.luau +6 -2
- package/src/codec/composite/Shared.luau +160 -69
- package/src/codec/composite/Struct.luau +283 -42
- package/src/codec/composite/Tagged.luau +13 -16
- package/src/codec/composite/Tuple.luau +21 -15
- package/src/codec/datatype/Buffer.luau +6 -4
- package/src/codec/datatype/CFrame.luau +56 -44
- 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/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 +69 -38
- package/src/index.d.ts +161 -53
- package/src/init.luau +123 -102
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +153 -154
- 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 +162 -105
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +92 -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 +60 -0
- package/src/internal/Util.luau +0 -26
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
--
|
|
3
|
+
-- struct() and deltaStruct(). Bool fields bit-pack into a tail block.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
|
|
6
7
|
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
7
|
-
local
|
|
8
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
9
|
+
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
8
10
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
9
11
|
|
|
10
12
|
-- Private ----------------------------------------------------------------
|
|
@@ -12,10 +14,41 @@ local Types = require(script.Parent.Parent.Parent.Types)
|
|
|
12
14
|
local alloc = Base.alloc
|
|
13
15
|
local writeByte = Channel.writeByte
|
|
14
16
|
local writeu8 = buffer.writeu8
|
|
17
|
+
local writeu16 = buffer.writeu16
|
|
18
|
+
local writeu32 = buffer.writeu32
|
|
15
19
|
local readu8 = buffer.readu8
|
|
20
|
+
local readu16 = buffer.readu16
|
|
21
|
+
local readu32 = buffer.readu32
|
|
16
22
|
local band = bit32.band
|
|
17
23
|
local bor = bit32.bor
|
|
18
24
|
local lshift = bit32.lshift
|
|
25
|
+
local rshift = bit32.rshift
|
|
26
|
+
local countrz = bit32.countrz
|
|
27
|
+
|
|
28
|
+
-- 1/2/4-byte direct writes; 3-byte case splits into u16 + u8 (no native u24).
|
|
29
|
+
local function writeBitmap(b: buffer, off: number, mask: number, bitmapBytes: number): ()
|
|
30
|
+
if bitmapBytes == 1 then
|
|
31
|
+
writeu8(b, off, mask)
|
|
32
|
+
elseif bitmapBytes == 2 then
|
|
33
|
+
writeu16(b, off, mask)
|
|
34
|
+
elseif bitmapBytes == 4 then
|
|
35
|
+
writeu32(b, off, mask)
|
|
36
|
+
else
|
|
37
|
+
writeu16(b, off, band(mask, 0xFFFF))
|
|
38
|
+
writeu8(b, off + 2, band(rshift(mask, 16), 0xFF))
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
local function readBitmap(b: buffer, off: number, bitmapBytes: number): number
|
|
43
|
+
if bitmapBytes == 1 then
|
|
44
|
+
return readu8(b, off)
|
|
45
|
+
elseif bitmapBytes == 2 then
|
|
46
|
+
return readu16(b, off)
|
|
47
|
+
elseif bitmapBytes == 4 then
|
|
48
|
+
return readu32(b, off)
|
|
49
|
+
end
|
|
50
|
+
return bor(readu16(b, off), lshift(readu8(b, off + 2), 16))
|
|
51
|
+
end
|
|
19
52
|
|
|
20
53
|
local extractFields = Shared.extractFields
|
|
21
54
|
local packBools = Shared.packBools
|
|
@@ -27,6 +60,7 @@ local releaseScratch = Shared.releaseScratch
|
|
|
27
60
|
local allocDeltaId = Shared.allocDeltaId
|
|
28
61
|
local DELTA_UNCHANGED = Shared.DELTA_UNCHANGED
|
|
29
62
|
local DELTA_FULL_OFFSET = Shared.DELTA_FULL_OFFSET
|
|
63
|
+
local DELTA_MULTI_OFFSET = Shared.DELTA_MULTI_OFFSET
|
|
30
64
|
|
|
31
65
|
local function buildFrozenSchema(
|
|
32
66
|
dataKeys: { string },
|
|
@@ -53,16 +87,23 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
53
87
|
local dataKeys, dataCodecs, boolKeys = extractFields(schema)
|
|
54
88
|
local dataCount = #dataKeys
|
|
55
89
|
local boolCount = #boolKeys
|
|
56
|
-
local boolBytes = (boolCount
|
|
90
|
+
local boolBytes = Shared.boolBytes(boolCount)
|
|
57
91
|
|
|
92
|
+
-- Single pass: detect all-direct fast-write path + propagate _hasUnknown.
|
|
58
93
|
local allDirect, fixedSize = true, 0
|
|
94
|
+
local hasUnknown = false
|
|
59
95
|
for i = 1, dataCount do
|
|
60
96
|
local c = dataCodecs[i]
|
|
61
|
-
if
|
|
62
|
-
|
|
63
|
-
|
|
97
|
+
if c._hasUnknown then
|
|
98
|
+
hasUnknown = true
|
|
99
|
+
end
|
|
100
|
+
if allDirect then
|
|
101
|
+
if c._size and c._directWrite and c._directRead then
|
|
102
|
+
fixedSize += c._size
|
|
103
|
+
else
|
|
104
|
+
allDirect = false
|
|
105
|
+
end
|
|
64
106
|
end
|
|
65
|
-
fixedSize += c._size
|
|
66
107
|
end
|
|
67
108
|
|
|
68
109
|
local totalFixed = fixedSize + boolBytes
|
|
@@ -72,19 +113,19 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
72
113
|
local offsets = table.create(dataCount)
|
|
73
114
|
local directWrites = table.create(dataCount)
|
|
74
115
|
local directReads = table.create(dataCount)
|
|
75
|
-
local
|
|
116
|
+
local runningOffset = 0
|
|
76
117
|
for i = 1, dataCount do
|
|
77
118
|
local c = dataCodecs[i]
|
|
78
|
-
offsets[i] =
|
|
119
|
+
offsets[i] = runningOffset
|
|
79
120
|
directWrites[i] = c._directWrite
|
|
80
121
|
directReads[i] = c._directRead
|
|
81
|
-
|
|
122
|
+
runningOffset += c._size :: number
|
|
82
123
|
end
|
|
83
124
|
table.freeze(offsets)
|
|
84
125
|
table.freeze(directWrites)
|
|
85
126
|
table.freeze(directReads)
|
|
86
127
|
|
|
87
|
-
|
|
128
|
+
local codec: Types.InternalCodec<any> = {
|
|
88
129
|
_size = totalFixed,
|
|
89
130
|
_schema = frozenSchema,
|
|
90
131
|
|
|
@@ -96,6 +137,7 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
96
137
|
return
|
|
97
138
|
end
|
|
98
139
|
|
|
140
|
+
-- Inline bool packing to avoid the cursor-based packBools call.
|
|
99
141
|
local boolOff = off + fixedSize
|
|
100
142
|
local idx = 1
|
|
101
143
|
for byteIdx = 0, boolBytes - 1 do
|
|
@@ -124,15 +166,15 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
124
166
|
end,
|
|
125
167
|
|
|
126
168
|
write = function(ch: Types.ChannelState, value: any): ()
|
|
127
|
-
local
|
|
128
|
-
if
|
|
169
|
+
local cursor = ch.cursor
|
|
170
|
+
if cursor + totalFixed > ch.size then
|
|
129
171
|
alloc(ch, totalFixed)
|
|
130
172
|
end
|
|
131
173
|
local b = ch.buff
|
|
132
174
|
for i = 1, dataCount do
|
|
133
|
-
directWrites[i](b,
|
|
175
|
+
directWrites[i](b, cursor + offsets[i], value[dataKeys[i]])
|
|
134
176
|
end
|
|
135
|
-
ch.cursor =
|
|
177
|
+
ch.cursor = cursor + fixedSize
|
|
136
178
|
if boolCount > 0 then
|
|
137
179
|
packBools(ch, value, boolKeys, boolCount)
|
|
138
180
|
end
|
|
@@ -148,10 +190,14 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
148
190
|
end
|
|
149
191
|
return result, totalFixed
|
|
150
192
|
end,
|
|
151
|
-
}
|
|
193
|
+
}
|
|
194
|
+
if hasUnknown then
|
|
195
|
+
codec._hasUnknown = true
|
|
196
|
+
end
|
|
197
|
+
return table.freeze(codec)
|
|
152
198
|
end
|
|
153
199
|
|
|
154
|
-
|
|
200
|
+
local codec: Types.InternalCodec<any> = {
|
|
155
201
|
_schema = frozenSchema,
|
|
156
202
|
|
|
157
203
|
write = function(ch: Types.ChannelState, value: any): ()
|
|
@@ -176,66 +222,261 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
176
222
|
end
|
|
177
223
|
return result, total
|
|
178
224
|
end,
|
|
179
|
-
}
|
|
225
|
+
}
|
|
226
|
+
if hasUnknown then
|
|
227
|
+
codec._hasUnknown = true
|
|
228
|
+
end
|
|
229
|
+
return table.freeze(codec)
|
|
180
230
|
end
|
|
181
231
|
|
|
232
|
+
--[[
|
|
233
|
+
Per-field delta encoder. Splits a struct into segments (one per data
|
|
234
|
+
field, plus one for the packed-bool block if any) and emits only changed
|
|
235
|
+
segments. Wire flag byte:
|
|
236
|
+
0 UNCHANGED (reuse cache)
|
|
237
|
+
[1 .. segCount] single segment k changed
|
|
238
|
+
segCount + DELTA_FULL_OFFSET FULL payload
|
|
239
|
+
segCount + DELTA_MULTI_OFFSET MULTI: bitmap + dirty segs in order
|
|
240
|
+
]]
|
|
182
241
|
function Struct.deltaStruct(
|
|
183
242
|
schema: { [string]: Types.InternalCodec<any> }
|
|
184
243
|
): Types.InternalCodec<any>
|
|
185
|
-
local dataKeys,
|
|
186
|
-
local
|
|
244
|
+
local dataKeys, dataCodecs, boolKeys = extractFields(schema)
|
|
245
|
+
local dataCount = #dataKeys
|
|
246
|
+
local boolCount = #boolKeys
|
|
247
|
+
local hasBoolSeg = boolCount > 0
|
|
248
|
+
local segCount = dataCount + (if hasBoolSeg then 1 else 0)
|
|
187
249
|
local FLAG_FULL = segCount + DELTA_FULL_OFFSET
|
|
250
|
+
local FLAG_MULTI = segCount + DELTA_MULTI_OFFSET
|
|
251
|
+
local boolSegIdx = segCount
|
|
252
|
+
|
|
253
|
+
if FLAG_MULTI > 0xFF then
|
|
254
|
+
Log.error(`too many segments ({segCount}); flag byte would overflow`)
|
|
255
|
+
end
|
|
256
|
+
if segCount > 32 then
|
|
257
|
+
Log.error(`dirty bitmask is u32, segCount {segCount} exceeds 32`)
|
|
258
|
+
end
|
|
259
|
+
|
|
188
260
|
local deltaId = allocDeltaId()
|
|
189
|
-
local frozenSchema = buildFrozenSchema(dataKeys, boolKeys, schema)
|
|
190
261
|
local innerStruct = Struct.struct(schema)
|
|
262
|
+
-- Reuse innerStruct's frozen schema; avoids a second buildFrozenSchema.
|
|
263
|
+
local frozenSchema = innerStruct._schema :: { [string]: Types.InternalCodec<any> }
|
|
264
|
+
|
|
265
|
+
-- All-direct + fixed-size: segment offsets/lengths are statically known.
|
|
266
|
+
local allDirect = dataCount > 0
|
|
267
|
+
local fixedSize = 0
|
|
268
|
+
for i = 1, dataCount do
|
|
269
|
+
local c = dataCodecs[i]
|
|
270
|
+
if not c._size or not c._directWrite or not c._directRead then
|
|
271
|
+
allDirect = false
|
|
272
|
+
break
|
|
273
|
+
end
|
|
274
|
+
fixedSize += c._size :: number
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
local boolBytes = Shared.boolBytes(boolCount)
|
|
278
|
+
local bitmapBytes = Shared.boolBytes(segCount)
|
|
279
|
+
|
|
280
|
+
local staticSegOff: { number }? = nil
|
|
281
|
+
local staticSegLen: { number }? = nil
|
|
282
|
+
if allDirect then
|
|
283
|
+
local off = 0
|
|
284
|
+
local segOff = table.create(segCount)
|
|
285
|
+
local segLen = table.create(segCount)
|
|
286
|
+
for i = 1, dataCount do
|
|
287
|
+
segOff[i] = off
|
|
288
|
+
segLen[i] = dataCodecs[i]._size :: number
|
|
289
|
+
off += dataCodecs[i]._size :: number
|
|
290
|
+
end
|
|
291
|
+
if hasBoolSeg then
|
|
292
|
+
segOff[boolSegIdx] = off
|
|
293
|
+
segLen[boolSegIdx] = boolBytes
|
|
294
|
+
end
|
|
295
|
+
staticSegOff = table.freeze(segOff)
|
|
296
|
+
staticSegLen = table.freeze(segLen)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
-- Apply one segment's bytes onto `target`. Returns bytes consumed.
|
|
300
|
+
local function applySegmentRead(
|
|
301
|
+
target: { [string]: any },
|
|
302
|
+
segIdx: number,
|
|
303
|
+
src: buffer,
|
|
304
|
+
pos: number,
|
|
305
|
+
refs: { Instance }?
|
|
306
|
+
): number
|
|
307
|
+
if hasBoolSeg and segIdx == boolSegIdx then
|
|
308
|
+
return unpackBools(src, pos, boolKeys, boolCount, target)
|
|
309
|
+
end
|
|
310
|
+
local v, consumed = dataCodecs[segIdx].read(src, pos, refs)
|
|
311
|
+
target[dataKeys[segIdx]] = v
|
|
312
|
+
return consumed
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
-- Splice `src[srcOff..srcOff+len)` into the channel's main buffer.
|
|
316
|
+
local function writeBytes(ch: Types.ChannelState, src: buffer, srcOff: number, len: number): ()
|
|
317
|
+
if len == 0 then
|
|
318
|
+
return
|
|
319
|
+
end
|
|
320
|
+
local cursor = ch.cursor
|
|
321
|
+
if cursor + len > ch.size then
|
|
322
|
+
alloc(ch, len)
|
|
323
|
+
end
|
|
324
|
+
buffer.copy(ch.buff, cursor, src, srcOff, len)
|
|
325
|
+
ch.cursor = cursor + len
|
|
326
|
+
end
|
|
191
327
|
|
|
192
328
|
return table.freeze({
|
|
193
329
|
_isDelta = true,
|
|
194
330
|
_schema = frozenSchema,
|
|
195
331
|
|
|
196
332
|
write = function(ch: Types.ChannelState, value: any): ()
|
|
333
|
+
local scratch = acquireScratch()
|
|
334
|
+
local segOff: { number }, segLen: { number }
|
|
335
|
+
|
|
336
|
+
if staticSegOff then
|
|
337
|
+
-- Bulk write via innerStruct, constant offsets.
|
|
338
|
+
innerStruct.write(scratch, value)
|
|
339
|
+
segOff = staticSegOff :: { number }
|
|
340
|
+
segLen = staticSegLen :: { number }
|
|
341
|
+
else
|
|
342
|
+
segOff = table.create(segCount)
|
|
343
|
+
segLen = table.create(segCount)
|
|
344
|
+
local prev = 0
|
|
345
|
+
for i = 1, dataCount do
|
|
346
|
+
dataCodecs[i].write(scratch, value[dataKeys[i]])
|
|
347
|
+
local cur = scratch.cursor
|
|
348
|
+
segOff[i] = prev
|
|
349
|
+
segLen[i] = cur - prev
|
|
350
|
+
prev = cur
|
|
351
|
+
end
|
|
352
|
+
if hasBoolSeg then
|
|
353
|
+
packBools(scratch, value, boolKeys, boolCount)
|
|
354
|
+
segOff[boolSegIdx] = prev
|
|
355
|
+
segLen[boolSegIdx] = scratch.cursor - prev
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
local scratchLen = scratch.cursor
|
|
197
360
|
local cache = ch.deltas[deltaId]
|
|
198
361
|
|
|
199
362
|
if not cache then
|
|
200
363
|
writeByte(ch, FLAG_FULL)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
ch.deltas[deltaId] = {
|
|
207
|
-
|
|
364
|
+
writeBytes(ch, scratch.buff, 0, scratchLen)
|
|
365
|
+
--[[
|
|
366
|
+
Variable-size case stores fresh segOff/segLen by ref;
|
|
367
|
+
static case stores the module-level frozen tables.
|
|
368
|
+
]]
|
|
369
|
+
ch.deltas[deltaId] = {
|
|
370
|
+
raw = snapshot(scratch.buff, scratchLen),
|
|
371
|
+
len = scratchLen,
|
|
372
|
+
segOff = segOff,
|
|
373
|
+
segLen = segLen,
|
|
374
|
+
} :: any
|
|
208
375
|
releaseScratch()
|
|
209
376
|
return
|
|
210
377
|
end
|
|
211
378
|
|
|
212
|
-
local
|
|
213
|
-
|
|
214
|
-
local
|
|
379
|
+
local cacheRaw = cache.raw
|
|
380
|
+
local cacheOff = (cache :: any).segOff :: { number }
|
|
381
|
+
local cacheLen = (cache :: any).segLen :: { number }
|
|
382
|
+
|
|
383
|
+
local dirtyMask = 0
|
|
384
|
+
local dirtyCount = 0
|
|
385
|
+
for i = 1, segCount do
|
|
386
|
+
local newLen = segLen[i]
|
|
387
|
+
if
|
|
388
|
+
newLen ~= cacheLen[i]
|
|
389
|
+
or not rangeEqual(scratch.buff, segOff[i], cacheRaw, cacheOff[i], newLen)
|
|
390
|
+
then
|
|
391
|
+
dirtyMask = bor(dirtyMask, lshift(1, i - 1))
|
|
392
|
+
dirtyCount += 1
|
|
393
|
+
end
|
|
394
|
+
end
|
|
215
395
|
|
|
216
|
-
if
|
|
217
|
-
scratchLen == cache.len
|
|
218
|
-
and rangeEqual(scratch.buff, 0, cache.raw, 0, scratchLen)
|
|
219
|
-
then
|
|
396
|
+
if dirtyCount == 0 then
|
|
220
397
|
writeByte(ch, DELTA_UNCHANGED)
|
|
221
398
|
releaseScratch()
|
|
222
399
|
return
|
|
223
400
|
end
|
|
224
401
|
|
|
225
|
-
|
|
226
|
-
|
|
402
|
+
if dirtyCount == segCount then
|
|
403
|
+
writeByte(ch, FLAG_FULL)
|
|
404
|
+
writeBytes(ch, scratch.buff, 0, scratchLen)
|
|
405
|
+
elseif dirtyCount == 1 then
|
|
406
|
+
local segIdx = countrz(dirtyMask) + 1
|
|
407
|
+
writeByte(ch, segIdx)
|
|
408
|
+
writeBytes(ch, scratch.buff, segOff[segIdx], segLen[segIdx])
|
|
409
|
+
else
|
|
410
|
+
writeByte(ch, FLAG_MULTI)
|
|
411
|
+
local cursor = ch.cursor
|
|
412
|
+
if cursor + bitmapBytes > ch.size then
|
|
413
|
+
alloc(ch, bitmapBytes)
|
|
414
|
+
end
|
|
415
|
+
writeBitmap(ch.buff, cursor, dirtyMask, bitmapBytes)
|
|
416
|
+
ch.cursor = cursor + bitmapBytes
|
|
417
|
+
for i = 1, segCount do
|
|
418
|
+
if band(dirtyMask, lshift(1, i - 1)) ~= 0 then
|
|
419
|
+
writeBytes(ch, scratch.buff, segOff[i], segLen[i])
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
end
|
|
227
423
|
|
|
228
424
|
cache.raw = snapshot(scratch.buff, scratchLen)
|
|
229
|
-
cache.len = scratchLen
|
|
425
|
+
cache.len = scratchLen;
|
|
426
|
+
(cache :: any).segOff = segOff;
|
|
427
|
+
(cache :: any).segLen = segLen
|
|
230
428
|
releaseScratch()
|
|
231
429
|
end,
|
|
232
430
|
|
|
233
431
|
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
234
|
-
|
|
235
|
-
|
|
432
|
+
local flag = readu8(src, pos)
|
|
433
|
+
|
|
434
|
+
if flag == DELTA_UNCHANGED then
|
|
435
|
+
local cached = Baseline.getCache(deltaId)
|
|
436
|
+
if not cached then
|
|
437
|
+
Log.error("UNCHANGED flag before any FULL frame; baseline missing")
|
|
438
|
+
end
|
|
439
|
+
return cached, 1
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
if flag == FLAG_FULL then
|
|
443
|
+
local value, consumed = innerStruct.read(src, pos + 1, refs)
|
|
444
|
+
Baseline.setCache(deltaId, value)
|
|
445
|
+
return value, 1 + consumed
|
|
236
446
|
end
|
|
237
|
-
|
|
238
|
-
|
|
447
|
+
|
|
448
|
+
if flag <= segCount then
|
|
449
|
+
local cached = Baseline.getCache(deltaId)
|
|
450
|
+
if cached == nil then
|
|
451
|
+
Log.error(
|
|
452
|
+
`per-field update (seg {flag}) before any FULL frame; baseline missing`
|
|
453
|
+
)
|
|
454
|
+
end
|
|
455
|
+
local newValue = table.clone(cached)
|
|
456
|
+
local consumed = applySegmentRead(newValue, flag, src, pos + 1, refs)
|
|
457
|
+
Baseline.setCache(deltaId, newValue)
|
|
458
|
+
return newValue, 1 + consumed
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
if flag == FLAG_MULTI then
|
|
462
|
+
local cached = Baseline.getCache(deltaId)
|
|
463
|
+
if not cached then
|
|
464
|
+
Log.error("multi-field update before any FULL frame")
|
|
465
|
+
end
|
|
466
|
+
local newValue = table.clone(cached)
|
|
467
|
+
local mask = readBitmap(src, pos + 1, bitmapBytes)
|
|
468
|
+
local total = 1 + bitmapBytes
|
|
469
|
+
for i = 1, segCount do
|
|
470
|
+
if band(mask, lshift(1, i - 1)) ~= 0 then
|
|
471
|
+
local consumed = applySegmentRead(newValue, i, src, pos + total, refs)
|
|
472
|
+
total += consumed
|
|
473
|
+
end
|
|
474
|
+
end
|
|
475
|
+
Baseline.setCache(deltaId, newValue)
|
|
476
|
+
return newValue, total
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
Log.error(`invalid delta flag {flag}`)
|
|
239
480
|
end,
|
|
240
481
|
}) :: Types.InternalCodec<any>
|
|
241
482
|
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
2
|
+
--!native
|
|
3
3
|
-- Discriminated union codec. u8 variant tag + variant payload.
|
|
4
4
|
|
|
5
5
|
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
|
|
8
9
|
-- Private ----------------------------------------------------------------
|
|
@@ -18,10 +19,9 @@ function Tagged.define(
|
|
|
18
19
|
tagField: string,
|
|
19
20
|
variants: { [string]: Types.InternalCodec<any> }
|
|
20
21
|
): Types.InternalCodec<any>
|
|
21
|
-
|
|
22
|
-
error("[Lync] Lync.tagged: tagField cannot be empty")
|
|
23
|
-
end
|
|
22
|
+
Log.assert(tagField ~= "", "tagField cannot be empty")
|
|
24
23
|
|
|
24
|
+
-- Sort variant names so the wire-form tag id is stable across runs.
|
|
25
25
|
local names: { string } = {}
|
|
26
26
|
for name in variants do
|
|
27
27
|
table.insert(names, name)
|
|
@@ -29,16 +29,13 @@ function Tagged.define(
|
|
|
29
29
|
table.sort(names)
|
|
30
30
|
|
|
31
31
|
local variantCount = #names
|
|
32
|
-
|
|
33
|
-
error("[Lync] Lync.tagged: requires at least one variant")
|
|
34
|
-
end
|
|
32
|
+
Log.assert(variantCount > 0, "requires at least one variant")
|
|
35
33
|
if variantCount > 256 then
|
|
36
|
-
error(
|
|
34
|
+
Log.error(`exceeds 256 variants ({variantCount})`)
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
local nameToTag: { [string]: number } = {}
|
|
40
38
|
local tagToCodec = table.create(variantCount)
|
|
41
|
-
local tagToName = table.create(variantCount)
|
|
42
39
|
local hasUnknown = false
|
|
43
40
|
|
|
44
41
|
for i = 1, variantCount do
|
|
@@ -46,15 +43,14 @@ function Tagged.define(
|
|
|
46
43
|
local codec = variants[name]
|
|
47
44
|
nameToTag[name] = i - 1
|
|
48
45
|
tagToCodec[i] = codec
|
|
49
|
-
tagToName[i] = name
|
|
50
46
|
if codec._hasUnknown then
|
|
51
47
|
hasUnknown = true
|
|
52
48
|
end
|
|
53
49
|
end
|
|
54
50
|
|
|
51
|
+
table.freeze(names)
|
|
55
52
|
table.freeze(nameToTag)
|
|
56
53
|
table.freeze(tagToCodec)
|
|
57
|
-
table.freeze(tagToName)
|
|
58
54
|
|
|
59
55
|
local codec: Types.InternalCodec<any> = {
|
|
60
56
|
_isTagged = true,
|
|
@@ -64,11 +60,11 @@ function Tagged.define(
|
|
|
64
60
|
write = function(ch: Types.ChannelState, value: any): ()
|
|
65
61
|
local name = value[tagField]
|
|
66
62
|
if name == nil then
|
|
67
|
-
error(`
|
|
63
|
+
Log.error(`missing tagField "{tagField}"`)
|
|
68
64
|
end
|
|
69
65
|
local tag = nameToTag[name]
|
|
70
66
|
if tag == nil then
|
|
71
|
-
error(`
|
|
67
|
+
Log.error(`unknown variant "{name}"`)
|
|
72
68
|
end
|
|
73
69
|
|
|
74
70
|
writeByte(ch, tag)
|
|
@@ -79,12 +75,13 @@ function Tagged.define(
|
|
|
79
75
|
local tag = readu8(src, pos)
|
|
80
76
|
local varCodec = tagToCodec[tag + 1]
|
|
81
77
|
if not varCodec then
|
|
82
|
-
error(`
|
|
78
|
+
Log.error(`unknown tag {tag}`)
|
|
83
79
|
end
|
|
84
80
|
|
|
85
81
|
local data, consumed = varCodec.read(src, pos + 1, refs)
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
-- Re-attach the discriminator so callers can dispatch on it.
|
|
83
|
+
if typeof(data) == "table" then
|
|
84
|
+
data[tagField] = names[tag + 1]
|
|
88
85
|
end
|
|
89
86
|
return data, 1 + consumed
|
|
90
87
|
end,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
2
|
+
--!native
|
|
3
3
|
-- Positional ordered codec. Like struct but indexed.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
|
|
8
9
|
-- Private ----------------------------------------------------------------
|
|
@@ -16,36 +17,38 @@ local Tuple = {}
|
|
|
16
17
|
function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
|
|
17
18
|
local codecs = { ... }
|
|
18
19
|
local count = #codecs
|
|
19
|
-
|
|
20
|
-
error("[Lync] Lync.tuple: requires at least one codec")
|
|
21
|
-
end
|
|
20
|
+
Log.assert(count > 0, "requires at least one codec")
|
|
22
21
|
|
|
22
|
+
-- Single pass: detect all-direct fast path + propagate _hasUnknown.
|
|
23
23
|
local allDirect, totalSize, hasUnknown = true, 0, false
|
|
24
24
|
for i = 1, count do
|
|
25
25
|
local c = codecs[i]
|
|
26
26
|
if c._hasUnknown then
|
|
27
27
|
hasUnknown = true
|
|
28
28
|
end
|
|
29
|
-
if
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
if allDirect then
|
|
30
|
+
if c._size and c._directWrite and c._directRead then
|
|
31
|
+
totalSize += c._size
|
|
32
|
+
else
|
|
33
|
+
allDirect = false
|
|
34
|
+
end
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
table.freeze(codecs)
|
|
36
38
|
|
|
39
|
+
-- All-direct: pre-compute offsets so write/read avoids the cursor dance per element.
|
|
37
40
|
if allDirect then
|
|
38
41
|
local offsets = table.create(count)
|
|
39
42
|
local directWrites = table.create(count)
|
|
40
43
|
local directReads = table.create(count)
|
|
41
|
-
local
|
|
44
|
+
local runningOffset = 0
|
|
42
45
|
|
|
43
46
|
for i = 1, count do
|
|
44
47
|
local c = codecs[i]
|
|
45
|
-
offsets[i] =
|
|
48
|
+
offsets[i] = runningOffset
|
|
46
49
|
directWrites[i] = c._directWrite
|
|
47
50
|
directReads[i] = c._directRead
|
|
48
|
-
|
|
51
|
+
runningOffset += c._size :: number
|
|
49
52
|
end
|
|
50
53
|
table.freeze(offsets)
|
|
51
54
|
table.freeze(directWrites)
|
|
@@ -71,15 +74,15 @@ function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
|
|
|
71
74
|
end,
|
|
72
75
|
|
|
73
76
|
write = function(ch: Types.ChannelState, value: any): ()
|
|
74
|
-
local
|
|
75
|
-
if
|
|
77
|
+
local cursor = ch.cursor
|
|
78
|
+
if cursor + totalSize > ch.size then
|
|
76
79
|
alloc(ch, totalSize)
|
|
77
80
|
end
|
|
78
81
|
local b = ch.buff
|
|
79
82
|
for i = 1, count do
|
|
80
|
-
directWrites[i](b,
|
|
83
|
+
directWrites[i](b, cursor + offsets[i], value[i])
|
|
81
84
|
end
|
|
82
|
-
ch.cursor =
|
|
85
|
+
ch.cursor = cursor + totalSize
|
|
83
86
|
end,
|
|
84
87
|
|
|
85
88
|
read = function(src: buffer, pos: number, _refs: { Instance }?): (any, number)
|
|
@@ -111,6 +114,9 @@ function Tuple.define(...: Types.InternalCodec<any>): Types.InternalCodec<any>
|
|
|
111
114
|
local total = 0
|
|
112
115
|
for i = 1, count do
|
|
113
116
|
local v, consumed = codecs[i].read(src, pos + total, refs)
|
|
117
|
+
if consumed == 0 then
|
|
118
|
+
Log.error(`element codec consumed 0 bytes at index {i}`)
|
|
119
|
+
end
|
|
114
120
|
result[i] = v
|
|
115
121
|
total += consumed
|
|
116
122
|
end
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
--!strict
|
|
2
|
-
--!
|
|
2
|
+
--!native
|
|
3
3
|
-- Raw buffer codec with dense-prefix-varint length prefix.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
6
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
8
|
local Varint = require(script.Parent.Parent.primitive.Varint)
|
|
8
9
|
|
|
9
10
|
-- Private ----------------------------------------------------------------
|
|
10
11
|
|
|
11
12
|
local alloc = Base.alloc
|
|
12
|
-
local
|
|
13
|
+
local INLINE_MAX = Varint.INLINE_MAX
|
|
13
14
|
|
|
14
15
|
local function writeBuffer(ch: Types.ChannelState, value: buffer): ()
|
|
15
16
|
local len = buffer.len(value)
|
|
16
17
|
local cursor = ch.cursor
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
-- Single-byte length fast path skips the full varint encode.
|
|
20
|
+
if len <= INLINE_MAX then
|
|
19
21
|
if cursor + 1 + len > ch.size then
|
|
20
22
|
alloc(ch, 1 + len)
|
|
21
23
|
end
|
|
@@ -45,7 +47,7 @@ local function readBuffer(src: buffer, pos: number, _refs: { Instance }?): (buff
|
|
|
45
47
|
|
|
46
48
|
local dataStart = pos + lenBytes
|
|
47
49
|
if dataStart + len > buffer.len(src) then
|
|
48
|
-
error(
|
|
50
|
+
Log.error(`length {len}B exceeds remaining buffer`)
|
|
49
51
|
end
|
|
50
52
|
|
|
51
53
|
local out = buffer.create(len)
|