@axpecter/lync 2.2.0 → 2.3.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 (62) hide show
  1. package/README.md +95 -143
  2. package/package.json +1 -1
  3. package/src/Types.luau +22 -16
  4. package/src/api/Group.luau +40 -33
  5. package/src/api/Packet.luau +95 -72
  6. package/src/api/Query.luau +128 -79
  7. package/src/api/Scope.luau +26 -10
  8. package/src/api/Signal.luau +43 -52
  9. package/src/codec/Base.luau +21 -14
  10. package/src/codec/composite/Array.luau +56 -134
  11. package/src/codec/composite/Map.luau +103 -72
  12. package/src/codec/composite/Optional.luau +6 -2
  13. package/src/codec/composite/Shared.luau +160 -69
  14. package/src/codec/composite/Struct.luau +283 -42
  15. package/src/codec/composite/Tagged.luau +13 -16
  16. package/src/codec/composite/Tuple.luau +21 -15
  17. package/src/codec/datatype/Buffer.luau +6 -4
  18. package/src/codec/datatype/CFrame.luau +56 -44
  19. package/src/codec/datatype/Color.luau +10 -10
  20. package/src/codec/datatype/Instance.luau +15 -12
  21. package/src/codec/datatype/IntVector.luau +2 -2
  22. package/src/codec/datatype/NumberRange.luau +15 -8
  23. package/src/codec/datatype/Ray.luau +13 -14
  24. package/src/codec/datatype/Rect.luau +12 -12
  25. package/src/codec/datatype/Region.luau +13 -14
  26. package/src/codec/datatype/Sequence.luau +87 -65
  27. package/src/codec/datatype/String.luau +17 -10
  28. package/src/codec/datatype/UDim.luau +10 -8
  29. package/src/codec/datatype/Vector.luau +20 -59
  30. package/src/codec/meta/Auto.luau +94 -126
  31. package/src/codec/meta/Bitfield.luau +12 -14
  32. package/src/codec/meta/Custom.luau +3 -1
  33. package/src/codec/meta/Enum.luau +9 -9
  34. package/src/codec/meta/Float.luau +6 -28
  35. package/src/codec/meta/Nothing.luau +1 -1
  36. package/src/codec/meta/Unknown.luau +10 -7
  37. package/src/codec/primitive/Bool.luau +6 -4
  38. package/src/codec/primitive/Float16.luau +5 -2
  39. package/src/codec/primitive/Int.luau +5 -6
  40. package/src/codec/primitive/Number.luau +6 -4
  41. package/src/codec/primitive/Signed.luau +2 -2
  42. package/src/codec/primitive/Varint.luau +69 -38
  43. package/src/index.d.ts +161 -53
  44. package/src/init.luau +123 -102
  45. package/src/internal/Baseline.luau +9 -1
  46. package/src/internal/Channel.luau +153 -154
  47. package/src/internal/Middleware.luau +22 -6
  48. package/src/internal/Pool.luau +12 -4
  49. package/src/internal/Registry.luau +25 -16
  50. package/src/internal/Transport.luau +1 -1
  51. package/src/transport/Bridge.luau +47 -33
  52. package/src/transport/Client.luau +25 -21
  53. package/src/transport/Gate.luau +227 -172
  54. package/src/transport/Reader.luau +162 -105
  55. package/src/transport/Server.luau +46 -57
  56. package/src/util/Array.luau +18 -0
  57. package/src/util/Buffer.luau +92 -0
  58. package/src/util/Constants.luau +30 -0
  59. package/src/util/Log.luau +68 -0
  60. package/src/util/Player.luau +14 -0
  61. package/src/util/Quantize.luau +60 -0
  62. package/src/internal/Util.luau +0 -26
@@ -7,67 +7,77 @@ local RunService = game:GetService("RunService")
7
7
 
8
8
  local Baseline = require(script.Parent.Parent.internal.Baseline)
9
9
  local Channel = require(script.Parent.Parent.internal.Channel)
10
+ local Log = require(script.Parent.Parent.util.Log)
10
11
  local Middleware = require(script.Parent.Parent.internal.Middleware)
12
+ local Player = require(script.Parent.Parent.util.Player)
11
13
  local Registry = require(script.Parent.Parent.internal.Registry)
