@axpecter/lync 2.0.0 → 2.1.1

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 (55) hide show
  1. package/README.md +342 -428
  2. package/package.json +1 -1
  3. package/src/Types.luau +63 -31
  4. package/src/api/Group.luau +101 -106
  5. package/src/api/Packet.luau +187 -186
  6. package/src/api/Query.luau +223 -284
  7. package/src/api/Scope.luau +46 -57
  8. package/src/api/Signal.luau +104 -137
  9. package/src/codec/Base.luau +69 -20
  10. package/src/codec/composite/Array.luau +179 -270
  11. package/src/codec/composite/Map.luau +97 -347
  12. package/src/codec/composite/Optional.luau +27 -24
  13. package/src/codec/composite/Shared.luau +96 -65
  14. package/src/codec/composite/Struct.luau +200 -360
  15. package/src/codec/composite/Tagged.luau +62 -187
  16. package/src/codec/composite/Tuple.luau +79 -103
  17. package/src/codec/datatype/Buffer.luau +49 -21
  18. package/src/codec/datatype/CFrame.luau +207 -30
  19. package/src/codec/datatype/Color.luau +21 -11
  20. package/src/codec/datatype/Instance.luau +28 -21
  21. package/src/codec/datatype/IntVector.luau +33 -21
  22. package/src/codec/datatype/NumberRange.luau +19 -10
  23. package/src/codec/datatype/Ray.luau +26 -22
  24. package/src/codec/datatype/Rect.luau +20 -16
  25. package/src/codec/datatype/Region.luau +51 -45
  26. package/src/codec/datatype/Sequence.luau +64 -56
  27. package/src/codec/datatype/String.luau +89 -56
  28. package/src/codec/datatype/UDim.luau +40 -22
  29. package/src/codec/datatype/Vector.luau +181 -17
  30. package/src/codec/meta/Auto.luau +295 -253
  31. package/src/codec/meta/Bitfield.luau +104 -148
  32. package/src/codec/meta/Custom.luau +8 -4
  33. package/src/codec/meta/Enum.luau +29 -57
  34. package/src/codec/meta/Float.luau +64 -0
  35. package/src/codec/meta/Nothing.luau +9 -5
  36. package/src/codec/meta/Unknown.luau +44 -36
  37. package/src/codec/primitive/Bool.luau +16 -26
  38. package/src/codec/primitive/Float16.luau +58 -64
  39. package/src/codec/primitive/Int.luau +68 -0
  40. package/src/codec/primitive/Number.luau +21 -43
  41. package/src/codec/primitive/Varint.luau +67 -46
  42. package/src/index.d.ts +251 -335
  43. package/src/init.luau +319 -207
  44. package/src/internal/Baseline.luau +29 -24
  45. package/src/internal/Channel.luau +333 -278
  46. package/src/internal/Middleware.luau +60 -85
  47. package/src/internal/Pool.luau +19 -30
  48. package/src/internal/Registry.luau +56 -113
  49. package/src/transport/Bridge.luau +64 -43
  50. package/src/transport/Client.luau +59 -170
  51. package/src/transport/Gate.luau +282 -142
  52. package/src/transport/Reader.luau +148 -140
  53. package/src/transport/Server.luau +138 -488
  54. package/src/api/Namespace.luau +0 -256
  55. package/src/codec/meta/Quantized.luau +0 -174
@@ -1,31 +1,24 @@
1
1
  --!strict
2
2
  --!native
