@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
@@ -1,233 +1,234 @@
1
1
  --!strict
2
2
  --!optimize 2
3
- -- Packet definition and send/listen API.
3
+ -- Packet definition, send, on, once, wait, stats.
4
4
 
5
+ local Players = game:GetService("Players")
5
6
  local RunService = game:GetService("RunService")
6
7
 
8
+ local Baseline = require(script.Parent.Parent.internal.Baseline)
7
9
  local Channel = require(script.Parent.Parent.internal.Channel)
8
- local Client = require(script.Parent.Parent.transport.Client)
10
+ local Middleware = require(script.Parent.Parent.internal.Middleware)
9
11
  local Registry = require(script.Parent.Parent.internal.Registry)
10
- local Server = require(script.Parent.Parent.transport.Server)
11
- local Signal = require(script.Parent.Signal)
12
12
  local Types = require(script.Parent.Parent.Types)
13
13
 
14
- type Connection = Types.Connection
15
- type Codec<T> = Types.Codec<T>
16
- type InternalCodec<T> = Types.InternalCodec<T>
17
- type PacketConfig<T> = Types.PacketConfig<T>
18
- type Registration = Types.Registration
19
-
20
14
  -- Constants -----------------------------------------------------------
21
15
 
22
16
  local IS_SERVER = RunService:IsServer()
23
17
 
24
- local DELTA_TARGETED = 1
25
- local DELTA_BROADCAST = 2
26
-
27
- local TS_LOOKUP = {
28
- frame = Channel.TS_FRAME,
29
- offset = Channel.TS_OFFSET,
30
- full = Channel.TS_FULL,
31
- }
32
-
33
18
  -- Private -------------------------------------------------------------
34
19
 
35
- type PacketFields = {
36
- _reg: Registration,
37
- _isUnreliable: boolean,
38
- _isDelta: boolean,
39
- _deltaMode: number,
40
- _signal: Signal.Signal,
41
- }
42
-
43
- local function checkDeltaMode(self: any, mode: number): ()
44
- if not self._isDelta then
45
- return
46
- end
47
- local current = self._deltaMode
48
- if current == 0 then
49
- self._deltaMode = mode
50
- elseif current ~= mode then
51
- error(`[Lync] Delta packet cannot mix targeted and broadcast: "{self._reg.name}"`)
20
+ local function resolveTimestampMode(ts: string?): (number, number)
21
+ if ts == "frame" then
22
+ Channel.enableTimestamps()
23
+ return Channel.TS_FRAME, 2
24
+ elseif ts == "offset" then
25
+ Channel.enableTimestamps()
26
+ return Channel.TS_OFFSET, 3
27
+ elseif ts == "full" then
28
+ Channel.enableTimestamps()
29
+ return Channel.TS_FULL, 9
52
30
  end
31
+ return Channel.TS_NONE, 1
53
32
  end
54
33
 
55
- -- Server-only metatable
56
- local ServerImpl = {}
57
- ServerImpl.__index = ServerImpl
58
-
59
- export type Packet = typeof(setmetatable({} :: PacketFields, ServerImpl))
60
-
61
- function ServerImpl.listen(self: Packet, callback: (...any) -> ()): Connection
62
- return self._signal:connect(callback)
63
- end
64
-
65
- function ServerImpl.once(self: Packet, callback: (...any) -> ()): Connection
66
- return self._signal:once(callback)
67
- end
68
-
69
- function ServerImpl.wait(self: Packet): ...any
70
- return self._signal:wait()
71
- end
72
-
73
- function ServerImpl.disconnectAll(self: Packet): ()
74
- self._signal:disconnectAll()
75
- end
76
-
77
- function ServerImpl.send(self: Packet, data: any, target: any?): ()
78
- if target == nil then
79
- error("[Lync] Server packet:send requires a target")
80
- end
34
+ -- Public --------------------------------------------------------------
81
35
 
82
- local reg = self._reg
83
- local isUnreliable = self._isUnreliable
36
+ local Packet = {}
84
37
 
