@axpecter/lync 2.1.1 → 2.2.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 +241 -349
- package/package.json +1 -1
- package/src/Types.luau +53 -13
- package/src/api/Group.luau +41 -51
- package/src/api/Packet.luau +145 -143
- package/src/api/Query.luau +204 -202
- package/src/api/Scope.luau +36 -38
- package/src/api/Signal.luau +68 -94
- package/src/codec/Base.luau +43 -23
- package/src/codec/composite/Array.luau +161 -152
- package/src/codec/composite/Map.luau +37 -53
- package/src/codec/composite/Optional.luau +15 -21
- package/src/codec/composite/Shared.luau +78 -41
- package/src/codec/composite/Struct.luau +64 -125
- package/src/codec/composite/Tagged.luau +15 -25
- package/src/codec/composite/Tuple.luau +15 -20
- package/src/codec/datatype/Buffer.luau +44 -51
- package/src/codec/datatype/CFrame.luau +72 -104
- package/src/codec/datatype/Color.luau +14 -10
- package/src/codec/datatype/Instance.luau +32 -42
- package/src/codec/datatype/IntVector.luau +24 -22
- package/src/codec/datatype/NumberRange.luau +21 -12
- package/src/codec/datatype/Ray.luau +13 -7
- package/src/codec/datatype/Rect.luau +13 -7
- package/src/codec/datatype/Region.luau +25 -22
- package/src/codec/datatype/Sequence.luau +144 -118
- package/src/codec/datatype/String.luau +29 -59
- package/src/codec/datatype/UDim.luau +29 -30
- package/src/codec/datatype/Vector.luau +89 -105
- package/src/codec/meta/Auto.luau +194 -246
- package/src/codec/meta/Bitfield.luau +37 -36
- package/src/codec/meta/Custom.luau +10 -10
- package/src/codec/meta/Enum.luau +8 -11
- package/src/codec/meta/Float.luau +16 -20
- package/src/codec/meta/Nothing.luau +2 -2
- package/src/codec/meta/Unknown.luau +22 -32
- package/src/codec/primitive/Bool.luau +9 -5
- package/src/codec/primitive/Float16.luau +13 -23
- package/src/codec/primitive/Int.luau +20 -28
- package/src/codec/primitive/Number.luau +6 -10
- package/src/codec/primitive/Signed.luau +32 -0
- package/src/codec/primitive/Varint.luau +59 -28
- package/src/index.d.ts +9 -3
- package/src/init.luau +118 -143
- package/src/internal/Baseline.luau +17 -18
- package/src/internal/Channel.luau +143 -212
- package/src/internal/Middleware.luau +37 -47
- package/src/internal/Pool.luau +10 -10
- package/src/internal/Registry.luau +38 -34
- package/src/internal/Transport.luau +28 -0
- package/src/internal/Util.luau +26 -0
- package/src/transport/Bridge.luau +32 -24
- package/src/transport/Client.luau +51 -53
- package/src/transport/Gate.luau +183 -113
- package/src/transport/Reader.luau +95 -66
- package/src/transport/Server.luau +130 -125
|
@@ -5,36 +5,67 @@
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
6
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
7
|
|
|
8
|
-
-- Constants
|
|
9
|
-
|
|
8
|
+
-- Constants --------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
--[[
|
|
11
|
+
Delta encoding flag space, shared across deltaStruct/deltaArray/deltaMap:
|
|
12
|
+
0 UNCHANGED : sender skipped this entry, decoder reuses cache
|
|
13
|
+
1 FULL : full payload follows
|
|
14
|
+
2.. reserved : per-segment / per-key delta encodings (future)
|
|
15
|
+
|
|
16
|
+
Struct's per-segment scheme uses [1..segCount] as "single dirty
|
|
17
|
+
segment index" then segCount + DELTA_FULL_OFFSET for full and
|
|
18
|
+
segCount + DELTA_MULTI_OFFSET for multi-dirty bitmap.
|
|
19
|
+
]]
|
|
10
20
|
local DELTA_UNCHANGED = 0
|
|
21
|
+
local DELTA_FULL = 1
|
|
11
22
|
local DELTA_FULL_OFFSET = 1
|
|
12
23
|
local DELTA_MULTI_OFFSET = 2
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
local INITIAL_SCRATCH = 1024
|
|
26
|
+
|
|
27
|
+
-- State ------------------------------------------------------------------
|
|
15
28
|
|
|
16
29
|
local _nextDeltaId = 0
|
|
17
30
|
local _scratchDepth = 0
|
|
18
31
|
local _scratchStack: { Types.ChannelState } = {}
|
|
19
32
|
|
|
20
|
-
-- Private
|
|
33
|
+
-- Private ----------------------------------------------------------------
|
|
21
34
|
|
|
22
35
|
local alloc = Base.alloc
|
|
23
36
|
local band = bit32.band
|
|
24
37
|
local bor = bit32.bor
|
|
25
38
|
local bxor = bit32.bxor
|
|
26
39
|
local lshift = bit32.lshift
|
|
27
|
-
local rshift = bit32.rshift
|
|
28
|
-
|
|
29
40
|
local readu8 = buffer.readu8
|
|
30
41
|
local readu32 = buffer.readu32
|
|
31
42
|
local writeu8 = buffer.writeu8
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
local function newScratch(): Types.ChannelState
|
|
45
|
+
return {
|
|
46
|
+
buff = buffer.create(INITIAL_SCRATCH),
|
|
47
|
+
cursor = 0,
|
|
48
|
+
size = INITIAL_SCRATCH,
|
|
49
|
+
refs = {},
|
|
50
|
+
refCount = 0,
|
|
51
|
+
lastId = -1,
|
|
52
|
+
countPos = -1,
|
|
53
|
+
itemCount = 0,
|
|
54
|
+
singleMode = false,
|
|
55
|
+
singlePos = 0,
|
|
56
|
+
deltas = {},
|
|
57
|
+
prevDump = nil,
|
|
58
|
+
prevDumpLen = 0,
|
|
59
|
+
currentPacket = nil,
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
-- Public -----------------------------------------------------------------
|
|
34
64
|
|
|
35
65
|
local Shared = {}
|
|
36
66
|
|
|
37
67
|
Shared.DELTA_UNCHANGED = DELTA_UNCHANGED
|
|
68
|
+
Shared.DELTA_FULL = DELTA_FULL
|
|
38
69
|
Shared.DELTA_FULL_OFFSET = DELTA_FULL_OFFSET
|
|
39
70
|
Shared.DELTA_MULTI_OFFSET = DELTA_MULTI_OFFSET
|
|
40
71
|
|
|
@@ -43,31 +74,21 @@ function Shared.allocDeltaId(): number
|
|
|
43
74
|
return _nextDeltaId
|
|
44
75
|
end
|
|
45
76
|
|
|
77
|
+
--[[
|
|
78
|
+
Acquire a scratch ChannelState. Stack-based so re-entrant codec
|
|
79
|
+
serialization (a codec writing into scratch that triggers another
|
|
80
|
+
codec's scratch acquire) works without aliasing.
|
|
81
|
+
]]
|
|
46
82
|
function Shared.acquireScratch(): Types.ChannelState
|
|
47
83
|
_scratchDepth += 1
|
|
48
84
|
local ch = _scratchStack[_scratchDepth]
|
|
49
|
-
if
|
|
50
|
-
ch = {
|
|
51
|
-
buff = buffer.create(1024),
|
|
52
|
-
cursor = 0,
|
|
53
|
-
size = 1024,
|
|
54
|
-
refs = {},
|
|
55
|
-
refCount = 0,
|
|
56
|
-
lastId = -1,
|
|
57
|
-
countPos = 0,
|
|
58
|
-
itemCount = 0,
|
|
59
|
-
singleMode = false,
|
|
60
|
-
singlePos = 0,
|
|
61
|
-
deltas = {},
|
|
62
|
-
prevDump = nil,
|
|
63
|
-
prevDumpLen = 0,
|
|
64
|
-
currentPacket = nil,
|
|
65
|
-
}
|
|
66
|
-
_scratchStack[_scratchDepth] = ch
|
|
67
|
-
else
|
|
85
|
+
if ch then
|
|
68
86
|
ch.cursor = 0
|
|
69
87
|
ch.refCount = 0
|
|
88
|
+
return ch
|
|
70
89
|
end
|
|
90
|
+
ch = newScratch()
|
|
91
|
+
_scratchStack[_scratchDepth] = ch
|
|
71
92
|
return ch
|
|
72
93
|
end
|
|
73
94
|
|
|
@@ -76,8 +97,7 @@ function Shared.releaseScratch(): ()
|
|
|
76
97
|
end
|
|
77
98
|
|
|
78
99
|
function Shared.rangeEqual(a: buffer, offA: number, b: buffer, offB: number, len: number): boolean
|
|
79
|
-
|
|
80
|
-
local aligned = len - (len % 4)
|
|
100
|
+
local aligned = band(len, -4)
|
|
81
101
|
local i = 0
|
|
82
102
|
while i < aligned do
|
|
83
103
|
if bxor(readu32(a, offA + i), readu32(b, offB + i)) ~= 0 then
|
|
@@ -85,27 +105,35 @@ function Shared.rangeEqual(a: buffer, offA: number, b: buffer, offB: number, len
|
|
|
85
105
|
end
|
|
86
106
|
i += 4
|
|
87
107
|
end
|
|
88
|
-
|
|
89
|
-
-- u8 remainder
|
|
90
108
|
while i < len do
|
|
91
109
|
if readu8(a, offA + i) ~= readu8(b, offB + i) then
|
|
92
110
|
return false
|
|
93
111
|
end
|
|
94
112
|
i += 1
|
|
95
113
|
end
|
|
96
|
-
|
|
97
114
|
return true
|
|
98
115
|
end
|
|
99
116
|
|
|
117
|
+
function Shared.snapshot(src: buffer, len: number): buffer
|
|
118
|
+
local out = buffer.create(len)
|
|
119
|
+
if len > 0 then
|
|
120
|
+
buffer.copy(out, 0, src, 0, len)
|
|
121
|
+
end
|
|
122
|
+
return out
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
--[[
|
|
126
|
+
Split a struct schema into (dataKeys, dataCodecs, boolKeys), all
|
|
127
|
+
sorted alphabetically for deterministic wire layout.
|
|
128
|
+
]]
|
|
100
129
|
function Shared.extractFields(
|
|
101
130
|
schema: { [string]: Types.InternalCodec<any> }
|
|
102
131
|
): ({ string }, { Types.InternalCodec<any> }, { string })
|
|
103
132
|
local dataKeys: { string } = {}
|
|
104
|
-
local dataCodecs: { Types.InternalCodec<any> } = {}
|
|
105
133
|
local boolKeys: { string } = {}
|
|
106
134
|
|
|
107
135
|
for key, codec in schema do
|
|
108
|
-
if
|
|
136
|
+
if codec._isBool then
|
|
109
137
|
table.insert(boolKeys, key)
|
|
110
138
|
else
|
|
111
139
|
table.insert(dataKeys, key)
|
|
@@ -119,23 +147,28 @@ function Shared.extractFields(
|
|
|
119
147
|
table.sort(dataKeys)
|
|
120
148
|
table.sort(boolKeys)
|
|
121
149
|
|
|
122
|
-
|
|
150
|
+
local dataCount = #dataKeys
|
|
151
|
+
local dataCodecs = table.create(dataCount)
|
|
152
|
+
for i = 1, dataCount do
|
|
123
153
|
dataCodecs[i] = schema[dataKeys[i]]
|
|
124
154
|
end
|
|
125
155
|
|
|
126
156
|
table.freeze(dataKeys)
|
|
127
157
|
table.freeze(dataCodecs)
|
|
128
158
|
table.freeze(boolKeys)
|
|
129
|
-
|
|
130
159
|
return dataKeys, dataCodecs, boolKeys
|
|
131
160
|
end
|
|
132
161
|
|
|
133
162
|
function Shared.packBools(
|
|
134
163
|
ch: Types.ChannelState,
|
|
135
|
-
value: any,
|
|
164
|
+
value: { [string]: any },
|
|
136
165
|
boolKeys: { string },
|
|
137
166
|
boolCount: number
|
|
138
167
|
): ()
|
|
168
|
+
if boolCount == 0 then
|
|
169
|
+
return
|
|
170
|
+
end
|
|
171
|
+
|
|
139
172
|
local byteCount = (boolCount + 7) // 8
|
|
140
173
|
local cursor = ch.cursor
|
|
141
174
|
if cursor + byteCount > ch.size then
|
|
@@ -146,7 +179,8 @@ function Shared.packBools(
|
|
|
146
179
|
local idx = 1
|
|
147
180
|
for byteIdx = 0, byteCount - 1 do
|
|
148
181
|
local packed = 0
|
|
149
|
-
local
|
|
182
|
+
local remaining = boolCount - idx + 1
|
|
183
|
+
local limit = if remaining >= 8 then 8 else remaining
|
|
150
184
|
for bit = 0, limit - 1 do
|
|
151
185
|
if value[boolKeys[idx]] then
|
|
152
186
|
packed = bor(packed, lshift(1, bit))
|
|
@@ -164,20 +198,23 @@ function Shared.unpackBools(
|
|
|
164
198
|
pos: number,
|
|
165
199
|
boolKeys: { string },
|
|
166
200
|
boolCount: number,
|
|
167
|
-
result: any
|
|
201
|
+
result: { [string]: any }
|
|
168
202
|
): number
|
|
203
|
+
if boolCount == 0 then
|
|
204
|
+
return 0
|
|
205
|
+
end
|
|
206
|
+
|
|
169
207
|
local byteCount = (boolCount + 7) // 8
|
|
170
208
|
local idx = 1
|
|
171
|
-
|
|
172
209
|
for byteIdx = 0, byteCount - 1 do
|
|
173
210
|
local byte = readu8(src, pos + byteIdx)
|
|
174
|
-
local
|
|
211
|
+
local remaining = boolCount - idx + 1
|
|
212
|
+
local limit = if remaining >= 8 then 8 else remaining
|
|
175
213
|
for bit = 0, limit - 1 do
|
|
176
214
|
result[boolKeys[idx]] = band(byte, lshift(1, bit)) ~= 0
|
|
177
215
|
idx += 1
|
|
178
216
|
end
|
|
179
217
|
end
|
|
180
|
-
|
|
181
218
|
return byteCount
|
|
182
219
|
end
|
|
183
220
|
|
|
@@ -1,33 +1,51 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!native
|
|
3
|
-
--
|
|
3
|
+
-- Struct and deltaStruct codecs.
|
|
4
4
|
|
|
5
5
|
local Base = require(script.Parent.Parent.Base)
|
|
6
|
+
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
6
7
|
local Shared = require(script.Parent.Shared)
|
|
7
8
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
8
9
|
|
|
9
|
-
-- Private
|
|
10
|
+
-- Private ----------------------------------------------------------------
|
|
10
11
|
|
|
11
12
|
local alloc = Base.alloc
|
|
13
|
+
local writeByte = Channel.writeByte
|
|
12
14
|
local writeu8 = buffer.writeu8
|
|
13
15
|
local readu8 = buffer.readu8
|
|
16
|
+
local band = bit32.band
|
|
17
|
+
local bor = bit32.bor
|
|
18
|
+
local lshift = bit32.lshift
|
|
19
|
+
|
|
14
20
|
local extractFields = Shared.extractFields
|
|
15
21
|
local packBools = Shared.packBools
|
|
16
22
|
local unpackBools = Shared.unpackBools
|
|
17
23
|
local rangeEqual = Shared.rangeEqual
|
|
24
|
+
local snapshot = Shared.snapshot
|
|
18
25
|
local acquireScratch = Shared.acquireScratch
|
|
19
26
|
local releaseScratch = Shared.releaseScratch
|
|
20
27
|
local allocDeltaId = Shared.allocDeltaId
|
|
21
|
-
|
|
22
28
|
local DELTA_UNCHANGED = Shared.DELTA_UNCHANGED
|
|
23
29
|
local DELTA_FULL_OFFSET = Shared.DELTA_FULL_OFFSET
|
|
24
|
-
local DELTA_MULTI_OFFSET = Shared.DELTA_MULTI_OFFSET
|
|
25
30
|
|
|
26
|
-
local
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
local function buildFrozenSchema(
|
|
32
|
+
dataKeys: { string },
|
|
33
|
+
boolKeys: { string },
|
|
34
|
+
schema: { [string]: Types.InternalCodec<any> }
|
|
35
|
+
): { [string]: Types.InternalCodec<any> }
|
|
36
|
+
local frozen: { [string]: Types.InternalCodec<any> } = {}
|
|
37
|
+
for i = 1, #dataKeys do
|
|
38
|
+
local k = dataKeys[i]
|
|
39
|
+
frozen[k] = schema[k]
|
|
40
|
+
end
|
|
41
|
+
for i = 1, #boolKeys do
|
|
42
|
+
local k = boolKeys[i]
|
|
43
|
+
frozen[k] = schema[k]
|
|
44
|
+
end
|
|
45
|
+
return table.freeze(frozen)
|
|
46
|
+
end
|
|
29
47
|
|
|
30
|
-
-- Public
|
|
48
|
+
-- Public -----------------------------------------------------------------
|
|
31
49
|
|
|
32
50
|
local Struct = {}
|
|
33
51
|
|
|
@@ -37,12 +55,9 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
37
55
|
local boolCount = #boolKeys
|
|
38
56
|
local boolBytes = (boolCount + 7) // 8
|
|
39
57
|
|
|
40
|
-
|
|
41
|
-
local allDirect = true
|
|
42
|
-
local fixedSize = 0
|
|
43
|
-
|
|
58
|
+
local allDirect, fixedSize = true, 0
|
|
44
59
|
for i = 1, dataCount do
|
|
45
|
-
local c = dataCodecs[i]
|
|
60
|
+
local c = dataCodecs[i]
|
|
46
61
|
if not c._size or not c._directWrite or not c._directRead then
|
|
47
62
|
allDirect = false
|
|
48
63
|
break
|
|
@@ -51,29 +66,20 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
51
66
|
end
|
|
52
67
|
|
|
53
68
|
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
|
|
59
|
-
end
|
|
60
|
-
table.freeze(frozenSchema)
|
|
69
|
+
local frozenSchema = buildFrozenSchema(dataKeys, boolKeys, schema)
|
|
61
70
|
|
|
62
71
|
if allDirect and dataCount > 0 then
|
|
63
|
-
-- Precompute offsets
|
|
64
72
|
local offsets = table.create(dataCount)
|
|
65
73
|
local directWrites = table.create(dataCount)
|
|
66
74
|
local directReads = table.create(dataCount)
|
|
67
75
|
local cursor = 0
|
|
68
|
-
|
|
69
76
|
for i = 1, dataCount do
|
|
70
|
-
local c = dataCodecs[i]
|
|
77
|
+
local c = dataCodecs[i]
|
|
71
78
|
offsets[i] = cursor
|
|
72
79
|
directWrites[i] = c._directWrite
|
|
73
80
|
directReads[i] = c._directRead
|
|
74
|
-
cursor += c._size
|
|
81
|
+
cursor += c._size :: number
|
|
75
82
|
end
|
|
76
|
-
|
|
77
83
|
table.freeze(offsets)
|
|
78
84
|
table.freeze(directWrites)
|
|
79
85
|
table.freeze(directReads)
|
|
@@ -86,22 +92,23 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
86
92
|
for i = 1, dataCount do
|
|
87
93
|
directWrites[i](b, off + offsets[i], value[dataKeys[i]])
|
|
88
94
|
end
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
95
|
+
if boolCount == 0 then
|
|
96
|
+
return
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
local boolOff = off + fixedSize
|
|
100
|
+
local idx = 1
|
|
101
|
+
for byteIdx = 0, boolBytes - 1 do
|
|
102
|
+
local packed = 0
|
|
103
|
+
local remaining = boolCount - idx + 1
|
|
104
|
+
local limit = if remaining >= 8 then 8 else remaining
|
|
105
|
+
for bit = 0, limit - 1 do
|
|
106
|
+
if value[boolKeys[idx]] then
|
|
107
|
+
packed = bor(packed, lshift(1, bit))
|
|
102
108
|
end
|
|
103
|
-
|
|
109
|
+
idx += 1
|
|
104
110
|
end
|
|
111
|
+
writeu8(b, boolOff + byteIdx, packed)
|
|
105
112
|
end
|
|
106
113
|
end,
|
|
107
114
|
|
|
@@ -141,10 +148,9 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
141
148
|
end
|
|
142
149
|
return result, totalFixed
|
|
143
150
|
end,
|
|
144
|
-
} :: any
|
|
151
|
+
}) :: Types.InternalCodec<any>
|
|
145
152
|
end
|
|
146
153
|
|
|
147
|
-
-- Generic path
|
|
148
154
|
return table.freeze({
|
|
149
155
|
_schema = frozenSchema,
|
|
150
156
|
|
|
@@ -170,38 +176,17 @@ function Struct.struct(schema: { [string]: Types.InternalCodec<any> }): Types.In
|
|
|
170
176
|
end
|
|
171
177
|
return result, total
|
|
172
178
|
end,
|
|
173
|
-
} :: any
|
|
179
|
+
}) :: Types.InternalCodec<any>
|
|
174
180
|
end
|
|
175
181
|
|
|
176
182
|
function Struct.deltaStruct(
|
|
177
183
|
schema: { [string]: Types.InternalCodec<any> }
|
|
178
184
|
): Types.InternalCodec<any>
|
|
179
|
-
local dataKeys,
|
|
180
|
-
local
|
|
181
|
-
local boolCount = #boolKeys
|
|
182
|
-
local boolBytes = (boolCount + 7) // 8
|
|
183
|
-
local segCount = dataCount + (if boolCount > 0 then 1 else 0)
|
|
184
|
-
local maskBytes = (segCount + 7) // 8
|
|
185
|
-
local deltaId = allocDeltaId()
|
|
186
|
-
|
|
187
|
-
-- Freeze schema for Gate
|
|
188
|
-
local frozenSchema: { [string]: Types.InternalCodec<any> } = {}
|
|
189
|
-
for key, codec in schema do
|
|
190
|
-
frozenSchema[key] = codec
|
|
191
|
-
end
|
|
192
|
-
table.freeze(frozenSchema)
|
|
193
|
-
|
|
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
|
-
]]
|
|
185
|
+
local dataKeys, _dataCodecs, boolKeys = extractFields(schema)
|
|
186
|
+
local segCount = #dataKeys + (if #boolKeys > 0 then 1 else 0)
|
|
201
187
|
local FLAG_FULL = segCount + DELTA_FULL_OFFSET
|
|
202
|
-
local
|
|
203
|
-
|
|
204
|
-
-- Inner struct codec for full serialization
|
|
188
|
+
local deltaId = allocDeltaId()
|
|
189
|
+
local frozenSchema = buildFrozenSchema(dataKeys, boolKeys, schema)
|
|
205
190
|
local innerStruct = Struct.struct(schema)
|
|
206
191
|
|
|
207
192
|
return table.freeze({
|
|
@@ -212,93 +197,47 @@ function Struct.deltaStruct(
|
|
|
212
197
|
local cache = ch.deltas[deltaId]
|
|
213
198
|
|
|
214
199
|
if not cache then
|
|
215
|
-
|
|
216
|
-
local cursor = ch.cursor
|
|
217
|
-
if cursor + 1 > ch.size then
|
|
218
|
-
alloc(ch, 1)
|
|
219
|
-
end
|
|
220
|
-
writeu8(ch.buff, cursor, FLAG_FULL)
|
|
221
|
-
ch.cursor = cursor + 1
|
|
200
|
+
writeByte(ch, FLAG_FULL)
|
|
222
201
|
|
|
223
|
-
-- Serialize to scratch for caching
|
|
224
202
|
local scratch = acquireScratch()
|
|
225
203
|
innerStruct.write(scratch, value)
|
|
226
|
-
|
|
227
|
-
-- Snapshot scratch bytes as the baseline for future comparison
|
|
228
204
|
local snapLen = scratch.cursor
|
|
229
|
-
local snapBuf = buffer.create(snapLen)
|
|
230
|
-
buffer.copy(snapBuf, 0, scratch.buff, 0, snapLen)
|
|
231
|
-
|
|
232
|
-
ch.deltas[deltaId] = {
|
|
233
|
-
raw = snapBuf,
|
|
234
|
-
len = snapLen,
|
|
235
|
-
}
|
|
236
205
|
|
|
206
|
+
ch.deltas[deltaId] = { raw = snapshot(scratch.buff, snapLen), len = snapLen }
|
|
237
207
|
innerStruct.write(ch, value)
|
|
238
208
|
releaseScratch()
|
|
239
209
|
return
|
|
240
210
|
end
|
|
241
211
|
|
|
242
|
-
-- Compare current serialization against cached baseline
|
|
243
212
|
local scratch = acquireScratch()
|
|
244
213
|
innerStruct.write(scratch, value)
|
|
245
|
-
|
|
246
|
-
local scratchBuf = scratch.buff
|
|
247
214
|
local scratchLen = scratch.cursor
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if cursor + 1 > ch.size then
|
|
255
|
-
alloc(ch, 1)
|
|
256
|
-
end
|
|
257
|
-
writeu8(ch.buff, cursor, DELTA_UNCHANGED)
|
|
258
|
-
ch.cursor = cursor + 1
|
|
215
|
+
|
|
216
|
+
if
|
|
217
|
+
scratchLen == cache.len
|
|
218
|
+
and rangeEqual(scratch.buff, 0, cache.raw, 0, scratchLen)
|
|
219
|
+
then
|
|
220
|
+
writeByte(ch, DELTA_UNCHANGED)
|
|
259
221
|
releaseScratch()
|
|
260
222
|
return
|
|
261
223
|
end
|
|
262
224
|
|
|
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
|
|
225
|
+
writeByte(ch, FLAG_FULL)
|
|
273
226
|
innerStruct.write(ch, value)
|
|
274
227
|
|
|
275
|
-
|
|
276
|
-
local newBuf = buffer.create(scratchLen)
|
|
277
|
-
buffer.copy(newBuf, 0, scratchBuf, 0, scratchLen)
|
|
278
|
-
cache.raw = newBuf
|
|
228
|
+
cache.raw = snapshot(scratch.buff, scratchLen)
|
|
279
229
|
cache.len = scratchLen
|
|
280
|
-
|
|
281
230
|
releaseScratch()
|
|
282
231
|
end,
|
|
283
232
|
|
|
284
233
|
read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if flag == DELTA_UNCHANGED then
|
|
288
|
-
-- Return cached value (caller must have baseline)
|
|
234
|
+
if readu8(src, pos) == DELTA_UNCHANGED then
|
|
289
235
|
return nil, 1
|
|
290
236
|
end
|
|
291
|
-
|
|
292
|
-
if flag == FLAG_FULL then
|
|
293
|
-
local value, consumed = innerStruct.read(src, pos + 1, refs)
|
|
294
|
-
return value, 1 + consumed
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
-- For now, treat any other flag as full (future: per-segment delta)
|
|
298
237
|
local value, consumed = innerStruct.read(src, pos + 1, refs)
|
|
299
238
|
return value, 1 + consumed
|
|
300
239
|
end,
|
|
301
|
-
} :: any
|
|
240
|
+
}) :: Types.InternalCodec<any>
|
|
302
241
|
end
|
|
303
242
|
|
|
304
243
|
return table.freeze(Struct)
|
|
@@ -2,16 +2,15 @@
|
|
|
2
2
|
--!optimize 2
|
|
3
3
|
-- Discriminated union codec. u8 variant tag + variant payload.
|
|
4
4
|
|
|
5
|
-
local
|
|
5
|
+
local Channel = require(script.Parent.Parent.Parent.internal.Channel)
|
|
6
6
|
local Types = require(script.Parent.Parent.Parent.Types)
|
|
7
7
|
|
|
8
|
-
-- Private
|
|
8
|
+
-- Private ----------------------------------------------------------------
|
|
9
9
|
|
|
10
|
-
local
|
|
11
|
-
local writeu8 = buffer.writeu8
|
|
10
|
+
local writeByte = Channel.writeByte
|
|
12
11
|
local readu8 = buffer.readu8
|
|
13
12
|
|
|
14
|
-
-- Public
|
|
13
|
+
-- Public -----------------------------------------------------------------
|
|
15
14
|
|
|
16
15
|
local Tagged = {}
|
|
17
16
|
|
|
@@ -37,7 +36,6 @@ function Tagged.define(
|
|
|
37
36
|
error("[Lync] Lync.tagged: exceeds 256 variants")
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
-- Build lookup tables
|
|
41
39
|
local nameToTag: { [string]: number } = {}
|
|
42
40
|
local tagToCodec = table.create(variantCount)
|
|
43
41
|
local tagToName = table.create(variantCount)
|
|
@@ -49,7 +47,7 @@ function Tagged.define(
|
|
|
49
47
|
nameToTag[name] = i - 1
|
|
50
48
|
tagToCodec[i] = codec
|
|
51
49
|
tagToName[i] = name
|
|
52
|
-
if
|
|
50
|
+
if codec._hasUnknown then
|
|
53
51
|
hasUnknown = true
|
|
54
52
|
end
|
|
55
53
|
end
|
|
@@ -58,25 +56,22 @@ function Tagged.define(
|
|
|
58
56
|
table.freeze(tagToCodec)
|
|
59
57
|
table.freeze(tagToName)
|
|
60
58
|
|
|
61
|
-
local codec: any = {
|
|
59
|
+
local codec: Types.InternalCodec<any> = {
|
|
60
|
+
_isTagged = true,
|
|
61
|
+
_tagField = tagField,
|
|
62
|
+
_variants = variants,
|
|
63
|
+
|
|
62
64
|
write = function(ch: Types.ChannelState, value: any): ()
|
|
63
65
|
local name = value[tagField]
|
|
64
66
|
if name == nil then
|
|
65
|
-
error(`[Lync] Tagged: missing tagField "{tagField}"
|
|
67
|
+
error(`[Lync] Tagged.write: missing tagField "{tagField}"`)
|
|
66
68
|
end
|
|
67
|
-
|
|
68
69
|
local tag = nameToTag[name]
|
|
69
70
|
if tag == nil then
|
|
70
|
-
error(`[Lync] Tagged: unknown variant "{name}"`)
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
local cursor = ch.cursor
|
|
74
|
-
if cursor + 1 > ch.size then
|
|
75
|
-
alloc(ch, 1)
|
|
71
|
+
error(`[Lync] Tagged.write: unknown variant "{name}"`)
|
|
76
72
|
end
|
|
77
|
-
writeu8(ch.buff, cursor, tag)
|
|
78
|
-
ch.cursor = cursor + 1
|
|
79
73
|
|
|
74
|
+
writeByte(ch, tag)
|
|
80
75
|
tagToCodec[tag + 1].write(ch, value)
|
|
81
76
|
end,
|
|
82
77
|
|
|
@@ -84,21 +79,16 @@ function Tagged.define(
|
|
|
84
79
|
local tag = readu8(src, pos)
|
|
85
80
|
local varCodec = tagToCodec[tag + 1]
|
|
86
81
|
if not varCodec then
|
|
87
|
-
error(`[Lync] Tagged: unknown tag {tag}`)
|
|
82
|
+
error(`[Lync] Tagged.read: unknown tag {tag}`)
|
|
88
83
|
end
|
|
89
84
|
|
|
90
|
-
local name = tagToName[tag + 1]
|
|
91
85
|
local data, consumed = varCodec.read(src, pos + 1, refs)
|
|
92
|
-
|
|
93
|
-
-- Caller needs the tag field to discriminate the union
|
|
94
86
|
if type(data) == "table" then
|
|
95
|
-
data[tagField] =
|
|
87
|
+
data[tagField] = tagToName[tag + 1]
|
|
96
88
|
end
|
|
97
|
-
|
|
98
89
|
return data, 1 + consumed
|
|
99
90
|
end,
|
|
100
91
|
}
|
|
101
|
-
|
|
102
92
|
if hasUnknown then
|
|
103
93
|
codec._hasUnknown = true
|
|
104
94
|
end
|