@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,28 +5,29 @@
5
5
  local Signal = require(script.Parent.Parent.api.Signal)
6
6
  local Types = require(script.Parent.Parent.Types)
7
7
 
8
- -- Constants -----------------------------------------------------------
8
+ -- Constants --------------------------------------------------------------
9
9
 
10
10
  local KIND_PACKET = 0
11
11
  local KIND_REQUEST = 1
12
12
  local KIND_RESPONSE = 2
13
13
  local MAX_ID = 127
14
14
 
15
- -- State ---------------------------------------------------------------
15
+ -- State ------------------------------------------------------------------
16
16
 
17
17
  local _nextId = 0
18
18
  local _byId: { [number]: Types.Registration } = {}
19
19
  local _byName: { [string]: Types.Registration } = {}
20
20
 
21
- -- Public --------------------------------------------------------------
21
+ -- Public -----------------------------------------------------------------
22
22
 
23
23
  local Registry = {}
24
24
 
25
25
  Registry.KIND_PACKET = KIND_PACKET
26
26
  Registry.KIND_REQUEST = KIND_REQUEST
27
27
  Registry.KIND_RESPONSE = KIND_RESPONSE
28
+ Registry.MAX_ID = MAX_ID
28
29
 
29
- function Registry.register(
30
+ export type RegisterOptions = {
30
31
  name: string,
31
32
  kind: number,
32
33
  codec: Types.InternalCodec<any>,
@@ -37,35 +38,36 @@ function Registry.register(
37
38
  timestampMode: number,
38
39
  openFn: (ch: Types.ChannelState, id: number) -> (),
39
40
  rateLimit: Types.RateLimitConfig?,
40
- validate: ((data: any, player: Player) -> (boolean, string?))?,
41
- maxPayloadBytes: number?
42
- ): Types.Registration
43
- _nextId += 1
44
- local id = _nextId
45
-
46
- if id > MAX_ID then
47
- error(`[Lync] Registry: ID limit exceeded ({MAX_ID}), too many packets/queries`)
41
+ validate: Types.ValidateFn?,
42
+ maxPayloadBytes: number?,
43
+ }
44
+
45
+ function Registry.register(opts: RegisterOptions): Types.Registration
46
+ local nextCandidate = _nextId + 1
47
+ if nextCandidate > MAX_ID then
48
+ error(`[Lync] Registry.register: ID limit exceeded ({MAX_ID})`)
48
49
  end
49
-
50
- if _byName[name] and kind ~= KIND_RESPONSE then
51
- error(`[Lync] Registry: duplicate name "{name}"`)
50
+ if opts.kind ~= KIND_RESPONSE and _byName[opts.name] then
51
+ error(`[Lync] Registry.register: duplicate name "{opts.name}"`)
52
52
  end
53
53
 
54
+ _nextId = nextCandidate
55
+
54
56
  local reg: Types.Registration = {
55
- id = id,
56
- name = name,
57
- kind = kind,
58
- codec = codec,
59
- isUnreliable = isUnreliable,
60
- partner = partner,
57
+ id = nextCandidate,
58
+ name = opts.name,
59
+ kind = opts.kind,
60
+ codec = opts.codec,
61
+ isUnreliable = opts.isUnreliable,
62
+ partner = opts.partner,
61
63
  signal = Signal.create(),
62
- rateLimit = rateLimit,
63
- validate = validate,
64
- needsGate = needsGate,
65
- maxPayloadBytes = maxPayloadBytes,
66
- timestampMode = timestampMode,
67
- headerSize = headerSize,
68
- _openFn = openFn,
64
+ rateLimit = opts.rateLimit,
65
+ validate = opts.validate,
66
+ needsGate = opts.needsGate,
67
+ maxPayloadBytes = opts.maxPayloadBytes,
68
+ timestampMode = opts.timestampMode,
69
+ headerSize = opts.headerSize,
70
+ _openFn = opts.openFn,
69
71
  bytesSent = 0,
70
72
  bytesReceived = 0,
71
73
  fires = 0,
@@ -73,11 +75,10 @@ function Registry.register(
73
75
  drops = 0,
74
76
  }
75
77
 
76
- _byId[id] = reg
77
- if kind ~= KIND_RESPONSE then
78
- _byName[name] = reg
78
+ _byId[nextCandidate] = reg
79
+ if opts.kind ~= KIND_RESPONSE then
80
+ _byName[opts.name] = reg
79
81
  end
80
-
81
82
  return reg
82
83
  end
83
84
 
@@ -95,9 +96,12 @@ end
95
96
 
96
97
  function Registry.all(): { Types.Registration }
97
98
  local result = table.create(_nextId)
99
+ local n = 0
98
100
  for i = 1, _nextId do
99
- if _byId[i] then
100
- table.insert(result, _byId[i])
101
+ local reg = _byId[i]
102
+ if reg then
103
+ n += 1
104
+ result[n] = reg
101
105
  end
102
106
  end
103
107
  return result
@@ -0,0 +1,28 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Lazy-require Server/Client to break the api -> transport import cycle.
4
+
5
+ -- State ------------------------------------------------------------------
6
+
7
+ local _server: any = nil
8
+ local _client: any = nil
9
+
10
+ -- Public -----------------------------------------------------------------
11
+
12
+ local Transport = {}
13
+
14
+ function Transport.server(): any
15
+ if not _server then
16
+ _server = require(script.Parent.Parent.transport.Server)
17
+ end
18
+ return _server
19
+ end
20
+
21
+ function Transport.client(): any
22
+ if not _client then
23
+ _client = require(script.Parent.Parent.transport.Client)
24
+ end
25
+ return _client
26
+ end
27
+
28
+ return table.freeze(Transport)
@@ -0,0 +1,26 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Cross-module utility helpers.
4
+
5
+ -- Public -----------------------------------------------------------------
6
+
7
+ local Util = {}
8
+
9
+ function Util.isPlayer(value: any): boolean
10
+ return typeof(value) == "Instance" and (value :: Instance):IsA("Player")
11
+ end
12
+
13
+ --[[
14
+ O(1) array remove via swap with last element. The slot becomes nil
15
+ after this returns; callers that track an external count must
16
+ decrement it themselves.
17
+ ]]
18
+ function Util.swapRemove<T>(arr: { T }, idx: number): ()
19
+ local last = #arr
20
+ if idx ~= last then
21
+ arr[idx] = arr[last]
22
+ end
23
+ arr[last] = nil
24
+ end
25
+
26
+ return table.freeze(Util)
@@ -1,21 +1,39 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Remote instance creation and access.
3
+ -- RemoteEvent / UnreliableRemoteEvent thin wrapper.
4
4
 
5
5
  local RunService = game:GetService("RunService")
6
6
 
7
- -- Constants -----------------------------------------------------------
7
+ -- Constants --------------------------------------------------------------
8
8
 
9
9
  local RELIABLE_NAME = "LyncReliable"
10
10
  local UNRELIABLE_NAME = "LyncUnreliable"
11
11
 
12
- -- State ---------------------------------------------------------------
12
+ -- State ------------------------------------------------------------------
13
13
 
14
14
  local _reliable: RemoteEvent? = nil
15
15
  local _unreliable: UnreliableRemoteEvent? = nil
16
16
  local _isServer = RunService:IsServer()
17
17
 
18
- -- Public --------------------------------------------------------------
18
+ -- Private ----------------------------------------------------------------
19
+
20
+ local function ensureReliable(): RemoteEvent
21
+ local re = _reliable
22
+ if not re then
23
+ error("[Lync] Bridge.reliable: not set up; call Lync.start() first")
24
+ end
25
+ return re
26
+ end
27
+
28
+ local function ensureUnreliable(): UnreliableRemoteEvent
29
+ local ure = _unreliable
30
+ if not ure then
31
+ error("[Lync] Bridge.unreliable: not set up; call Lync.start() first")
32
+ end
33
+ return ure
34
+ end
35
+
36
+ -- Public -----------------------------------------------------------------
19
37
 
20
38
  local Bridge = {}
21
39
 
@@ -29,28 +47,18 @@ function Bridge.setup(container: Instance): ()
29
47
  ure.Name = UNRELIABLE_NAME
30
48
  ure.Parent = container
31
49
 
32
- _reliable = re
33
- _unreliable = ure
34
- else
35
- _reliable = container:WaitForChild(RELIABLE_NAME) :: RemoteEvent
36
- _unreliable = container:WaitForChild(UNRELIABLE_NAME) :: UnreliableRemoteEvent
50
+ _reliable, _unreliable = re, ure
51
+ return
37
52
  end
53
+ _reliable = container:WaitForChild(RELIABLE_NAME) :: RemoteEvent
54
+ _unreliable = container:WaitForChild(UNRELIABLE_NAME) :: UnreliableRemoteEvent
38
55
  end
39
56
 
40
- function Bridge.reliable(): RemoteEvent
41
- return _reliable :: RemoteEvent
42
- end
43
-
44
- function Bridge.unreliable(): UnreliableRemoteEvent
45
- return _unreliable :: UnreliableRemoteEvent
46
- end
47
-
48
- function Bridge.isServer(): boolean
49
- return _isServer
50
- end
57
+ Bridge.reliable = ensureReliable
58
+ Bridge.unreliable = ensureUnreliable
51
59
 
52
60
  function Bridge.fireServer(data: buffer, refs: { Instance }): ()
53
- local re = _reliable :: RemoteEvent
61
+ local re = ensureReliable()
54
62
  if #refs > 0 then
55
63
  re:FireServer(data, refs)
56
64
  else
@@ -59,7 +67,7 @@ function Bridge.fireServer(data: buffer, refs: { Instance }): ()
59
67
  end
60
68
 
61
69
  function Bridge.fireClient(player: Player, data: buffer, refs: { Instance }): ()
62
- local re = _reliable :: RemoteEvent
70
+ local re = ensureReliable()
63
71
  if #refs > 0 then
64
72
  re:FireClient(player, data, refs)
65
73
  else
@@ -68,7 +76,7 @@ function Bridge.fireClient(player: Player, data: buffer, refs: { Instance }): ()
68
76
  end
69
77
 
70
78
  function Bridge.fireServerUnreliable(data: buffer, refs: { Instance }): ()
71
- local ure = _unreliable :: UnreliableRemoteEvent
79
+ local ure = ensureUnreliable()
72
80
  if #refs > 0 then
73
81
  ure:FireServer(data, refs)
74
82
  else
@@ -77,7 +85,7 @@ function Bridge.fireServerUnreliable(data: buffer, refs: { Instance }): ()
77
85
  end
78
86
 
79
87
  function Bridge.fireClientUnreliable(player: Player, data: buffer, refs: { Instance }): ()
80
- local ure = _unreliable :: UnreliableRemoteEvent
88
+ local ure = ensureUnreliable()
81
89
  if #refs > 0 then
82
90
  ure:FireClient(player, data, refs)
83
91
  else
@@ -1,20 +1,59 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Client-side transport: single channel pair, flush.
3
+ -- Client-side transport: single channel pair, flush, with outbound XOR delta.
4
+
5
+ local ReplicatedStorage = game:GetService("ReplicatedStorage")
4
6
 
5
7
  local Bridge = require(script.Parent.Parent.transport.Bridge)
6
8
  local Channel = require(script.Parent.Parent.internal.Channel)
7
9
  local Reader = require(script.Parent.Parent.transport.Reader)
8
10
  local Types = require(script.Parent.Parent.Types)
9
11
 
10
- -- State ---------------------------------------------------------------
12
+ -- Constants --------------------------------------------------------------
13
+
14
+ local CONTAINER_NAME = "LyncRemotes"
15
+
16
+ -- State ------------------------------------------------------------------
11
17
 
12
18
  local _reliable: Types.ChannelState = Channel.create()
13
19
  local _unreliable: Types.ChannelState = Channel.create()
14
20
  local _prevIncoming: buffer? = nil
15
21
  local _prevIncomingLen = 0
16
22
 
17
- -- Public --------------------------------------------------------------
23
+ -- Private ----------------------------------------------------------------
24
+
25
+ local function handleIncomingReliable(data: any, refs: any?): ()
26
+ local incoming = Reader.decodeIncoming(data)
27
+ if not incoming then
28
+ return
29
+ end
30
+
31
+ local incomingLen = buffer.len(incoming)
32
+ local decoded = if _prevIncoming
33
+ then Channel.xorApply(incoming, incomingLen, _prevIncoming, _prevIncomingLen)
34
+ else incoming
35
+ _prevIncoming = decoded
36
+ _prevIncomingLen = incomingLen
37
+
38
+ local ok, err = pcall(Reader.process, decoded, incomingLen, refs, nil, false)
39
+ if not ok then
40
+ warn(`[Lync] Client.receive: {err}`)
41
+ end
42
+ end
43
+
44
+ local function handleIncomingUnreliable(data: any, refs: any?): ()
45
+ local incoming = Reader.decodeIncoming(data)
46
+ if not incoming then
47
+ return
48
+ end
49
+
50
+ local ok, err = pcall(Reader.process, incoming, buffer.len(incoming), refs, nil, false)
51
+ if not ok then
52
+ warn(`[Lync] Client.receiveUnreliable: {err}`)
53
+ end
54
+ end
55
+
56
+ -- Public -----------------------------------------------------------------
18
57
 
19
58
  local Client = {}
20
59
 
@@ -27,69 +66,28 @@ function Client.flush(): ()
27
66
  Channel.updateTimestamp()
28
67
  end
29
68
 
30
- -- Reliable
31
69
  if _reliable.cursor > 0 then
32
- local snapshot, refs, byteLen, _ = Channel.sealAndDump(_reliable)
33
- Bridge.fireServer(snapshot, refs)
70
+ local snapshot, refs, byteLen = Channel.sealAndDump(_reliable)
71
+ local xored = Channel.xorApply(snapshot, byteLen, _reliable.prevDump, _reliable.prevDumpLen)
72
+ _reliable.prevDump = snapshot
73
+ _reliable.prevDumpLen = byteLen
74
+ Bridge.fireServer(xored, refs)
34
75
  Channel.reset(_reliable)
35
76
  end
36
77
 
37
- -- Unreliable
38
78
  if _unreliable.cursor > 0 then
39
- local snapshot, refs, byteLen, _ = Channel.sealAndDump(_unreliable)
79
+ local snapshot, refs = Channel.sealAndDump(_unreliable)
40
80
  Bridge.fireServerUnreliable(snapshot, refs)
41
81
  Channel.reset(_unreliable)
42
82
  end
43
83
  end
44
84
 
45
85
  function Client.start(): ()
46
- local container = game:GetService("ReplicatedStorage"):WaitForChild("LyncRemotes")
86
+ local container = ReplicatedStorage:WaitForChild(CONTAINER_NAME)
47
87
  Bridge.setup(container)
48
88
 
49
- local reliable = Bridge.reliable()
50
- local unreliable = Bridge.unreliable()
51
-
52
- -- Incoming reliable (XOR encoded)
53
- reliable.OnClientEvent:Connect(function(data: any, refs: any?)
54
- local incoming = Reader.decodeIncoming(data)
55
- if not incoming then
56
- return
57
- end
58
-
59
- local incomingLen = buffer.len(incoming)
60
-
61
- -- XOR decode against previous frame
62
- local decoded: buffer
63
- if _prevIncoming then
64
- decoded = Channel.xorApply(incoming, incomingLen, _prevIncoming, _prevIncomingLen)
65
- else
66
- decoded = incoming
67
- end
68
-
69
- -- Store decoded (not XOR'd) for next frame's XOR base
70
- _prevIncoming = decoded
71
- _prevIncomingLen = incomingLen
72
-
73
- local ok, err = pcall(Reader.process, decoded, incomingLen, refs :: any, nil, false)
74
- if not ok then
75
- warn(`[Lync] Client: read error: {err}`)
76
- end
77
- end)
78
-
79
- -- Incoming unreliable (no XOR)
80
- unreliable.OnClientEvent:Connect(function(data: any, refs: any?)
81
- local incoming = Reader.decodeIncoming(data)
82
- if not incoming then
83
- return
84
- end
85
-
86
- local incomingLen = buffer.len(incoming)
87
-
88
- local ok, err = pcall(Reader.process, incoming, incomingLen, refs :: any, nil, false)
89
- if not ok then
90
- warn(`[Lync] Client: read error (unreliable): {err}`)
91
- end
92
- end)
89
+ Bridge.reliable().OnClientEvent:Connect(handleIncomingReliable)
90
+ Bridge.unreliable().OnClientEvent:Connect(handleIncomingUnreliable)
93
91
  end
94
92
 
95
93
  return table.freeze(Client)