3
- -- Auto(self-describing) codec. Writes a u8 type tag then the value.
4
-
5
- local BoolCodec = require(script.Parent.Parent.primitive.Bool)
6
- local BufferCodec = require(script.Parent.Parent.datatype.Buffer)
7
- local CFrameCodec = require(script.Parent.Parent.datatype.CFrame)
8
- local Channel = require(script.Parent.Parent.Parent.internal.Channel)
9
- local ColorCodec = require(script.Parent.Parent.datatype.Color)
10
- local IntVectorCodec = require(script.Parent.Parent.datatype.IntVector)
11
- local NumberCodec = require(script.Parent.Parent.primitive.Number)
12
- local NumberRangeCodec = require(script.Parent.Parent.datatype.NumberRange)
13
- local RayCodec = require(script.Parent.Parent.datatype.Ray)
14
- local RectCodec = require(script.Parent.Parent.datatype.Rect)
15
- local RegionCodec = require(script.Parent.Parent.datatype.Region)
16
- local SequenceCodec = require(script.Parent.Parent.datatype.Sequence)
17
- local StringCodec = require(script.Parent.Parent.datatype.String)
3
+ -- Self-describing codec. u8 type tag + value payload.
4
+
5
+ local Base = require(script.Parent.Parent.Base)
6
+ local BoolC = require(script.Parent.Parent.primitive.Bool)
7
+ local BufferC = require(script.Parent.Parent.datatype.Buffer)
8
+ local CFrameC = require(script.Parent.Parent.datatype.CFrame)
9
+ local ColorC = require(script.Parent.Parent.datatype.Color)
10
+ local Float16C = require(script.Parent.Parent.primitive.Float16)
11
+ local IVC = require(script.Parent.Parent.datatype.IntVector)
12
+ local NRC = require(script.Parent.Parent.datatype.NumberRange)
13
+ local NumberC = require(script.Parent.Parent.primitive.Number)
14
+ local RayC = require(script.Parent.Parent.datatype.Ray)
15
+ local RectC = require(script.Parent.Parent.datatype.Rect)
16
+ local RegionC = require(script.Parent.Parent.datatype.Region)
17
+ local SeqC = require(script.Parent.Parent.datatype.Sequence)
18
18
  local Types = require(script.Parent.Parent.Parent.Types)
19
- local UDimCodec = require(script.Parent.Parent.datatype.UDim)
19
+ local UDimC = require(script.Parent.Parent.datatype.UDim)
20
20
  local Varint = require(script.Parent.Parent.primitive.Varint)
21
- local VectorCodec = require(script.Parent.Parent.datatype.Vector)
22
-
23
- type ChannelState = Types.ChannelState
24
- type Codec<T> = Types.Codec<T>
25
- type InternalCodec<T> = Types.InternalCodec<T>
26
-
27
- local alloc = Channel.alloc
28
- local floor = math.floor
21
+ local VecC = require(script.Parent.Parent.datatype.Vector)
29
22
 
30
23
  -- Constants -----------------------------------------------------------
31
24
 
@@ -47,314 +40,363 @@ local TAG_CFRAME = 14
47
40
  local TAG_BUFFER = 15
48
41
  local TAG_UDIM = 16
49
42
  local TAG_UDIM2 = 17
50
- local TAG_NUMBERRANGE = 18
43
+ local TAG_NUMRANGE = 18
51
44
  local TAG_RECT = 19
52
- local TAG_VEC2INT16 = 20
53
- local TAG_VEC3INT16 = 21
45
+ local TAG_V2I16 = 20
46
+ local TAG_V3I16 = 21
54
47
  local TAG_REGION3 = 22
55
- local TAG_REGION3INT16 = 23
48
+ local TAG_R3I16 = 23
56
49
  local TAG_RAY = 24
