@axpecter/lync 1.5.2 → 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 +499 -357
  2. package/package.json +2 -2
  3. package/src/Types.luau +69 -27
  4. package/src/api/Group.luau +101 -106
  5. package/src/api/Packet.luau +191 -157
  6. package/src/api/Query.luau +223 -282
  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 +189 -201
  11. package/src/codec/composite/Map.luau +97 -341
  12. package/src/codec/composite/Optional.luau +27 -23
  13. package/src/codec/composite/Shared.luau +96 -65
  14. package/src/codec/composite/Struct.luau +201 -339
  15. package/src/codec/composite/Tagged.luau +64 -178
  16. package/src/codec/composite/Tuple.luau +81 -95
  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 +29 -19
  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 -35
  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 -306
  43. package/src/init.luau +332 -173
  44. package/src/internal/Baseline.luau +29 -24
  45. package/src/internal/Channel.luau +346 -161
  46. package/src/internal/Middleware.luau +60 -85
  47. package/src/internal/Pool.luau +19 -30
  48. package/src/internal/Registry.luau +61 -101
  49. package/src/transport/Bridge.luau +64 -43
  50. package/src/transport/Client.luau +60 -117
  51. package/src/transport/Gate.luau +282 -133
  52. package/src/transport/Reader.luau +159 -100
  53. package/src/transport/Server.luau +147 -292
  54. package/src/api/Namespace.luau +0 -251
  55. package/src/codec/meta/Quantized.luau +0 -174
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axpecter/lync",
3
- "version": "1.5.2",
4
- "description": "Buffer networking for Roblox. Delta compression, XOR framing, built-in security.",
3
+ "version": "2.1.0",
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",
7
7
  "module": "commonjs",
package/src/Types.luau CHANGED
@@ -7,11 +7,16 @@ export type ChannelState = {
7
7
  cursor: number,
8
8
  size: number,
9
9
  refs: { Instance },
10
+ refCount: number,
10
11
  lastId: number,
11
12
  countPos: number,
12
13
  itemCount: number,
13
- deltas: { [number]: any }, -- per-codec delta caches; shape varies by codec kind
14
+ singleMode: boolean,
15
+ singlePos: number,
16
+ deltas: { [number]: any },
14
17
  prevDump: buffer?,
18
+ prevDumpLen: number,
19
+ currentPacket: string?,
15
20
  }
16
21
 
