@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
@@ -1,200 +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
 
7
- local Client = require(script.Parent.Parent.transport.Client)
8
+ local Baseline = require(script.Parent.Parent.internal.Baseline)
9
+ local Channel = require(script.Parent.Parent.internal.Channel)
10
+ local Middleware = require(script.Parent.Parent.internal.Middleware)
8
11
  local Registry = require(script.Parent.Parent.internal.Registry)
9
- local Server = require(script.Parent.Parent.transport.Server)
10
- local Signal = require(script.Parent.Signal)
11
12
  local Types = require(script.Parent.Parent.Types)
12
13
 
13
- type Connection = Types.Connection
14
- type Codec<T> = Types.Codec<T>
15
- type InternalCodec<T> = Types.InternalCodec<T>
16
- type PacketConfig<T> = Types.PacketConfig<T>
17
-
18
14
  -- Constants -----------------------------------------------------------
19
15
 
20
16
  local IS_SERVER = RunService:IsServer()
21
17
 
22
- local serverWriteTo = Server.writeTo
23
- local serverWriteToAll = Server.writeToAll
24
- local serverWriteToList = Server.writeToList
25
- local serverWriteToAllExcept = Server.writeToAllExcept
26
- local serverWriteToSet = Server.writeToSet
27
- local clientWrite = Client.write
28
-
29
- local DELTA_TARGETED = 1
30
- local DELTA_BROADCAST = 2
31
-
32
18
  -- Private -------------------------------------------------------------
33
19
 