85
- if typeof(target) == "Instance" then
86
- checkDeltaMode(self, DELTA_TARGETED)
87
- Server.writeTo(target :: Player, reg, data, isUnreliable)
88
- return
89
- end
38
+ export type PacketHandle = {
39
+ send: (self: PacketHandle, data: any, target: any?) -> (),
40
+ on: (self: PacketHandle, fn: (...any) -> ()) -> Types.Connection,
41
+ once: (self: PacketHandle, fn: (...any) -> ()) -> Types.Connection,
42
+ wait: (self: PacketHandle) -> ...any,
43
+ name: (self: PacketHandle) -> string,
44
+ stats: (self: PacketHandle) -> Types.PacketStats,
45
+ }
90
46
 
91
- local tag = target._tag
47
+ function Packet.define(
48
+ name: string,
49
+ codec: Types.InternalCodec<any>,
50
+ options: Types.PacketOptions?
51
+ ): PacketHandle
52
+ local opts = options or {} :: Types.PacketOptions
53
+ local isUnreliable = opts.unreliable or false
92
54
 
93
- if tag == "all" then
94
- checkDeltaMode(self, DELTA_BROADCAST)
95
- Server.writeToAll(reg, data, isUnreliable)
96
- return
55
+ if isUnreliable and (codec :: any)._isDelta then
56
+ error(`[Lync] Lync.packet: "{name}" cannot use delta codecs with unreliable transport`)
97
57
  end
98
58
 
99
- if tag == "except" then
100
- checkDeltaMode(self, DELTA_BROADCAST)
101
- Server.writeToAllExcept(target._set, reg, data, isUnreliable)
102
- return
59
+ if (codec :: any)._hasUnknown and not opts.validate then
60
+ warn(
61
+ `[Lync] Lync.packet: "{name}" uses unknown codec without validate — data bypasses schema validation`
62
+ )
103
63
  end
104
64
 
105
- if tag == "group" then
106
- checkDeltaMode(self, DELTA_BROADCAST)
107
- Server.writeToSet(target:getSet(), reg, data, isUnreliable)
108
- return
65
+ if (codec :: any)._isDelta then
66
+ Baseline.markHasDelta()
109
67
  end
110
68
 
111
- checkDeltaMode(self, DELTA_TARGETED)
112
- Server.writeToList(target :: { Player }, reg, data, isUnreliable)
113
- end
69
+ local tsMode, headerSize = resolveTimestampMode(opts.timestamp)
70
+ local needsGate = IS_SERVER
114
71
 
115
- -- Stats: read directly from registration
116
- function ServerImpl.getBytesSent(self: Packet): number
117
- return self._reg.bytesSent
118
- end
119
-
120
- function ServerImpl.getBytesReceived(self: Packet): number
121
- return self._reg.bytesReceived
122
- end
123
-
124
- function ServerImpl.getFires(self: Packet): number
125
- return self._reg.fires
126
- end
127
-
128
- function ServerImpl.getRecvFires(self: Packet): number
129
- return self._reg.recvFires
130
- end
72
+ local openFn = Channel.resolveOpenFn(tsMode, name)
131
73
 
