@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
@@ -3,29 +3,29 @@
3
3
  -- User-defined fixed-size codec.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Types = require(script.Parent.Parent.Parent.Types)
6
7
 
7
- -- Private -------------------------------------------------------------
8
+ -- Private ----------------------------------------------------------------
8
9
 
9
10
  local floor = math.floor
10
11
 
11
- -- Public --------------------------------------------------------------
12
+ -- Public -----------------------------------------------------------------
12
13
 
13
14
  local Custom = {}
14
15
 
15
- function Custom.define(
16
+ function Custom.define<T>(
16
17
  size: number,
17
- writeFn: (b: buffer, offset: number, value: any) -> (),
18
- readFn: (b: buffer, offset: number) -> any
19
- ): any
18
+ writeFn: (b: buffer, offset: number, value: T) -> (),
19
+ readFn: (b: buffer, offset: number) -> T,
20
+ typeCheck: string?
21
+ ): Types.InternalCodec<T>
20
22
  if type(size) ~= "number" or size < 0 or floor(size) ~= size then
21
23
  error("[Lync] Lync.custom: size must be a non-negative integer")
22
24
  end
23
-
24
25
  if size == 0 then
25
- return Base.zero()
26
+ return Base.constant(nil :: any) :: Types.InternalCodec<T>
26
27
  end
27
-
28
- return Base.define(size, writeFn, readFn)
28
+ return Base.define(size, writeFn, readFn, if typeCheck then { _typeCheck = typeCheck } else nil)
29
29
  end
30
30
 
31
31
  return table.freeze(Custom)
@@ -1,14 +1,15 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- String enum codec. Maps values to u8 indices at define time.
3
+ -- String-keyed enum codec. Maps values to u8 indices at definition time.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Types = require(script.Parent.Parent.Parent.Types)
6
7
 
7
- -- Public --------------------------------------------------------------
8
+ -- Public -----------------------------------------------------------------
8
9
 
9
10
  local EnumCodec = {}
10
11
 
11
- function EnumCodec.define(...: string): any
12
+ function EnumCodec.define(...: string): Types.InternalCodec<string>
12
13
  local values = { ... }
13
14
  local count = #values
14
15
  if count == 0 then
@@ -18,10 +19,8 @@ function EnumCodec.define(...: string): any
18
19
  error("[Lync] Lync.enum: exceeds 256 variants")
19
20
  end
20
21
 
21
- -- Build lookup tables
22
22
  local toIndex: { [string]: number } = {}
23
23
  local toValue = table.create(count)
24
-
25
24
  for i = 1, count do
26
25
  local val = values[i]
27
26
  if toIndex[val] then
@@ -30,24 +29,22 @@ function EnumCodec.define(...: string): any
30
29
  toIndex[val] = i - 1
31
30
  toValue[i] = val
32
31
  end
33
-
34
32
  table.freeze(toIndex)
35
33
  table.freeze(toValue)
36
34
 
37
35
  return Base.define(1, function(b: buffer, off: number, value: string): ()
38
36
  local idx = toIndex[value]
39
37
  if idx == nil then
40
- error(`[Lync] Enum: invalid value "{value}"`)
38
+ error(`[Lync] Enum.write: invalid value "{value}"`)
41
39
  end
42
40
  buffer.writeu8(b, off, idx)
43
41
  end, function(b: buffer, off: number): string
44
- local idx = buffer.readu8(b, off)
45
- local val = toValue[idx + 1]
42
+ local val = toValue[buffer.readu8(b, off) + 1]
46
43
  if val == nil then
47
- error(`[Lync] Enum: invalid index {idx}`)
44
+ error(`[Lync] Enum.read: invalid index`)
48
45
  end
49
46
  return val
50
- end, { _typeCheck = "string" })
47
+ end, { _typeCheck = "string", _isEnum = true, _enumValues = toIndex })
51
48
  end
52
49
 