12
14
  local Transport = require(script.Parent.Parent.internal.Transport)
13
15
  local Types = require(script.Parent.Parent.Types)
14
- local Util = require(script.Parent.Parent.internal.Util)
15
16
 
16
17
  -- Constants --------------------------------------------------------------
17
18
 
18
19
  local IS_SERVER = RunService:IsServer()
19
20
 
21
+ -- mode: TS_* tag; headerSize: 1 (frame byte) + timestamp bytes (1, 2, or 8).
22
+ local TS_MODES: { [string]: { mode: number, headerSize: number } } = table.freeze({
23
+ frame = table.freeze({ mode = Channel.TS_FRAME, headerSize = 2 }),
24
+ offset = table.freeze({ mode = Channel.TS_OFFSET, headerSize = 3 }),
25
+ full = table.freeze({ mode = Channel.TS_FULL, headerSize = 9 }),
26
+ })
27
+
28
+ local EMPTY_OPTIONS: Types.PacketOptions = table.freeze({})
29
+
20
30
  -- Private ----------------------------------------------------------------
21
31
 
22
- local isPlayer = Util.isPlayer
32
+ local isPlayer = Player.is
23
33
 
24
34
  local function resolveTimestampMode(ts: ("frame" | "offset" | "full")?): (number, number)
25
- if ts == "frame" then
26
- Channel.enableTimestamps()
27
- return Channel.TS_FRAME, 2
28
- end
29
- if ts == "offset" then
30
- Channel.enableTimestamps()
31
- return Channel.TS_OFFSET, 3
35
+ if ts == nil then
36
+ return Channel.TS_NONE, 1
32
37
  end
33
- if ts == "full" then
34
- Channel.enableTimestamps()
35
- return Channel.TS_FULL, 9
36
- end
37
- return Channel.TS_NONE, 1
38
+ local entry = TS_MODES[ts]
39
+ Channel.enableTimestamps()
40
+ return entry.mode, entry.headerSize
38
41
  end
39
42
 
40
43
  -- Public types -----------------------------------------------------------
41
44
 
