@axpecter/lync 2.1.2 → 2.2.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 (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 +163 -152
  6. package/src/api/Query.luau +224 -211
  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 +133 -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axpecter/lync",
3
- "version": "2.1.2",
3
+ "version": "2.2.1",
4
4
  "description": "Buffer networking for Roblox. Delta compression, XOR framing, built-in security",
5
5
  "main": "src/init.luau",
6
6
  "types": "src/index.d.ts",
package/src/Types.luau CHANGED
@@ -1,5 +1,4 @@
1
1
  --!strict
2
- --!optimize 2
3
2
  -- Shared type definitions for the Lync networking library.
4
3
 
5
4
  export type ChannelState = {
@@ -13,17 +12,27 @@ export type ChannelState = {
13
12
  itemCount: number,
14
13
  singleMode: boolean,
15
14
  singlePos: number,
16
- deltas: { [number]: any },
15
+ deltas: { [number]: DeltaCacheEntry },
17
16
  prevDump: buffer?,
18
17
  prevDumpLen: number,
19
18
  currentPacket: string?,
20
19
  }
21
20
 
21
+ export type DeltaCacheEntry = {
22
+ raw: buffer,
23
+ len: number,
24
+ }
25
+
22
26
  export type Codec<T> = {
23
27
  write: (ch: ChannelState, value: T) -> (),
24
28
  read: (src: buffer, pos: number, refs: { Instance }?) -> (T, number),
25
29
  }
26
30
 
31
+ --[[
32
+ InternalCodec carries optional metadata used by Gate (validation),
33
+ Reader (max-item bound), Auto (type dispatch), and composite codecs
34
+ (direct write/read fast path).
35
+ ]]
27
36
  export type InternalCodec<T> = {
28
37
  write: (ch: ChannelState, value: T) -> (),
29
38
  read: (src: buffer, pos: number, refs: { Instance }?) -> (T, number),
@@ -38,6 +47,25 @@ export type InternalCodec<T> = {
38
47
  _isInteger: boolean?,
39
48
  _schema: { [string]: InternalCodec<any> }?,
40
49
  _typeCheck: string?,
50
+ --[[
51
+ Composite-codec metadata. Gate uses these to recurse element /
52
+ variant validation rather than fall through to a generic NaN scan.
53
+ ]]
54
+ _isOptional: boolean?,
55
+ _inner: InternalCodec<any>?,
56
+ _isTagged: boolean?,
57
+ _variants: { [string]: InternalCodec<any> }?,
58
+ _tagField: string?,
59
+ _isArray: boolean?,
60
+ _element: InternalCodec<any>?,
61
+ _maxCount: number?,
62
+ _isMap: boolean?,
63
+ _keyCodec: InternalCodec<any>?,
64
+ _valueCodec: InternalCodec<any>?,
65
+ _isTuple: boolean?,
66
+ _elements: { InternalCodec<any> }?,
67
+ _isEnum: boolean?,
68
+ _enumValues: { [string]: number }?,
41
69
  }
42
70
 
43
71
  export type RateLimitState = {
@@ -46,24 +74,30 @@ export type RateLimitState = {
46
74
  lastAccepted: number,
47
75
  }
48
76
 
77
+ --[[
78
+ Token-bucket (maxPerSecond + optional burst) or cooldown (cooldown
79
+ seconds between accepts). Mixing both forms is undefined.
80
+ ]]
49
81
  export type RateLimitConfig = {
50
82
  maxPerSecond: number?,
51
83
  burst: number?,
52
84
  cooldown: number?,
53
85
  }
54
86
 
87
+ export type ValidateFn = (data: any, player: Player) -> (boolean, string?)
88
+
55
89
  export type PacketOptions = {
56
90
  unreliable: boolean?,
57
91
  rateLimit: RateLimitConfig?,
58
- validate: ((data: any, player: Player) -> (boolean, string?))?,
92
+ validate: ValidateFn?,
59
93
  maxPayloadBytes: number?,
60
- timestamp: string?,
94
+ timestamp: ("frame" | "offset" | "full")?,
61
95
  }
62
96
 
63
97
  export type QueryOptions = {
64
98
  timeout: number?,
65
99
  rateLimit: RateLimitConfig?,
66
- validate: ((data: any, player: Player) -> (boolean, string?))?,
100
+ validate: ValidateFn?,
67
101
  }
68
102
 
69
103
  export type Registration = {
@@ -75,7 +109,7 @@ export type Registration = {
75
109
  partner: number?,
76
110
  signal: SignalLike,
77
111
  rateLimit: RateLimitConfig?,
78
- validate: ((data: any, player: Player) -> (boolean, string?))?,
112
+ validate: ValidateFn?,
79
113
  needsGate: boolean,
80
114
  maxPayloadBytes: number?,
81
115
  timestampMode: number,
@@ -90,7 +124,12 @@ export type Registration = {
90
124
 
91
125
  export type SignalLike = {
92
126
  fire: (self: SignalLike, ...any) -> (),
127
+ fireSync: (self: SignalLike, ...any) -> (),
93
128
  connect: (self: SignalLike, callback: (...any) -> ()) -> Connection,
129
+ once: (self: SignalLike, callback: (...any) -> ()) -> Connection,
130
+ wait: (self: SignalLike) -> ...any,
131
+ count: (self: SignalLike) -> number,
132
+ hasListeners: (self: SignalLike) -> boolean,
94
133
  }
95
134
 
96
135
  export type Connection = {
@@ -102,13 +141,8 @@ export type ConfigureOptions = {
102
141
  channelMaxSize: number?,
103
142
  validationDepth: number?,
104
143
  poolSize: number?,
105
- bandwidthLimit: {
106
- softLimit: number,
107
- maxStrikes: number,
108
- }?,
109
- globalRateLimit: {
110
- maxPerSecond: number,
111
- }?,
144
+ bandwidthLimit: { softLimit: number, maxStrikes: number }?,
145
+ globalRateLimit: { maxPerSecond: number }?,
112
146
  stats: boolean?,
113
147
  }
114
148
 
@@ -125,4 +159,10 @@ export type PlayerStats = {
125
159
  bytesReceived: number,
126
160
  }
127
161
 
162
+ --[[
163
+ Server keys baselines per Player; client uses `false` since booleans
164
+ are valid table keys but Players never collide with that.
165
+ ]]
166
+ export type BaselineKey = Player | false
167
+
128
168
  return nil
@@ -4,15 +4,31 @@
4
4
 
5
5
  local Players = game:GetService("Players")
6
6
 
7
- local Types = require(script.Parent.Parent.Types)
7
+ local Util = require(script.Parent.Parent.internal.Util)
8
8
 
9
- -- State ---------------------------------------------------------------
9
+ -- Public types -----------------------------------------------------------
10
10
 
11
- local _groups: { [string]: any } = {}
12
- local _playerCleanup: { [Player]: { any } } = {}
11
+ export type GroupHandle = {
12
+ add: (self: GroupHandle, player: Player) -> boolean,
13
+ remove: (self: GroupHandle, player: Player) -> boolean,
14
+ has: (self: GroupHandle, player: Player) -> boolean,
15
+ count: (self: GroupHandle) -> number,
16
+ destroy: (self: GroupHandle) -> (),
17
+ _lyncGroup: boolean,
18
+ _members: { [Player]: boolean },
19
+ }
20
+
21
+ type Group = GroupHandle & { _count: number, _name: string }
22
+
23
+ -- State ------------------------------------------------------------------
24
+
25
+ local _groups: { [string]: Group } = {}
26
+ local _playerCleanup: { [Player]: { Group } } = {}
13
27
  local _cleanupConnected = false
14
28
 
15
- -- Private -------------------------------------------------------------
29
+ -- Private ----------------------------------------------------------------
30
+
31
+ local swapRemove = Util.swapRemove
16
32
 
17
33
  local function ensureCleanup(): ()
18
34
  if _cleanupConnected then
@@ -25,48 +41,30 @@ local function ensureCleanup(): ()
25
41
  if not groups then
26
42
  return
27
43
  end
28
-
29
44
  for _, group in groups do
30
45
  group._members[player] = nil
31
46
  group._count -= 1
32
47
  end
33
-
34
48
  _playerCleanup[player] = nil
35
49
  end)
36
50
  end
37
51
 
38
- -- Public --------------------------------------------------------------
52
+ -- Public -----------------------------------------------------------------
39
53
 
40
54
  local Group = {}
41
55
 
42
- export type GroupHandle = {
43
- add: (self: GroupHandle, player: Player) -> boolean,
44
- remove: (self: GroupHandle, player: Player) -> boolean,
45
- has: (self: GroupHandle, player: Player) -> boolean,
46
- count: (self: GroupHandle) -> number,
47
- destroy: (self: GroupHandle) -> (),
48
- _lyncGroup: boolean,
49
- _members: { [Player]: boolean },
50
- }
51
-
52
56
  local GroupMeta = {}
53
57
  GroupMeta.__index = GroupMeta
54
58
 
55
- function GroupMeta.__iter(self: any): () -> (Player?, boolean?)
56
- local members = self._members
57
- local key: Player? = nil
58
-
59
- return function(): (Player?, boolean?)
60
- local nextKey, nextVal = next(members, key)
61
- key = nextKey
62
- if nextKey then
63
- return nextKey, nextVal
64
- end
65
- return nil, nil
66
- end
59
+ --[[
60
+ __iter returns the standard 3-tuple iterator so `for player in group do`
61
+ uses Luau's builtin path (luaH_next) with zero closure allocation.
62
+ ]]
63
+ function GroupMeta.__iter(self: Group): (any, any, any)
64
+ return next, self._members, nil
67
65
  end
68
66
 
69
- function GroupMeta.add(self: any, player: Player): boolean
67
+ function GroupMeta.add(self: Group, player: Player): boolean
70
68
  if self._members[player] then
71
69
  return false
72
70
  end
@@ -74,18 +72,16 @@ function GroupMeta.add(self: any, player: Player): boolean
74
72
  self._members[player] = true
75
73
  self._count += 1
76
74
 
77
- -- Track for auto-cleanup
78
75
  local cleanup = _playerCleanup[player]
79
76
  if not cleanup then
80
77
  cleanup = {}
81
78
  _playerCleanup[player] = cleanup
82
79
  end
83
80
  table.insert(cleanup, self)
84
-
85
81
  return true
86
82
  end
87
83
 
88
- function GroupMeta.remove(self: any, player: Player): boolean
84
+ function GroupMeta.remove(self: Group, player: Player): boolean
89
85
  if not self._members[player] then
90
86
  return false
91
87
  end
@@ -93,43 +89,35 @@ function GroupMeta.remove(self: any, player: Player): boolean
93
89
  self._members[player] = nil
94
90
  self._count -= 1
95
91
 
96
- -- Remove from cleanup tracking
97
92
  local cleanup = _playerCleanup[player]
98
93
  if cleanup then
99
94
  local idx = table.find(cleanup, self)
100
95
  if idx then
101
- local last = #cleanup
102
- cleanup[idx] = cleanup[last]
103
- cleanup[last] = nil
96
+ swapRemove(cleanup, idx)
104
97
  end
105
98
  end
106
-
107
99
  return true
108
100
  end
109
101
 
110
- function GroupMeta.has(self: any, player: Player): boolean
102
+ function GroupMeta.has(self: Group, player: Player): boolean
111
103
  return self._members[player] == true
112
104
  end
113
105
 
114
- function GroupMeta.count(self: any): number
106
+ function GroupMeta.count(self: Group): number
115
107
  return self._count
116
108
  end
117
109
 
118
- function GroupMeta.destroy(self: any): ()
119
- local name = self._name
120
- if name then
121
- _groups[name] = nil
110
+ function GroupMeta.destroy(self: Group): ()
111
+ if _groups[self._name] == self then
112
+ _groups[self._name] = nil
122
113
  end
123
114
 
124
- -- Remove all members from cleanup tracking
125
115
  for player in self._members do
126
116
  local cleanup = _playerCleanup[player]
127
117
  if cleanup then
128
118
  local idx = table.find(cleanup, self)
129
119
  if idx then
130
- local last = #cleanup
131
- cleanup[idx] = cleanup[last]
132
- cleanup[last] = nil
120
+ swapRemove(cleanup, idx)
133
121
  end
134
122
  end
135
123
  end
@@ -138,6 +126,8 @@ function GroupMeta.destroy(self: any): ()
138
126
  self._count = 0
139
127
  end
140
128
 
129
+ table.freeze(GroupMeta)
130
+
141
131
  function Group.create(name: string): GroupHandle
142
132
  if _groups[name] then
143
133
  error(`[Lync] Lync.group: "{name}" already exists`)
@@ -150,10 +140,10 @@ function Group.create(name: string): GroupHandle
150
140
  _members = {} :: { [Player]: boolean },
151
141
  _count = 0,
152
142
  _name = name,
153
- }, GroupMeta)
143
+ }, GroupMeta) :: any
154
144
 
155
145
  _groups[name] = group
156
- return group :: any
146
+ return group
157
147
  end
158
148
 
159
149
  return table.freeze(Group)