53
50
  return table.freeze(EnumCodec)
@@ -1,22 +1,27 @@
1
1
  --!strict
2
2
  --!native
3
- -- Fixed-point quantization codec. Maps float ranges to integer ranges.
3
+ -- Fixed-point quantization codec. Maps a float range to u8/u16/u32.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Types = require(script.Parent.Parent.Parent.Types)
6
7
 
7
- -- Private -------------------------------------------------------------
8
+ -- Private ----------------------------------------------------------------
8
9
 
9
10
  local ceil = math.ceil
10
11
  local clamp = math.clamp
11
12
  local round = math.round
12
- local max = math.max
13
- local min = math.min
13
+ local maxN = math.max
14
+ local minN = math.min
14
15
 
15
- -- Public --------------------------------------------------------------
16
+ -- Public -----------------------------------------------------------------
16
17
 
17
18
  local Float = {}
18
19
 
19
- function Float.float(rangeMin: number, rangeMax: number, precision: number): any
20
+ function Float.float(
21
+ rangeMin: number,
22
+ rangeMax: number,
23
+ precision: number
24
+ ): Types.InternalCodec<number>
20
25
  if rangeMin > rangeMax then
21
26
  error(`[Lync] Lync.float: min ({rangeMin}) must be <= max ({rangeMax})`)
22
27
  end
@@ -25,30 +30,21 @@ function Float.float(rangeMin: number, rangeMax: number, precision: number): any
25
30
  end
26
31
 
27
32
  local delta = rangeMax - rangeMin
28
-
29
- -- Constant value: zero bytes
30
33
  if delta == 0 then
31
- return Base.zero()
34
+ return Base.constant(rangeMin)
32
35
  end
33
36
 
34
- local maxInt = min(0xFFFFFFFF, max(1, ceil(delta / precision)))
37
+ local maxInt = minN(0xFFFFFFFF, maxN(1, ceil(delta / precision)))
35
38
 
36
39
  local compBytes: number
37
40
  local wfn: (buffer, number, number) -> ()
38
41
  local rfn: (buffer, number) -> number
39
-
40
42
  if maxInt <= 0xFF then
41
- compBytes = 1
42
- wfn = buffer.writeu8
43
- rfn = buffer.readu8
43
+ compBytes, wfn, rfn = 1, buffer.writeu8, buffer.readu8
44
44
  elseif maxInt <= 0xFFFF then
45
- compBytes = 2
46
- wfn = buffer.writeu16
47
- rfn = buffer.readu16
45
+ compBytes, wfn, rfn = 2, buffer.writeu16, buffer.readu16
48
46
  else
49
- compBytes = 4
50
- wfn = buffer.writeu32
51
- rfn = buffer.readu32
47
+ compBytes, wfn, rfn = 4, buffer.writeu32, buffer.readu32
52
48
  end
53
49
 
54
50
  local scale = maxInt / delta
@@ -4,10 +4,10 @@
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
- -- Public --------------------------------------------------------------
7
+ -- Public -----------------------------------------------------------------
8
8
 
9
9
  local Nothing = {}
10
10
 
11
- Nothing.nothing = Base.zero()
11
+ Nothing.nothing = Base.constant(nil :: nil)
12
12
 
13
13
  return table.freeze(Nothing)
@@ -2,47 +2,37 @@
2
2
  --!optimize 2
3
3
  -- Bypasses buffer serialization. Uses Roblox remote sidecar refs.
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 writeu16 = buffer.writeu16
10
+ local pushRef = Channel.pushRef
12
11
  local readu16 = buffer.readu16
13
12
 
