@axpecter/lync 1.3.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 (52) hide show
  1. package/README.md +300 -0
  2. package/package.json +38 -0
  3. package/src/Types.luau +63 -0
  4. package/src/api/Group.luau +126 -0
  5. package/src/api/Namespace.luau +226 -0
  6. package/src/api/Packet.luau +147 -0
  7. package/src/api/Query.luau +295 -0
  8. package/src/api/Signal.luau +224 -0
  9. package/src/codec/Base.luau +49 -0
  10. package/src/codec/composite/Array.luau +275 -0
  11. package/src/codec/composite/Map.luau +395 -0
  12. package/src/codec/composite/Optional.luau +47 -0
  13. package/src/codec/composite/Shared.luau +151 -0
  14. package/src/codec/composite/Struct.luau +440 -0
  15. package/src/codec/composite/Tagged.luau +222 -0
  16. package/src/codec/composite/Tuple.luau +143 -0
  17. package/src/codec/datatype/Buffer.luau +44 -0
  18. package/src/codec/datatype/CFrame.luau +51 -0
  19. package/src/codec/datatype/Color.luau +22 -0
  20. package/src/codec/datatype/Instance.luau +48 -0
  21. package/src/codec/datatype/IntVector.luau +25 -0
  22. package/src/codec/datatype/NumberRange.luau +14 -0
  23. package/src/codec/datatype/Ray.luau +27 -0
  24. package/src/codec/datatype/Rect.luau +21 -0
  25. package/src/codec/datatype/Region.luau +58 -0
  26. package/src/codec/datatype/Sequence.luau +129 -0
  27. package/src/codec/datatype/String.luau +87 -0
  28. package/src/codec/datatype/UDim.luau +27 -0
  29. package/src/codec/datatype/Vector.luau +25 -0
  30. package/src/codec/meta/Auto.luau +353 -0
  31. package/src/codec/meta/Bitfield.luau +191 -0
  32. package/src/codec/meta/Custom.luau +27 -0
  33. package/src/codec/meta/Enum.luau +80 -0
  34. package/src/codec/meta/Nothing.luau +9 -0
  35. package/src/codec/meta/Quantized.luau +170 -0
  36. package/src/codec/meta/Unknown.luau +35 -0
  37. package/src/codec/primitive/Bool.luau +30 -0
  38. package/src/codec/primitive/Float16.luau +111 -0
  39. package/src/codec/primitive/Number.luau +48 -0
  40. package/src/codec/primitive/Varint.luau +76 -0
  41. package/src/index.d.ts +279 -0
  42. package/src/init.luau +161 -0
  43. package/src/internal/Baseline.luau +41 -0
  44. package/src/internal/Channel.luau +235 -0
  45. package/src/internal/Middleware.luau +109 -0
  46. package/src/internal/Pool.luau +68 -0
  47. package/src/internal/Registry.luau +146 -0
  48. package/src/transport/Bridge.luau +66 -0
  49. package/src/transport/Client.luau +151 -0
  50. package/src/transport/Gate.luau +222 -0
  51. package/src/transport/Reader.luau +175 -0
  52. package/src/transport/Server.luau +364 -0