57
- local TAG_NUMBERSEQUENCE = 25
58
- local TAG_COLORSEQUENCE = 26
59
-
60
- local READ_DISPATCH = table.freeze({
61
- [TAG_BOOL] = BoolCodec.bool,
62
- [TAG_U8] = NumberCodec.u8,
63
- [TAG_U16] = NumberCodec.u16,
64
- [TAG_U32] = NumberCodec.u32,
65
- [TAG_I8] = NumberCodec.i8,
66
- [TAG_I16] = NumberCodec.i16,
67
- [TAG_I32] = NumberCodec.i32,
68
- [TAG_F32] = NumberCodec.f32,
69
- [TAG_F64] = NumberCodec.f64,
70
- [TAG_STRING] = StringCodec.string,
71
- [TAG_VEC2] = VectorCodec.vec2,
72
- [TAG_VEC3] = VectorCodec.vec3,
73
- [TAG_COLOR3] = ColorCodec.color3,
74
- [TAG_CFRAME] = CFrameCodec.cframe,
75
- [TAG_BUFFER] = BufferCodec.buff,
76
- [TAG_UDIM] = UDimCodec.udim,
77
- [TAG_UDIM2] = UDimCodec.udim2,
78
- [TAG_NUMBERRANGE] = NumberRangeCodec.numberRange,
79
- [TAG_RECT] = RectCodec.rect,
80
- [TAG_VEC2INT16] = IntVectorCodec.vec2int16,
81
- [TAG_VEC3INT16] = IntVectorCodec.vec3int16,
82
- [TAG_REGION3] = RegionCodec.region3,
83
- [TAG_REGION3INT16] = RegionCodec.region3int16,
84
- [TAG_RAY] = RayCodec.ray,
85
- [TAG_NUMBERSEQUENCE] = SequenceCodec.numberSequence,
86
- [TAG_COLORSEQUENCE] = SequenceCodec.colorSequence,
87
- })
50
+ local TAG_NUMSEQ = 25
51
+ local TAG_COLSEQ = 26
88
52
 
89
- local function extractDw(codec: any): any
90
- return (codec :: InternalCodec<any>)._directWrite
91
- end
53
+ local INLINE_MAX = Varint.INLINE_MAX
92
54
 
93
- local TYPE_DISPATCH = table.freeze({
94
- boolean = { tag = TAG_BOOL, codec = BoolCodec.bool, size = 1, dw = nil },
95
- string = { tag = TAG_STRING, codec = StringCodec.string, size = nil, dw = nil },
96
- buffer = { tag = TAG_BUFFER, codec = BufferCodec.buff, size = nil, dw = nil },
97
- Vector2 = {
98
- tag = TAG_VEC2,
99
- codec = VectorCodec.vec2,
100
- size = 8,
101
- dw = extractDw(VectorCodec.vec2),
102
- },
103
- Vector3 = {
104
- tag = TAG_VEC3,
105
- codec = VectorCodec.vec3,
106
- size = 12,
107
- dw = extractDw(VectorCodec.vec3),
108
- },
109
- Color3 = {
110
- tag = TAG_COLOR3,
111
- codec = ColorCodec.color3,
112
- size = 3,
113
- dw = extractDw(ColorCodec.color3),
114
- },
115
- CFrame = {
116
- tag = TAG_CFRAME,
117
- codec = CFrameCodec.cframe,
118
- size = 24,
119
- dw = extractDw(CFrameCodec.cframe),
120
- },
121
- UDim = {
122
- tag = TAG_UDIM,
123
- codec = UDimCodec.udim,
124
- size = 8,
125
- dw = extractDw(UDimCodec.udim),
126
- },
127
- UDim2 = {
128
- tag = TAG_UDIM2,
129
- codec = UDimCodec.udim2,
130
- size = 16,
131
- dw = extractDw(UDimCodec.udim2),
132
- },
133
- NumberRange = {
134
- tag = TAG_NUMBERRANGE,
135
- codec = NumberRangeCodec.numberRange,
136
- size = 8,
137
- dw = extractDw(NumberRangeCodec.numberRange),
138
- },
139
- Rect = {
140
- tag = TAG_RECT,
141
- codec = RectCodec.rect,
142
- size = 16,
143
- dw = extractDw(RectCodec.rect),
144
- },
145
- Vector2int16 = {
146
- tag = TAG_VEC2INT16,
147
- codec = IntVectorCodec.vec2int16,
148
- size = 4,
149
- dw = extractDw(IntVectorCodec.vec2int16),
150
- },
151
- Vector3int16 = {
152
- tag = TAG_VEC3INT16,
153
- codec = IntVectorCodec.vec3int16,
154
- size = 6,
155
- dw = extractDw(IntVectorCodec.vec3int16),
156
- },
157
- Region3 = {
158
- tag = TAG_REGION3,
159
- codec = RegionCodec.region3,
160
- size = 24,
161
- dw = extractDw(RegionCodec.region3),
162
- },
163
- Region3int16 = {
164
- tag = TAG_REGION3INT16,
165
- codec = RegionCodec.region3int16,
166
- size = 12,
167
- dw = extractDw(RegionCodec.region3int16),
168
- },
169
- Ray = {
170
- tag = TAG_RAY,
171
- codec = RayCodec.ray,
172
- size = 24,
173
- dw = extractDw(RayCodec.ray),
174
- },
175
- NumberSequence = {
176
- tag = TAG_NUMBERSEQUENCE,
177
- codec = SequenceCodec.numberSequence,
178
- size = nil,
179
- dw = nil,
180
- },
181
- ColorSequence = {
182
- tag = TAG_COLORSEQUENCE,
183
- codec = SequenceCodec.colorSequence,
184
- size = nil,
185
- dw = nil,
186
- },
187
- })
55
+ -- Private -------------------------------------------------------------
56
+
57
+ local alloc = Base.alloc
58
+ local writeu8 = buffer.writeu8
59
+ local readu8 = buffer.readu8
60
+ local floor = math.floor
61
+ local abs = math.abs
62
+
63
+ local F32_SCRATCH = NumberC._f32Scratch
64
+
65
+ --[[
66
+ Write dispatch tables: tag { directWrite, payloadSize }.
67
+ Used for integer/float types where the codec has _directWrite + _size.
68
+ ]]
69
+ local NUM_WRITE: { [number]: (buffer, number, number) -> () } = {
70
+ [TAG_U8] = buffer.writeu8,
71
+ [TAG_U16] = buffer.writeu16,
72
+ [TAG_U32] = buffer.writeu32,
73
+ [TAG_F32] = buffer.writef32,
74
+ [TAG_F64] = buffer.writef64,
75
+ }
188
76
 