14
- -- Public --------------------------------------------------------------
13
+ local function writeUnknown(ch: Types.ChannelState, value: any): ()
14
+ pushRef(ch, value)
15
+ end
16
+
17
+ local function readUnknown(src: buffer, pos: number, refs: { Instance }?): (any, number)
18
+ if not refs then
19
+ error("[Lync] Unknown.read: refs array is required")
20
+ end
21
+ local idx = readu16(src, pos)
22
+ if idx < 1 or idx > #refs then
23
+ error(`[Lync] Unknown.read: ref index {idx} out of bounds`)
24
+ end
25
+ return refs[idx], 2
26
+ end
27
+
28
+ -- Public -----------------------------------------------------------------
15
29
 
16
30
  local Unknown = {}
17
31
 
18
32
  Unknown.unknown = table.freeze({
19
33
  _hasUnknown = true,
20
-
21
- write = function(ch: Types.ChannelState, value: any): ()
22
- local idx = ch.refCount + 1
23
- if idx > 0xFFFF then
24
- error("[Lync] Unknown: ref overflow (>65535)")
25
- end
26
-
27
- ch.refs[idx] = value :: any
28
- ch.refCount = idx
29
-
30
- local cursor = ch.cursor
31
- if cursor + 2 > ch.size then
32
- alloc(ch, 2)
33
- end
34
- writeu16(ch.buff, cursor, idx)
35
- ch.cursor = cursor + 2
36
- end,
37
-
38
- read = function(src: buffer, pos: number, refs: { Instance }?): (any, number)
39
- if not refs then
40
- error("[Lync] Unknown: read requires refs array")
41
- end
42
-
43
- local idx = readu16(src, pos)
44
- return (refs :: any)[idx], 2
45
- end,
46
- } :: any)
34
+ write = writeUnknown,
35
+ read = readUnknown,
36
+ })
47
37
 
48
38
  return table.freeze(Unknown)
@@ -1,18 +1,22 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Boolean codec with bitpacking flag for struct and array codecs.
3
+ -- Single-byte boolean codec. _isBool flag enables struct/array bit packing.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
- -- Public --------------------------------------------------------------
7
+ -- Public -----------------------------------------------------------------
8
8
 
9
9
  local Bool = {}
10
10
 