42
- export type PacketHandle = {
43
- send: (self: PacketHandle, data: any, target: any?) -> (),
44
- on: (self: PacketHandle, fn: (...any) -> ()) -> Types.Connection,
45
- once: (self: PacketHandle, fn: (...any) -> ()) -> Types.Connection,
46
- wait: (self: PacketHandle) -> ...any,
47
- name: (self: PacketHandle) -> string,
48
- stats: (self: PacketHandle) -> Types.PacketStats,
45
+ export type PacketHandle<T> = {
46
+ send: (self: PacketHandle<T>, data: T, target: any?) -> (),
47
+ on: (
48
+ self: PacketHandle<T>,
49
+ fn: (data: T, sender: Player?, timestamp: number?) -> ()
50
+ ) -> Types.Connection,
51
+ once: (
52
+ self: PacketHandle<T>,
53
+ fn: (data: T, sender: Player?, timestamp: number?) -> ()
54
+ ) -> Types.Connection,
55
+ wait: (self: PacketHandle<T>) -> (T, Player?, number?),
56
+ name: (self: PacketHandle<T>) -> string,
57
+ stats: (self: PacketHandle<T>) -> Types.PacketStats,
49
58
  }
50
59
 
51
60
  -- Public -----------------------------------------------------------------
52
61
 
53
62
  local Packet = {}
54
63
 
55
- function Packet.define(
64
+ function Packet.define<T>(
56
65
  name: string,
57
- codec: Types.InternalCodec<any>,
66
+ codec: Types.InternalCodec<T>,
58
67
  options: Types.PacketOptions?
59
- ): PacketHandle
60
- local opts = options or ({} :: Types.PacketOptions)
68
+ ): PacketHandle<T>
69
+ local opts = options or EMPTY_OPTIONS
61
70
  local isUnreliable = opts.unreliable or false
62
71
 
72
+ -- Delta + unreliable is unsafe: a dropped frame permanently desyncs the receiver baseline.
63
73
  if isUnreliable and codec._isDelta then
64
- error(`[Lync] Lync.packet: "{name}" cannot use delta codecs with unreliable transport`)
74
+ Log.error(
75
+ `"{name}" delta codecs need reliable transport (lost packets desync the baseline)`
76
+ )
65
77
  end
66
78
 
67
79
  if codec._hasUnknown and not opts.validate then
68
- warn(
69
- `[Lync] Lync.packet: "{name}" uses unknown codec without validate; data bypasses schema validation`
70
- )
80
+ Log.warn(`"{name}" uses unknown codec without validate; data bypasses schema validation`)
71
81
  end
72
82
 
73
83
  if codec._isDelta then
@@ -103,26 +113,34 @@ function Packet.define(
103
113
  end
104
114
  end
105
115
 
106
- local function applySend(data: any, player: Player?): any?
116
+ --[[
117
+ Run the send middleware chain. Returns (dropped, value). A nil value
118
+ is a legitimate payload (Lync.nothing); only the explicit Lync.DROP
119
+ sentinel or a hook returning DROP triggers the drop branch.
120
+ ]]
121
+ local function applySend(data: any, player: Player?): (boolean, any)
107
122
  if not Middleware.hasSendHooks() then
108
- return data
123
+ return false, data
109
124
  end
110
-
111
125
  local sendData = Middleware.runSend(data, name, player)
112
- if typeof(sendData) == "table" and sendData._lyncDrop then
113
- return nil
126
+ if typeof(sendData) == "table" and sendData._lyncKind == "drop" then
127
+ return true, nil
114
128
  end
115
- return sendData
129
+ return false, sendData
116
130
  end
117
131
 
118
- local function sendFromServer(data: any, target: any): ()
119
- if target == nil then
120
- error(`[Lync] Packet.send: "{name}" server send requires a target`)
132
+ -- Caller has already run send middleware once for the broadcast.
133
+ local function broadcast(players: { Player }, data: any): ()
134
+ for _, p in players do
135
+ sendToPlayer(p, data)
121
136
  end
137
+ bumpFires()
138
+ end
122
139
 
140
+ local function sendToTarget(data: any, target: any): ()
123
141
  if isPlayer(target) then
124
- local sendData = applySend(data, target :: Player)
125
- if sendData == nil then
142
+ local dropped, sendData = applySend(data, target :: Player)
143
+ if dropped then
126
144
  return
127
145
  end
128
146
  sendToPlayer(target :: Player, sendData)
@@ -131,57 +149,59 @@ function Packet.define(
131
149
  end
132
150
 
133
151
  if typeof(target) == "table" then
134
- local sendData = applySend(data, nil)
135
- if sendData == nil then
152
+ local dropped, sendData = applySend(data, nil)
153
+ if dropped then
136
154
  return
137
155
  end
138
156
 
139
- if target._lyncAll then
140
- for _, player in Players:GetPlayers() do
141
- sendToPlayer(player, sendData)
142
- end
143
- bumpFires()
157
+ local kind = target._lyncKind
158
+ if kind == "all" then
159
+ broadcast(Players:GetPlayers(), sendData)
144
160
  return
145
161
  end
146
162
 
147
- if target._lyncExcept then
163
+ if kind == "except" then
148
164
  local excluded = target._excluded :: { [Player]: boolean }
149
- for _, player in Players:GetPlayers() do
150
- if not excluded[player] then
151
- sendToPlayer(player, sendData)
165
+ for _, p in Players:GetPlayers() do
166
+ if not excluded[p] then
167
+ sendToPlayer(p, sendData)
152
168
  end
153
169
  end
154
170
  bumpFires()
155
171
  return
156
172
  end
157
173
 
158
- if target._lyncGroup then
159
- for player in target._members :: { [Player]: boolean } do
160
- sendToPlayer(player, sendData)
174
+ if kind == "group" then
175
+ for p in target._members :: { [Player]: boolean } do
176
+ sendToPlayer(p, sendData)
161
177
  end
162
178
  bumpFires()
163
179
  return
164
180
  end
165
181
 
166
- for _, player in target do
167
- if isPlayer(player) then
168
- sendToPlayer(player :: Player, sendData)
182
+ -- Plain { Player } array.
183
+ for _, p in target do
184
+ if isPlayer(p) then
185
+ sendToPlayer(p :: Player, sendData)
169
186
  end
170
187
  end
171
188
  bumpFires()
172
189
  return
173
190
  end
174
191
 
175
- error(
176
- `[Lync] Packet.send: "{name}" expected Player, table, Group, or Lync.all as target, got {typeof(
177
- target
178
- )}`
179
- )
192
+ Log.error(`"{name}" expected Player, table, Group, or Lync.all, got {typeof(target)}`)
193
+ end
194
+
195
+ local function sendFromServer(data: any, target: any): ()
196
+ if target == nil then
197
+ Log.error(`"{name}" server send requires a target`)
198
+ end
199
+ sendToTarget(data, target)
180
200
  end
181
201
 
182
202
  local function sendFromClient(data: any): ()
183
- local sendData = applySend(data, nil)
184
- if sendData == nil then
203
+ local dropped, sendData = applySend(data, nil)
204
+ if dropped then
185
205
  return
186
206
  end
187
207
  local ch = Transport.client().getChannel(isUnreliable)
@@ -189,8 +209,11 @@ function Packet.define(
189
209
  bumpFires()
190
210
  end
191
211
 
212
+ type Self = PacketHandle<T>
213
+ type Listener = (data: T, sender: Player?, timestamp: number?) -> ()
214
+
192
215
  local handle = {
193
- send = function(_self: PacketHandle, data: any, target: any?): ()
216
+ send = function(_self: Self, data: T, target: any?): ()
194
217
  if IS_SERVER then
195
218
  sendFromServer(data, target)
196
219
  else
@@ -198,11 +221,11 @@ function Packet.define(
198
221
  end
199
222
  end,
200
223
 
201
- on = function(_self: PacketHandle, fn: (...any) -> ()): Types.Connection
202
- return reg.signal:connect(fn)
224
+ on = function(_self: Self, fn: Listener): Types.Connection
225
+ return reg.signal:connect(fn :: any)
203
226
  end,
204
227
 
205
- once = function(_self: PacketHandle, fn: (...any) -> ()): Types.Connection
228
+ once = function(_self: Self, fn: Listener): Types.Connection
206
229
  local conn: Types.Connection
207
230
  conn = reg.signal:connect(function(...: any)
208
231
  conn:disconnect()
@@ -211,15 +234,15 @@ function Packet.define(
211
234
  return conn
212
235
  end,
213
236
 
214
- wait = function(_self: PacketHandle): ...any
237
+ wait = function(_self: Self): (T, Player?, number?)
215
238
  return reg.signal:wait()
216
239
  end,
217
240
 
218
- name = function(_self: PacketHandle): string
241
+ name = function(_self: Self): string
219
242
  return reg.name
220
243
  end,
221
244
 
222
- stats = function(_self: PacketHandle): Types.PacketStats
245
+ stats = function(_self: Self): Types.PacketStats
223
246
  return {
224
247
  bytesSent = reg.bytesSent,
225
248
  bytesReceived = reg.bytesReceived,
@@ -230,7 +253,7 @@ function Packet.define(
230
253
  end,
231
254
  }
232
255
 
233
- return table.freeze(handle) :: PacketHandle
256
+ return table.freeze(handle) :: PacketHandle<T>
234
257
  end
235
258
 
236
259
  return table.freeze(Packet)
@@ -6,54 +6,108 @@ local Players = game:GetService("Players")
6
6
  local RunService = game:GetService("RunService")
7
7
 
8
8
  local Channel = require(script.Parent.Parent.internal.Channel)
9
+ local Log = require(script.Parent.Parent.util.Log)
10
+ local Player = require(script.Parent.Parent.util.Player)
9
11
  local Registry = require(script.Parent.Parent.internal.Registry)
10
12
  local Transport = require(script.Parent.Parent.internal.Transport)
11
13
  local Types = require(script.Parent.Parent.Types)
12
- local Util = require(script.Parent.Parent.internal.Util)
13
14
 
14
15
  -- Constants --------------------------------------------------------------
15
16
 
16
17
  local IS_SERVER = RunService:IsServer()
17
18
  local DEFAULT_TIMEOUT = 5
18
19
 
20
+ local EMPTY_OPTIONS: Types.QueryOptions = table.freeze({})
21
+
19
22
  -- State ------------------------------------------------------------------
20
23
 
21
24
  local _nextCorrId = 0
25
+ -- Correlation -> waiting thread / completion closure. Cleared on resolve or timeout.
22
26
  local _pending: { [number]: thread } = {}
23
27
  local _pendingGather: { [number]: (any) -> () } = {}
24
28
 
25
29
  -- Private ----------------------------------------------------------------
26
30
 
27
- local isPlayer = Util.isPlayer
31
+ local isPlayer = Player.is
28
32
 
29
33
  local function nextCorr(): number
30
34
  _nextCorrId += 1
31
35
  return _nextCorrId
32
36
  end
33
37
 
38
+ local function writeQueryTracked(
39
+ ch: Types.ChannelState,
40
+ reg: Types.Registration,
41
+ corrId: number,
42
+ codec: Types.InternalCodec<any>?,
43
+ data: any
44
+ ): ()
45
+ if Channel.statsEnabled() then
46
+ local before = ch.cursor
47
+ Channel.writeQuery(ch, reg.id, corrId, codec, data)
48
+ reg.bytesSent += ch.cursor - before
49
+ else
50
+ Channel.writeQuery(ch, reg.id, corrId, codec, data)
51
+ end
52
+ end
53
+
54
+ local function collectTargets(target: any): { Player }
55
+ local targets: { Player } = {}
56
+ if typeof(target) ~= "table" then
57
+ return targets
58
+ end
59
+
60
+ local kind = target._lyncKind
61
+ if kind == "all" then
62
+ for _, p in Players:GetPlayers() do
63
+ table.insert(targets, p)
64
+ end
65
+ elseif kind == "group" then
66
+ for p in target._members do
67
+ table.insert(targets, p)
68
+ end
69
+ else
70
+ for _, p in target do
71
+ if isPlayer(p) then
72
+ table.insert(targets, p :: Player)
73
+ end
74
+ end
75
+ end
76
+ return targets
77
+ end
78
+
34
79
  -- Public types -----------------------------------------------------------
35
80
 
36
- export type QueryHandle = {
37
- handle: (self: QueryHandle, fn: (request: any, player: Player?) -> any) -> Types.Connection,
38
- request: (self: QueryHandle, data: any, target: any?) -> any,
39
- name: (self: QueryHandle) -> string,
40
- stats: (self: QueryHandle) -> Types.PacketStats,
81
+ --[[
82
+ Server `request(data, target)` returns `Resp?` for a Player target and
83
+ `{ [Player]: Resp? }` for a multi-target table; Luau lacks overloads on
84
+ table fields, so the type advertises `Resp?` and multi-target callers cast.
85
+ ]]
86
+ export type QueryHandle<Req, Resp> = {
87
+ handle: (
88
+ self: QueryHandle<Req, Resp>,
89
+ fn: (request: Req, player: Player?) -> Resp?
90
+ ) -> Types.Connection,
91
+ request: (self: QueryHandle<Req, Resp>, data: Req, target: any?) -> Resp?,
92
+ name: (self: QueryHandle<Req, Resp>) -> string,
93
+ stats: (self: QueryHandle<Req, Resp>) -> Types.PacketStats,
41
94
  }
42
95
 
43
96
  -- Public -----------------------------------------------------------------
44
97
 
45
98
  local Query = {}
46
99
 
47
- function Query.define(
100
+ function Query.define<Req, Resp>(
48
101
  name: string,
49
- requestCodec: Types.InternalCodec<any>,
50
- responseCodec: Types.InternalCodec<any>,
102
+ requestCodec: Types.InternalCodec<Req>,
103
+ responseCodec: Types.InternalCodec<Resp>,
51
104
  options: Types.QueryOptions?
52
- ): QueryHandle
53
- local opts = options or ({} :: Types.QueryOptions)
105
+ ): QueryHandle<Req, Resp>
106
+ local opts = options or EMPTY_OPTIONS
54
107
  local timeout = opts.timeout or DEFAULT_TIMEOUT
55
108
 
56
109
  local reqOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, name)
110
+ -- Embedded "\0resp" suffix stays uniquely paired with the request reg.
57
111
  local respName = `{name}\0resp`
58
112
  local respOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, respName)
59
113
 
@@ -89,6 +143,7 @@ function Query.define(
89
143
 
90
144
  reqReg.partner = respReg.id
91
145
 
146
+ -- Single active handler; replacing it disconnects the prior one via handlerToken.
92
147
  local activeHandler: ((any, Player?) -> any)? = nil
93
148
  local handlerToken = 0
94
149
 
@@ -113,18 +168,22 @@ function Query.define(
113
168
  end
114
169
 
115
170
  local ok, response = pcall(handler, value, sender)
171
+ if not ok then
172
+ Log.warn(`query "{name}" handler errored: {response}`)
173
+ end
174
+ -- pcall failure path returns nil response (writeQuery encodes a nil-MSB header).
116
175
  local payloadCodec = if ok then responseCodec else nil
117
176
  local payloadData = if ok then response else nil
118
177
 
119
- local ch = if IS_SERVER and sender
120
- then Transport.server().getChannel(sender, false)
121
- elseif not IS_SERVER then Transport.client().getChannel(false)
122
- else nil
178
+ local ch: Types.ChannelState? = if IS_SERVER
179
+ then (if sender then Transport.server().getChannel(sender, false) else nil)
180
+ else Transport.client().getChannel(false)
123
181
  if ch then
124
- Channel.writeQuery(ch, respReg.id, corrId, payloadCodec, payloadData)
182
+ writeQueryTracked(ch, respReg, corrId, payloadCodec, payloadData)
125
183
  end
126
184
  end)
127
185
 
186
+ -- Resolve the waiting thread with nil after `timeout` seconds.
128
187
  local function scheduleSingleTimeout(corrId: number, running: thread): ()
129
188
  task.delay(timeout, function()
130
189
  if _pending[corrId] == running then
@@ -134,10 +193,10 @@ function Query.define(
134
193
  end)
135
194
  end
136
195
 
137
- local function requestSingle(target: Player, data: any): any
196
+ -- Issue a single-target query on `ch` and yield until reply or timeout.
197
+ local function requestOnChannel(ch: Types.ChannelState, data: any): any
138
198
  local corrId = nextCorr()
139
- local ch = Transport.server().getChannel(target, false)
140
- Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
199
+ writeQueryTracked(ch, reqReg, corrId, requestCodec, data)
141
200
 
142
201
  local running = coroutine.running()
143
202
  _pending[corrId] = running
@@ -146,30 +205,41 @@ function Query.define(
146
205
  end
147
206
 
148
207
  local function requestMulti(targets: { Player }, data: any): { [Player]: any }
149
- if #targets == 0 then
208
+ local targetCount = #targets
209
+ if targetCount == 0 then
150
210
  return {}
151
211
  end
152
212
 
153
213
  local results: { [Player]: any } = {}
154
- local remaining = #targets
214
+ local remaining = targetCount
155
215
  local running = coroutine.running()
156
216
  local resolved = false
217
+ local corrIds = table.create(targetCount)
157
218
 
219
+ --[[
220
+ On timeout, surface the partial result table AND evict every
221
+ still-pending closure. Without eviction each unanswered closure
222
+ keeps `results`/`remaining`/`resolveOnce` alive: a slow leak.
223
+ ]]
158
224
  local function resolveOnce(): ()
159
225
  if resolved then
160
226
  return
161
227
  end
162
228
  resolved = true
229
+ for i = 1, targetCount do
230
+ _pendingGather[corrIds[i]] = nil
231
+ end
163
232
  task.spawn(running, results)
164
233
  end
165
234
 
166
235
  local Server = Transport.server()
167
- for _, player in targets do
236
+ for i, p in targets do
168
237
  local pCorr = nextCorr()
169
- local ch = Server.getChannel(player, false)
170
- Channel.writeQuery(ch, reqReg.id, pCorr, requestCodec, data)
238
+ corrIds[i] = pCorr
239
+ local ch = Server.getChannel(p, false)
240
+ writeQueryTracked(ch, reqReg, pCorr, requestCodec, data)
171
241
 
172
- local pRef = player
242
+ local pRef = p
173
243
  _pendingGather[pCorr] = function(response: any)
174
244
  results[pRef] = response
175
245
  remaining -= 1
@@ -183,85 +253,64 @@ function Query.define(
183
253
  return coroutine.yield()
184
254
  end
185
255
 
186
- local function collectTargets(target: any): { Player }
187
- local targets: { Player } = {}
188
- if typeof(target) ~= "table" then
189
- return targets
190
- end
191
-
192
- if target._lyncAll then
193
- for _, p in Players:GetPlayers() do
194
- table.insert(targets, p)
195
- end
196
- elseif target._lyncGroup then
197
- for player in target._members do
198
- table.insert(targets, player)
199
- end
200
- else
201
- for _, p in target do
202
- if isPlayer(p) then
203
- table.insert(targets, p :: Player)
204
- end
205
- end
206
- end
207
- return targets
208
- end
209
-
210
256
  local function requestFromServer(data: any, target: any): any
211
257
  if target == nil then
212
- error(`[Lync] Query.request: "{name}" server request requires a target`)
258
+ Log.error(`"{name}" server request requires a target`)
213
259
  end
214
260
  if isPlayer(target) then
215
- return requestSingle(target :: Player, data)
261
+ return requestOnChannel(Transport.server().getChannel(target :: Player, false), data)
216
262
  end
217
263
  return requestMulti(collectTargets(target), data)
218
264
  end
219
265
 
220
266
  local function requestFromClient(data: any): any
221
- local corrId = nextCorr()
222
- local ch = Transport.client().getChannel(false)
223
- Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
224
-
225
- local running = coroutine.running()
226
- _pending[corrId] = running
227
- scheduleSingleTimeout(corrId, running)
228
- return coroutine.yield()
267
+ return requestOnChannel(Transport.client().getChannel(false), data)
229
268
  end
230
269
 
270
+ type Self = QueryHandle<Req, Resp>
271
+
231
272
  local handle = {
273
+ --[[
274
+ Replace the active handler. The returned Connection only clears
275
+ the active slot if no later :handle has overwritten it: token
276
+ comparison guards against a stale disconnect wiping a fresh handler.
277
+ ]]
232
278
  handle = function(
233
- _self: QueryHandle,
234
- fn: (request: any, player: Player?) -> any
279
+ _self: Self,
280
+ fn: (request: Req, player: Player?) -> Resp?
235
281
  ): Types.Connection
236
282
  handlerToken += 1
237
283
  local myToken = handlerToken
238
- activeHandler = fn
239
-
240
- local conn = { connected = true }
241
- function conn.disconnect(self): ()
242
- if not self.connected then
243
- return
244
- end
245
- self.connected = false
246
- if handlerToken == myToken then
247
- activeHandler = nil
248
- end
249
- end
250
- return conn :: Types.Connection
284
+ activeHandler = fn :: any
285
+
286
+ local conn: Types.Connection
287
+ conn = {
288
+ connected = true,
289
+ disconnect = function(self): ()
290
+ if not self.connected then
291
+ return
292
+ end
293
+ self.connected = false
294
+ if handlerToken == myToken then
295
+ activeHandler = nil
296
+ end
297
+ end,
298
+ }
299
+ return conn
251
300
  end,
252
301
 
253
- request = function(_self: QueryHandle, data: any, target: any?): any
302
+ request = function(_self: Self, data: Req, target: any?): Resp?
254
303
  if IS_SERVER then
255
304
  return requestFromServer(data, target)
256
305
  end
257
306
  return requestFromClient(data)
258
307
  end,
259
308
 
260
- name = function(_self: QueryHandle): string
309
+ name = function(_self: Self): string
261
310
  return name
262
311
  end,
263
312
 
264
- stats = function(_self: QueryHandle): Types.PacketStats
313
+ stats = function(_self: Self): Types.PacketStats
265
314
  return {
266
315
  bytesSent = reqReg.bytesSent + respReg.bytesSent,
267
316
  bytesReceived = reqReg.bytesReceived + respReg.bytesReceived,
@@ -272,7 +321,7 @@ function Query.define(
272
321
  end,
273
322
  }
274
323
 
275
- return table.freeze(handle) :: QueryHandle
324
+ return table.freeze(handle) :: QueryHandle<Req, Resp>
276
325
  end
277
326
 
278
327
  function Query.pendingCount(): number