@axpecter/lync 2.1.2 → 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.
Files changed (56) hide show
  1. package/README.md +241 -349
  2. package/package.json +1 -1
  3. package/src/Types.luau +53 -13
  4. package/src/api/Group.luau +41 -51
  5. package/src/api/Packet.luau +145 -143
  6. package/src/api/Query.luau +204 -202
  7. package/src/api/Scope.luau +36 -38
  8. package/src/api/Signal.luau +68 -94
  9. package/src/codec/Base.luau +43 -23
  10. package/src/codec/composite/Array.luau +161 -152
  11. package/src/codec/composite/Map.luau +37 -53
  12. package/src/codec/composite/Optional.luau +15 -21
  13. package/src/codec/composite/Shared.luau +78 -41
  14. package/src/codec/composite/Struct.luau +64 -125
  15. package/src/codec/composite/Tagged.luau +15 -25
  16. package/src/codec/composite/Tuple.luau +15 -20
  17. package/src/codec/datatype/Buffer.luau +44 -51
  18. package/src/codec/datatype/CFrame.luau +72 -104
  19. package/src/codec/datatype/Color.luau +14 -10
  20. package/src/codec/datatype/Instance.luau +32 -42
  21. package/src/codec/datatype/IntVector.luau +24 -22
  22. package/src/codec/datatype/NumberRange.luau +21 -12
  23. package/src/codec/datatype/Ray.luau +13 -7
  24. package/src/codec/datatype/Rect.luau +13 -7
  25. package/src/codec/datatype/Region.luau +25 -22
  26. package/src/codec/datatype/Sequence.luau +144 -118
  27. package/src/codec/datatype/String.luau +29 -59
  28. package/src/codec/datatype/UDim.luau +29 -30
  29. package/src/codec/datatype/Vector.luau +89 -105
  30. package/src/codec/meta/Auto.luau +194 -246
  31. package/src/codec/meta/Bitfield.luau +37 -36
  32. package/src/codec/meta/Custom.luau +10 -10
  33. package/src/codec/meta/Enum.luau +8 -11
  34. package/src/codec/meta/Float.luau +16 -20
  35. package/src/codec/meta/Nothing.luau +2 -2
  36. package/src/codec/meta/Unknown.luau +22 -32
  37. package/src/codec/primitive/Bool.luau +9 -5
  38. package/src/codec/primitive/Float16.luau +13 -23
  39. package/src/codec/primitive/Int.luau +20 -28
  40. package/src/codec/primitive/Number.luau +6 -10
  41. package/src/codec/primitive/Signed.luau +32 -0
  42. package/src/codec/primitive/Varint.luau +59 -28
  43. package/src/index.d.ts +8 -2
  44. package/src/init.luau +118 -143
  45. package/src/internal/Baseline.luau +17 -18
  46. package/src/internal/Channel.luau +143 -212
  47. package/src/internal/Middleware.luau +37 -47
  48. package/src/internal/Pool.luau +10 -10
  49. package/src/internal/Registry.luau +38 -34
  50. package/src/internal/Transport.luau +28 -0
  51. package/src/internal/Util.luau +26 -0
  52. package/src/transport/Bridge.luau +32 -24
  53. package/src/transport/Client.luau +51 -53
  54. package/src/transport/Gate.luau +183 -113
  55. package/src/transport/Reader.luau +95 -66
  56. 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
- -- State ---------------------------------------------------------------
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
- -- Public --------------------------------------------------------------
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 not ch then
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
- -- u32 chunks
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 (codec :: any)._isBool then
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
- for i = 1, #dataKeys do
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 limit = if idx + 8 <= boolCount + 1 then 8 else boolCount - idx + 1
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 limit = if idx + 8 <= boolCount + 1 then 8 else boolCount - idx + 1
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
- -- struct and deltaStruct codecs with single-field delta shortcut.
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 band = bit32.band
27
- local bor = bit32.bor
28
- local lshift = bit32.lshift
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
- -- Check all-direct path
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] :: any
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] :: any
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
- -- 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
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
- buffer.writeu8(b, boolOff + byteIdx, packed)
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, dataCodecs, boolKeys = extractFields(schema)
180
- local dataCount = #dataKeys
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 FLAG_MULTI = segCount + DELTA_MULTI_OFFSET
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
- -- First frame: FLAG_FULL + full payload
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
- 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)
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
- -- Update cache
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
- local flag = readu8(src, pos)
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 Base = require(script.Parent.Parent.Base)
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 alloc = Base.alloc
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 (codec :: any)._hasUnknown then
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}" in value`)
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] = name
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