@@ -0,0 +1,27 @@
1
+ --!strict
2
+ --!native
3
+ -- UDim (8B) and UDim2 (16B) codecs.
4
+
5
+ local Base = require (script.Parent.Parent.Base)
6
+
7
+ return table.freeze ({
8
+ udim = Base.define (8, function (b: buffer, o: number, v: UDim): ()
9
+ buffer.writef32 (b, o, v.Scale)
10
+ buffer.writei32 (b, o + 4, v.Offset)
11
+ end, function (b: buffer, o: number): UDim
12
+ return UDim.new (buffer.readf32 (b, o), buffer.readi32 (b, o + 4))
13
+ end),
14
+ udim2 = Base.define (16, function (b: buffer, o: number, v: UDim2): ()
15
+ local x = v.X
16
+ local y = v.Y
17
+ buffer.writef32 (b, o, x.Scale)
18
+ buffer.writei32 (b, o + 4, x.Offset)
19
+ buffer.writef32 (b, o + 8, y.Scale)
20
+ buffer.writei32 (b, o + 12, y.Offset)
21
+ end, function (b: buffer, o: number): UDim2
22
+ return UDim2.new (
23
+ UDim.new (buffer.readf32 (b, o), buffer.readi32 (b, o + 4)),
24
+ UDim.new (buffer.readf32 (b, o + 8), buffer.readi32 (b, o + 12))
25
+ )
26
+ end),
27
+ })
@@ -0,0 +1,25 @@
1
+ --!strict
2
+ --!native
3
+ -- Vector2 (8B) and Vector3 (12B) codecs.
4
+
5
+ local Base = require (script.Parent.Parent.Base)
6
+
7
+ return table.freeze ({
8
+ vec2 = Base.define (8, function (b: buffer, o: number, v: Vector2): ()
9
+ buffer.writef32 (b, o, v.X)
10
+ buffer.writef32 (b, o + 4, v.Y)
11
+ end, function (b: buffer, o: number): Vector2
12
+ return Vector2.new (buffer.readf32 (b, o), buffer.readf32 (b, o + 4))
13
+ end),
14
+ vec3 = Base.define (12, function (b: buffer, o: number, v: Vector3): ()
15
+ buffer.writef32 (b, o, v.X)
16
+ buffer.writef32 (b, o + 4, v.Y)
17
+ buffer.writef32 (b, o + 8, v.Z)
18
+ end, function (b: buffer, o: number): Vector3
19
+ return Vector3.new (
20
+ buffer.readf32 (b, o),
21
+ buffer.readf32 (b, o + 4),
22
+ buffer.readf32 (b, o + 8)
23
+ )
24
+ end),
25
+ })
@@ -0,0 +1,353 @@
1
+ --!strict
2
+ --!native
3
+ -- Auto (self-describing) codec. Writes a u8 type tag then the value.
4
+
5
+ local Buffer = require (script.Parent.Parent.datatype.Buffer)
6
+ local CFrameCodec = require (script.Parent.Parent.datatype.CFrame)
7
+ local Channel = require (script.Parent.Parent.Parent.internal.Channel)
8
+ local alloc = Channel.alloc
9
+ local BoolCodec = require (script.Parent.Parent.primitive.Bool)
10
+ local Color = require (script.Parent.Parent.datatype.Color)
11
+ local IntVector = require (script.Parent.Parent.datatype.IntVector)
12
+ local Number = require (script.Parent.Parent.primitive.Number)
13
+ local NumberRangeCodec = require (script.Parent.Parent.datatype.NumberRange)
14
+ local RayCodec = require (script.Parent.Parent.datatype.Ray)
15
+ local RectCodec = require (script.Parent.Parent.datatype.Rect)
16
+ local Region = require (script.Parent.Parent.datatype.Region)
17
+ local Sequence = require (script.Parent.Parent.datatype.Sequence)
18
+ local String = require (script.Parent.Parent.datatype.String)
19
+ local Types = require (script.Parent.Parent.Parent.Types)
20
+ local UDimCodec = require (script.Parent.Parent.datatype.UDim)
21
+ local Varint = require (script.Parent.Parent.primitive.Varint)
22
+ local Vector = require (script.Parent.Parent.datatype.Vector)
23
+
24
+ type ChannelState = Types.ChannelState
25
+ type Codec<T> = Types.Codec<T>
26
+
27
+ -- Constants --------------------------------------------------------------
28
+
29
+ local TAG_NIL = 0
30
+ local TAG_BOOL = 1
31
+ local TAG_U8 = 2
32
+ local TAG_U16 = 3
33
+ local TAG_U32 = 4
34
+ local TAG_I8 = 5
35
+ local TAG_I16 = 6
36
+ local TAG_I32 = 7
37
+ local TAG_F32 = 8
38
+ local TAG_F64 = 9
39
+ local TAG_STRING = 10
40
+ local TAG_VEC2 = 11
41
+ local TAG_VEC3 = 12
42
+ local TAG_COLOR3 = 13
43
+ local TAG_CFRAME = 14
44
+ local TAG_BUFFER = 15
45
+ local TAG_UDIM = 16
46
+ local TAG_UDIM2 = 17
47
+ local TAG_NUMBERRANGE = 18
48
+ local TAG_RECT = 19
49
+ local TAG_VEC2INT16 = 20
50
+ local TAG_VEC3INT16 = 21
51
+ local TAG_REGION3 = 22
52
+ local TAG_REGION3INT16 = 23
53
+ local TAG_RAY = 24
54
+ local TAG_NUMBERSEQUENCE = 25
55
+ local TAG_COLORSEQUENCE = 26
56
+
57
+ local floor = math.floor
58
+
59
+ local READ_DISPATCH = table.freeze ({
60
+ [TAG_BOOL] = BoolCodec.bool,
61
+ [TAG_U8] = Number.u8,
62
+ [TAG_U16] = Number.u16,
63
+ [TAG_U32] = Number.u32,
64
+ [TAG_I8] = Number.i8,
65
+ [TAG_I16] = Number.i16,
66
+ [TAG_I32] = Number.i32,
67
+ [TAG_F32] = Number.f32,
68
+ [TAG_F64] = Number.f64,
69
+ [TAG_STRING] = String.string,
70
+ [TAG_VEC2] = Vector.vec2,
71
+ [TAG_VEC3] = Vector.vec3,
72
+ [TAG_COLOR3] = Color.color3,
73
+ [TAG_CFRAME] = CFrameCodec.cframe,
74
+ [TAG_BUFFER] = Buffer.buff,
75
+ [TAG_UDIM] = UDimCodec.udim,
76
+ [TAG_UDIM2] = UDimCodec.udim2,
77
+ [TAG_NUMBERRANGE] = NumberRangeCodec.numberRange,
78
+ [TAG_RECT] = RectCodec.rect,
79
+ [TAG_VEC2INT16] = IntVector.vec2int16,
80
+ [TAG_VEC3INT16] = IntVector.vec3int16,
81
+ [TAG_REGION3] = Region.region3,
82
+ [TAG_REGION3INT16] = Region.region3int16,
83
+ [TAG_RAY] = RayCodec.ray,
84
+ [TAG_NUMBERSEQUENCE] = Sequence.numberSequence,
85
+ [TAG_COLORSEQUENCE] = Sequence.colorSequence,
86
+ })
87
+
88
+ local TYPE_DISPATCH = table.freeze ({
89
+ boolean = { tag = TAG_BOOL, codec = BoolCodec.bool, size = 1, dw = nil },
90
+ string = { tag = TAG_STRING, codec = String.string, size = nil, dw = nil },
91
+ Vector2 = {
92
+ tag = TAG_VEC2,
93
+ codec = Vector.vec2,
94
+ size = 8,
95
+ dw = (Vector.vec2 :: any)._directWrite,
96
+ },
97
+ Vector3 = {
98
+ tag = TAG_VEC3,
99
+ codec = Vector.vec3,
100
+ size = 12,
101
+ dw = (Vector.vec3 :: any)._directWrite,
102
+ },
103
+ Color3 = {
104
+ tag = TAG_COLOR3,
105
+ codec = Color.color3,
106
+ size = 3,
107
+ dw = (Color.color3 :: any)._directWrite,
108
+ },
109
+ CFrame = {
110
+ tag = TAG_CFRAME,
111
+ codec = CFrameCodec.cframe,
112
+ size = 24,
113
+ dw = (CFrameCodec.cframe :: any)._directWrite,
114
+ },
115
+ buffer = { tag = TAG_BUFFER, codec = Buffer.buff, size = nil, dw = nil },
116
+ UDim = {
117
+ tag = TAG_UDIM,
118
+ codec = UDimCodec.udim,
119
+ size = 8,
120
+ dw = (UDimCodec.udim :: any)._directWrite,
121
+ },
122
+ UDim2 = {
123
+ tag = TAG_UDIM2,
124
+ codec = UDimCodec.udim2,
125
+ size = 16,
126
+ dw = (UDimCodec.udim2 :: any)._directWrite,
127
+ },
128
+ NumberRange = {
129
+ tag = TAG_NUMBERRANGE,
130
+ codec = NumberRangeCodec.numberRange,
131
+ size = 8,
132
+ dw = (NumberRangeCodec.numberRange :: any)._directWrite,
133
+ },
134
+ Rect = {
135
+ tag = TAG_RECT,
136
+ codec = RectCodec.rect,
137
+ size = 16,
138
+ dw = (RectCodec.rect :: any)._directWrite,
139
+ },
140
+ Vector2int16 = {
141
+ tag = TAG_VEC2INT16,
142
+ codec = IntVector.vec2int16,
143
+ size = 4,
144
+ dw = (IntVector.vec2int16 :: any)._directWrite,
145
+ },
146
+ Vector3int16 = {
147
+ tag = TAG_VEC3INT16,
148
+ codec = IntVector.vec3int16,
149
+ size = 6,
150
+ dw = (IntVector.vec3int16 :: any)._directWrite,
151
+ },
152
+ Region3 = {
153
+ tag = TAG_REGION3,
154
+ codec = Region.region3,
155
+ size = 24,
156
+ dw = (Region.region3 :: any)._directWrite,
157
+ },
158
+ Region3int16 = {
159
+ tag = TAG_REGION3INT16,
160
+ codec = Region.region3int16,
161
+ size = 12,
162
+ dw = (Region.region3int16 :: any)._directWrite,
163
+ },
164
+ Ray = {
165
+ tag = TAG_RAY,
166
+ codec = RayCodec.ray,
167
+ size = 24,
168
+ dw = (RayCodec.ray :: any)._directWrite,
169
+ },
170
+ NumberSequence = {
171
+ tag = TAG_NUMBERSEQUENCE,
172
+ codec = Sequence.numberSequence,
173
+ size = nil,
174
+ dw = nil,
175
+ },
176
+ ColorSequence = {
177
+ tag = TAG_COLORSEQUENCE,
178
+ codec = Sequence.colorSequence,
179
+ size = nil,
180
+ dw = nil,
181
+ },
182
+ })
183
+
184
+ -- Numeric write dispatch: one lookup for both codec and payload size.
185
+ local NUM_SIZES = {} :: { [number]: number }
186
+ local NUM_DWRITES = {} :: { [number]: any }
187
+
188
+ for _, pair in
189
+ {
190
+ { TAG_U8, Number.u8, 1 },
191
+ { TAG_U16, Number.u16, 2 },
192
+ { TAG_U32, Number.u32, 4 },
193
+ { TAG_I8, Number.i8, 1 },
194
+ { TAG_I16, Number.i16, 2 },
195
+ { TAG_I32, Number.i32, 4 },
196
+ { TAG_F32, Number.f32, 4 },
197
+ { TAG_F64, Number.f64, 8 },
198
+ }
199
+ do
200
+ NUM_SIZES[pair[1]] = pair[3]
201
+ NUM_DWRITES[pair[1]] = (pair[2] :: any)._directWrite
202
+ end
203
+
204
+ table.freeze (NUM_SIZES)
205
+ table.freeze (NUM_DWRITES)
206
+
207
+ -- Private ----------------------------------------------------------------
208
+
209
+ local _f32Temp = buffer.create (4)
210
+
211
+ local function selectNumberTag (value: number): number
212
+ if value ~= value then
213
+ return TAG_F64
214
+ end
215
+
216
+ if value == floor (value) then
217
+ if value >= 0 then
218
+ if value <= 255 then
219
+ return TAG_U8
220
+ end
221
+ if value <= 65535 then
222
+ return TAG_U16
223
+ end
224
+ if value <= 4294967295 then
225
+ return TAG_U32
226
+ end
227
+ else
228
+ if value >= -128 and value <= 127 then
229
+ return TAG_I8
230
+ end
231
+ if value >= -32768 and value <= 32767 then
232
+ return TAG_I16
233
+ end
234
+ if value >= -2147483648 and value <= 2147483647 then
235
+ return TAG_I32
236
+ end
237
+ end
238
+ return TAG_F64
239
+ end
240
+
241
+ buffer.writef32 (_f32Temp, 0, value)
242
+ if buffer.readf32 (_f32Temp, 0) == value then
243
+ return TAG_F32
244
+ end
245
+
246
+ return TAG_F64
247
+ end
248
+
249
+ -- Public -----------------------------------------------------------------
250
+
251
+ local auto = table.freeze ({
252
+ write = function (ch: ChannelState, value: any): ()
253
+ if value == nil then
254
+ local c = ch.cursor
255
+ if c + 1 > ch.size then
256
+ alloc (ch, 1)
257
+ end
258
+ buffer.writeu8 (ch.buff, c, TAG_NIL)
259
+ ch.cursor = c + 1
260
+ return
261
+ end
262
+
263
+ local t = type (value)
264
+
265
+ if t == "number" then
266
+ local tag = selectNumberTag (value)
267
+ local payloadSize = NUM_SIZES[tag]
268
+ local totalSize = 1 + payloadSize
269
+
270
+ local c = ch.cursor
271
+ if c + totalSize > ch.size then
272
+ alloc (ch, totalSize)
273
+ end
274
+ local b = ch.buff
275
+ buffer.writeu8 (b, c, tag)
276
+ NUM_DWRITES[tag] (b, c + 1, value)
277
+ ch.cursor = c + totalSize
278
+ return
279
+ end
280
+
281
+ if t == "string" then
282
+ local len = #value
283
+ local total = 6 + len
284
+ local c = ch.cursor
285
+ if c + total > ch.size then
286
+ alloc (ch, total)
287
+ end
288
+ buffer.writeu8 (ch.buff, c, TAG_STRING)
289
+ ch.cursor = c + 1
290
+ Varint.write (ch, len)
291
+ local c2 = ch.cursor
292
+ buffer.writestring (ch.buff, c2, value)
293
+ ch.cursor = c2 + len
294
+ return
295
+ end
296
+
297
+ if t == "boolean" then
298
+ local c = ch.cursor
299
+ if c + 2 > ch.size then
300
+ alloc (ch, 2)
301
+ end
302
+ local b = ch.buff
303
+ buffer.writeu8 (b, c, TAG_BOOL)
304
+ buffer.writeu8 (b, c + 1, if value then 1 else 0)
305
+ ch.cursor = c + 2
306
+ return
307
+ end
308
+
309
+ local valueType = typeof (value)
310
+ local entry = TYPE_DISPATCH[valueType]
311
+ if not entry then
312
+ error (`[Lync] Auto unsupported type: {valueType}`)
313
+ end
314
+
315
+ local dw = entry.dw
316
+ local size = entry.size
317
+ if dw and size then
318
+ local totalSize = 1 + size
319
+ local c = ch.cursor
320
+ if c + totalSize > ch.size then
321
+ alloc (ch, totalSize)
322
+ end
323
+ local b = ch.buff
324
+ buffer.writeu8 (b, c, entry.tag)
325
+ dw (b, c + 1, value)
326
+ ch.cursor = c + totalSize
327
+ else
328
+ local c = ch.cursor
329
+ if c + 1 > ch.size then
330
+ alloc (ch, 1)
331
+ end
332
+ buffer.writeu8 (ch.buff, c, entry.tag)
333
+ ch.cursor = c + 1
334
+ entry.codec.write (ch, value)
335
+ end
336
+ end,
337
+ read = function (src: buffer, pos: number, refs: { Instance }?): (any, number)
338
+ local tag = buffer.readu8 (src, pos)
339
+ if tag == TAG_NIL then
340
+ return nil, 1
341
+ end
342
+
343
+ local codec = READ_DISPATCH[tag]
344
+ if not codec then
345
+ error (`[Lync] Auto unknown tag: {tag}`)
346
+ end
347
+
348
+ local value, n = codec.read (src, pos + 1, refs)
349
+ return value, 1 + n
350
+ end,
351
+ })
352
+
353
+ return table.freeze ({ auto = auto })
@@ -0,0 +1,191 @@
1
+ --!strict
2
+ --!native
3
+ -- Pack booleans, unsigned and signed integers at the bit level.
4
+
5
+ local Channel = require (script.Parent.Parent.Parent.internal.Channel)
6
+ local alloc = Channel.alloc
7
+ local Types = require (script.Parent.Parent.Parent.Types)
8
+
9
+ type ChannelState = Types.ChannelState
10
+ type Codec<T> = Types.Codec<T>
11
+
12
+ -- Constants --------------------------------------------------------------
13
+
14
+ local band = bit32.band
15
+ local bor = bit32.bor
16
+ local lshift = bit32.lshift
17
+ local rshift = bit32.rshift
18
+
19
+ -- Private ----------------------------------------------------------------
20
+
21
+ type FieldSpec = {
22
+ type: "bool" | "uint" | "int",
23
+ width: number?,
24
+ }
25
+
26
+ local function selectIO (bytes: number): ((b: buffer, offset: number, v: number) -> (), (
27
+ b: buffer,
28
+ offset: number
29
+ ) -> number)
30
+ if bytes == 1 then
31
+ return buffer.writeu8, buffer.readu8
32
+ end
33
+ if bytes == 2 then
34
+ return buffer.writeu16, buffer.readu16
35
+ end
36
+ return buffer.writeu32, buffer.readu32
37
+ end
38
+
39
+ -- Public -----------------------------------------------------------------
40
+
41
+ local Bitfield = {}
42
+
43
+ function Bitfield.define (schema: { [string]: FieldSpec }): Codec<any>
44
+ local sortedKeys = {} :: { string }
45
+
46
+ for key in schema do
47
+ table.insert (sortedKeys, key)
48
+ end
49
+
50
+ table.sort (sortedKeys)
51
+
52
+ local count = #sortedKeys
53
+ if count == 0 then
54
+ error ("[Lync] Bitfield requires at least one field")
55
+ end
56
+
57
+ local keys = table.create (count) :: { string }
58
+ local isBool = table.create (count) :: { boolean }
59
+ local offsets = table.create (count) :: { number }
60
+ local masks = table.create (count) :: { number }
61
+
62
+ local hasSigned = false
63
+ local signBits = nil :: { number }?
64
+ local signExtends = nil :: { number }?
65
+
66
+ local totalBits = 0
67
+ for i = 1, count do
68
+ local key = sortedKeys[i]
69
+ local spec = schema[key]
70
+ local specIsBool = spec.type == "bool"
71
+ local specIsSigned = spec.type == "int"
72
+ local width: number
73
+
74
+ if specIsBool then
75
+ width = 1
76
+ else
77
+ if not spec.width then
78
+ error (`[Lync] Bitfield field requires a width: "{key}"`)
79
+ end
80
+ width = spec.width
81
+ if width < 1 or width > 32 then
82
+ error (`[Lync] Bitfield width must be 1-32: "{key}" got {width}`)
83
+ end
84
+ end
85
+
86
+ if specIsSigned then
87
+ hasSigned = true
88
+ end
89
+
90
+ keys[i] = key
91
+ isBool[i] = specIsBool
92
+ offsets[i] = totalBits
93
+ masks[i] = lshift (1, width) - 1
94
+ totalBits += width
95
+ end
96
+
97
+ if totalBits > 32 then
98
+ error (`[Lync] Bitfield exceeds 32 bits: {totalBits}`)
99
+ end
100
+
101
+ -- Only build sign tables when at least one field is signed
102
+ if hasSigned then
103
+ signBits = table.create (count) :: { number }
104
+ signExtends = table.create (count) :: { number }
105
+
106
+ for i = 1, count do
107
+ local spec = schema[sortedKeys[i]]
108
+ if spec.type == "int" then
109
+ (signBits :: { number })[i] = lshift (1, (spec.width :: number) - 1);
110
+ (signExtends :: { number })[i] = lshift (1, spec.width :: number)
111
+ else
112
+ (signBits :: { number })[i] = 0
113
+ (signExtends :: { number })[i] = 0
114
+ end
115
+ end
116
+
117
+ table.freeze (signBits :: { number })
118
+ table.freeze (signExtends :: { number })
119
+ end
120
+
121
+ table.freeze (keys)
122
+ table.freeze (isBool)
123
+ table.freeze (offsets)
124
+ table.freeze (masks)
125
+
126
+ local bytes = if totalBits <= 8 then 1 elseif totalBits <= 16 then 2 else 4
127
+ local wfn, rfn = selectIO (bytes)
128
+
129
+ -- Shared pack logic
130
+ local function packValue (value: any): number
131
+ local packed = 0
132
+ for i = 1, count do
133
+ local bits = if isBool[i]
134
+ then (if value[keys[i]] then 1 else 0)
135
+ else band (value[keys[i]], masks[i])
136
+ packed = bor (packed, lshift (bits, offsets[i]))
137
+ end
138
+ return packed
139
+ end
140
+
141
+ -- Shared unpack logic
142
+ local function unpackValue (packed: number): any
143
+ local result = {}
144
+
145
+ if hasSigned then
146
+ local sb = signBits :: { number }
147
+ local se = signExtends :: { number }
148
+
149
+ for i = 1, count do
150
+ local raw = band (rshift (packed, offsets[i]), masks[i])
151
+ if isBool[i] then
152
+ result[keys[i]] = raw ~= 0
153
+ elseif sb[i] > 0 and raw >= sb[i] then
154
+ result[keys[i]] = raw - se[i]
155
+ else
156
+ result[keys[i]] = raw
157
+ end
158
+ end
159
+ else
160
+ for i = 1, count do
161
+ local raw = band (rshift (packed, offsets[i]), masks[i])
162
+ result[keys[i]] = if isBool[i] then raw ~= 0 else raw
163
+ end
164
+ end
165
+
166
+ return result
167
+ end
168
+
169
+ return table.freeze ({
170
+ _size = bytes,
171
+ _directWrite = function (b: buffer, offset: number, value: any): ()
172
+ wfn (b, offset, packValue (value))
173
+ end,
174
+ _directRead = function (b: buffer, offset: number): any
175
+ return unpackValue (rfn (b, offset))
176
+ end,
177
+ write = function (ch: ChannelState, value: any): ()
178
+ local c = ch.cursor
179
+ if c + bytes > ch.size then
180
+ alloc (ch, bytes)
181
+ end
182
+ wfn (ch.buff, c, packValue (value))
183
+ ch.cursor = c + bytes
184
+ end,
185
+ read = function (src: buffer, pos: number, _refs: { Instance }?): (any, number)
186
+ return unpackValue (rfn (src, pos)), bytes
187
+ end,
188
+ })
189
+ end
190
+
191
+ return table.freeze (Bitfield)
@@ -0,0 +1,27 @@
1
+ --!strict
2
+ --!native
3
+ -- User-defined fixed-size codec factory. Public API wrapper over Base.define.
4
+
5
+ local Base = require (script.Parent.Parent.Base)
6
+
7
+ local floor = math.floor
8
+
9
+ local Custom = {}
10
+
11
+ function Custom.define (
12
+ size: number,
13
+ writeFn: (b: buffer, offset: number, value: any) -> (),
14
+ readFn: (b: buffer, offset: number) -> any
15
+ ): any
16
+ if size < 0 or size ~= floor (size) then
17
+ error (`[Lync] Custom codec size must be a non-negative integer, got {size}`)
18
+ end
19
+
20
+ if size == 0 then
21
+ return Base.zero ()
22
+ end
23
+
24
+ return Base.define (size, writeFn, readFn)
25
+ end
26
+
27
+ return table.freeze (Custom)
@@ -0,0 +1,80 @@
1
+ --!strict
2
+ --!native
3
+ -- String enum codec. Maps values to u8 indices at definition time.
4
+
5
+ local Channel = require (script.Parent.Parent.Parent.internal.Channel)
6
+ local alloc = Channel.alloc
7
+ local Types = require (script.Parent.Parent.Parent.Types)
8
+
9
+ type ChannelState = Types.ChannelState
10
+ type Codec<T> = Types.Codec<T>
11
+
12
+ -- Public -----------------------------------------------------------------
13
+
14
+ local EnumCodec = {}
15
+
16
+ function EnumCodec.define (...: string): Codec<string>
17
+ local values = { ... }
18
+ local count = #values
19
+ if count == 0 then
20
+ error ("[Lync] Enum requires at least one value")
21
+ end
22
+ if count > 256 then
23
+ error (`[Lync] Enum exceeds 256 values: {count}`)
24
+ end
25
+
26
+ local toIndex = {} :: { [string]: number }
27
+ local toValue = table.create (count) :: { string }
28
+
29
+ for i, value in values do
30
+ if toIndex[value] ~= nil then
31
+ error (`[Lync] Duplicate enum value: \"{value}\"`)
32
+ end
33
+ toIndex[value] = i - 1
34
+ toValue[i] = value
35
+ end
36
+
37
+ table.freeze (toIndex)
38
+ table.freeze (toValue)
39
+
40
+ return table.freeze ({
41
+ _size = 1,
42
+ _directWrite = function (b: buffer, offset: number, value: string): ()
43
+ local idx = toIndex[value]
44
+ if idx == nil then
45
+ error (`[Lync] Invalid enum value: \"{value}\"`)
46
+ end
47
+ buffer.writeu8 (b, offset, idx)
48
+ end,
49
+ _directRead = function (b: buffer, offset: number): string
50
+ local idx = buffer.readu8 (b, offset)
51
+ local value = toValue[idx + 1]
52
+ if value == nil then
53
+ error (`[Lync] Invalid enum index: {idx}`)
54
+ end
55
+ return value
56
+ end,
57
+ write = function (ch: ChannelState, value: string): ()
58
+ local idx = toIndex[value]
59
+ if idx == nil then
60
+ error (`[Lync] Invalid enum value: \"{value}\"`)
61
+ end
62
+ local c = ch.cursor
63
+ if c + 1 > ch.size then
64
+ alloc (ch, 1)
65
+ end
66
+ buffer.writeu8 (ch.buff, c, idx)
67
+ ch.cursor = c + 1
68
+ end,
69
+ read = function (src: buffer, pos: number, _refs: { Instance }?): (string, number)
70
+ local idx = buffer.readu8 (src, pos)
71
+ local value = toValue[idx + 1]
72
+ if value == nil then
73
+ error (`[Lync] Invalid enum index: {idx}`)
74
+ end
75
+ return value, 1
76
+ end,
77
+ })
78
+ end
79
+
80
+ return table.freeze (EnumCodec)