132
- function ServerImpl.getDrops(self: Packet): number
133
- return self._reg.drops
134
- end
135
-
136
- table.freeze(ServerImpl)
137
-
138
- -- Client-only metatable
139
- local ClientImpl = {}
140
- ClientImpl.__index = ClientImpl
141
-
142
- function ClientImpl.listen(self: Packet, callback: (...any) -> ()): Connection
143
- return self._signal:connect(callback)
144
- end
145
-
146
- function ClientImpl.once(self: Packet, callback: (...any) -> ()): Connection
147
- return self._signal:once(callback)
148
- end
149
-
150
- function ClientImpl.wait(self: Packet): ...any
151
- return self._signal:wait()
152
- end
153
-
154
- function ClientImpl.disconnectAll(self: Packet): ()
155
- self._signal:disconnectAll()
156
- end
157
-
158
- function ClientImpl.send(self: Packet, data: any): ()
159
- Client.write(self._reg, data, self._isUnreliable)
160
- end
161
-
162
- ClientImpl.getBytesSent = ServerImpl.getBytesSent
163
- ClientImpl.getBytesReceived = ServerImpl.getBytesReceived
164
- ClientImpl.getFires = ServerImpl.getFires
165
- ClientImpl.getRecvFires = ServerImpl.getRecvFires
166
- ClientImpl.getDrops = ServerImpl.getDrops
167
-
168
- table.freeze(ClientImpl)
169
-
170
- -- Public --------------------------------------------------------------
74
+ local reg = Registry.register(
75
+ name,
76
+ Registry.KIND_PACKET,
77
+ codec,
78
+ isUnreliable,
79
+ nil,
80
+ needsGate,
81
+ headerSize,
82
+ tsMode,
83
+ openFn,
84
+ opts.rateLimit,
85
+ opts.validate,
86
+ opts.maxPayloadBytes
87
+ )
171
88
 
172
- local PacketModule = {}
89
+ -- Lazy-require Server/Client to avoid circular dependency
90
+ local _server: any = nil
91
+ local _client: any = nil
173
92
 
174
- function PacketModule.define(name: string, config: PacketConfig<any>): Packet
175
- if #name == 0 then
176
- error("[Lync] Packet name must not be empty")
93
+ local function getServer(): any
94
+ if not _server then
95
+ _server = require(script.Parent.Parent.transport.Server)
96
+ end
97
+ return _server
177
98
  end
178
- if not config.value then
179
- error(`[Lync] Packet requires a value codec: "{name}"`)
180
- end
181
-
182
- local isUnreliable = config.unreliable or false
183
99
 
184
- if (config.value :: InternalCodec<any>)._isDelta and isUnreliable then
185
- error(`[Lync] Delta codec requires reliable delivery: "{name}"`)
100
+ local function getClient(): any
101
+ if not _client then
102
+ _client = require(script.Parent.Parent.transport.Client)
103
+ end
104
+ return _client
186
105
  end
187
106
 
188
- local timestampMode = if config.timestamp then TS_LOOKUP[config.timestamp] else nil
189
- if config.timestamp and not timestampMode then
190
- error(`[Lync] Invalid timestamp mode: "{config.timestamp}" on packet "{name}"`)
107
+ local handle: any = {}
108
+
109
+ function handle.send(self: PacketHandle, data: any, target: any?): ()
110
+ if Middleware.hasSendHooks() then
111
+ data = Middleware.runSend(data, name, nil)
112
+ if typeof(data) == "table" and (data :: any)._lyncDrop then
113
+ return
114
+ end
115
+ end
116
+
117
+ if IS_SERVER then
118
+ if target == nil then
119
+ error(`[Lync] Packet "{name}": server send requires a target`)
120
+ end
121
+
122
+ local Server = getServer()
123
+
124
+ if typeof(target) == "Instance" and target:IsA("Player") then
125
+ local ch = Server.getChannel(target :: Player, isUnreliable)
126
+ Channel.writeBatch(ch, reg, data)
127
+ if Channel.statsEnabled() then
128
+ reg.fires += 1
129
+ end
130
+ return
131
+ end
132
+
133
+ if typeof(target) == "table" then
134
+ local tbl = target :: any
135
+
136
+ if tbl._lyncAll then
137
+ for _, player in Players:GetPlayers() do
138
+ local ch = Server.getChannel(player, isUnreliable)
139
+ Channel.writeBatch(ch, reg, data)
140
+ end
141
+ if Channel.statsEnabled() then
142
+ reg.fires += 1
143
+ end
144
+ return
145
+ end
146
+
147
+ if tbl._lyncExcept then
148
+ local excluded = tbl._excluded :: { [Player]: boolean }
149
+ for _, player in Players:GetPlayers() do
150
+ if not excluded[player] then
151
+ local ch = Server.getChannel(player, isUnreliable)
152
+ Channel.writeBatch(ch, reg, data)
153
+ end
154
+ end
155
+ if Channel.statsEnabled() then
156
+ reg.fires += 1
157
+ end
158
+ return
159
+ end
160
+
161
+ if tbl._lyncGroup then
162
+ local members = tbl._members :: { [Player]: boolean }
163
+ for player in members do
164
+ local ch = Server.getChannel(player, isUnreliable)
165
+ Channel.writeBatch(ch, reg, data)
166
+ end
167
+ if Channel.statsEnabled() then
168
+ reg.fires += 1
169
+ end
170
+ return
171
+ end
172
+
173
+ for _, player in tbl do
174
+ if typeof(player) == "Instance" and (player :: Instance):IsA("Player") then
175
+ local ch = Server.getChannel(player :: Player, isUnreliable)
176
+ Channel.writeBatch(ch, reg, data)
177
+ end
178
+ end
179
+ if Channel.statsEnabled() then
180
+ reg.fires += 1
181
+ end
182
+ return
183
+ end
184
+
185
+ error(
186
+ `[Lync] Packet "{name}": expected Player, table, Group, or Lync.all as target, got {typeof(
187
+ target
188
+ )}`
189
+ )
190
+ else
191
+ local Client = getClient()
192
+ local ch = Client.getChannel(isUnreliable)
193
+ Channel.writeBatch(ch, reg, data)
194
+ if Channel.statsEnabled() then
195
+ reg.fires += 1
196
+ end
197
+ end
191
198
  end