34
- type PacketFields = {
35
- _id: number,
36
- _name: string,
37
- _codec: Codec<any>,
38
- _isUnreliable: boolean,
39
- _isDelta: boolean,
40
- _deltaMode: number,
41
- _signal: Signal.Signal,
42
- }
43
-
44
- -- Server-only metatable. Single send() dispatches on target type.
45
- local ServerImpl = {}
46
- ServerImpl.__index = ServerImpl
47
-
48
- export type Packet = typeof(setmetatable({} :: PacketFields, ServerImpl))
49
-
50
- function ServerImpl.listen(self: Packet, callback: (...any) -> ()): Connection
51
- return self._signal:connect(callback)
52
- end
53
-
54
- function ServerImpl.once(self: Packet, callback: (...any) -> ()): Connection
55
- return self._signal:once(callback)
56
- end
57
-
58
- function ServerImpl.wait(self: Packet): ...any
59
- return self._signal:wait()
60
- end
61
-
62
- function ServerImpl.disconnectAll(self: Packet): ()
63
- self._signal:disconnectAll()
64
- end
65
-
66
- local function checkDeltaMode(self: Packet, mode: number): ()
67
- if not self._isDelta then
68
- return
69
- end
70
- local current = self._deltaMode
71
- if current == 0 then
72
- self._deltaMode = mode
73
- elseif current ~= mode then
74
- error(`[Lync] Delta packet cannot mix targeted and broadcast: "{self._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
75
30
  end
31
+ return Channel.TS_NONE, 1
76
32
  end
77
33
 
78
- function ServerImpl.send(self: Packet, data: any, target: any?): ()
79
- if target == nil then
80
- error("[Lync] Server packet:send requires a target")
81
- end
34
+ -- Public --------------------------------------------------------------
82
35
 
83
- local id = self._id
84
- local name = self._name
85
- local codec = self._codec
86
- local isUnreliable = self._isUnreliable
36
+ local Packet = {}
87
37
 
88
- -- Single player
89
- if typeof(target) == "Instance" then
90
- checkDeltaMode(self, DELTA_TARGETED)
91
- serverWriteTo(target :: Player, id, name, codec, data, isUnreliable)
92
- return
93
- 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
+ }
94
46
 
95
- -- Must be a table from here
96
- 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
97
54
 
98
- -- Lync.all sentinel
99
- if tag == "all" then
100
- checkDeltaMode(self, DELTA_BROADCAST)
101
- serverWriteToAll(id, name, codec, data, isUnreliable)
102
- return
55
+ if isUnreliable and (codec :: any)._isDelta then
56
+ error(`[Lync] Lync.packet: "{name}" cannot use delta codecs with unreliable transport`)
103
57
  end
104
58
 
105
- -- Lync.except(player, ...) descriptor
106
- if tag == "except" then
107
- checkDeltaMode(self, DELTA_BROADCAST)
108
- serverWriteToAllExcept(target._set, id, name, codec, data, isUnreliable)
109
- 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
+ )
110
63
  end
111
64
 
112
- -- Group object
113
- if tag == "group" then
114
- checkDeltaMode(self, DELTA_BROADCAST)
115
- serverWriteToSet(target:getSet(), id, name, codec, data, isUnreliable)
116
- return
65
+ if (codec :: any)._isDelta then
66
+ Baseline.markHasDelta()
117
67
  end
118
68
 
119
- -- Player list(array)
120
- checkDeltaMode(self, DELTA_TARGETED)
121
- serverWriteToList(target :: { Player }, id, name, codec, data, isUnreliable)
122
- end
123
-
124
- table.freeze(ServerImpl)
69
+ local tsMode, headerSize = resolveTimestampMode(opts.timestamp)
70
+ local needsGate = IS_SERVER
125
71
 
126
- -- Client-only metatable. Has send(), no target needed.
127
- local ClientImpl = {}
128
- ClientImpl.__index = ClientImpl
72
+ local openFn = Channel.resolveOpenFn(tsMode, name)
129
73
 
130
- function ClientImpl.listen(self: Packet, callback: (...any) -> ()): Connection
131
- return self._signal:connect(callback)
132
- end
133
-
134
- function ClientImpl.once(self: Packet, callback: (...any) -> ()): Connection
135
- return self._signal:once(callback)
136
- end
137
-
138
- function ClientImpl.wait(self: Packet): ...any
139
- return self._signal:wait()
140
- end
141
-
142
- function ClientImpl.disconnectAll(self: Packet): ()
143
- self._signal:disconnectAll()
144
- end
145
-
146
- function ClientImpl.send(self: Packet, data: any): ()
147
- clientWrite(self._id, self._name, self._codec, data, self._isUnreliable)
148
- end
149
-
150
- table.freeze(ClientImpl)
151
-
152
- -- 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
+ )
153
88
 
154
- local PacketModule = {}
89
+ -- Lazy-require Server/Client to avoid circular dependency
90
+ local _server: any = nil
91
+ local _client: any = nil
155
92
 
156
- function PacketModule.define(name: string, config: PacketConfig<any>): Packet
157
- if #name == 0 then
158
- error("[Lync] Packet name must not be empty")
159
- end
160
- if not config.value then
161
- error(`[Lync] Packet requires a value codec: "{name}"`)
93
+ local function getServer(): any
94
+ if not _server then
95
+ _server = require(script.Parent.Parent.transport.Server)
96
+ end
97
+ return _server
162
98
  end
163
99
 
164
- local isUnreliable = config.unreliable or false
100
+ local function getClient(): any
101
+ if not _client then
102
+ _client = require(script.Parent.Parent.transport.Client)
103
+ end
104
+ return _client
105
+ end
165
106
 
166
- if (config.value :: InternalCodec<any>)._isDelta and isUnreliable then
167
- error(`[Lync] Delta codec requires reliable delivery: "{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
168
198
  end
169
199
 
170
- local signal = Signal.create()
200
+ function handle.on(_self: PacketHandle, fn: (...any) -> ()): Types.Connection
201
+ return reg.signal:connect(fn)
202
+ end
171
203
 
172
- local reg = Registry.register(
173
- name,
174
- config.value,
175
- isUnreliable,
176
- signal,
177
- config.rateLimit,
178
- config.validate,
179
- config.maxPayloadBytes
180
- )
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
211
+ end
181
212
 
182
- local isDelta = (config.value :: InternalCodec<any>)._isDelta == true
213
+ function handle.wait(_self: PacketHandle): ...any
214
+ return reg.signal:wait()
215
+ end
183
216
 
184
- local fields: PacketFields = {
185
- _id = reg.id,
186
- _name = reg.name,
187
- _codec = reg.codec,
188
- _isUnreliable = isUnreliable,
189
- _isDelta = isDelta,
190
- _deltaMode = 0,
191
- _signal = signal,
192
- }
217
+ function handle.name(_self: PacketHandle): string
218
+ return reg.name
219
+ end
193
220
 
194
- if IS_SERVER then
195
- 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
+ }
196
229
  end
197
- return setmetatable(fields, ClientImpl) :: Packet
230
+
231
+ return table.freeze(handle)
198
232
  end
199
233
 
200
- return table.freeze(PacketModule)
234
+ return table.freeze(Packet)