189
- -- Numeric write dispatch: one lookup for both codec and payload size.
190
- local NUM_SIZES = {} :: { [number]: number }
191
- local NUM_DWRITES = {} :: { [number]: any }
192
-
193
- for _, pair in
194
- {
195
- { TAG_U8, NumberCodec.u8, 1 },
196
- { TAG_U16, NumberCodec.u16, 2 },
197
- { TAG_U32, NumberCodec.u32, 4 },
198
- { TAG_I8, NumberCodec.i8, 1 },
199
- { TAG_I16, NumberCodec.i16, 2 },
200
- { TAG_I32, NumberCodec.i32, 4 },
201
- { TAG_F32, NumberCodec.f32, 4 },
202
- { TAG_F64, NumberCodec.f64, 8 },
203
- }
204
- do
205
- NUM_SIZES[pair[1]] = pair[3]
206
- NUM_DWRITES[pair[1]] = (pair[2] :: InternalCodec<number>)._directWrite
77
+ local NUM_SIZE: { [number]: number } = {
78
+ [TAG_U8] = 1,
79
+ [TAG_U16] = 2,
80
+ [TAG_U32] = 4,
81
+ [TAG_F32] = 4,
82
+ [TAG_F64] = 8,
83
+ }
84
+
85
+ -- writei8/i16/i32 are NOT FASTCALL, so we use writeu* with two's complement conversion
86
+ local function writeI8(b: buffer, off: number, v: number): ()
87
+ writeu8(b, off, if v < 0 then v + 0x100 else v)
207
88
  end
208
89
 
209
- table.freeze(NUM_SIZES)
210
- table.freeze(NUM_DWRITES)
90
+ local function writeI16(b: buffer, off: number, v: number): ()
91
+ buffer.writeu16(b, off, if v < 0 then v + 0x10000 else v)
92
+ end
211
93
 
