@axpecter/lync 2.0.0 → 2.1.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 (55) hide show
  1. package/README.md +475 -426
  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 +249 -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
@@ -2,23 +2,9 @@
2
2
  --!native
3
3
  -- Half-precision float codec. 2 bytes, ±65504, ~3 decimal digits.
4
4
 
5
- local Channel = require(script.Parent.Parent.Parent.internal.Channel)
6
- local Types = require(script.Parent.Parent.Parent.Types)
5
+ local Base = require(script.Parent.Parent.Base)
7
6
 
8
- local alloc = Channel.alloc
9
-
10
- type ChannelState = Types.ChannelState
11
-
12
- -- Constants -----------------------------------------------------------
13
-
14
- local MAX_HALF = 65504
15
- local BIAS = 15
16
- local EXP_MAX = 31
17
- local MANT_BITS = 10
18
- local MANT_SCALE = 1024
19
- local SIGN_BIT = 0x8000
20
- local INF_BITS = 0x7C00
21
- local NAN_BITS = 0x7E00
7
+ -- Private -------------------------------------------------------------
22
8
 
23
9
  local band = bit32.band
24
10
  local bor = bit32.bor
@@ -29,84 +15,92 @@ local ldexp = math.ldexp
29
15
  local round = math.round
30
16
  local huge = math.huge
31
17
 
32
- -- Private -------------------------------------------------------------
33
-
34
18
  local function encode(value: number): number
19
+ -- NaN
35
20
  if value ~= value then
36
- return NAN_BITS
21
+ return 0x7E00
37
22
  end
23
+
24
+ -- Zero (both +0 and -0 map to positive zero)
38
25
  if value == 0 then
39
26
  return 0
40
27
  end
41
28
 
29
+ -- Sign extraction
42
30
  local sign = 0
43
31
  if value < 0 then
44
- sign = SIGN_BIT
32
+ sign = 0x8000
45
33
  value = -value
46
34
  end
47
35
 
48
- if value > MAX_HALF then
49
- return bor(sign, INF_BITS)
36
+ -- Overflow to infinity
37
+ if value > 65504 then
38
+ return bor(sign, 0x7C00)
50
39
  end
51
40
 
41
+ -- Decompose via frexp: value = mantissa * 2^exponent, mantissa in [0.5, 1)
52
42
  local mantissa, exponent = frexp(value)
53
- exponent += 14
54
-
55
- local bits: number
56
- if exponent <= 0 then
57
- local shift = exponent + MANT_BITS - 1
58
- if shift < 0 then
43
+ local biasedExp = exponent + 14
44
+
45
+ if biasedExp <= 0 then
46
+ -- Subnormal: frac = round(mantissa * 2^(biasedExp + 10))
47
+ local frac = round(ldexp(mantissa, biasedExp + 10))
48
+ if frac >= 1024 then
49
+ -- Rounds to minimum normal
50
+ return bor(sign, 0x0400)
51
+ end
52
+ if frac <= 0 then
59
53
  return sign
60
54
  end
61
- bits = round(mantissa * lshift(1, shift))
62
- elseif exponent >= EXP_MAX then
63
- bits = INF_BITS
64
- else
65
- bits = bor(lshift(exponent, MANT_BITS), round((mantissa * 2 - 1) * MANT_SCALE))
55
+ return bor(sign, frac)
56
+ end
57
+
58
+ -- Normal: frac = round((mantissa * 2 - 1) * 1024)
59
+ local frac = round((mantissa * 2 - 1) * 1024)
60
+
61
+ -- Carry: mantissa rounding overflows into exponent
62
+ if frac >= 1024 then
63
+ frac = 0
64
+ biasedExp += 1
66
65
  end
67
66
 
68
- return bor(sign, bits)
67
+ -- Overflow after carry
68
+ if biasedExp >= 31 then
69
+ return bor(sign, 0x7C00)
70
+ end
71
+
72
+ return bor(sign, lshift(biasedExp, 10), frac)
69
73
  end
70
74
 