192
199
 
193
- if timestampMode then
194
- Channel.enableTimestamps()
200
+ function handle.on(_self: PacketHandle, fn: (...any) -> ()): Types.Connection
201
+ return reg.signal:connect(fn)
195
202
  end
196
203
 
197
- local hasUnknown = (config.value :: any)._hasUnknown
198
- if hasUnknown and not config.validate then
199
- warn(
200
- `[Lync] Packet "{name}" uses Lync.unknown: client data bypasses schema validation. Add a validate callback.`
201
- )
204
+ function handle.once(_self: PacketHandle, fn: (...any) -> ()): Types.Connection
205
+ local conn: Types.Connection
206
+ conn = reg.signal:connect(function(...: any)
207
+ conn:disconnect()
208
+ fn(...)
209
+ end)
210
+ return conn
202
211
  end
203
212
 
204
- local signal = Signal.create()
205
-
206
- local reg = Registry.register(
207
- name,
208
- config.value,
209
- isUnreliable,
210
- signal,
211
- config.rateLimit,
212
- config.validate,
213
- config.maxPayloadBytes,
214
- timestampMode
215
- )
216
-
217
- local isDelta = (config.value :: InternalCodec<any>)._isDelta == true
213
+ function handle.wait(_self: PacketHandle): ...any
214
+ return reg.signal:wait()
215
+ end
218
216
 
219
- local fields: PacketFields = {
220
- _reg = reg,
221
- _isUnreliable = isUnreliable,
222
- _isDelta = isDelta,
223
- _deltaMode = 0,
224
- _signal = signal,
225
- }
217
+ function handle.name(_self: PacketHandle): string
218
+ return reg.name
219
+ end
226
220
 
227
- if IS_SERVER then
228
- return setmetatable(fields, ServerImpl)
221
+ function handle.stats(_self: PacketHandle): Types.PacketStats
222
+ return {
223
+ bytesSent = reg.bytesSent,
224
+ bytesReceived = reg.bytesReceived,
225
+ fires = reg.fires,
226
+ recvFires = reg.recvFires,
227
+ drops = reg.drops,
228
+ }
229
229
  end
230
- return setmetatable(fields, ClientImpl) :: Packet
230
+
231
+ return table.freeze(handle)
231
232
  end
232
233
 
233
- return table.freeze(PacketModule)
234
+ return table.freeze(Packet)