212
- -- Private -------------------------------------------------------------
94
+ local function writeI32(b: buffer, off: number, v: number): ()
95
+ buffer.writeu32(b, off, if v < 0 then v + 0x100000000 else v)
96
+ end
213
97
 
214
- local _f32Temp = buffer.create(4)
98
+ NUM_WRITE[TAG_I8] = writeI8
99
+ NUM_WRITE[TAG_I16] = writeI16
100
+ NUM_WRITE[TAG_I32] = writeI32
101
+ NUM_SIZE[TAG_I8] = 1
102
+ NUM_SIZE[TAG_I16] = 2
103
+ NUM_SIZE[TAG_I32] = 4
215
104
 
105
+ table.freeze(NUM_WRITE)
106
+ table.freeze(NUM_SIZE)
107
+
108
+ -- Selects the smallest integer/float tag for a number value
216
109
  local function selectNumberTag(value: number): number
110
+ -- NaN always f64
217
111
  if value ~= value then
218
112
  return TAG_F64
219
113
  end
220
114
 
221
- if value == floor(value) then
115
+ -- Integer path
116
+ if floor(value) == value then
222
117
  if value >= 0 then
223
- if value <= 255 then
118
+ if value <= 0xFF then
224
119
  return TAG_U8
225
120
  end
226
- if value <= 65535 then
121
+ if value <= 0xFFFF then
227
122
  return TAG_U16
228
123
  end
229
- if value <= 4294967295 then
124
+ if value <= 0xFFFFFFFF then
230
125
  return TAG_U32
231
126
  end
232
127
  else
233
- if value >= -128 and value <= 127 then
128
+ if value >= -0x80 then
234
129
  return TAG_I8
235
130
  end
236
- if value >= -32768 and value <= 32767 then
131
+ if value >= -0x8000 then
237
132
  return TAG_I16
238
133
  end
239
- if value >= -2147483648 and value <= 2147483647 then
134
+ if value >= -0x80000000 then
240
135
  return TAG_I32
241
136
  end
242
137
  end
243
- return TAG_F64
244
138
  end
245
139
 
246
- buffer.writef32(_f32Temp, 0, value)
247
- if buffer.readf32(_f32Temp, 0) == value then
140
+ -- Float path: try f32 round-trip
141
+ buffer.writef32(F32_SCRATCH, 0, value)
142
+ if buffer.readf32(F32_SCRATCH, 0) == value then
248
143
  return TAG_F32
249
144
  end
250
145
 
251
146
  return TAG_F64
252
147
  end
253
148
 
