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