71
- local function decode(packed: number): number
72
- local exponent = band(rshift(packed, MANT_BITS), 0x1F)
73
- local mantissa = band(packed, 0x03FF)
75
+ local function decode(raw: number): number
76
+ local sign = band(raw, 0x8000)
77
+ local exp = band(rshift(raw, 10), 0x1F)
78
+ local frac = band(raw, 0x03FF)
74
79
 
75
- local result: number
76
- if exponent == 0 then
77
- result = if mantissa == 0 then 0 else ldexp(mantissa / MANT_SCALE, 1 - BIAS)
78
- elseif exponent == EXP_MAX then
79
- result = if mantissa == 0 then huge else 0 / 0
80
+ local value: number
81
+
82
+ if exp == 0 then
83
+ -- Zero or subnormal
84
+ value = if frac == 0 then 0 else ldexp(frac, -24)
85
+ elseif exp == 31 then
86
+ -- Infinity or NaN
87
+ value = if frac == 0 then huge else 0 / 0
80
88
  else
81
- result = ldexp(mantissa / MANT_SCALE + 1, exponent - BIAS)
89
+ -- Normal: (frac + 1024) * 2^(exp - 25)
90
+ value = ldexp(frac + 1024, exp - 25)
82
91
  end
83
92
 
84
- return if band(packed, SIGN_BIT) ~= 0 then -result else result
93
+ return if sign ~= 0 then -value else value
85
94
  end
86
95
 
87
96
  -- Public --------------------------------------------------------------
88
97
 
89
98
  local Float16 = {}
90
99
 
91
- Float16.f16 = table.freeze({
92
- _size = 2,
93
- _directWrite = function(b: buffer, offset: number, value: number): ()
94
- buffer.writeu16(b, offset, encode(value))
95
- end,
96
- _directRead = function(b: buffer, offset: number): number
97
- return decode(buffer.readu16(b, offset))
98
- end,
99
- write = function(ch: ChannelState, value: number): ()
100
- local c = ch.cursor
101
- if c + 2 > ch.size then
102
- alloc(ch, 2)
103
- end
104
- buffer.writeu16(ch.buff, c, encode(value))
105
- ch.cursor = c + 2
106
- end,
107
- read = function(src: buffer, pos: number, _refs: { Instance }?): (number, number)
108
- return decode(buffer.readu16(src, pos)), 2
109
- end,
110
- })
100
+ Float16.f16 = Base.define(2, function(b: buffer, off: number, value: number): ()
101
+ buffer.writeu16(b, off, encode(value))
102
+ end, function(b: buffer, off: number): number
103
+ return decode(buffer.readu16(b, off))
104
+ end, { _typeCheck = "number" })
111
105
 
112
106
  return table.freeze(Float16)
@@ -0,0 +1,68 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Smart integer codec. Auto-selects u8/u16/u32/i8/i16/i32 from range.
4
+
5
+ local Base = require(script.Parent.Parent.Base)
6
+
7
+ -- Private -------------------------------------------------------------
8
+
9
+ local floor = math.floor
10
+
11
+ -- Public --------------------------------------------------------------
12
+
13
+ local Int = {}
14
+
15
+ function Int.int(min: number, max: number): any
16
+ if min > max then
17
+ error(`[Lync] Lync.int: min ({min}) must be <= max ({max})`)
18
+ end
19
+ if floor(min) ~= min or floor(max) ~= max then
20
+ error("[Lync] Lync.int: bounds must be integers")
21
+ end
22
+
23
+ local meta = {
24
+ _min = min,
25
+ _max = max,
26
+ _isInteger = true,
27
+ _typeCheck = "number",
28
+ }
29
+
30
+ if min >= 0 then
31
+ -- Unsigned path
32
+ if max <= 0xFF then
33
+ return Base.define(1, buffer.writeu8, buffer.readu8, meta)
34
+ elseif max <= 0xFFFF then
35
+ return Base.define(2, buffer.writeu16, buffer.readu16, meta)
36
+ elseif max <= 0xFFFFFFFF then
37
+ return Base.define(4, buffer.writeu32, buffer.readu32, meta)
38
+ end
39
+ else
40
+ -- Signed path: use unsigned write/read, interpret as signed
41
+ 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)
62
+ end
63
+ end
64
+
65
+ error(`[Lync] Lync.int: range [{min}, {max}] exceeds i32 bounds`)
66
+ end
67
+
68
+ return table.freeze(Int)
@@ -1,48 +1,26 @@
1
1
  --!strict