17
22
  export type Codec<T> = {
@@ -19,7 +24,6 @@ export type Codec<T> = {
19
24
  read: (src: buffer, pos: number, refs: { Instance }?) -> (T, number),
20
25
  }
21
26
 
22
- -- Extended codec exposing internal fast-path metadata used by composite codecs.
23
27
  export type InternalCodec<T> = {
24
28
  write: (ch: ChannelState, value: T) -> (),
25
29
  read: (src: buffer, pos: number, refs: { Instance }?) -> (T, number),
@@ -28,59 +32,97 @@ export type InternalCodec<T> = {
28
32
  _directRead: ((b: buffer, offset: number) -> T)?,
29
33
  _isDelta: boolean?,
30
34
  _isBool: boolean?,
35
+ _hasUnknown: boolean?,
36
+ _min: number?,
37
+ _max: number?,
38
+ _isInteger: boolean?,
39
+ _schema: { [string]: InternalCodec<any> }?,
40
+ _typeCheck: string?,
41
+ }
42
+
43
+ export type RateLimitState = {
44
+ tokens: number,
45
+ lastRefill: number,
46
+ lastAccepted: number,
31
47
  }
32
48
 
33
49
  export type RateLimitConfig = {
34
- maxPerSecond: number,
35
- burstAllowance: number?,
50
+ maxPerSecond: number?,
51
+ burst: number?,
52
+ cooldown: number?,
36
53
  }
37
54
 
38
- export type PacketConfig<T> = {
39
- value: Codec<T>,
55
+ export type PacketOptions = {
40
56
  unreliable: boolean?,
41
57
  rateLimit: RateLimitConfig?,
42
- validate: ((data: T, player: Player) -> (boolean, string?))?,
58
+ validate: ((data: any, player: Player) -> (boolean, string?))?,
43
59
  maxPayloadBytes: number?,
60
+ timestamp: string?,
44
61
  }
45
62
 
46
- export type QueryConfig<Req, Resp> = {
47
- request: Codec<Req>,
48
- response: Codec<Resp>,
63
+ export type QueryOptions = {
49
64
  timeout: number?,
50
65
  rateLimit: RateLimitConfig?,
51
- validate: ((data: Req, player: Player) -> (boolean, string?))?,
52
- }
53
-
54
- export type NamespaceConfig = {
55
- packets: { [string]: PacketConfig<any> }?,
56
- queries: { [string]: QueryConfig<any, any> }?,
57
- }
58
-
59
- -- Minimal signal interface for Registration. Avoids circular dependency
60
- -- with Signal.luau while typing the two methods consumers actually call.
61
- export type SignalLike = {
62
- fire: (self: SignalLike, ...any) -> (),
63
- connect: (self: SignalLike, callback: (...any) -> ()) -> Connection,
66
+ validate: ((data: any, player: Player) -> (boolean, string?))?,
64
67
  }
65
68
 
66
69
  export type Registration = {
67
70
  id: number,
68
71
  name: string,
69
- baseName: string,
70
- codec: Codec<any>,
71
- isUnreliable: boolean,
72
72
  kind: number,
73
+ codec: InternalCodec<any>,
74
+ isUnreliable: boolean,
73
75
  partner: number?,
74
76
  signal: SignalLike,
75
77
  rateLimit: RateLimitConfig?,
76
78
  validate: ((data: any, player: Player) -> (boolean, string?))?,
77
79
  needsGate: boolean,
78
80
  maxPayloadBytes: number?,
81
+ timestampMode: number,
82
+ headerSize: number,
83
+ _openFn: (ch: ChannelState, id: number) -> (),
84
+ bytesSent: number,
85
+ bytesReceived: number,
86
+ fires: number,
87
+ recvFires: number,
88
+ drops: number,
89
+ }
90
+
91
+ export type SignalLike = {
92
+ fire: (self: SignalLike, ...any) -> (),
93
+ connect: (self: SignalLike, callback: (...any) -> ()) -> Connection,
79
94
  }
80
95
 
81
96
  export type Connection = {
82
- disconnect: (self: Connection) -> (),
83
97
  connected: boolean,
98
+ disconnect: (self: Connection) -> (),
99
+ }
100
+
101
+ export type ConfigureOptions = {
102
+ channelMaxSize: number?,
103
+ validationDepth: number?,
104
+ poolSize: number?,
105
+ bandwidthLimit: {
106
+ softLimit: number,
107
+ maxStrikes: number,
108
+ }?,
109
+ globalRateLimit: {
110
+ maxPerSecond: number,
111
+ }?,
112
+ stats: boolean?,
113
+ }
114
+
115
+ export type PacketStats = {
116
+ bytesSent: number,
117
+ bytesReceived: number,
118
+ fires: number,
119
+ recvFires: number,
120
+ drops: number,
121
+ }
122
+
123
+ export type PlayerStats = {
124
+ bytesSent: number,
125
+ bytesReceived: number,
84
126
  }
85
127
 
86
128
  return nil
@@ -1,164 +1,159 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Named player sets with auto-cleanup on PlayerRemoving.
3
+ -- Named player sets with __iter, auto-cleanup on PlayerRemoving.
4
4
 
5
5
  local Players = game:GetService("Players")
6
6
 
7
+ local Types = require(script.Parent.Parent.Types)
8
+
7
9
  -- State ---------------------------------------------------------------
8
10
 
9
- local _groups = {} :: { [string]: { [Player]: true } }
10
- local _counts = {} :: { [string]: number }
11
- local _playerGroups = {} :: { [Player]: { [string]: true } }
11
+ local _groups: { [string]: any } = {}
12
+ local _playerCleanup: { [Player]: { any } } = {}
13
+ local _cleanupConnected = false
12
14
 
13
15
  -- Private -------------------------------------------------------------
14
16
 
15
- local function onPlayerRemoving(player: Player): ()
16
- local memberships = _playerGroups[player]
17
- if not memberships then
17
+ local function ensureCleanup(): ()
18
+ if _cleanupConnected then
18
19
  return
19
20
  end
21
+ _cleanupConnected = true
20
22
 
21
- for name in memberships do
22
- local set = _groups[name]
23
- if set and set[player] then
24
- set[player] = nil
25
- _counts[name] -= 1
23
+ Players.PlayerRemoving:Connect(function(player: Player)
24
+ local groups = _playerCleanup[player]
25
+ if not groups then
26
+ return
26
27
  end
27
- end
28
28
 
29
- _playerGroups[player] = nil
29
+ for _, group in groups do
30
+ group._members[player] = nil
31
+ group._count -= 1
32
+ end
33
+
34
+ _playerCleanup[player] = nil
35
+ end)
30
36
  end
31
37
 
32
- Players.PlayerRemoving:Connect(onPlayerRemoving)
38
+ -- Public --------------------------------------------------------------
33
39
 
34
- type GroupFields = {
35
- _tag: "group",
36
- _name: string,
40
+ local Group = {}
41
+
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 },
37
50
  }
38
51
 
39
- local GroupImpl = {}
40
- GroupImpl.__index = GroupImpl
52
+ local GroupMeta = {}
53
+ GroupMeta.__index = GroupMeta
41
54
 
42
- export type Group = typeof(setmetatable({} :: GroupFields, GroupImpl))
55
+ function GroupMeta.__iter(self: any): () -> (Player?, boolean?)
56
+ local members = self._members
57
+ local key: Player? = nil
43
58
 
44
- function GroupImpl.add(self: Group, player: Player): boolean
45
- local name = self._name
46
- local set = _groups[name]
47
- if not set then
48
- error(`[Lync] Group has been destroyed: "{name}"`)
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
49
66
  end
67
+ end
50
68
 
51
- if set[player] then
69
+ function GroupMeta.add(self: any, player: Player): boolean
70
+ if self._members[player] then
52
71
  return false
53
72
  end
54
73
 
55
- set[player] = true
56
- _counts[name] += 1
74
+ self._members[player] = true
75
+ self._count += 1
57
76
 
58
- local memberships = _playerGroups[player]
59
- if not memberships then
60
- memberships = {}
61
- _playerGroups[player] = memberships
77
+ -- Track for auto-cleanup
78
+ local cleanup = _playerCleanup[player]
79
+ if not cleanup then
80
+ cleanup = {}
81
+ _playerCleanup[player] = cleanup
62
82
  end
63
- memberships[name] = true
83
+ table.insert(cleanup, self)
64
84
 
65
85
  return true
66
86
  end
67
87
 
68
- function GroupImpl.remove(self: Group, player: Player): boolean
69
- local name = self._name
70
- local set = _groups[name]
71
- if not set then
72
- error(`[Lync] Group has been destroyed: "{name}"`)
73
- end
74
-
75
- if not set[player] then
88
+ function GroupMeta.remove(self: any, player: Player): boolean
89
+ if not self._members[player] then
76
90
  return false
77
91
  end
78
92
 
79
- set[player] = nil
80
- _counts[name] -= 1
93
+ self._members[player] = nil
94
+ self._count -= 1
81
95
 
82
- local memberships = _playerGroups[player]
83
- if memberships then
84
- memberships[name] = nil
96
+ -- Remove from cleanup tracking
97
+ local cleanup = _playerCleanup[player]
98
+ if cleanup then
99
+ local idx = table.find(cleanup, self)
100
+ if idx then
101
+ local last = #cleanup
102
+ cleanup[idx] = cleanup[last]
103
+ cleanup[last] = nil
104
+ end
85
105
  end
86
106
 
87
107
  return true
88
108
  end
89
109
 
90
- function GroupImpl.has(self: Group, player: Player): boolean
91
- local name = self._name
92
- local set = _groups[name]
93
- if not set then
94
- error(`[Lync] Group has been destroyed: "{name}"`)
95
- end
96
- return set[player] == true
97
- end
98
-
99
- function GroupImpl.count(self: Group): number
100
- local name = self._name
101
- if not _groups[name] then
102
- error(`[Lync] Group has been destroyed: "{name}"`)
103
- end
104
- return _counts[name]
110
+ function GroupMeta.has(self: any, player: Player): boolean
111
+ return self._members[player] == true
105
112
  end
106
113
 
107
- function GroupImpl.forEach(self: Group, fn: (player: Player) -> ()): ()
108
- local name = self._name
109
- local set = _groups[name]
110
- if not set then
111
- error(`[Lync] Group has been destroyed: "{name}"`)
112
- end
113
- for player in set do
114
- fn(player)
115
- end
116
- end
117
-
118
- function GroupImpl.getSet(self: Group): { [Player]: true }
119
- local name = self._name
120
- local set = _groups[name]
121
- if not set then
122
- error(`[Lync] Group has been destroyed: "{name}"`)
123
- end
124
- return set
114
+ function GroupMeta.count(self: any): number
115
+ return self._count
125
116
  end
126
117
 
127
- function GroupImpl.destroy(self: Group): ()
118
+ function GroupMeta.destroy(self: any): ()
128
119
  local name = self._name
129
- local set = _groups[name]
130
- if not set then
131
- error(`[Lync] Group has been destroyed: "{name}"`)
132
- end
133
-
134
- for player in set do
135
- local memberships = _playerGroups[player]
136
- if memberships then
137
- memberships[name] = nil
120
+ if name then
121
+ _groups[name] = nil
122
+ end
123
+
124
+ -- Remove all members from cleanup tracking
125
+ for player in self._members do
126
+ local cleanup = _playerCleanup[player]
127
+ if cleanup then
128
+ local idx = table.find(cleanup, self)
129
+ if idx then
130
+ local last = #cleanup
131
+ cleanup[idx] = cleanup[last]
132
+ cleanup[last] = nil
133
+ end
138
134
  end
139
135
  end
140
136
 
141
- _groups[name] = nil
142
- _counts[name] = nil
137
+ table.clear(self._members)
138
+ self._count = 0
143
139
  end
144
140
 
145
- table.freeze(GroupImpl)
146
-
147
- -- Public --------------------------------------------------------------
148
-
149
- local GroupModule = {}
150
-
151
- function GroupModule.create(name: string): Group
141
+ function Group.create(name: string): GroupHandle
152
142
  if _groups[name] then
153
- error(`[Lync] Group already exists: "{name}"`)
143
+ error(`[Lync] Lync.group: "{name}" already exists`)
154
144
  end
155
- _groups[name] = {}
156
- _counts[name] = 0
157
145
 
158
- return setmetatable({
159
- _tag = "group" :: "group",
146
+ ensureCleanup()
147
+
148
+ local group = setmetatable({
149
+ _lyncGroup = true,
150
+ _members = {} :: { [Player]: boolean },
151
+ _count = 0,
160
152
  _name = name,
161
- }, GroupImpl)
153
+ }, GroupMeta)
154
+
155
+ _groups[name] = group
156
+ return group :: any
162
157
  end
163
158
 
164
- return table.freeze(GroupModule)
159
+ return table.freeze(Group)