@axpecter/lync 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -0
- package/package.json +38 -0
- package/src/Types.luau +63 -0
- package/src/api/Group.luau +126 -0
- package/src/api/Namespace.luau +226 -0
- package/src/api/Packet.luau +147 -0
- package/src/api/Query.luau +295 -0
- package/src/api/Signal.luau +224 -0
- package/src/codec/Base.luau +49 -0
- package/src/codec/composite/Array.luau +275 -0
- package/src/codec/composite/Map.luau +395 -0
- package/src/codec/composite/Optional.luau +47 -0
- package/src/codec/composite/Shared.luau +151 -0
- package/src/codec/composite/Struct.luau +440 -0
- package/src/codec/composite/Tagged.luau +222 -0
- package/src/codec/composite/Tuple.luau +143 -0
- package/src/codec/datatype/Buffer.luau +44 -0
- package/src/codec/datatype/CFrame.luau +51 -0
- package/src/codec/datatype/Color.luau +22 -0
- package/src/codec/datatype/Instance.luau +48 -0
- package/src/codec/datatype/IntVector.luau +25 -0
- package/src/codec/datatype/NumberRange.luau +14 -0
- package/src/codec/datatype/Ray.luau +27 -0
- package/src/codec/datatype/Rect.luau +21 -0
- package/src/codec/datatype/Region.luau +58 -0
- package/src/codec/datatype/Sequence.luau +129 -0
- package/src/codec/datatype/String.luau +87 -0
- package/src/codec/datatype/UDim.luau +27 -0
- package/src/codec/datatype/Vector.luau +25 -0
- package/src/codec/meta/Auto.luau +353 -0
- package/src/codec/meta/Bitfield.luau +191 -0
- package/src/codec/meta/Custom.luau +27 -0
- package/src/codec/meta/Enum.luau +80 -0
- package/src/codec/meta/Nothing.luau +9 -0
- package/src/codec/meta/Quantized.luau +170 -0
- package/src/codec/meta/Unknown.luau +35 -0
- package/src/codec/primitive/Bool.luau +30 -0
- package/src/codec/primitive/Float16.luau +111 -0
- package/src/codec/primitive/Number.luau +48 -0
- package/src/codec/primitive/Varint.luau +76 -0
- package/src/index.d.ts +279 -0
- package/src/init.luau +161 -0
- package/src/internal/Baseline.luau +41 -0
- package/src/internal/Channel.luau +235 -0
- package/src/internal/Middleware.luau +109 -0
- package/src/internal/Pool.luau +68 -0
- package/src/internal/Registry.luau +146 -0
- package/src/transport/Bridge.luau +66 -0
- package/src/transport/Client.luau +151 -0
- package/src/transport/Gate.luau +222 -0
- package/src/transport/Reader.luau +175 -0
- package/src/transport/Server.luau +364 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!native
|
|
3
|
+
-- struct and deltaStruct codecs.
|
|
4
|
+
|
|
5
|
+
local Baseline = require (script.Parent.Parent.Parent.internal.Baseline)
|
|
6
|
+
local Channel = require (script.Parent.Parent.Parent.internal.Channel)
|
|
7
|
+
local alloc = Channel.alloc
|
|
8
|
+
local Shared = require (script.Parent.Shared)
|
|
9
|
+
local Types = require (script.Parent.Parent.Parent.Types)
|
|
10
|
+
|
|
11
|
+
type ChannelState = Types.ChannelState
|
|
12
|
+
type Codec<T> = Types.Codec<T>
|
|
13
|
+
|
|
14
|
+
local extractFields = Shared.extractFields
|
|
15
|
+
local packBools = Shared.packBools
|
|
16
|
+
local unpackBools = Shared.unpackBools
|
|
17
|
+
local rangeEqual = Shared.rangeEqual
|
|
18
|
+
local acquireScratch = Shared.acquireScratch
|
|
19
|
+
local releaseScratch = Shared.releaseScratch
|
|
20
|
+
|
|
21
|
+
local FLAG_DELTA = Shared.FLAG_DELTA
|
|
22
|
+
local FLAG_FULL = Shared.FLAG_FULL
|
|
23
|
+
local FLAG_UNCHANGED = Shared.FLAG_UNCHANGED
|
|
24
|
+
|
|
25
|
+
local band = bit32.band
|
|
26
|
+
local bor = bit32.bor
|
|
27
|
+
local lshift = bit32.lshift
|
|
28
|
+
local ceil = math.ceil
|
|
29
|
+
local min = math.min
|
|
30
|
+
|
|
31
|
+
type DeltaCache = {
|
|
32
|
+
raw: buffer,
|
|
33
|
+
bounds: { number },
|
|
34
|
+
total: number,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
-- Public -----------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
local Struct = {}
|
|
40
|
+
|
|
41
|
+
function Struct.struct (schema: { [string]: Codec<any> }): Codec<any>
|
|
42
|
+
local dataKeys, dataCodecs, boolKeys = extractFields (schema)
|
|
43
|
+
|
|
44
|
+
local dataCount = #dataKeys
|
|
45
|
+
local boolCount = #boolKeys
|
|
46
|
+
local boolByteCount = ceil (boolCount / 8)
|
|
47
|
+
local hasBools = boolCount > 0
|
|
48
|
+
|
|
49
|
+
local canDirect = true
|
|
50
|
+
local fixedSize = boolByteCount
|
|
51
|
+
local isAllFixed = true
|
|
52
|
+
|
|
53
|
+
local directWrites = table.create (dataCount) :: { any }
|
|
54
|
+
local directReads = table.create (dataCount) :: { any }
|
|
55
|
+
local fieldSizes = table.create (dataCount) :: { number }
|
|
56
|
+
|
|
57
|
+
for i = 1, dataCount do
|
|
58
|
+
local codec = dataCodecs[i] :: any
|
|
59
|
+
local size = codec._size
|
|
60
|
+
local dw = codec._directWrite
|
|
61
|
+
local dr = codec._directRead
|
|
62
|
+
|
|
63
|
+
if size then
|
|
64
|
+
fixedSize += size
|
|
65
|
+
fieldSizes[i] = size
|
|
66
|
+
else
|
|
67
|
+
isAllFixed = false
|
|
68
|
+
canDirect = false
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if not (dw and dr and size) then
|
|
72
|
+
canDirect = false
|
|
73
|
+
else
|
|
74
|
+
directWrites[i] = dw
|
|
75
|
+
directReads[i] = dr
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
if canDirect and isAllFixed then
|
|
80
|
+
table.freeze (directWrites)
|
|
81
|
+
table.freeze (directReads)
|
|
82
|
+
table.freeze (fieldSizes)
|
|
83
|
+
|
|
84
|
+
local function directWriteBody (b: buffer, c: number, value: any): ()
|
|
85
|
+
for i = 1, dataCount do
|
|
86
|
+
directWrites[i] (b, c, value[dataKeys[i]])
|
|
87
|
+
c += fieldSizes[i]
|
|
88
|
+
end
|
|
89
|
+
if hasBools then
|
|
90
|
+
for byteIdx = 0, boolByteCount - 1 do
|
|
91
|
+
local packed = 0
|
|
92
|
+
local base = byteIdx * 8
|
|
93
|
+
local limit = min (8, boolCount - base)
|
|
94
|
+
for bit = 0, limit - 1 do
|
|
95
|
+
if value[boolKeys[base + bit + 1]] then
|
|
96
|
+
packed = bor (packed, lshift (1, bit))
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
buffer.writeu8 (b, c + byteIdx, packed)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
local function directReadBody (b: buffer, pos: number): any
|
|
105
|
+
local result = {}
|
|
106
|
+
local c = pos
|
|
107
|
+
for i = 1, dataCount do
|
|
108
|
+
result[dataKeys[i]] = directReads[i] (b, c)
|
|
109
|
+
c += fieldSizes[i]
|
|
110
|
+
end
|
|
111
|
+
if hasBools then
|
|
112
|
+
for byteIdx = 0, boolByteCount - 1 do
|
|
113
|
+
local byte = buffer.readu8 (b, c + byteIdx)
|
|
114
|
+
local base = byteIdx * 8
|
|
115
|
+
local limit = min (8, boolCount - base)
|
|
116
|
+
for bit = 0, limit - 1 do
|
|
117
|
+
result[boolKeys[base + bit + 1]] = band (byte, lshift (1, bit)) ~= 0
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
return result
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
return table.freeze ({
|
|
125
|
+
_size = fixedSize,
|
|
126
|
+
_directWrite = directWriteBody,
|
|
127
|
+
_directRead = directReadBody,
|
|
128
|
+
|
|
129
|
+
write = function (ch: ChannelState, value: any): ()
|
|
130
|
+
local c = ch.cursor
|
|
131
|
+
if c + fixedSize > ch.size then
|
|
132
|
+
alloc (ch, fixedSize)
|
|
133
|
+
end
|
|
134
|
+
directWriteBody (ch.buff, c, value)
|
|
135
|
+
ch.cursor = c + fixedSize
|
|
136
|
+
end,
|
|
137
|
+
read = function (src: buffer, pos: number, _refs: { Instance }?): (any, number)
|
|
138
|
+
return directReadBody (src, pos), fixedSize
|
|
139
|
+
end,
|
|
140
|
+
})
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
local writeFns = table.create (dataCount) :: { any }
|
|
144
|
+
local readFns = table.create (dataCount) :: { any }
|
|
145
|
+
for i = 1, dataCount do
|
|
146
|
+
writeFns[i] = dataCodecs[i].write
|
|
147
|
+
readFns[i] = dataCodecs[i].read
|
|
148
|
+
end
|
|
149
|
+
table.freeze (writeFns)
|
|
150
|
+
table.freeze (readFns)
|
|
151
|
+
|
|
152
|
+
return table.freeze ({
|
|
153
|
+
_size = if isAllFixed then fixedSize else nil,
|
|
154
|
+
|
|
155
|
+
write = function (ch: ChannelState, value: any): ()
|
|
156
|
+
if isAllFixed then
|
|
157
|
+
alloc (ch, fixedSize)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
for i = 1, dataCount do
|
|
161
|
+
writeFns[i] (ch, value[dataKeys[i]])
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
if hasBools then
|
|
165
|
+
packBools (ch, boolKeys, boolCount, value)
|
|
166
|
+
end
|
|
167
|
+
end,
|
|
168
|
+
read = function (src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
169
|
+
local result = {}
|
|
170
|
+
local absPos = pos
|
|
171
|
+
|
|
172
|
+
for i = 1, dataCount do
|
|
173
|
+
local value, n = readFns[i] (src, absPos, refs)
|
|
174
|
+
result[dataKeys[i]] = value
|
|
175
|
+
absPos += n
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
if hasBools then
|
|
179
|
+
absPos += unpackBools (src, absPos, boolKeys, boolCount, result)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
return result, absPos - pos
|
|
183
|
+
end,
|
|
184
|
+
})
|
|
185
|
+
end
|
|
186
|
+
function Struct.deltaStruct (schema: { [string]: Codec<any> }): Codec<any>
|
|
187
|
+
local dataKeys, dataCodecs, boolKeys = extractFields (schema)
|
|
188
|
+
|
|
189
|
+
local dataCount = #dataKeys
|
|
190
|
+
local boolCount = #boolKeys
|
|
191
|
+
local boolByteCount = ceil (boolCount / 8)
|
|
192
|
+
local segCount = dataCount + (if boolCount > 0 then 1 else 0)
|
|
193
|
+
local bitmaskBytes = ceil (segCount / 8)
|
|
194
|
+
local deltaId = Shared.allocDeltaId ()
|
|
195
|
+
|
|
196
|
+
-- Scratch bounds reused every frame (only cloned into cache)
|
|
197
|
+
local _scratchBounds = table.create (segCount + 1) :: { number }
|
|
198
|
+
_scratchBounds[1] = 0
|
|
199
|
+
|
|
200
|
+
local writeFns = table.create (dataCount) :: { any }
|
|
201
|
+
local readFns = table.create (dataCount) :: { any }
|
|
202
|
+
for i = 1, dataCount do
|
|
203
|
+
writeFns[i] = dataCodecs[i].write
|
|
204
|
+
readFns[i] = dataCodecs[i].read
|
|
205
|
+
end
|
|
206
|
+
table.freeze (writeFns)
|
|
207
|
+
table.freeze (readFns)
|
|
208
|
+
|
|
209
|
+
local scratchDirect = true
|
|
210
|
+
local scratchDirectWrites = table.create (dataCount) :: { any }
|
|
211
|
+
local scratchFieldSizes = table.create (dataCount) :: { number }
|
|
212
|
+
local scratchFieldOffsets = table.create (dataCount) :: { number }
|
|
213
|
+
local scratchDataTotal = 0
|
|
214
|
+
|
|
215
|
+
for i = 1, dataCount do
|
|
216
|
+
local codec = dataCodecs[i] :: any
|
|
217
|
+
local size = codec._size
|
|
218
|
+
local dw = codec._directWrite
|
|
219
|
+
if size and dw then
|
|
220
|
+
scratchDirectWrites[i] = dw
|
|
221
|
+
scratchFieldSizes[i] = size
|
|
222
|
+
scratchFieldOffsets[i] = scratchDataTotal
|
|
223
|
+
scratchDataTotal += size
|
|
224
|
+
else
|
|
225
|
+
scratchDirect = false
|
|
226
|
+
break
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
local scratchTotalFixed = scratchDataTotal + boolByteCount
|
|
231
|
+
|
|
232
|
+
-- Skip scratch alloc at runtime when the total fits in the initial
|
|
233
|
+
-- 1024-byte scratch buffer.
|
|
234
|
+
local scratchSkipAlloc = scratchDirect and scratchTotalFixed <= 1024
|
|
235
|
+
|
|
236
|
+
return table.freeze ({
|
|
237
|
+
_isDelta = true,
|
|
238
|
+
|
|
239
|
+
write = function (ch: ChannelState, value: any): ()
|
|
240
|
+
local cache = ch.deltas[deltaId] :: DeltaCache?
|
|
241
|
+
local scratch = acquireScratch ()
|
|
242
|
+
|
|
243
|
+
local bounds = _scratchBounds
|
|
244
|
+
bounds[1] = 0
|
|
245
|
+
|
|
246
|
+
if scratchDirect then
|
|
247
|
+
if not scratchSkipAlloc then
|
|
248
|
+
alloc (scratch, scratchTotalFixed)
|
|
249
|
+
end
|
|
250
|
+
local b = scratch.buff
|
|
251
|
+
|
|
252
|
+
for i = 1, dataCount do
|
|
253
|
+
scratchDirectWrites[i] (b, scratchFieldOffsets[i], value[dataKeys[i]])
|
|
254
|
+
bounds[i + 1] = scratchFieldOffsets[i] + scratchFieldSizes[i]
|
|
255
|
+
end
|
|
256
|
+
scratch.cursor = scratchDataTotal
|
|
257
|
+
|
|
258
|
+
if boolCount > 0 then
|
|
259
|
+
packBools (scratch, boolKeys, boolCount, value)
|
|
260
|
+
bounds[segCount + 1] = scratch.cursor
|
|
261
|
+
end
|
|
262
|
+
else
|
|
263
|
+
for i = 1, dataCount do
|
|
264
|
+
writeFns[i] (scratch, value[dataKeys[i]])
|
|
265
|
+
bounds[i + 1] = scratch.cursor
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
if boolCount > 0 then
|
|
269
|
+
packBools (scratch, boolKeys, boolCount, value)
|
|
270
|
+
bounds[segCount + 1] = scratch.cursor
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
local scratchTotal = scratch.cursor
|
|
275
|
+
|
|
276
|
+
-- FLAG_FULL: first frame, no cache exists yet
|
|
277
|
+
if not cache then
|
|
278
|
+
alloc (ch, 1 + scratchTotal)
|
|
279
|
+
buffer.writeu8 (ch.buff, ch.cursor, FLAG_FULL)
|
|
280
|
+
ch.cursor += 1
|
|
281
|
+
buffer.copy (ch.buff, ch.cursor, scratch.buff, 0, scratchTotal)
|
|
282
|
+
ch.cursor += scratchTotal
|
|
283
|
+
|
|
284
|
+
-- FLAG_UNCHANGED: entire buffer is byte-identical to cache
|
|
285
|
+
elseif
|
|
286
|
+
scratchTotal == cache.total
|
|
287
|
+
and rangeEqual (scratch.buff, 0, cache.raw, 0, scratchTotal)
|
|
288
|
+
then
|
|
289
|
+
alloc (ch, 1)
|
|
290
|
+
buffer.writeu8 (ch.buff, ch.cursor, FLAG_UNCHANGED)
|
|
291
|
+
ch.cursor += 1
|
|
292
|
+
releaseScratch ()
|
|
293
|
+
return
|
|
294
|
+
|
|
295
|
+
-- FLAG_DELTA: per-segment dirty mask + dirty payloads
|
|
296
|
+
elseif bitmaskBytes == 1 then
|
|
297
|
+
local mask = 0
|
|
298
|
+
local cacheRaw = cache.raw
|
|
299
|
+
local cacheBds = cache.bounds
|
|
300
|
+
|
|
301
|
+
for i = 1, segCount do
|
|
302
|
+
local newOff = bounds[i]
|
|
303
|
+
local newLen = bounds[i + 1] - newOff
|
|
304
|
+
local oldOff = cacheBds[i]
|
|
305
|
+
local oldLen = cacheBds[i + 1] - oldOff
|
|
306
|
+
|
|
307
|
+
if
|
|
308
|
+
newLen ~= oldLen
|
|
309
|
+
or not rangeEqual (scratch.buff, newOff, cacheRaw, oldOff, newLen)
|
|
310
|
+
then
|
|
311
|
+
mask = bor (mask, lshift (1, i - 1))
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
alloc (ch, 2)
|
|
316
|
+
buffer.writeu8 (ch.buff, ch.cursor, FLAG_DELTA)
|
|
317
|
+
buffer.writeu8 (ch.buff, ch.cursor + 1, mask)
|
|
318
|
+
ch.cursor += 2
|
|
319
|
+
|
|
320
|
+
for i = 1, segCount do
|
|
321
|
+
if band (mask, lshift (1, i - 1)) ~= 0 then
|
|
322
|
+
local newOff = bounds[i]
|
|
323
|
+
local newLen = bounds[i + 1] - newOff
|
|
324
|
+
alloc (ch, newLen)
|
|
325
|
+
buffer.copy (ch.buff, ch.cursor, scratch.buff, newOff, newLen)
|
|
326
|
+
ch.cursor += newLen
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
else
|
|
330
|
+
local maskBuf = table.create (bitmaskBytes, 0)
|
|
331
|
+
local cacheRaw = cache.raw
|
|
332
|
+
local cacheBds = cache.bounds
|
|
333
|
+
|
|
334
|
+
for i = 1, segCount do
|
|
335
|
+
local newOff = bounds[i]
|
|
336
|
+
local newLen = bounds[i + 1] - newOff
|
|
337
|
+
local oldOff = cacheBds[i]
|
|
338
|
+
local oldLen = cacheBds[i + 1] - oldOff
|
|
339
|
+
|
|
340
|
+
if
|
|
341
|
+
newLen ~= oldLen
|
|
342
|
+
or not rangeEqual (scratch.buff, newOff, cacheRaw, oldOff, newLen)
|
|
343
|
+
then
|
|
344
|
+
local bitIdx = i - 1
|
|
345
|
+
local byteIdx = bitIdx // 8 + 1
|
|
346
|
+
maskBuf[byteIdx] = bor (maskBuf[byteIdx], lshift (1, bitIdx % 8))
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
alloc (ch, 1 + bitmaskBytes)
|
|
351
|
+
buffer.writeu8 (ch.buff, ch.cursor, FLAG_DELTA)
|
|
352
|
+
ch.cursor += 1
|
|
353
|
+
|
|
354
|
+
local maskStart = ch.cursor
|
|
355
|
+
for byteIdx = 1, bitmaskBytes do
|
|
356
|
+
buffer.writeu8 (ch.buff, maskStart + byteIdx - 1, maskBuf[byteIdx])
|
|
357
|
+
end
|
|
358
|
+
ch.cursor = maskStart + bitmaskBytes
|
|
359
|
+
|
|
360
|
+
for i = 1, segCount do
|
|
361
|
+
local bitIdx = i - 1
|
|
362
|
+
local byteIdx = bitIdx // 8 + 1
|
|
363
|
+
if band (maskBuf[byteIdx], lshift (1, bitIdx % 8)) ~= 0 then
|
|
364
|
+
local newOff = bounds[i]
|
|
365
|
+
local newLen = bounds[i + 1] - newOff
|
|
366
|
+
alloc (ch, newLen)
|
|
367
|
+
buffer.copy (ch.buff, ch.cursor, scratch.buff, newOff, newLen)
|
|
368
|
+
ch.cursor += newLen
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
local rawBuf = buffer.create (scratchTotal)
|
|
374
|
+
buffer.copy (rawBuf, 0, scratch.buff, 0, scratchTotal)
|
|
375
|
+
|
|
376
|
+
local cacheBounds = table.clone (bounds)
|
|
377
|
+
table.freeze (cacheBounds)
|
|
378
|
+
ch.deltas[deltaId] = {
|
|
379
|
+
raw = rawBuf,
|
|
380
|
+
bounds = cacheBounds,
|
|
381
|
+
total = scratchTotal,
|
|
382
|
+
} :: DeltaCache
|
|
383
|
+
|
|
384
|
+
releaseScratch ()
|
|
385
|
+
end,
|
|
386
|
+
|
|
387
|
+
read = function (src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
388
|
+
local flag = buffer.readu8 (src, pos)
|
|
389
|
+
local absPos = pos + 1
|
|
390
|
+
|
|
391
|
+
if flag == FLAG_UNCHANGED then
|
|
392
|
+
local cache = Baseline.getCache (deltaId) :: any
|
|
393
|
+
return if cache then table.clone (cache) else {}, 1
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
if flag == FLAG_FULL then
|
|
397
|
+
local result = {}
|
|
398
|
+
|
|
399
|
+
for i = 1, dataCount do
|
|
400
|
+
local val, n = readFns[i] (src, absPos, refs)
|
|
401
|
+
result[dataKeys[i]] = val
|
|
402
|
+
absPos += n
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
if boolCount > 0 then
|
|
406
|
+
absPos += unpackBools (src, absPos, boolKeys, boolCount, result)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
Baseline.setCache (deltaId, result)
|
|
410
|
+
return result, absPos - pos
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
local cache = Baseline.getCache (deltaId) :: any
|
|
414
|
+
local result = if cache then table.clone (cache) else {}
|
|
415
|
+
|
|
416
|
+
local maskStart = absPos
|
|
417
|
+
absPos += bitmaskBytes
|
|
418
|
+
|
|
419
|
+
for i = 1, segCount do
|
|
420
|
+
local bitIdx = i - 1
|
|
421
|
+
local byte = buffer.readu8 (src, maskStart + (bitIdx // 8))
|
|
422
|
+
|
|
423
|
+
if band (byte, lshift (1, bitIdx % 8)) ~= 0 then
|
|
424
|
+
if i <= dataCount then
|
|
425
|
+
local val, n = readFns[i] (src, absPos, refs)
|
|
426
|
+
result[dataKeys[i]] = val
|
|
427
|
+
absPos += n
|
|
428
|
+
else
|
|
429
|
+
absPos += unpackBools (src, absPos, boolKeys, boolCount, result)
|
|
430
|
+
end
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
Baseline.setCache (deltaId, result)
|
|
435
|
+
return result, absPos - pos
|
|
436
|
+
end,
|
|
437
|
+
})
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
return table.freeze (Struct)
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
--!native
|
|
3
|
+
-- Discriminated union codec. u8 variant tag + variant payload.
|
|
4
|
+
|
|
5
|
+
local Channel = require (script.Parent.Parent.Parent.internal.Channel)
|
|
6
|
+
local alloc = Channel.alloc
|
|
7
|
+
local Types = require (script.Parent.Parent.Parent.Types)
|
|
8
|
+
|
|
9
|
+
type ChannelState = Types.ChannelState
|
|
10
|
+
type Codec<T> = Types.Codec<T>
|
|
11
|
+
|
|
12
|
+
-- Public -----------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
local Tagged = {}
|
|
15
|
+
|
|
16
|
+
-- Read injects tagField into the decoded table. Variant codecs must return mutable tables.
|
|
17
|
+
function Tagged.define (tagField: string, variants: { [string]: Codec<any> }): Codec<any>
|
|
18
|
+
if #tagField == 0 then
|
|
19
|
+
error ("[Lync] Tagged union tagField must not be empty")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
local names = {} :: { string }
|
|
23
|
+
for name in variants do
|
|
24
|
+
if #name == 0 then
|
|
25
|
+
error ("[Lync] Tagged union variant name must not be empty")
|
|
26
|
+
end
|
|
27
|
+
table.insert (names, name)
|
|
28
|
+
end
|
|
29
|
+
table.sort (names)
|
|
30
|
+
|
|
31
|
+
local count = #names
|
|
32
|
+
if count == 0 then
|
|
33
|
+
error ("[Lync] Tagged union requires at least one variant")
|
|
34
|
+
end
|
|
35
|
+
if count > 256 then
|
|
36
|
+
error (`[Lync] Tagged union exceeds 256 variants: {count}`)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
local nameToTag = {} :: { [string]: number }
|
|
40
|
+
local tagCodecs = table.create (count) :: { Codec<any> }
|
|
41
|
+
local tagNames = table.create (count) :: { string }
|
|
42
|
+
local tagSizes = table.create (count) :: { number? }
|
|
43
|
+
|
|
44
|
+
local allDirect = true
|
|
45
|
+
local uniformSize: number? = (variants[names[1]] :: any)._size
|
|
46
|
+
local directWrites = table.create (count) :: { any }
|
|
47
|
+
local directReads = table.create (count) :: { any }
|
|
48
|
+
|
|
49
|
+
for i, name in names do
|
|
50
|
+
local codec = variants[name] :: any
|
|
51
|
+
nameToTag[name] = i
|
|
52
|
+
tagCodecs[i] = codec
|
|
53
|
+
tagNames[i] = name
|
|
54
|
+
|
|
55
|
+
local size = codec._size
|
|
56
|
+
tagSizes[i] = size
|
|
57
|
+
|
|
58
|
+
if size ~= uniformSize then
|
|
59
|
+
uniformSize = nil
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
local dw = codec._directWrite
|
|
63
|
+
local dr = codec._directRead
|
|
64
|
+
if dw and dr and size then
|
|
65
|
+
directWrites[i] = dw
|
|
66
|
+
directReads[i] = dr
|
|
67
|
+
else
|
|
68
|
+
allDirect = false
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
table.freeze (nameToTag)
|
|
73
|
+
table.freeze (tagCodecs)
|
|
74
|
+
table.freeze (tagNames)
|
|
75
|
+
table.freeze (tagSizes)
|
|
76
|
+
|
|
77
|
+
local fixedSize: number? = if uniformSize then 1 + uniformSize else nil
|
|
78
|
+
|
|
79
|
+
if allDirect then
|
|
80
|
+
table.freeze (directWrites)
|
|
81
|
+
table.freeze (directReads)
|
|
82
|
+
|
|
83
|
+
-- If uniform size, expose _directWrite/_directRead for parent struct/array
|
|
84
|
+
if fixedSize then
|
|
85
|
+
local variantBytes = uniformSize :: number
|
|
86
|
+
|
|
87
|
+
return table.freeze ({
|
|
88
|
+
_size = fixedSize,
|
|
89
|
+
_directWrite = function (b: buffer, offset: number, value: any): ()
|
|
90
|
+
local tag = nameToTag[value[tagField]]
|
|
91
|
+
if not tag then
|
|
92
|
+
error (`[Lync] Unknown variant: {value[tagField]}`)
|
|
93
|
+
end
|
|
94
|
+
buffer.writeu8 (b, offset, tag - 1)
|
|
95
|
+
directWrites[tag] (b, offset + 1, value)
|
|
96
|
+
end,
|
|
97
|
+
_directRead = function (b: buffer, offset: number): any
|
|
98
|
+
local tag = buffer.readu8 (b, offset) + 1
|
|
99
|
+
local name = tagNames[tag]
|
|
100
|
+
if not name then
|
|
101
|
+
error (`[Lync] Unknown variant index: {tag - 1}`)
|
|
102
|
+
end
|
|
103
|
+
local data = directReads[tag] (b, offset + 1)
|
|
104
|
+
if type (data) == "table" then
|
|
105
|
+
data[tagField] = name
|
|
106
|
+
end
|
|
107
|
+
return data
|
|
108
|
+
end,
|
|
109
|
+
write = function (ch: ChannelState, value: any): ()
|
|
110
|
+
local tag = nameToTag[value[tagField]]
|
|
111
|
+
if not tag then
|
|
112
|
+
error (`[Lync] Unknown variant: {value[tagField]}`)
|
|
113
|
+
end
|
|
114
|
+
local c = ch.cursor
|
|
115
|
+
if c + (fixedSize :: number) > ch.size then
|
|
116
|
+
alloc (ch, fixedSize :: number)
|
|
117
|
+
end
|
|
118
|
+
local b = ch.buff
|
|
119
|
+
buffer.writeu8 (b, c, tag - 1)
|
|
120
|
+
directWrites[tag] (b, c + 1, value)
|
|
121
|
+
ch.cursor = c + (fixedSize :: number)
|
|
122
|
+
end,
|
|
123
|
+
read = function (src: buffer, pos: number, _refs: { Instance }?): (any, number)
|
|
124
|
+
local tag = buffer.readu8 (src, pos) + 1
|
|
125
|
+
local name = tagNames[tag]
|
|
126
|
+
if not name then
|
|
127
|
+
error (`[Lync] Unknown variant index: {tag - 1}`)
|
|
128
|
+
end
|
|
129
|
+
local data = directReads[tag] (src, pos + 1)
|
|
130
|
+
if type (data) == "table" then
|
|
131
|
+
data[tagField] = name
|
|
132
|
+
end
|
|
133
|
+
return data, fixedSize :: number
|
|
134
|
+
end,
|
|
135
|
+
})
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
-- Non-uniform sizes but all have direct: use direct dispatch internally
|
|
139
|
+
local fieldSizes = table.clone (tagSizes) :: { number }
|
|
140
|
+
table.freeze (fieldSizes)
|
|
141
|
+
|
|
142
|
+
return table.freeze ({
|
|
143
|
+
_size = nil,
|
|
144
|
+
write = function (ch: ChannelState, value: any): ()
|
|
145
|
+
local tag = nameToTag[value[tagField]]
|
|
146
|
+
if not tag then
|
|
147
|
+
error (`[Lync] Unknown variant: {value[tagField]}`)
|
|
148
|
+
end
|
|
149
|
+
local varSize = fieldSizes[tag]
|
|
150
|
+
local totalNeeded = 1 + varSize
|
|
151
|
+
local c = ch.cursor
|
|
152
|
+
if c + totalNeeded > ch.size then
|
|
153
|
+
alloc (ch, totalNeeded)
|
|
154
|
+
end
|
|
155
|
+
local b = ch.buff
|
|
156
|
+
buffer.writeu8 (b, c, tag - 1)
|
|
157
|
+
directWrites[tag] (b, c + 1, value)
|
|
158
|
+
ch.cursor = c + totalNeeded
|
|
159
|
+
end,
|
|
160
|
+
read = function (src: buffer, pos: number, _refs: { Instance }?): (any, number)
|
|
161
|
+
local tag = buffer.readu8 (src, pos) + 1
|
|
162
|
+
local name = tagNames[tag]
|
|
163
|
+
if not name then
|
|
164
|
+
error (`[Lync] Unknown variant index: {tag - 1}`)
|
|
165
|
+
end
|
|
166
|
+
local data = directReads[tag] (src, pos + 1)
|
|
167
|
+
if type (data) == "table" then
|
|
168
|
+
data[tagField] = name
|
|
169
|
+
end
|
|
170
|
+
return data, 1 + fieldSizes[tag]
|
|
171
|
+
end,
|
|
172
|
+
})
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
local writeFns = table.create (count) :: { any }
|
|
176
|
+
local readFns = table.create (count) :: { any }
|
|
177
|
+
for i = 1, count do
|
|
178
|
+
writeFns[i] = tagCodecs[i].write
|
|
179
|
+
readFns[i] = tagCodecs[i].read
|
|
180
|
+
end
|
|
181
|
+
table.freeze (writeFns)
|
|
182
|
+
table.freeze (readFns)
|
|
183
|
+
|
|
184
|
+
return table.freeze ({
|
|
185
|
+
_size = fixedSize,
|
|
186
|
+
write = function (ch: ChannelState, value: any): ()
|
|
187
|
+
local tag = nameToTag[value[tagField]]
|
|
188
|
+
if not tag then
|
|
189
|
+
error (`[Lync] Unknown variant: {value[tagField]}`)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
local variantSize = tagSizes[tag]
|
|
193
|
+
if variantSize then
|
|
194
|
+
alloc (ch, 1 + variantSize)
|
|
195
|
+
else
|
|
196
|
+
alloc (ch, 1)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
local c = ch.cursor
|
|
200
|
+
buffer.writeu8 (ch.buff, c, tag - 1)
|
|
201
|
+
ch.cursor = c + 1
|
|
202
|
+
writeFns[tag] (ch, value)
|
|
203
|
+
end,
|
|
204
|
+
read = function (src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
205
|
+
local tag = buffer.readu8 (src, pos) + 1
|
|
206
|
+
local name = tagNames[tag]
|
|
207
|
+
if not name then
|
|
208
|
+
error (`[Lync] Unknown variant index: {tag - 1}`)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
local data, bytes = readFns[tag] (src, pos + 1, refs)
|
|
212
|
+
|
|
213
|
+
if type (data) == "table" then
|
|
214
|
+
data[tagField] = name
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
return data, 1 + bytes
|
|
218
|
+
end,
|
|
219
|
+
})
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
return table.freeze (Tagged)
|