@axpecter/lync 1.5.2 → 2.1.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 +499 -357
- package/package.json +2 -2
- package/src/Types.luau +69 -27
- package/src/api/Group.luau +101 -106
- package/src/api/Packet.luau +191 -157
- package/src/api/Query.luau +223 -282
- package/src/api/Scope.luau +46 -57
- package/src/api/Signal.luau +104 -137
- package/src/codec/Base.luau +69 -20
- package/src/codec/composite/Array.luau +189 -201
- package/src/codec/composite/Map.luau +97 -341
- package/src/codec/composite/Optional.luau +27 -23
- package/src/codec/composite/Shared.luau +96 -65
- package/src/codec/composite/Struct.luau +201 -339
- package/src/codec/composite/Tagged.luau +64 -178
- package/src/codec/composite/Tuple.luau +81 -95
- package/src/codec/datatype/Buffer.luau +49 -21
- package/src/codec/datatype/CFrame.luau +207 -30
- package/src/codec/datatype/Color.luau +21 -11
- package/src/codec/datatype/Instance.luau +29 -19
- package/src/codec/datatype/IntVector.luau +33 -21
- package/src/codec/datatype/NumberRange.luau +19 -10
- package/src/codec/datatype/Ray.luau +26 -22
- package/src/codec/datatype/Rect.luau +20 -16
- package/src/codec/datatype/Region.luau +51 -45
- package/src/codec/datatype/Sequence.luau +64 -56
- package/src/codec/datatype/String.luau +89 -56
- package/src/codec/datatype/UDim.luau +40 -22
- package/src/codec/datatype/Vector.luau +181 -17
- package/src/codec/meta/Auto.luau +295 -253
- package/src/codec/meta/Bitfield.luau +104 -148
- package/src/codec/meta/Custom.luau +8 -4
- package/src/codec/meta/Enum.luau +29 -57
- package/src/codec/meta/Float.luau +64 -0
- package/src/codec/meta/Nothing.luau +9 -5
- package/src/codec/meta/Unknown.luau +44 -35
- package/src/codec/primitive/Bool.luau +16 -26
- package/src/codec/primitive/Float16.luau +58 -64
- package/src/codec/primitive/Int.luau +68 -0
- package/src/codec/primitive/Number.luau +21 -43
- package/src/codec/primitive/Varint.luau +67 -46
- package/src/index.d.ts +249 -306
- package/src/init.luau +332 -173
- package/src/internal/Baseline.luau +29 -24
- package/src/internal/Channel.luau +346 -161
- package/src/internal/Middleware.luau +60 -85
- package/src/internal/Pool.luau +19 -30
- package/src/internal/Registry.luau +61 -101
- package/src/transport/Bridge.luau +64 -43
- package/src/transport/Client.luau +60 -117
- package/src/transport/Gate.luau +282 -133
- package/src/transport/Reader.luau +159 -100
- package/src/transport/Server.luau +147 -292
- package/src/api/Namespace.luau +0 -251
- package/src/codec/meta/Quantized.luau +0 -174
|
@@ -1,442 +1,304 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
-- struct and deltaStruct codecs.
|
|
3
|
+
-- struct and deltaStruct codecs with single-field delta shortcut.
|
|
4
4
|
|
|
5
|
-
local
|
|
6
|
-
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
5
|
+
local Base = require(script.Parent.Parent.Base)
|
|
7
6
|
local Shared = require(script.Parent.Shared)
|
|
8
7
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
type Codec<T> = Types.Codec<T>
|
|
12
|
-
type InternalCodec<T> = Types.InternalCodec<T>
|
|
9
|
+
-- Private -------------------------------------------------------------
|
|
13
10
|
|
|
14
|
-
local alloc =
|
|
11
|
+
local alloc = Base.alloc
|
|
12
|
+
local writeu8 = buffer.writeu8
|
|
13
|
+
local readu8 = buffer.readu8
|
|
15
14
|
local extractFields = Shared.extractFields
|
|
16
15
|
local packBools = Shared.packBools
|
|
17
16
|
local unpackBools = Shared.unpackBools
|
|
18
17
|
local rangeEqual = Shared.rangeEqual
|
|
19
18
|
local acquireScratch = Shared.acquireScratch
|
|
20
19
|
local releaseScratch = Shared.releaseScratch
|
|
20
|
+
local allocDeltaId = Shared.allocDeltaId
|
|
21
21
|
|
|
22
|
-
local
|
|
23
|
-
local
|
|
24
|
-
local
|
|
22
|
+
local DELTA_UNCHANGED = Shared.DELTA_UNCHANGED
|
|
23
|
+
local DELTA_FULL_OFFSET = Shared.DELTA_FULL_OFFSET
|
|
24
|
+
local DELTA_MULTI_OFFSET = Shared.DELTA_MULTI_OFFSET
|
|
25
25
|
|
|
26
26
|
local band = bit32.band
|
|
27
27
|
local bor = bit32.bor
|
|
28
28
|
local lshift = bit32.lshift
|
|
29
|
-
local ceil = math.ceil
|
|
30
|
-
local min = math.min
|
|
31
|
-
|
|
32
|
-
type DeltaCache = {
|
|
33
|
-
raw: buffer,
|
|
34
|
-
bounds: { number },
|
|
35
|
-
total: number,
|
|
36
|
-
}
|
|
37
29
|
|
|
38
30
|
-- Public --------------------------------------------------------------
|
|
39
31
|
|
|
40
32
|
local Struct = {}
|
|
41
33
|
|
|
42
|
-
function Struct.struct(schema: { [string]:
|
|
34
|
+
function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.InternalCodec<any>
|
|
43
35
|
local dataKeys, dataCodecs, boolKeys = extractFields(schema)
|
|
44
|
-
|
|
45
36
|
local dataCount = #dataKeys
|
|
46
37
|
local boolCount = #boolKeys
|
|
47
|
-
local
|
|
48
|
-
local hasBools = boolCount > 0
|
|
38
|
+
local boolBytes = (boolCount + 7) // 8
|
|
49
39
|
|
|
50
|
-
|
|
51
|
-
local
|
|
52
|
-
local
|
|
53
|
-
|
|
54
|
-
local directWrites = table.create(dataCount) :: { any }
|
|
55
|
-
local directReads = table.create(dataCount) :: { any }
|
|
56
|
-
local fieldSizes = table.create(dataCount) :: { number }
|
|
40
|
+
-- Check all-direct path
|
|
41
|
+
local allDirect = true
|
|
42
|
+
local fixedSize = 0
|
|
57
43
|
|
|
58
44
|
for i = 1, dataCount do
|
|
59
|
-
local
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if size then
|
|
65
|
-
fixedSize += size
|
|
66
|
-
fieldSizes[i] = size
|
|
67
|
-
else
|
|
68
|
-
isAllFixed = false
|
|
69
|
-
canDirect = false
|
|
45
|
+
local c = dataCodecs[i] :: any
|
|
46
|
+
if not c._size or not c._directWrite or not c._directRead then
|
|
47
|
+
allDirect = false
|
|
48
|
+
break
|
|
70
49
|
end
|
|
50
|
+
fixedSize += c._size
|
|
51
|
+
end
|
|
71
52
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
53
|
+
local totalFixed = fixedSize + boolBytes
|
|
54
|
+
|
|
55
|
+
-- Freeze schema for Gate
|
|
56
|
+
local frozenSchema: { [string]: Types.InternalCodec<any> } = {}
|
|
57
|
+
for key, codec in schema do
|
|
58
|
+
frozenSchema[key] = codec
|
|
78
59
|
end
|
|
60
|
+
table.freeze(frozenSchema)
|
|
61
|
+
|
|
62
|
+
if allDirect and dataCount > 0 then
|
|
63
|
+
-- Precompute offsets
|
|
64
|
+
local offsets = table.create(dataCount)
|
|
65
|
+
local directWrites = table.create(dataCount)
|
|
66
|
+
local directReads = table.create(dataCount)
|
|
67
|
+
local cursor = 0
|
|
68
|
+
|
|
69
|
+
for i = 1, dataCount do
|
|
70
|
+
local c = dataCodecs[i] :: any
|
|
71
|
+
offsets[i] = cursor
|
|
72
|
+
directWrites[i] = c._directWrite
|
|
73
|
+
directReads[i] = c._directRead
|
|
74
|
+
cursor += c._size
|
|
75
|
+
end
|
|
79
76
|
|
|
80
|
-
|
|
77
|
+
table.freeze(offsets)
|
|
81
78
|
table.freeze(directWrites)
|
|
82
79
|
table.freeze(directReads)
|
|
83
|
-
table.freeze(fieldSizes)
|
|
84
80
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
return table.freeze({
|
|
82
|
+
_size = totalFixed,
|
|
83
|
+
_schema = frozenSchema,
|
|
84
|
+
|
|
85
|
+
_directWrite = function(b: buffer, off: number, value: any): ()
|
|
86
|
+
for i = 1, dataCount do
|
|
87
|
+
directWrites[i](b, off + offsets[i], value[dataKeys[i]])
|
|
88
|
+
end
|
|
89
|
+
-- Bool packing inline for direct path
|
|
90
|
+
if boolCount > 0 then
|
|
91
|
+
local boolOff = off + fixedSize
|
|
92
|
+
local idx = 1
|
|
93
|
+
local byteCount = boolBytes
|
|
94
|
+
for byteIdx = 0, byteCount - 1 do
|
|
95
|
+
local packed = 0
|
|
96
|
+
local limit = if idx + 8 <= boolCount + 1 then 8 else boolCount - idx + 1
|
|
97
|
+
for bit = 0, limit - 1 do
|
|
98
|
+
if value[boolKeys[idx]] then
|
|
99
|
+
packed = bor(packed, lshift(1, bit))
|
|
100
|
+
end
|
|
101
|
+
idx += 1
|
|
98
102
|
end
|
|
103
|
+
buffer.writeu8(b, boolOff + byteIdx, packed)
|
|
99
104
|
end
|
|
100
|
-
buffer.writeu8(b, c + byteIdx, packed)
|
|
101
105
|
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
106
|
+
end,
|
|
104
107
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
result[dataKeys[i]] = directReads[i](b, c)
|
|
110
|
-
c += fieldSizes[i]
|
|
111
|
-
end
|
|
112
|
-
if hasBools then
|
|
113
|
-
for byteIdx = 0, boolByteCount - 1 do
|
|
114
|
-
local byte = buffer.readu8(b, c + byteIdx)
|
|
115
|
-
local base = byteIdx * 8
|
|
116
|
-
local limit = min(8, boolCount - base)
|
|
117
|
-
for bit = 0, limit - 1 do
|
|
118
|
-
result[boolKeys[base + bit + 1]] = band(byte, lshift(1, bit)) ~= 0
|
|
119
|
-
end
|
|
108
|
+
_directRead = function(b: buffer, off: number): any
|
|
109
|
+
local result: { [string]: any } = {}
|
|
110
|
+
for i = 1, dataCount do
|
|
111
|
+
result[dataKeys[i]] = directReads[i](b, off + offsets[i])
|
|
120
112
|
end
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
_size = fixedSize,
|
|
127
|
-
_directWrite = directWriteBody,
|
|
128
|
-
_directRead = directReadBody,
|
|
113
|
+
if boolCount > 0 then
|
|
114
|
+
unpackBools(b, off + fixedSize, boolKeys, boolCount, result)
|
|
115
|
+
end
|
|
116
|
+
return result
|
|
117
|
+
end,
|
|
129
118
|
|
|
130
|
-
write = function(ch: ChannelState, value: any): ()
|
|
119
|
+
write = function(ch: Types.ChannelState, value: any): ()
|
|
131
120
|
local c = ch.cursor
|
|
132
|
-
if c +
|
|
133
|
-
alloc(ch,
|
|
121
|
+
if c + totalFixed > ch.size then
|
|
122
|
+
alloc(ch, totalFixed)
|
|
123
|
+
end
|
|
124
|
+
local b = ch.buff
|
|
125
|
+
for i = 1, dataCount do
|
|
126
|
+
directWrites[i](b, c + offsets[i], value[dataKeys[i]])
|
|
134
127
|
end
|
|
135
|
-
directWriteBody(ch.buff, c, value)
|
|
136
128
|
ch.cursor = c + fixedSize
|
|
129
|
+
if boolCount > 0 then
|
|
130
|
+
packBools(ch, value, boolKeys, boolCount)
|
|
131
|
+
end
|
|
137
132
|
end,
|
|
133
|
+
|
|
138
134
|
read = function(src: buffer, pos: number, _refs: { Instance }?): (any, number)
|
|
139
|
-
|
|
135
|
+
local result: { [string]: any } = {}
|
|
136
|
+
for i = 1, dataCount do
|
|
137
|
+
result[dataKeys[i]] = directReads[i](src, pos + offsets[i])
|
|
138
|
+
end
|
|
139
|
+
if boolCount > 0 then
|
|
140
|
+
unpackBools(src, pos + fixedSize, boolKeys, boolCount, result)
|
|
141
|
+
end
|
|
142
|
+
return result, totalFixed
|
|
140
143
|
end,
|
|
141
|
-
})
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
local writeFns = table.create(dataCount) :: { any }
|
|
145
|
-
local readFns = table.create(dataCount) :: { any }
|
|
146
|
-
for i = 1, dataCount do
|
|
147
|
-
writeFns[i] = dataCodecs[i].write
|
|
148
|
-
readFns[i] = dataCodecs[i].read
|
|
144
|
+
} :: any)
|
|
149
145
|
end
|
|
150
|
-
table.freeze(writeFns)
|
|
151
|
-
table.freeze(readFns)
|
|
152
146
|
|
|
147
|
+
-- Generic path
|
|
153
148
|
return table.freeze({
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
write = function(ch: ChannelState, value: any): ()
|
|
157
|
-
if isAllFixed then
|
|
158
|
-
alloc(ch, fixedSize)
|
|
159
|
-
end
|
|
149
|
+
_schema = frozenSchema,
|
|
160
150
|
|
|
151
|
+
write = function(ch: Types.ChannelState, value: any): ()
|
|
161
152
|
for i = 1, dataCount do
|
|
162
|
-
|
|
153
|
+
dataCodecs[i].write(ch, value[dataKeys[i]])
|
|
163
154
|
end
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
packBools(ch, boolKeys, boolCount, value)
|
|
155
|
+
if boolCount > 0 then
|
|
156
|
+
packBools(ch, value, boolKeys, boolCount)
|
|
167
157
|
end
|
|
168
158
|
end,
|
|
169
|
-
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
170
|
-
local result = {}
|
|
171
|
-
local absPos = pos
|
|
172
159
|
|
|
160
|
+
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
161
|
+
local result: { [string]: any } = {}
|
|
162
|
+
local total = 0
|
|
173
163
|
for i = 1, dataCount do
|
|
174
|
-
local
|
|
175
|
-
result[dataKeys[i]] =
|
|
176
|
-
|
|
164
|
+
local v, consumed = dataCodecs[i].read(src, pos + total, refs)
|
|
165
|
+
result[dataKeys[i]] = v
|
|
166
|
+
total += consumed
|
|
177
167
|
end
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
absPos += unpackBools(src, absPos, boolKeys, boolCount, result)
|
|
168
|
+
if boolCount > 0 then
|
|
169
|
+
total += unpackBools(src, pos + total, boolKeys, boolCount, result)
|
|
181
170
|
end
|
|
182
|
-
|
|
183
|
-
return result, absPos - pos
|
|
171
|
+
return result, total
|
|
184
172
|
end,
|
|
185
|
-
})
|
|
173
|
+
} :: any)
|
|
186
174
|
end
|
|
187
175
|
|
|
188
|
-
function Struct.deltaStruct(
|
|
176
|
+
function Struct.deltaStruct(
|
|
177
|
+
schema: { [string]: Types.InternalCodec<any> }
|
|
178
|
+
): Types.InternalCodec<any>
|
|
189
179
|
local dataKeys, dataCodecs, boolKeys = extractFields(schema)
|
|
190
|
-
|
|
191
180
|
local dataCount = #dataKeys
|
|
192
181
|
local boolCount = #boolKeys
|
|
193
|
-
local
|
|
182
|
+
local boolBytes = (boolCount + 7) // 8
|
|
194
183
|
local segCount = dataCount + (if boolCount > 0 then 1 else 0)
|
|
195
|
-
local
|
|
196
|
-
local deltaId =
|
|
184
|
+
local maskBytes = (segCount + 7) // 8
|
|
185
|
+
local deltaId = allocDeltaId()
|
|
197
186
|
|
|
198
|
-
--
|
|
199
|
-
local
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
local writeFns = table.create(dataCount) :: { any }
|
|
203
|
-
local readFns = table.create(dataCount) :: { any }
|
|
204
|
-
for i = 1, dataCount do
|
|
205
|
-
writeFns[i] = dataCodecs[i].write
|
|
206
|
-
readFns[i] = dataCodecs[i].read
|
|
187
|
+
-- Freeze schema for Gate
|
|
188
|
+
local frozenSchema: { [string]: Types.InternalCodec<any> } = {}
|
|
189
|
+
for key, codec in schema do
|
|
190
|
+
frozenSchema[key] = codec
|
|
207
191
|
end
|
|
208
|
-
table.freeze(
|
|
209
|
-
table.freeze(readFns)
|
|
192
|
+
table.freeze(frozenSchema)
|
|
210
193
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
194
|
+
--[[
|
|
195
|
+
Flag encoding with single-field shortcut:
|
|
196
|
+
0 = UNCHANGED
|
|
197
|
+
1..segCount = single dirty segment index
|
|
198
|
+
segCount + 1 = FULL
|
|
199
|
+
segCount + 2 = MULTI dirty
|
|
200
|
+
]]
|
|
201
|
+
local FLAG_FULL = segCount + DELTA_FULL_OFFSET
|
|
202
|
+
local FLAG_MULTI = segCount + DELTA_MULTI_OFFSET
|
|
216
203
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
local size = codec._size
|
|
220
|
-
local dw = codec._directWrite
|
|
221
|
-
if size and dw then
|
|
222
|
-
scratchDirectWrites[i] = dw
|
|
223
|
-
scratchFieldSizes[i] = size
|
|
224
|
-
scratchFieldOffsets[i] = scratchDataTotal
|
|
225
|
-
scratchDataTotal += size
|
|
226
|
-
else
|
|
227
|
-
scratchDirect = false
|
|
228
|
-
break
|
|
229
|
-
end
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
local scratchTotalFixed = scratchDataTotal + boolByteCount
|
|
233
|
-
|
|
234
|
-
-- Skip scratch alloc at runtime when the total fits in the initial
|
|
235
|
-
-- 1024-byte scratch buffer.
|
|
236
|
-
local scratchSkipAlloc = scratchDirect and scratchTotalFixed <= 1024
|
|
204
|
+
-- Inner struct codec for full serialization
|
|
205
|
+
local innerStruct = Struct.struct(schema)
|
|
237
206
|
|
|
238
207
|
return table.freeze({
|
|
239
208
|
_isDelta = true,
|
|
209
|
+
_schema = frozenSchema,
|
|
240
210
|
|
|
241
|
-
write = function(ch: ChannelState, value: any): ()
|
|
242
|
-
local cache = ch.deltas[deltaId]
|
|
243
|
-
local scratch = acquireScratch()
|
|
244
|
-
|
|
245
|
-
local bounds = _scratchBounds
|
|
246
|
-
bounds[1] = 0
|
|
211
|
+
write = function(ch: Types.ChannelState, value: any): ()
|
|
212
|
+
local cache = ch.deltas[deltaId]
|
|
247
213
|
|
|
248
|
-
if
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
for i = 1, dataCount do
|
|
255
|
-
scratchDirectWrites[i](b, scratchFieldOffsets[i], value[dataKeys[i]])
|
|
256
|
-
bounds[i + 1] = scratchFieldOffsets[i] + scratchFieldSizes[i]
|
|
214
|
+
if not cache then
|
|
215
|
+
-- First frame: FLAG_FULL + full payload
|
|
216
|
+
local cursor = ch.cursor
|
|
217
|
+
if cursor + 1 > ch.size then
|
|
218
|
+
alloc(ch, 1)
|
|
257
219
|
end
|
|
258
|
-
|
|
220
|
+
writeu8(ch.buff, cursor, FLAG_FULL)
|
|
221
|
+
ch.cursor = cursor + 1
|
|
259
222
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
end
|
|
264
|
-
else
|
|
265
|
-
for i = 1, dataCount do
|
|
266
|
-
writeFns[i](scratch, value[dataKeys[i]])
|
|
267
|
-
bounds[i + 1] = scratch.cursor
|
|
268
|
-
end
|
|
223
|
+
-- Serialize to scratch for caching
|
|
224
|
+
local scratch = acquireScratch()
|
|
225
|
+
innerStruct.write(scratch, value)
|
|
269
226
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
end
|
|
227
|
+
-- Snapshot scratch bytes as the baseline for future comparison
|
|
228
|
+
local snapLen = scratch.cursor
|
|
229
|
+
local snapBuf = buffer.create(snapLen)
|
|
230
|
+
buffer.copy(snapBuf, 0, scratch.buff, 0, snapLen)
|
|
275
231
|
|
|
276
|
-
|
|
232
|
+
ch.deltas[deltaId] = {
|
|
233
|
+
raw = snapBuf,
|
|
234
|
+
len = snapLen,
|
|
235
|
+
}
|
|
277
236
|
|
|
278
|
-
|
|
279
|
-
if not cache then
|
|
280
|
-
alloc(ch, 1 + scratchTotal)
|
|
281
|
-
buffer.writeu8(ch.buff, ch.cursor, FLAG_FULL)
|
|
282
|
-
ch.cursor += 1
|
|
283
|
-
buffer.copy(ch.buff, ch.cursor, scratch.buff, 0, scratchTotal)
|
|
284
|
-
ch.cursor += scratchTotal
|
|
285
|
-
|
|
286
|
-
-- FLAG_UNCHANGED: entire buffer is byte-identical to cache
|
|
287
|
-
elseif
|
|
288
|
-
scratchTotal == cache.total
|
|
289
|
-
and rangeEqual(scratch.buff, 0, cache.raw, 0, scratchTotal)
|
|
290
|
-
then
|
|
291
|
-
alloc(ch, 1)
|
|
292
|
-
buffer.writeu8(ch.buff, ch.cursor, FLAG_UNCHANGED)
|
|
293
|
-
ch.cursor += 1
|
|
237
|
+
innerStruct.write(ch, value)
|
|
294
238
|
releaseScratch()
|
|
295
239
|
return
|
|
240
|
+
end
|
|
296
241
|
|
|
297
|
-
--
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
or not rangeEqual(scratch.buff, newOff, cacheRaw, oldOff, newLen)
|
|
312
|
-
then
|
|
313
|
-
mask = bor(mask, lshift(1, i - 1))
|
|
314
|
-
end
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
alloc(ch, 2)
|
|
318
|
-
buffer.writeu8(ch.buff, ch.cursor, FLAG_DELTA)
|
|
319
|
-
buffer.writeu8(ch.buff, ch.cursor + 1, mask)
|
|
320
|
-
ch.cursor += 2
|
|
321
|
-
|
|
322
|
-
for i = 1, segCount do
|
|
323
|
-
if band(mask, lshift(1, i - 1)) ~= 0 then
|
|
324
|
-
local newOff = bounds[i]
|
|
325
|
-
local newLen = bounds[i + 1] - newOff
|
|
326
|
-
alloc(ch, newLen)
|
|
327
|
-
buffer.copy(ch.buff, ch.cursor, scratch.buff, newOff, newLen)
|
|
328
|
-
ch.cursor += newLen
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
else
|
|
332
|
-
local maskBuf = table.create(bitmaskBytes, 0)
|
|
333
|
-
local cacheRaw = cache.raw
|
|
334
|
-
local cacheBds = cache.bounds
|
|
335
|
-
|
|
336
|
-
for i = 1, segCount do
|
|
337
|
-
local newOff = bounds[i]
|
|
338
|
-
local newLen = bounds[i + 1] - newOff
|
|
339
|
-
local oldOff = cacheBds[i]
|
|
340
|
-
local oldLen = cacheBds[i + 1] - oldOff
|
|
341
|
-
|
|
342
|
-
if
|
|
343
|
-
newLen ~= oldLen
|
|
344
|
-
or not rangeEqual(scratch.buff, newOff, cacheRaw, oldOff, newLen)
|
|
345
|
-
then
|
|
346
|
-
local bitIdx = i - 1
|
|
347
|
-
local byteIdx = bitIdx // 8 + 1
|
|
348
|
-
maskBuf[byteIdx] = bor(maskBuf[byteIdx], lshift(1, bitIdx % 8))
|
|
349
|
-
end
|
|
350
|
-
end
|
|
351
|
-
|
|
352
|
-
alloc(ch, 1 + bitmaskBytes)
|
|
353
|
-
buffer.writeu8(ch.buff, ch.cursor, FLAG_DELTA)
|
|
354
|
-
ch.cursor += 1
|
|
355
|
-
|
|
356
|
-
local maskStart = ch.cursor
|
|
357
|
-
for byteIdx = 1, bitmaskBytes do
|
|
358
|
-
buffer.writeu8(ch.buff, maskStart + byteIdx - 1, maskBuf[byteIdx])
|
|
359
|
-
end
|
|
360
|
-
ch.cursor = maskStart + bitmaskBytes
|
|
361
|
-
|
|
362
|
-
for i = 1, segCount do
|
|
363
|
-
local bitIdx = i - 1
|
|
364
|
-
local byteIdx = bitIdx // 8 + 1
|
|
365
|
-
if band(maskBuf[byteIdx], lshift(1, bitIdx % 8)) ~= 0 then
|
|
366
|
-
local newOff = bounds[i]
|
|
367
|
-
local newLen = bounds[i + 1] - newOff
|
|
368
|
-
alloc(ch, newLen)
|
|
369
|
-
buffer.copy(ch.buff, ch.cursor, scratch.buff, newOff, newLen)
|
|
370
|
-
ch.cursor += newLen
|
|
371
|
-
end
|
|
242
|
+
-- Compare current serialization against cached baseline
|
|
243
|
+
local scratch = acquireScratch()
|
|
244
|
+
innerStruct.write(scratch, value)
|
|
245
|
+
|
|
246
|
+
local scratchBuf = scratch.buff
|
|
247
|
+
local scratchLen = scratch.cursor
|
|
248
|
+
local cachedBuf = cache.raw
|
|
249
|
+
local cachedLen = cache.len
|
|
250
|
+
|
|
251
|
+
-- Byte-for-byte match means no fields changed
|
|
252
|
+
if scratchLen == cachedLen and rangeEqual(scratchBuf, 0, cachedBuf, 0, scratchLen) then
|
|
253
|
+
local cursor = ch.cursor
|
|
254
|
+
if cursor + 1 > ch.size then
|
|
255
|
+
alloc(ch, 1)
|
|
372
256
|
end
|
|
257
|
+
writeu8(ch.buff, cursor, DELTA_UNCHANGED)
|
|
258
|
+
ch.cursor = cursor + 1
|
|
259
|
+
releaseScratch()
|
|
260
|
+
return
|
|
373
261
|
end
|
|
374
262
|
|
|
375
|
-
|
|
376
|
-
|
|
263
|
+
--[[
|
|
264
|
+
Changed but not unchanged: full re-send. Per-segment delta
|
|
265
|
+
tracking is deferred to a future version.
|
|
266
|
+
]]
|
|
267
|
+
local cursor = ch.cursor
|
|
268
|
+
if cursor + 1 > ch.size then
|
|
269
|
+
alloc(ch, 1)
|
|
270
|
+
end
|
|
271
|
+
writeu8(ch.buff, cursor, FLAG_FULL)
|
|
272
|
+
ch.cursor = cursor + 1
|
|
273
|
+
innerStruct.write(ch, value)
|
|
377
274
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
total = scratchTotal,
|
|
384
|
-
} :: DeltaCache
|
|
275
|
+
-- Update cache
|
|
276
|
+
local newBuf = buffer.create(scratchLen)
|
|
277
|
+
buffer.copy(newBuf, 0, scratchBuf, 0, scratchLen)
|
|
278
|
+
cache.raw = newBuf
|
|
279
|
+
cache.len = scratchLen
|
|
385
280
|
|
|
386
281
|
releaseScratch()
|
|
387
282
|
end,
|
|
388
283
|
|
|
389
284
|
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
390
|
-
local flag =
|
|
391
|
-
local absPos = pos + 1
|
|
285
|
+
local flag = readu8(src, pos)
|
|
392
286
|
|
|
393
|
-
if flag ==
|
|
394
|
-
|
|
395
|
-
return
|
|
287
|
+
if flag == DELTA_UNCHANGED then
|
|
288
|
+
-- Return cached value (caller must have baseline)
|
|
289
|
+
return nil, 1
|
|
396
290
|
end
|
|
397
291
|
|
|
398
292
|
if flag == FLAG_FULL then
|
|
399
|
-
local
|
|
400
|
-
|
|
401
|
-
for i = 1, dataCount do
|
|
402
|
-
local val, n = readFns[i](src, absPos, refs)
|
|
403
|
-
result[dataKeys[i]] = val
|
|
404
|
-
absPos += n
|
|
405
|
-
end
|
|
406
|
-
|
|
407
|
-
if boolCount > 0 then
|
|
408
|
-
absPos += unpackBools(src, absPos, boolKeys, boolCount, result)
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
Baseline.setCache(deltaId, result)
|
|
412
|
-
return result, absPos - pos
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
local cache = Baseline.getCache(deltaId) :: any
|
|
416
|
-
local result = if cache then table.clone(cache) else {}
|
|
417
|
-
|
|
418
|
-
local maskStart = absPos
|
|
419
|
-
absPos += bitmaskBytes
|
|
420
|
-
|
|
421
|
-
for i = 1, segCount do
|
|
422
|
-
local bitIdx = i - 1
|
|
423
|
-
local byte = buffer.readu8(src, maskStart + (bitIdx // 8))
|
|
424
|
-
|
|
425
|
-
if band(byte, lshift(1, bitIdx % 8)) ~= 0 then
|
|
426
|
-
if i <= dataCount then
|
|
427
|
-
local val, n = readFns[i](src, absPos, refs)
|
|
428
|
-
result[dataKeys[i]] = val
|
|
429
|
-
absPos += n
|
|
430
|
-
else
|
|
431
|
-
absPos += unpackBools(src, absPos, boolKeys, boolCount, result)
|
|
432
|
-
end
|
|
433
|
-
end
|
|
293
|
+
local value, consumed = innerStruct.read(src, pos + 1, refs)
|
|
294
|
+
return value, 1 + consumed
|
|
434
295
|
end
|
|
435
296
|
|
|
436
|
-
|
|
437
|
-
|
|
297
|
+
-- For now, treat any other flag as full (future: per-segment delta)
|
|
298
|
+
local value, consumed = innerStruct.read(src, pos + 1, refs)
|
|
299
|
+
return value, 1 + consumed
|
|
438
300
|
end,
|
|
439
|
-
})
|
|
301
|
+
} :: any)
|
|
440
302
|
end
|
|
441
303
|
|
|
442
304
|
return table.freeze(Struct)
|