149
+ --[[
150
+ Roblox type dispatch: typeof string → { tag, codec }.
151
+ For types with _directWrite + _size, we use the fast path.
152
+ ]]
153
+ type TypeEntry = {
154
+ tag: number,
155
+ codec: Types.InternalCodec<any>,
156
+ }
157
+
158
+ local TYPE_DISPATCH: { [string]: TypeEntry } = table.freeze({
159
+ Vector2 = { tag = TAG_VEC2, codec = VecC.vec2 },
160
+ Vector3 = { tag = TAG_VEC3, codec = VecC.vec3 },
161
+ Color3 = { tag = TAG_COLOR3, codec = ColorC.color3 },
162
+ CFrame = { tag = TAG_CFRAME, codec = CFrameC.cframe },
163
+ UDim = { tag = TAG_UDIM, codec = UDimC.udim },
164
+ UDim2 = { tag = TAG_UDIM2, codec = UDimC.udim2 },
165
+ NumberRange = { tag = TAG_NUMRANGE, codec = NRC.numberRange },
166
+ Rect = { tag = TAG_RECT, codec = RectC.rect },
167
+ Vector2int16 = { tag = TAG_V2I16, codec = IVC.vec2int16 },
168
+ Vector3int16 = { tag = TAG_V3I16, codec = IVC.vec3int16 },
169
+ Region3 = { tag = TAG_REGION3, codec = RegionC.region3 },
170
+ Region3int16 = { tag = TAG_R3I16, codec = RegionC.region3int16 },
171
+ Ray = { tag = TAG_RAY, codec = RayC.ray },
172
+ NumberSequence = { tag = TAG_NUMSEQ, codec = SeqC.numberSequence },
173
+ ColorSequence = { tag = TAG_COLSEQ, codec = SeqC.colorSequence },
174
+ } :: any)
175
+
176
+ -- Read dispatch: tag → codec
177
+ local READ_DISPATCH: { [number]: Types.InternalCodec<any> } = table.freeze({
178
+ [TAG_VEC2] = VecC.vec2,
179
+ [TAG_VEC3] = VecC.vec3,
180
+ [TAG_COLOR3] = ColorC.color3,
181
+ [TAG_CFRAME] = CFrameC.cframe,
182
+ [TAG_UDIM] = UDimC.udim,
183
+ [TAG_UDIM2] = UDimC.udim2,
184
+ [TAG_NUMRANGE] = NRC.numberRange,
185
+ [TAG_RECT] = RectC.rect,
186
+ [TAG_V2I16] = IVC.vec2int16,
187
+ [TAG_V3I16] = IVC.vec3int16,
188
+ [TAG_REGION3] = RegionC.region3,
189
+ [TAG_R3I16] = RegionC.region3int16,
190
+ [TAG_RAY] = RayC.ray,
191
+ [TAG_NUMSEQ] = SeqC.numberSequence,
192
+ [TAG_COLSEQ] = SeqC.colorSequence,
193
+ } :: any)
194
+
195
+ -- Numeric read helpers
196
+ local function readI8(b: buffer, off: number): number
197
+ local raw = readu8(b, off)
198
+ return if raw >= 0x80 then raw - 0x100 else raw
199
+ end
200
+
201
+ local function readI16(b: buffer, off: number): number
202
+ local raw = buffer.readu16(b, off)
203
+ return if raw >= 0x8000 then raw - 0x10000 else raw
204
+ end
205
+
206
+ local function readI32(b: buffer, off: number): number
207
+ local raw = buffer.readu32(b, off)
208
+ return if raw >= 0x80000000 then raw - 0x100000000 else raw
209
+ end
210
+
211
+ local NUM_READ: { [number]: (buffer, number) -> number } = table.freeze({
212
+ [TAG_U8] = buffer.readu8,
213
+ [TAG_U16] = buffer.readu16,
214
+ [TAG_U32] = buffer.readu32,
215
+ [TAG_I8] = readI8,
216
+ [TAG_I16] = readI16,
217
+ [TAG_I32] = readI32,
218
+ [TAG_F32] = buffer.readf32,
219
+ [TAG_F64] = buffer.readf64,
220
+ })
221
+
254
222
  -- Public --------------------------------------------------------------
255
223
 