2
- --!native
3
- -- u8, u16, u32, i8, i16, i32, f32, f64 codecs.
2
+ --!optimize 2
3
+ -- Explicit-precision float codecs: f32 and f64.
4
4
 
5
5
  local Base = require(script.Parent.Parent.Base)
6
6
 
7
- return table.freeze({
8
- u8 = Base.define(1, function(b: buffer, o: number, v: number): ()
9
- buffer.writeu8(b, o, v)
10
- end, function(b: buffer, o: number): number
11
- return buffer.readu8(b, o)
12
- end),
13
- u16 = Base.define(2, function(b: buffer, o: number, v: number): ()
14
- buffer.writeu16(b, o, v)
15
- end, function(b: buffer, o: number): number
16
- return buffer.readu16(b, o)
17
- end),
18
- u32 = Base.define(4, function(b: buffer, o: number, v: number): ()
19
- buffer.writeu32(b, o, v)
20
- end, function(b: buffer, o: number): number
21
- return buffer.readu32(b, o)
22
- end),
23
- i8 = Base.define(1, function(b: buffer, o: number, v: number): ()
24
- buffer.writei8(b, o, v)
25
- end, function(b: buffer, o: number): number
26
- return buffer.readi8(b, o)
27
- end),
28
- i16 = Base.define(2, function(b: buffer, o: number, v: number): ()
29
- buffer.writei16(b, o, v)
30
- end, function(b: buffer, o: number): number
31
- return buffer.readi16(b, o)
32
- end),
33
- i32 = Base.define(4, function(b: buffer, o: number, v: number): ()
34
- buffer.writei32(b, o, v)
35
- end, function(b: buffer, o: number): number
36
- return buffer.readi32(b, o)
37
- end),
38
- f32 = Base.define(4, function(b: buffer, o: number, v: number): ()
39
- buffer.writef32(b, o, v)
40
- end, function(b: buffer, o: number): number
41
- return buffer.readf32(b, o)
42
- end),
43
- f64 = Base.define(8, function(b: buffer, o: number, v: number): ()
44
- buffer.writef64(b, o, v)
45
- end, function(b: buffer, o: number): number
46
- return buffer.readf64(b, o)
47
- end),
7
+ -- Constants -----------------------------------------------------------
8
+
9
+ local F32_SCRATCH = buffer.create(4)
10
+
11
+ -- Public --------------------------------------------------------------
12
+
13
+ local Number = {}
14
+
15
+ Number.f32 = Base.define(4, buffer.writef32, buffer.readf32, {
16
+ _typeCheck = "number",
48
17
  })
18
+
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
+ Number._f32Scratch = F32_SCRATCH
25
+
26
+ return table.freeze(Number)
@@ -1,76 +1,97 @@
1
1
  --!strict
2
2
  --!native
3
- -- LEB128 variable-length integer encoding and decoding.
3
+ -- Dense prefix-varint encoding with single-branch decode.
4
4
 
5
- local Channel = require(script.Parent.Parent.Parent.internal.Channel)
5
+ local Base = require(script.Parent.Parent.Base)
6
6
  local Types = require(script.Parent.Parent.Parent.Types)
7
7
 
8
- type ChannelState = Types.ChannelState
9
-
10
- local alloc = Channel.alloc
11
-
12
8
  -- Constants -----------------------------------------------------------
13
9
 
14
- local CONT = 0x80
15
- local MASK = 0x7F
16
- local MAX_BYTES = 5
17
- local MAX_VALUE = 4294967295
10
+ local INLINE_MAX = 0xBF
11
+
12
+ -- Private -------------------------------------------------------------
18
13
 
14
+ local alloc = Base.alloc
19
15
  local band = bit32.band
20
16
  local bor = bit32.bor
21
17
  local rshift = bit32.rshift
22
- local lshift = bit32.lshift
18
+ local writeu8 = buffer.writeu8
19
+ local writeu16 = buffer.writeu16
20
+ local writeu32 = buffer.writeu32
21
+ local readu8 = buffer.readu8
22
+ local readu16 = buffer.readu16
23
+ local readu32 = buffer.readu32
23
24
 
24
25
  -- Public --------------------------------------------------------------
25
26
 
26
27
  local Varint = {}
27
28
 
28
- function Varint.write(ch: ChannelState, value: number): ()
29
- if value < 0 or value > MAX_VALUE then
30
- error(`[Lync] Varint value out of range: {value}`)
31
- end
29
+ Varint.INLINE_MAX = INLINE_MAX
32
30
 
33
- -- Fast path: ~80%+ of real-world varints are single-byte(lengths, counts)
34
- if value < CONT then
35
- local c = ch.cursor
36
- if c + 1 > ch.size then
37
- alloc(ch, 1)
38
- end
39
- buffer.writeu8(ch.buff, c, value)
40
- ch.cursor = c + 1
41
- return
31
+ function Varint.write(ch: Types.ChannelState, value: number): ()
32
+ 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}`)
42
34
  end
43
35
 
44
- local c = ch.cursor
45
- if c + MAX_BYTES > ch.size then
46
- alloc(ch, MAX_BYTES)
47
- end
36
+ local cursor = ch.cursor
48
37
  local b = ch.buff
49
38
 
50
- while value >= CONT do
51
- buffer.writeu8(b, c, bor(band(value, MASK), CONT))
52
- value = rshift(value, 7)
53
- c += 1
39
+ if value <= INLINE_MAX then
40
+ if cursor + 1 > ch.size then
41
+ alloc(ch, 1)
42
+ end
43
+ writeu8(b, cursor, value)
44
+ ch.cursor = cursor + 1
45
+ elseif value <= 8383 then
46
+ if cursor + 2 > ch.size then
47
+ alloc(ch, 2)
48
+ end
49
+ local adjusted = value - 192
50
+ b = ch.buff -- alloc may have replaced the buffer
51
+ writeu8(b, cursor, bor(0xC0, rshift(adjusted, 8)))
52
+ writeu8(b, cursor + 1, band(adjusted, 0xFF))
53
+ ch.cursor = cursor + 2
54
+ elseif value <= 1056959 then
55
+ if cursor + 3 > ch.size then
56
+ alloc(ch, 3)
57
+ end
58
+ local adjusted = value - 8384
59
+ b = ch.buff
60
+ writeu8(b, cursor, bor(0xE0, rshift(adjusted, 16)))
61
+ writeu16(b, cursor + 1, band(adjusted, 0xFFFF))
62
+ 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
54
71
  end
55
-
56
- buffer.writeu8(b, c, value)
57
- ch.cursor = c + 1
58
72
  end
59
73
 
60
- -- Reads rely on buffer.readu8 throwing on OOB. MAX_BYTES rejects malformed loops.
61
74
  function Varint.read(src: buffer, pos: number): (number, number)
62
- local result = 0
63
-
64
- for offset = 0, MAX_BYTES - 1 do
65
- local byte = buffer.readu8(src, pos + offset)
66
- result = bor(result, lshift(band(byte, MASK), offset * 7))
67
-
68
- if band(byte, CONT) == 0 then
69
- return result, offset + 1
70
- end
75
+ local b0 = readu8(src, pos)
76
+
77
+ if b0 <= INLINE_MAX then
78
+ 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
71
85
  end
86
+ end
72
87
 
73
- error(`[Lync] Varint exceeds {MAX_BYTES} bytes at position {pos}`)
88
+ function Varint.writeInline(ch: Types.ChannelState, value: number): ()
89
+ local cursor = ch.cursor
90
+ if cursor + 1 > ch.size then
91
+ alloc(ch, 1)
92
+ end
93
+ writeu8(ch.buff, cursor, value)
94
+ ch.cursor = cursor + 1
74
95
  end
75
96
 
76
97
  return table.freeze(Varint)