11
- Bool.bool = Base.define(1, function(b: buffer, off: number, value: boolean): ()
11
+ local function writeBool(b: buffer, off: number, value: boolean): ()
12
12
  buffer.writeu8(b, off, if value then 1 else 0)
13
- end, function(b: buffer, off: number): boolean
13
+ end
14
+
15
+ local function readBool(b: buffer, off: number): boolean
14
16
  return buffer.readu8(b, off) ~= 0
15
- end, {
17
+ end
18
+
19
+ Bool.bool = Base.define(1, writeBool, readBool, {
16
20
  _isBool = true,
17
21
  _typeCheck = "boolean",
18
22
  })
@@ -1,10 +1,16 @@
1
1
  --!strict
2
2
  --!native
3
- -- Half-precision float codec. 2 bytes, ±65504, ~3 decimal digits.
3
+ -- IEEE 754 binary16 codec. 2 bytes, +/- 65504, ~3 decimal digits of precision.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
- -- Private -------------------------------------------------------------
7
+ -- Constants --------------------------------------------------------------
8
+
9
+ local MAX_FINITE = 65504
10
+ local NAN_BITS = 0x7E00
11
+ local INF_BITS = 0x7C00
12
+
13
+ -- Private ----------------------------------------------------------------
8
14
 
9
15
  local band = bit32.band
10
16
  local bor = bit32.bor
@@ -16,37 +22,29 @@ local round = math.round
16
22
  local huge = math.huge
17
23
 
18
24
  local function encode(value: number): number
19
- -- NaN
20
25
  if value ~= value then
21
- return 0x7E00
26
+ return NAN_BITS
22
27
  end
23
-
24
- -- Zero (both +0 and -0 map to positive zero)
25
28
  if value == 0 then
26
29
  return 0
27
30
  end
28
31
 
29
- -- Sign extraction
30
32
  local sign = 0
31
33
  if value < 0 then
32
34
  sign = 0x8000
33
35
  value = -value
34
36
  end
35
37
 
36
- -- Overflow to infinity
37
- if value > 65504 then
38
- return bor(sign, 0x7C00)
38
+ if value > MAX_FINITE then
39
+ return bor(sign, INF_BITS)
39
40
  end
40
41
 
41
- -- Decompose via frexp: value = mantissa * 2^exponent, mantissa in [0.5, 1)
42
42
  local mantissa, exponent = frexp(value)
43
43
  local biasedExp = exponent + 14
44
44
 
45
45
  if biasedExp <= 0 then
46
- -- Subnormal: frac = round(mantissa * 2^(biasedExp + 10))
47
46
  local frac = round(ldexp(mantissa, biasedExp + 10))
48
47
  if frac >= 1024 then
49
- -- Rounds to minimum normal
50
48
  return bor(sign, 0x0400)
51
49
  end
52
50
  if frac <= 0 then
@@ -55,18 +53,14 @@ local function encode(value: number): number
55
53
  return bor(sign, frac)
56
54
  end
57
55
 
58
- -- Normal: frac = round((mantissa * 2 - 1) * 1024)
59
56
  local frac = round((mantissa * 2 - 1) * 1024)
60
-
61
- -- Carry: mantissa rounding overflows into exponent
62
57
  if frac >= 1024 then
63
58
  frac = 0
64
59
  biasedExp += 1
65
60
  end
66
61
 
67
- -- Overflow after carry
68
62
  if biasedExp >= 31 then
69
- return bor(sign, 0x7C00)
63
+ return bor(sign, INF_BITS)
70
64
  end
71
65
 
72
66
  return bor(sign, lshift(biasedExp, 10), frac)
@@ -78,22 +72,18 @@ local function decode(raw: number): number
78
72
  local frac = band(raw, 0x03FF)
79
73
 
80
74
  local value: number
81
-
82
75
  if exp == 0 then
83
- -- Zero or subnormal
84
76
  value = if frac == 0 then 0 else ldexp(frac, -24)
85
77
  elseif exp == 31 then
86
- -- Infinity or NaN
87
78
  value = if frac == 0 then huge else 0 / 0
88
79
  else
89
- -- Normal: (frac + 1024) * 2^(exp - 25)
90
80
  value = ldexp(frac + 1024, exp - 25)
91
81
  end
92
82
 
93
83
  return if sign ~= 0 then -value else value
94
84
  end
95
85
 
96
- -- Public --------------------------------------------------------------
86
+ -- Public -----------------------------------------------------------------
97
87
 
98
88
  local Float16 = {}
99
89
 
@@ -1,18 +1,23 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Smart integer codec. Auto-selects u8/u16/u32/i8/i16/i32 from range.
3
+ -- Range-typed integer codec. Picks the narrowest u8/u16/u32/i8/i16/i32 wire form.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
+ local Signed = require(script.Parent.Signed)
7
+ local Types = require(script.Parent.Parent.Parent.Types)
6
8
 
7
- -- Private -------------------------------------------------------------
9
+ -- Private ----------------------------------------------------------------
8
10
 
9
11
  local floor = math.floor
12
+ local writeI8 = Signed.writeI8
13
+ local writeI16 = Signed.writeI16
14
+ local writeI32 = Signed.writeI32
10
15
 
11
- -- Public --------------------------------------------------------------
16
+ -- Public -----------------------------------------------------------------
12
17
 
13
18
  local Int = {}
14
19
 
15
- function Int.int(min: number, max: number): any
20
+ function Int.int(min: number, max: number): Types.InternalCodec<number>
16
21
  if min > max then
17
22
  error(`[Lync] Lync.int: min ({min}) must be <= max ({max})`)
18
23
  end
@@ -28,37 +33,24 @@ function Int.int(min: number, max: number): any
28
33
  }
29
34
 
30
35
  if min >= 0 then
31
- -- Unsigned path
32
36
  if max <= 0xFF then
33
37
  return Base.define(1, buffer.writeu8, buffer.readu8, meta)
34
- elseif max <= 0xFFFF then
38
+ end
39
+ if max <= 0xFFFF then
35
40
  return Base.define(2, buffer.writeu16, buffer.readu16, meta)
36
- elseif max <= 0xFFFFFFFF then
41
+ end
42
+ if max <= 0xFFFFFFFF then
37
43
  return Base.define(4, buffer.writeu32, buffer.readu32, meta)
38
44
  end
39
45
  else
40
- -- Signed path: use unsigned write/read, interpret as signed
41
46
  if min >= -0x80 and max <= 0x7F then
42
- return Base.define(1, function(b: buffer, off: number, v: number): ()
43
- buffer.writeu8(b, off, if v < 0 then v + 0x100 else v)
44
- end, function(b: buffer, off: number): number
45
- local raw = buffer.readu8(b, off)
46
- return if raw >= 0x80 then raw - 0x100 else raw
47
- end, meta)
48
- elseif min >= -0x8000 and max <= 0x7FFF then
49
- return Base.define(2, function(b: buffer, off: number, v: number): ()
50
- buffer.writeu16(b, off, if v < 0 then v + 0x10000 else v)
51
- end, function(b: buffer, off: number): number
52
- local raw = buffer.readu16(b, off)
53
- return if raw >= 0x8000 then raw - 0x10000 else raw
54
- end, meta)
55
- elseif min >= -0x80000000 and max <= 0x7FFFFFFF then
56
- return Base.define(4, function(b: buffer, off: number, v: number): ()
57
- buffer.writeu32(b, off, if v < 0 then v + 0x100000000 else v)
58
- end, function(b: buffer, off: number): number
59
- local raw = buffer.readu32(b, off)
60
- return if raw >= 0x80000000 then raw - 0x100000000 else raw
61
- end, meta)
47
+ return Base.define(1, writeI8, buffer.readi8, meta)
48
+ end
49
+ if min >= -0x8000 and max <= 0x7FFF then
50
+ return Base.define(2, writeI16, buffer.readi16, meta)
51
+ end
52
+ if min >= -0x80000000 and max <= 0x7FFFFFFF then
53
+ return Base.define(4, writeI32, buffer.readi32, meta)
62
54
  end
63
55
  end
64
56
 
@@ -4,23 +4,19 @@
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
- -- Constants -----------------------------------------------------------
7
+ -- Constants --------------------------------------------------------------
8
8
 
9
+ -- Shared scratch for Auto codec's f32 round-trip test. Roblox is
10
+ -- single-threaded so a module-level scratch cannot be aliased.
9
11
  local F32_SCRATCH = buffer.create(4)
10
12
 
11
- -- Public --------------------------------------------------------------
13
+ -- Public -----------------------------------------------------------------
12
14
 
13
15
  local Number = {}
14
16
 
15
- Number.f32 = Base.define(4, buffer.writef32, buffer.readf32, {
16
- _typeCheck = "number",
17
- })
17
+ Number.f32 = Base.define(4, buffer.writef32, buffer.readf32, { _typeCheck = "number" })
18
+ Number.f64 = Base.define(8, buffer.writef64, buffer.readf64, { _typeCheck = "number" })
18
19
 
19
- Number.f64 = Base.define(8, buffer.writef64, buffer.readf64, {
20
- _typeCheck = "number",
21
- })
22
-
23
- -- Shared scratch buffer for Auto codec f32 round-trip test
24
20
  Number._f32Scratch = F32_SCRATCH
25
21
 
26
22
  return table.freeze(Number)
@@ -0,0 +1,32 @@
1
+ --!strict
2
+ --!native
3
+ -- Signed integer buffer helpers. Reads use FASTCALL2 readi*; writes
4
+ -- emulate via writeu* + two's complement (writei* is not FASTCALL).
5
+
6
+ -- Private ----------------------------------------------------------------
7
+
8
+ local writeu8 = buffer.writeu8
9
+ local writeu16 = buffer.writeu16
10
+ local writeu32 = buffer.writeu32
11
+
12
+ -- Public -----------------------------------------------------------------
13
+
14
+ local Signed = {}
15
+
16
+ function Signed.writeI8(b: buffer, off: number, value: number): ()
17
+ writeu8(b, off, if value < 0 then value + 0x100 else value)
18
+ end
19
+
20
+ function Signed.writeI16(b: buffer, off: number, value: number): ()
21
+ writeu16(b, off, if value < 0 then value + 0x10000 else value)
22
+ end
23
+
24
+ function Signed.writeI32(b: buffer, off: number, value: number): ()
25
+ writeu32(b, off, if value < 0 then value + 0x100000000 else value)
26
+ end
27
+
28
+ Signed.readI8 = buffer.readi8
29
+ Signed.readI16 = buffer.readi16
30
+ Signed.readI32 = buffer.readi32
31
+
32
+ return table.freeze(Signed)
@@ -1,15 +1,26 @@
1
1
  --!strict
2
2
  --!native
3
- -- Dense prefix-varint encoding with single-branch decode.
3
+ -- Dense prefix varint. Single-branch decode, range up to 2^32 - 1.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
  local Types = require(script.Parent.Parent.Parent.Types)
7
7
 
8
- -- Constants -----------------------------------------------------------
8
+ -- Constants --------------------------------------------------------------
9
9
 
10
+ --[[
11
+ Wire layout (first byte determines length):
12
+ 0x00..0xBF 1 byte value = b0 [0..191]
13
+ 0xC0..0xDF 2 bytes value = (b0 & 0x1F)<<8 | b1 + 192 [192..8383]
14
+ 0xE0..0xEF 3 bytes value = (b0 & 0x0F)<<16 | u16 + 8384 [8384..1056959]
15
+ 0xF0..0xFF 5 bytes value = u32 [..0xFFFFFFFF]
16
+ ]]
10
17
  local INLINE_MAX = 0xBF
18
+ local TWO_MAX = 8383
19
+ local THREE_MAX = 1056959
20
+ local TWO_BIAS = 192
21
+ local THREE_BIAS = 8384
11
22
 
12
- -- Private -------------------------------------------------------------
23
+ -- Private ----------------------------------------------------------------
13
24
 
14
25
  local alloc = Base.alloc
15
26
  local band = bit32.band
@@ -22,7 +33,7 @@ local readu8 = buffer.readu8
22
33
  local readu16 = buffer.readu16
23
34
  local readu32 = buffer.readu32
24
35
 
25
- -- Public --------------------------------------------------------------
36
+ -- Public -----------------------------------------------------------------
26
37
 
27
38
  local Varint = {}
28
39
 
@@ -30,61 +41,68 @@ Varint.INLINE_MAX = INLINE_MAX
30
41
 
31
42
  function Varint.write(ch: Types.ChannelState, value: number): ()
32
43
  if value < 0 or value > 0xFFFFFFFF or value % 1 ~= 0 then
33
- error(`[Lync] Varint: value must be an integer in [0, 4294967295], got {value}`)
44
+ error(`[Lync] Varint.write: value must be an integer in [0, 4294967295], got {value}`)
34
45
  end
35
46
 
36
47
  local cursor = ch.cursor
37
- local b = ch.buff
38
48
 
39
49
  if value <= INLINE_MAX then
40
50
  if cursor + 1 > ch.size then
41
51
  alloc(ch, 1)
42
52
  end
43
- writeu8(b, cursor, value)
53
+ writeu8(ch.buff, cursor, value)
44
54
  ch.cursor = cursor + 1
45
- elseif value <= 8383 then
55
+ return
56
+ end
57
+
58
+ if value <= TWO_MAX then
46
59
  if cursor + 2 > ch.size then
47
60
  alloc(ch, 2)
48
61
  end
49
- local adjusted = value - 192
50
- b = ch.buff -- alloc may have replaced the buffer
62
+ local adjusted = value - TWO_BIAS
63
+ local b = ch.buff
51
64
  writeu8(b, cursor, bor(0xC0, rshift(adjusted, 8)))
52
65
  writeu8(b, cursor + 1, band(adjusted, 0xFF))
53
66
  ch.cursor = cursor + 2
54
- elseif value <= 1056959 then
67
+ return
68
+ end
69
+
70
+ if value <= THREE_MAX then
55
71
  if cursor + 3 > ch.size then
56
72
  alloc(ch, 3)
57
73
  end
58
- local adjusted = value - 8384
59
- b = ch.buff
74
+ local adjusted = value - THREE_BIAS
75
+ local b = ch.buff
60
76
  writeu8(b, cursor, bor(0xE0, rshift(adjusted, 16)))
61
77
  writeu16(b, cursor + 1, band(adjusted, 0xFFFF))
62
78
  ch.cursor = cursor + 3
63
- else
64
- if cursor + 5 > ch.size then
65
- alloc(ch, 5)
66
- end
67
- b = ch.buff
68
- writeu8(b, cursor, 0xF0)
69
- writeu32(b, cursor + 1, value)
70
- ch.cursor = cursor + 5
79
+ return
80
+ end
81
+
82
+ if cursor + 5 > ch.size then
83
+ alloc(ch, 5)
71
84
  end
85
+ local b = ch.buff
86
+ writeu8(b, cursor, 0xF0)
87
+ writeu32(b, cursor + 1, value)
88
+ ch.cursor = cursor + 5
72
89
  end
73
90
 
74
91
  function Varint.read(src: buffer, pos: number): (number, number)
75
92
  local b0 = readu8(src, pos)
76
-
77
93
  if b0 <= INLINE_MAX then
78
94
  return b0, 1
79
- elseif b0 <= 0xDF then
80
- return band(b0, 0x1F) * 256 + readu8(src, pos + 1) + 192, 2
81
- elseif b0 <= 0xEF then
82
- return band(b0, 0x0F) * 65536 + readu16(src, pos + 1) + 8384, 3
83
- else
84
- return readu32(src, pos + 1), 5
85
95
  end
96
+ if b0 <= 0xDF then
97
+ return band(b0, 0x1F) * 256 + readu8(src, pos + 1) + TWO_BIAS, 2
98
+ end
99
+ if b0 <= 0xEF then
100
+ return band(b0, 0x0F) * 65536 + readu16(src, pos + 1) + THREE_BIAS, 3
101
+ end
102
+ return readu32(src, pos + 1), 5
86
103
  end
87
104
 
105
+ -- Encode a value already known to fit in INLINE_MAX (single byte).
88
106
  function Varint.writeInline(ch: Types.ChannelState, value: number): ()
89
107
  local cursor = ch.cursor
90
108
  if cursor + 1 > ch.size then
@@ -94,4 +112,17 @@ function Varint.writeInline(ch: Types.ChannelState, value: number): ()
94
112
  ch.cursor = cursor + 1
95
113
  end
96
114
 
115
+ --[[
116
+ Decode a length prefix that uses the single-byte fast path for
117
+ lengths in [0, INLINE_MAX] and the full varint encoding above that.
118
+ Used by Buffer, String, and Auto's string branch.
119
+ ]]
120
+ function Varint.readLengthPrefix(src: buffer, pos: number): (number, number)
121
+ local b0 = readu8(src, pos)
122
+ if b0 <= INLINE_MAX then
123
+ return b0, 1
124
+ end
125
+ return Varint.read(src, pos)
126
+ end
127
+
97
128
  return table.freeze(Varint)