256
- local auto = table.freeze({
257
- write = function(ch: ChannelState, value: any): ()
224
+ local AutoModule = {}
225
+
226
+ AutoModule.auto = table.freeze({
227
+ write = function(ch: Types.ChannelState, value: any): ()
258
228
  if value == nil then
259
- local c = ch.cursor
260
- if c + 1 > ch.size then
229
+ local cursor = ch.cursor
230
+ if cursor + 1 > ch.size then
261
231
  alloc(ch, 1)
262
232
  end
263
- buffer.writeu8(ch.buff, c, TAG_NIL)
264
- ch.cursor = c + 1
233
+ writeu8(ch.buff, cursor, TAG_NIL)
234
+ ch.cursor = cursor + 1
265
235
  return
266
236
  end
267
237
 
268
- local t = type(value)
238
+ local t = typeof(value)
239
+
240
+ if t == "boolean" then
241
+ local cursor = ch.cursor
242
+ if cursor + 2 > ch.size then
243
+ alloc(ch, 2)
244
+ end
245
+ local b = ch.buff
246
+ writeu8(b, cursor, TAG_BOOL)
247
+ writeu8(b, cursor + 1, if value then 1 else 0)
248
+ ch.cursor = cursor + 2
249
+ return
250
+ end
269
251
 
270
252
  if t == "number" then
271
253
  local tag = selectNumberTag(value)
272
- local payloadSize = NUM_SIZES[tag]
254
+ local payloadSize = NUM_SIZE[tag]
273
255
  local totalSize = 1 + payloadSize
274
-
275
- local c = ch.cursor
276
- if c + totalSize > ch.size then
256
+ local cursor = ch.cursor
257
+ if cursor + totalSize > ch.size then
277
258
  alloc(ch, totalSize)
278
259
  end
279
260
  local b = ch.buff
280
- buffer.writeu8(b, c, tag)
281
- NUM_DWRITES[tag](b, c + 1, value)
282
- ch.cursor = c + totalSize
261
+ writeu8(b, cursor, tag)
262
+ NUM_WRITE[tag](b, cursor + 1, value)
263
+ ch.cursor = cursor + totalSize
283
264
  return
284
265
  end
285
266
 
286
267
  if t == "string" then
268
+ local cursor = ch.cursor
269
+ if cursor + 1 > ch.size then
270
+ alloc(ch, 1)
271
+ end
272
+ writeu8(ch.buff, cursor, TAG_STRING)
273
+ ch.cursor = cursor + 1
274
+ -- Inline string write (same as String codec)
287
275
  local len = #value
288
- local total = 6 + len
289
- local c = ch.cursor
290
- if c + total > ch.size then
291
- alloc(ch, total)
276
+ cursor = ch.cursor
277
+ if len <= INLINE_MAX then
278
+ if cursor + 1 + len > ch.size then
279
+ alloc(ch, 1 + len)
280
+ end
281
+ local b = ch.buff
282
+ writeu8(b, cursor, len)
283
+ if len > 0 then
284
+ buffer.writestring(b, cursor + 1, value)
285
+ end
286
+ ch.cursor = cursor + 1 + len
287
+ else
288
+ Varint.write(ch, len)
289
+ cursor = ch.cursor
290
+ if cursor + len > ch.size then
291
+ alloc(ch, len)
292
+ end
293
+ buffer.writestring(ch.buff, cursor, value)
294
+ ch.cursor = cursor + len
292
295
  end
293
- buffer.writeu8(ch.buff, c, TAG_STRING)
294
- ch.cursor = c + 1
295
- Varint.write(ch, len)
296
- local c2 = ch.cursor
297
- buffer.writestring(ch.buff, c2, value)
298
- ch.cursor = c2 + len
299
296
  return
300
297
  end
301
298
 
302
- if t == "boolean" then
303
- local c = ch.cursor
304
- if c + 2 > ch.size then
305
- alloc(ch, 2)
299
+ if t == "buffer" then
300
+ local cursor = ch.cursor
301
+ if cursor + 1 > ch.size then
302
+ alloc(ch, 1)
306
303
  end
307
- local b = ch.buff
308
- buffer.writeu8(b, c, TAG_BOOL)
309
- buffer.writeu8(b, c + 1, if value then 1 else 0)
310
- ch.cursor = c + 2
304
+ writeu8(ch.buff, cursor, TAG_BUFFER)
305
+ ch.cursor = cursor + 1
306
+ BufferC.buff.write(ch, value)
311
307
  return
312
308
  end
313
309
 
314
- local valueType = typeof(value)
315
- local entry = TYPE_DISPATCH[valueType]
316
- if not entry then
317
- error(`[Lync] Auto unsupported type: {valueType}`)
318
- end
310
+ -- Roblox type dispatch
311
+ local entry = TYPE_DISPATCH[t]
312
+ if entry then
313
+ local codec = entry.codec
314
+ local size = codec._size
315
+ local dw = codec._directWrite
319
316
 
320
- local entryDw = entry.dw
321
- local entrySize = entry.size
322
- if entryDw and entrySize then
323
- local totalSize = 1 + entrySize
324
- local c = ch.cursor
325
- if c + totalSize > ch.size then
326
- alloc(ch, totalSize)
317
+ if size and dw then
318
+ -- Fast path: direct write
319
+ local totalSize = 1 + size
320
+ local cursor = ch.cursor
321
+ if cursor + totalSize > ch.size then
322
+ alloc(ch, totalSize)
323
+ end
324
+ local b = ch.buff
325
+ writeu8(b, cursor, entry.tag)
326
+ dw(b, cursor + 1, value)
327
+ ch.cursor = cursor + totalSize
328
+ else
329
+ -- Generic path
330
+ local cursor = ch.cursor
331
+ if cursor + 1 > ch.size then
332
+ alloc(ch, 1)
333
+ end
334
+ writeu8(ch.buff, cursor, entry.tag)
335
+ ch.cursor = cursor + 1
336
+ codec.write(ch, value)
327
337
  end
328
- local b = ch.buff
329
- buffer.writeu8(b, c, entry.tag)
330
- entryDw(b, c + 1, value)
331
- ch.cursor = c + totalSize
332
- else
333
- local c = ch.cursor
334
- if c + 1 > ch.size then
335
- alloc(ch, 1)
336
- end
337
- buffer.writeu8(ch.buff, c, entry.tag)
338
- ch.cursor = c + 1
339
- entry.codec.write(ch, value)
338
+ return
340
339
  end
340
+
341
+ error(`[Lync] Auto: unsupported type "{t}"`)
341
342
  end,
343
+
342
344
  read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
343
- local tag = buffer.readu8(src, pos)
345
+ local tag = readu8(src, pos)
346
+
347
+ -- Nil
344
348
  if tag == TAG_NIL then
345
349
  return nil, 1
346
350
  end
347
351
 
352
+ -- Bool
353
+ if tag == TAG_BOOL then
354
+ return readu8(src, pos + 1) ~= 0, 2
355
+ end
356
+
357
+ -- Numeric types (tags 2-9)
358
+ local numRead = NUM_READ[tag]
359
+ if numRead then
360
+ local payloadSize = NUM_SIZE[tag]
361
+ return numRead(src, pos + 1), 1 + payloadSize
362
+ end
363
+
364
+ -- String
365
+ if tag == TAG_STRING then
366
+ local b0 = readu8(src, pos + 1)
367
+ local len: number
368
+ local lenBytes: number
369
+
370
+ if b0 <= INLINE_MAX then
371
+ len = b0
372
+ lenBytes = 1
373
+ else
374
+ len, lenBytes = Varint.read(src, pos + 1)
375
+ end
376
+
377
+ if len == 0 then
378
+ return "", 1 + lenBytes
379
+ end
380
+
381
+ local dataStart = pos + 1 + lenBytes
382
+ return buffer.readstring(src, dataStart, len), 1 + lenBytes + len
383
+ end
384
+
385
+ -- Buffer
386
+ if tag == TAG_BUFFER then
387
+ local value, consumed = BufferC.buff.read(src, pos + 1, refs)
388
+ return value, 1 + consumed
389
+ end
390
+
391
+ -- Roblox types
348
392
  local codec = READ_DISPATCH[tag]
349
- if not codec then
350
- error(`[Lync] Auto unknown tag: {tag}`)
393
+ if codec then
394
+ local value, consumed = codec.read(src, pos + 1, refs)
395
+ return value, 1 + consumed
351
396
  end
352
397
 
353
- local value, n = codec.read(src, pos + 1, refs)
354
- return value, 1 + n
398
+ error(`[Lync] Auto: unknown tag {tag}`)
355
399
  end,
356
- })
400
+ } :: any)
357
401
 
358
- local AutoModule = {}
359
- AutoModule.auto = auto
360
402
  return table.freeze(AutoModule)