@axpecter/lync 2.3.2 → 3.0.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/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- package/src/util/Quat.luau +0 -124
|
@@ -1,494 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!native
|
|
3
|
-
-- struct() and deltaStruct(). Bool fields bit-pack into a tail block.
|
|
4
|
-
|
|
5
|
-
local Base = require(script.Parent.Parent.Base)
|
|
6
|
-
local Baseline = require(script.Parent.Parent.Parent.internal.Baseline)
|
|
7
|
-
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
8
|
-
local Log = require(script.Parent.Parent.Parent.util.Log)
|
|
9
|
-
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
10
|
-
local Types = require(script.Parent.Parent.Parent.Types)
|
|
11
|
-
|
|
12
|
-
-- Private ----------------------------------------------------------------
|
|
13
|
-
|
|
14
|
-
local alloc = Base.alloc
|
|
15
|
-
local writeByte = Channel.writeByte
|
|
16
|
-
local writeu8 = buffer.writeu8
|
|
17
|
-
local writeu16 = buffer.writeu16
|
|
18
|
-
local writeu32 = buffer.writeu32
|
|
19
|
-
local readu8 = buffer.readu8
|
|
20
|
-
local readu16 = buffer.readu16
|
|
21
|
-
local readu32 = buffer.readu32
|
|
22
|
-
local band = bit32.band
|
|
23
|
-
local bor = bit32.bor
|
|
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
|
|
52
|
-
|
|
53
|
-
local extractFields = Shared.extractFields
|
|
54
|
-
local packBools = Shared.packBools
|
|
55
|
-
local unpackBools = Shared.unpackBools
|
|
56
|
-
local rangeEqual = Shared.rangeEqual
|
|
57
|
-
local snapshot = Shared.snapshot
|
|
58
|
-
local refreshCache = Shared.refreshCache
|
|
59
|
-
local acquireScratch = Shared.acquireScratch
|
|
60
|
-
local releaseScratch = Shared.releaseScratch
|
|
61
|
-
local allocDeltaId = Shared.allocDeltaId
|
|
62
|
-
local DELTA_UNCHANGED = Shared.DELTA_UNCHANGED
|
|
63
|
-
local DELTA_FULL_OFFSET = Shared.DELTA_FULL_OFFSET
|
|
64
|
-
local DELTA_MULTI_OFFSET = Shared.DELTA_MULTI_OFFSET
|
|
65
|
-
|
|
66
|
-
local function buildFrozenSchema(
|
|
67
|
-
dataKeys: { string },
|
|
68
|
-
boolKeys: { string },
|
|
69
|
-
schema: { [string]: Types.InternalCodec<any> }
|
|
70
|
-
): { [string]: Types.InternalCodec<any> }
|
|
71
|
-
local frozen: { [string]: Types.InternalCodec<any> } = {}
|
|
72
|
-
for i = 1, #dataKeys do
|
|
73
|
-
local k = dataKeys[i]
|
|
74
|
-
frozen[k] = schema[k]
|
|
75
|
-
end
|
|
76
|
-
for i = 1, #boolKeys do
|
|
77
|
-
local k = boolKeys[i]
|
|
78
|
-
frozen[k] = schema[k]
|
|
79
|
-
end
|
|
80
|
-
return table.freeze(frozen)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
-- Public -----------------------------------------------------------------
|
|
84
|
-
|
|
85
|
-
local Struct = {}
|
|
86
|
-
|
|
87
|
-
function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.InternalCodec<any>
|
|
88
|
-
local dataKeys, dataCodecs, boolKeys = extractFields(schema)
|
|
89
|
-
local dataCount = #dataKeys
|
|
90
|
-
local boolCount = #boolKeys
|
|
91
|
-
local boolBytes = Shared.boolBytes(boolCount)
|
|
92
|
-
|
|
93
|
-
-- Single pass: detect all-direct fast-write path + propagate _hasUnknown / _hasDelta.
|
|
94
|
-
local allDirect, fixedSize = true, 0
|
|
95
|
-
local hasUnknown = false
|
|
96
|
-
local hasDelta = false
|
|
97
|
-
for i = 1, dataCount do
|
|
98
|
-
local c = dataCodecs[i]
|
|
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
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
local totalFixed = fixedSize + boolBytes
|
|
115
|
-
local frozenSchema = buildFrozenSchema(dataKeys, boolKeys, schema)
|
|
116
|
-
|
|
117
|
-
if allDirect and dataCount > 0 then
|
|
118
|
-
local offsets = table.create(dataCount)
|
|
119
|
-
local directWrites = table.create(dataCount)
|
|
120
|
-
local directReads = table.create(dataCount)
|
|
121
|
-
local runningOffset = 0
|
|
122
|
-
for i = 1, dataCount do
|
|
123
|
-
local c = dataCodecs[i]
|
|
124
|
-
offsets[i] = runningOffset
|
|
125
|
-
directWrites[i] = c._directWrite
|
|
126
|
-
directReads[i] = c._directRead
|
|
127
|
-
runningOffset += c._size :: number
|
|
128
|
-
end
|
|
129
|
-
table.freeze(offsets)
|
|
130
|
-
table.freeze(directWrites)
|
|
131
|
-
table.freeze(directReads)
|
|
132
|
-
|
|
133
|
-
local codec: Types.InternalCodec<any> = {
|
|
134
|
-
_size = totalFixed,
|
|
135
|
-
_schema = frozenSchema,
|
|
136
|
-
|
|
137
|
-
_directWrite = function(b: buffer, off: number, value: any): ()
|
|
138
|
-
for i = 1, dataCount do
|
|
139
|
-
directWrites[i](b, off + offsets[i], value[dataKeys[i]])
|
|
140
|
-
end
|
|
141
|
-
if boolCount == 0 then
|
|
142
|
-
return
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
-- Inline bool packing to avoid the cursor-based packBools call.
|
|
146
|
-
local boolOff = off + fixedSize
|
|
147
|
-
local idx = 1
|
|
148
|
-
for byteIdx = 0, boolBytes - 1 do
|
|
149
|
-
local packed = 0
|
|
150
|
-
local remaining = boolCount - idx + 1
|
|
151
|
-
local limit = if remaining >= 8 then 8 else remaining
|
|
152
|
-
for bit = 0, limit - 1 do
|
|
153
|
-
if value[boolKeys[idx]] then
|
|
154
|
-
packed = bor(packed, lshift(1, bit))
|
|
155
|
-
end
|
|
156
|
-
idx += 1
|
|
157
|
-
end
|
|
158
|
-
writeu8(b, boolOff + byteIdx, packed)
|
|
159
|
-
end
|
|
160
|
-
end,
|
|
161
|
-
|
|
162
|
-
_directRead = function(b: buffer, off: number): any
|
|
163
|
-
local result: { [string]: any } = {}
|
|
164
|
-
for i = 1, dataCount do
|
|
165
|
-
result[dataKeys[i]] = directReads[i](b, off + offsets[i])
|
|
166
|
-
end
|
|
167
|
-
if boolCount > 0 then
|
|
168
|
-
unpackBools(b, off + fixedSize, boolKeys, boolCount, result)
|
|
169
|
-
end
|
|
170
|
-
return result
|
|
171
|
-
end,
|
|
172
|
-
|
|
173
|
-
write = function(ch: Types.ChannelState, value: any): ()
|
|
174
|
-
local cursor = ch.cursor
|
|
175
|
-
if cursor + totalFixed > ch.size then
|
|
176
|
-
alloc(ch, totalFixed)
|
|
177
|
-
end
|
|
178
|
-
local b = ch.buff
|
|
179
|
-
for i = 1, dataCount do
|
|
180
|
-
directWrites[i](b, cursor + offsets[i], value[dataKeys[i]])
|
|
181
|
-
end
|
|
182
|
-
ch.cursor = cursor + fixedSize
|
|
183
|
-
if boolCount > 0 then
|
|
184
|
-
packBools(ch, value, boolKeys, boolCount)
|
|
185
|
-
end
|
|
186
|
-
end,
|
|
187
|
-
|
|
188
|
-
read = function(src: buffer, pos: number, _refs: { Instance }?): (any, number)
|
|
189
|
-
local result: { [string]: any } = {}
|
|
190
|
-
for i = 1, dataCount do
|
|
191
|
-
result[dataKeys[i]] = directReads[i](src, pos + offsets[i])
|
|
192
|
-
end
|
|
193
|
-
if boolCount > 0 then
|
|
194
|
-
unpackBools(src, pos + fixedSize, boolKeys, boolCount, result)
|
|
195
|
-
end
|
|
196
|
-
return result, totalFixed
|
|
197
|
-
end,
|
|
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)
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
local codec: Types.InternalCodec<any> = {
|
|
209
|
-
_schema = frozenSchema,
|
|
210
|
-
|
|
211
|
-
write = function(ch: Types.ChannelState, value: any): ()
|
|
212
|
-
for i = 1, dataCount do
|
|
213
|
-
dataCodecs[i].write(ch, value[dataKeys[i]])
|
|
214
|
-
end
|
|
215
|
-
if boolCount > 0 then
|
|
216
|
-
packBools(ch, value, boolKeys, boolCount)
|
|
217
|
-
end
|
|
218
|
-
end,
|
|
219
|
-
|
|
220
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
221
|
-
local result: { [string]: any } = {}
|
|
222
|
-
local total = 0
|
|
223
|
-
for i = 1, dataCount do
|
|
224
|
-
local v, consumed = dataCodecs[i].read(src, pos + total, refs)
|
|
225
|
-
result[dataKeys[i]] = v
|
|
226
|
-
total += consumed
|
|
227
|
-
end
|
|
228
|
-
if boolCount > 0 then
|
|
229
|
-
total += unpackBools(src, pos + total, boolKeys, boolCount, result)
|
|
230
|
-
end
|
|
231
|
-
return result, total
|
|
232
|
-
end,
|
|
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)
|
|
241
|
-
end
|
|
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
|
-
]]
|
|
252
|
-
function Struct.deltaStruct(
|
|
253
|
-
schema: { [string]: Types.InternalCodec<any> }
|
|
254
|
-
): Types.InternalCodec<any>
|
|
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)
|
|
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
|
-
|
|
271
|
-
local deltaId = allocDeltaId()
|
|
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
|
|
338
|
-
|
|
339
|
-
return table.freeze({
|
|
340
|
-
_isDelta = true,
|
|
341
|
-
_schema = frozenSchema,
|
|
342
|
-
|
|
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
|
|
371
|
-
local cache = ch.deltas[deltaId]
|
|
372
|
-
|
|
373
|
-
if not cache then
|
|
374
|
-
writeByte(ch, FLAG_FULL)
|
|
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
|
|
386
|
-
releaseScratch()
|
|
387
|
-
return
|
|
388
|
-
end
|
|
389
|
-
|
|
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
|
|
406
|
-
|
|
407
|
-
if dirtyCount == 0 then
|
|
408
|
-
writeByte(ch, DELTA_UNCHANGED)
|
|
409
|
-
releaseScratch()
|
|
410
|
-
return
|
|
411
|
-
end
|
|
412
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
refreshCache(cache, scratch.buff, scratchLen);
|
|
436
|
-
(cache :: any).segOff = segOff;
|
|
437
|
-
(cache :: any).segLen = segLen
|
|
438
|
-
releaseScratch()
|
|
439
|
-
end,
|
|
440
|
-
|
|
441
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
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
|
|
487
|
-
end
|
|
488
|
-
|
|
489
|
-
Log.error(`invalid delta flag {flag}`)
|
|
490
|
-
end,
|
|
491
|
-
}) :: Types.InternalCodec<any>
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
return table.freeze(Struct)
|
|
@@ -1,103 +0,0 @@
|
|
|
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 Log = require(script.Parent.Parent.Parent.util.Log)
|
|
7
|
-
local Shared = require(script.Parent.Parent.composite.Shared)
|
|
8
|
-
local Types = require(script.Parent.Parent.Parent.Types)
|
|
9
|
-
|
|
10
|
-
-- Private ----------------------------------------------------------------
|
|
11
|
-
|
|
12
|
-
local writeByte = Channel.writeByte
|
|
13
|
-
local readu8 = buffer.readu8
|
|
14
|
-
|
|
15
|
-
-- Public -----------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
local Tagged = {}
|
|
18
|
-
|
|
19
|
-
function Tagged.define(
|
|
20
|
-
tagField: string,
|
|
21
|
-
variants: { [string]: Types.InternalCodec<any> }
|
|
22
|
-
): Types.InternalCodec<any>
|
|
23
|
-
Log.assert(tagField ~= "", "tagField cannot be empty")
|
|
24
|
-
|
|
25
|
-
-- Sort variant names so the wire-form tag id is stable across runs.
|
|
26
|
-
local names: { string } = {}
|
|
27
|
-
for name in variants do
|
|
28
|
-
table.insert(names, name)
|
|
29
|
-
end
|
|
30
|
-
table.sort(names)
|
|
31
|
-
|
|
32
|
-
local variantCount = #names
|
|
33
|
-
Log.assert(variantCount > 0, "requires at least one variant")
|
|
34
|
-
if variantCount > 256 then
|
|
35
|
-
Log.error(`exceeds 256 variants ({variantCount})`)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
local nameToTag: { [string]: number } = {}
|
|
39
|
-
local tagToCodec = table.create(variantCount)
|
|
40
|
-
local hasUnknown = false
|
|
41
|
-
local hasDelta = false
|
|
42
|
-
|
|
43
|
-
for i = 1, variantCount do
|
|
44
|
-
local name = names[i]
|
|
45
|
-
local codec = variants[name]
|
|
46
|
-
nameToTag[name] = i - 1
|
|
47
|
-
tagToCodec[i] = codec
|
|
48
|
-
if codec._hasUnknown then
|
|
49
|
-
hasUnknown = true
|
|
50
|
-
end
|
|
51
|
-
if Shared.containsDelta(codec) then
|
|
52
|
-
hasDelta = true
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
table.freeze(names)
|
|
57
|
-
table.freeze(nameToTag)
|
|
58
|
-
table.freeze(tagToCodec)
|
|
59
|
-
|
|
60
|
-
local codec: Types.InternalCodec<any> = {
|
|
61
|
-
_isTagged = true,
|
|
62
|
-
_tagField = tagField,
|
|
63
|
-
_variants = variants,
|
|
64
|
-
|
|
65
|
-
write = function(ch: Types.ChannelState, value: any): ()
|
|
66
|
-
local name = value[tagField]
|
|
67
|
-
if name == nil then
|
|
68
|
-
Log.error(`missing tagField "{tagField}"`)
|
|
69
|
-
end
|
|
70
|
-
local tag = nameToTag[name]
|
|
71
|
-
if tag == nil then
|
|
72
|
-
Log.error(`unknown variant "{name}"`)
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
writeByte(ch, tag)
|
|
76
|
-
tagToCodec[tag + 1].write(ch, value)
|
|
77
|
-
end,
|
|
78
|
-
|
|
79
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
80
|
-
local tag = readu8(src, pos)
|
|
81
|
-
local varCodec = tagToCodec[tag + 1]
|
|
82
|
-
if not varCodec then
|
|
83
|
-
Log.error(`unknown tag {tag}`)
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
local data, consumed = varCodec.read(src, pos + 1, refs)
|
|
87
|
-
-- Re-attach the discriminator so callers can dispatch on it.
|
|
88
|
-
if typeof(data) == "table" then
|
|
89
|
-
data[tagField] = names[tag + 1]
|
|
90
|
-
end
|
|
91
|
-
return data, 1 + consumed
|
|
92
|
-
end,
|
|
93
|
-
}
|
|
94
|
-
if hasUnknown then
|
|
95
|
-
codec._hasUnknown = true
|
|
96
|
-
end
|
|
97
|
-
if hasDelta then
|
|
98
|
-
codec._hasDelta = true
|
|
99
|
-
end
|
|
100
|
-
return table.freeze(codec)
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
return table.freeze(Tagged)
|