@axpecter/lync 2.2.1 → 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.
- package/README.md +95 -143
- package/package.json +1 -1
- package/src/Types.luau +22 -16
- package/src/api/Group.luau +40 -33
- package/src/api/Packet.luau +68 -54
- package/src/api/Query.luau +103 -65
- package/src/api/Scope.luau +26 -10
- package/src/api/Signal.luau +43 -52
- package/src/codec/Base.luau +21 -14
- package/src/codec/composite/Array.luau +56 -134
- package/src/codec/composite/Map.luau +103 -72
- package/src/codec/composite/Optional.luau +6 -2
- package/src/codec/composite/Shared.luau +160 -69
- package/src/codec/composite/Struct.luau +283 -42
- package/src/codec/composite/Tagged.luau +13 -16
- package/src/codec/composite/Tuple.luau +21 -15
- package/src/codec/datatype/Buffer.luau +6 -4
- package/src/codec/datatype/CFrame.luau +56 -44
- package/src/codec/datatype/Color.luau +10 -10
- package/src/codec/datatype/Instance.luau +15 -12
- package/src/codec/datatype/IntVector.luau +2 -2
- package/src/codec/datatype/NumberRange.luau +15 -8
- package/src/codec/datatype/Ray.luau +13 -14
- package/src/codec/datatype/Rect.luau +12 -12
- package/src/codec/datatype/Region.luau +13 -14
- package/src/codec/datatype/Sequence.luau +87 -65
- package/src/codec/datatype/String.luau +17 -10
- package/src/codec/datatype/UDim.luau +10 -8
- package/src/codec/datatype/Vector.luau +20 -59
- package/src/codec/meta/Auto.luau +94 -126
- package/src/codec/meta/Bitfield.luau +12 -14
- package/src/codec/meta/Custom.luau +3 -1
- package/src/codec/meta/Enum.luau +9 -9
- package/src/codec/meta/Float.luau +6 -28
- package/src/codec/meta/Nothing.luau +1 -1
- package/src/codec/meta/Unknown.luau +10 -7
- package/src/codec/primitive/Bool.luau +6 -4
- package/src/codec/primitive/Float16.luau +5 -2
- package/src/codec/primitive/Int.luau +5 -6
- package/src/codec/primitive/Number.luau +6 -4
- package/src/codec/primitive/Signed.luau +2 -2
- package/src/codec/primitive/Varint.luau +69 -38
- package/src/index.d.ts +161 -53
- package/src/init.luau +109 -103
- package/src/internal/Baseline.luau +9 -1
- package/src/internal/Channel.luau +153 -154
- package/src/internal/Middleware.luau +22 -6
- package/src/internal/Pool.luau +12 -4
- package/src/internal/Registry.luau +25 -16
- package/src/internal/Transport.luau +1 -1
- package/src/transport/Bridge.luau +47 -33
- package/src/transport/Client.luau +25 -21
- package/src/transport/Gate.luau +227 -172
- package/src/transport/Reader.luau +162 -105
- package/src/transport/Server.luau +46 -57
- package/src/util/Array.luau +18 -0
- package/src/util/Buffer.luau +92 -0
- package/src/util/Constants.luau +30 -0
- package/src/util/Log.luau +68 -0
- package/src/util/Player.luau +14 -0
- package/src/util/Quantize.luau +60 -0
- package/src/internal/Util.luau +0 -26
package/src/api/Packet.luau
CHANGED
|
@@ -7,34 +7,37 @@ 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 =
|
|
32
|
+
local isPlayer = Player.is
|
|
23
33
|
|
|
24
34
|
local function resolveTimestampMode(ts: ("frame" | "offset" | "full")?): (number, number)
|
|
25
|
-
if ts ==
|
|
26
|
-
Channel.
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 -----------------------------------------------------------
|
|
@@ -63,17 +66,18 @@ function Packet.define<T>(
|
|
|
63
66
|
codec: Types.InternalCodec<T>,
|
|
64
67
|
options: Types.PacketOptions?
|
|
65
68
|
): PacketHandle<T>
|
|
66
|
-
local opts = options or
|
|
69
|
+
local opts = options or EMPTY_OPTIONS
|
|
67
70
|
local isUnreliable = opts.unreliable or false
|
|
68
71
|
|
|
72
|
+
-- Delta + unreliable is unsafe: a dropped frame permanently desyncs the receiver baseline.
|
|
69
73
|
if isUnreliable and codec._isDelta then
|
|
70
|
-
error(
|
|
74
|
+
Log.error(
|
|
75
|
+
`"{name}" delta codecs need reliable transport (lost packets desync the baseline)`
|
|
76
|
+
)
|
|
71
77
|
end
|
|
72
78
|
|
|
73
79
|
if codec._hasUnknown and not opts.validate then
|
|
74
|
-
warn(
|
|
75
|
-
`[Lync] Lync.packet: "{name}" uses unknown codec without validate; data bypasses schema validation`
|
|
76
|
-
)
|
|
80
|
+
Log.warn(`"{name}" uses unknown codec without validate; data bypasses schema validation`)
|
|
77
81
|
end
|
|
78
82
|
|
|
79
83
|
if codec._isDelta then
|
|
@@ -109,26 +113,34 @@ function Packet.define<T>(
|
|
|
109
113
|
end
|
|
110
114
|
end
|
|
111
115
|
|
|
112
|
-
|
|
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)
|
|
113
122
|
if not Middleware.hasSendHooks() then
|
|
114
|
-
return data
|
|
123
|
+
return false, data
|
|
115
124
|
end
|
|
116
|
-
|
|
117
125
|
local sendData = Middleware.runSend(data, name, player)
|
|
118
|
-
if typeof(sendData) == "table" and sendData.
|
|
119
|
-
return nil
|
|
126
|
+
if typeof(sendData) == "table" and sendData._lyncKind == "drop" then
|
|
127
|
+
return true, nil
|
|
120
128
|
end
|
|
121
|
-
return sendData
|
|
129
|
+
return false, sendData
|
|
122
130
|
end
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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)
|
|
127
136
|
end
|
|
137
|
+
bumpFires()
|
|
138
|
+
end
|
|
128
139
|
|
|
140
|
+
local function sendToTarget(data: any, target: any): ()
|
|
129
141
|
if isPlayer(target) then
|
|
130
|
-
local sendData = applySend(data, target :: Player)
|
|
131
|
-
if
|
|
142
|
+
local dropped, sendData = applySend(data, target :: Player)
|
|
143
|
+
if dropped then
|
|
132
144
|
return
|
|
133
145
|
end
|
|
134
146
|
sendToPlayer(target :: Player, sendData)
|
|
@@ -137,57 +149,59 @@ function Packet.define<T>(
|
|
|
137
149
|
end
|
|
138
150
|
|
|
139
151
|
if typeof(target) == "table" then
|
|
140
|
-
local sendData = applySend(data, nil)
|
|
141
|
-
if
|
|
152
|
+
local dropped, sendData = applySend(data, nil)
|
|
153
|
+
if dropped then
|
|
142
154
|
return
|
|
143
155
|
end
|
|
144
156
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
end
|
|
149
|
-
bumpFires()
|
|
157
|
+
local kind = target._lyncKind
|
|
158
|
+
if kind == "all" then
|
|
159
|
+
broadcast(Players:GetPlayers(), sendData)
|
|
150
160
|
return
|
|
151
161
|
end
|
|
152
162
|
|
|
153
|
-
if
|
|
163
|
+
if kind == "except" then
|
|
154
164
|
local excluded = target._excluded :: { [Player]: boolean }
|
|
155
|
-
for _,
|
|
156
|
-
if not excluded[
|
|
157
|
-
sendToPlayer(
|
|
165
|
+
for _, p in Players:GetPlayers() do
|
|
166
|
+
if not excluded[p] then
|
|
167
|
+
sendToPlayer(p, sendData)
|
|
158
168
|
end
|
|
159
169
|
end
|
|
160
170
|
bumpFires()
|
|
161
171
|
return
|
|
162
172
|
end
|
|
163
173
|
|
|
164
|
-
if
|
|
165
|
-
for
|
|
166
|
-
sendToPlayer(
|
|
174
|
+
if kind == "group" then
|
|
175
|
+
for p in target._members :: { [Player]: boolean } do
|
|
176
|
+
sendToPlayer(p, sendData)
|
|
167
177
|
end
|
|
168
178
|
bumpFires()
|
|
169
179
|
return
|
|
170
180
|
end
|
|
171
181
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
182
|
+
-- Plain { Player } array.
|
|
183
|
+
for _, p in target do
|
|
184
|
+
if isPlayer(p) then
|
|
185
|
+
sendToPlayer(p :: Player, sendData)
|
|
175
186
|
end
|
|
176
187
|
end
|
|
177
188
|
bumpFires()
|
|
178
189
|
return
|
|
179
190
|
end
|
|
180
191
|
|
|
181
|
-
error(
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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)
|
|
186
200
|
end
|
|
187
201
|
|
|
188
202
|
local function sendFromClient(data: any): ()
|
|
189
|
-
local sendData = applySend(data, nil)
|
|
190
|
-
if
|
|
203
|
+
local dropped, sendData = applySend(data, nil)
|
|
204
|
+
if dropped then
|
|
191
205
|
return
|
|
192
206
|
end
|
|
193
207
|
local ch = Transport.client().getChannel(isUnreliable)
|
package/src/api/Query.luau
CHANGED
|
@@ -6,38 +6,82 @@ 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 =
|
|
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
81
|
--[[
|
|
37
82
|
Server `request(data, target)` returns `Resp?` for a Player target and
|
|
38
|
-
`{ [Player]: Resp? }` for a multi-target table; Luau
|
|
39
|
-
|
|
40
|
-
single-target case). Multi-target callers must cast the return.
|
|
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.
|
|
41
85
|
]]
|
|
42
86
|
export type QueryHandle<Req, Resp> = {
|
|
43
87
|
handle: (
|
|
@@ -59,10 +103,11 @@ function Query.define<Req, Resp>(
|
|
|
59
103
|
responseCodec: Types.InternalCodec<Resp>,
|
|
60
104
|
options: Types.QueryOptions?
|
|
61
105
|
): QueryHandle<Req, Resp>
|
|
62
|
-
local opts = options or
|
|
106
|
+
local opts = options or EMPTY_OPTIONS
|
|
63
107
|
local timeout = opts.timeout or DEFAULT_TIMEOUT
|
|
64
108
|
|
|
65
109
|
local reqOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, name)
|
|
110
|
+
-- Embedded "\0resp" suffix stays uniquely paired with the request reg.
|
|
66
111
|
local respName = `{name}\0resp`
|
|
67
112
|
local respOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, respName)
|
|
68
113
|
|
|
@@ -98,6 +143,7 @@ function Query.define<Req, Resp>(
|
|
|
98
143
|
|
|
99
144
|
reqReg.partner = respReg.id
|
|
100
145
|
|
|
146
|
+
-- Single active handler; replacing it disconnects the prior one via handlerToken.
|
|
101
147
|
local activeHandler: ((any, Player?) -> any)? = nil
|
|
102
148
|
local handlerToken = 0
|
|
103
149
|
|
|
@@ -122,18 +168,22 @@ function Query.define<Req, Resp>(
|
|
|
122
168
|
end
|
|
123
169
|
|
|
124
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).
|
|
125
175
|
local payloadCodec = if ok then responseCodec else nil
|
|
126
176
|
local payloadData = if ok then response else nil
|
|
127
177
|
|
|
128
|
-
local ch = if IS_SERVER
|
|
129
|
-
then Transport.server().getChannel(sender, false)
|
|
130
|
-
|
|
131
|
-
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)
|
|
132
181
|
if ch then
|
|
133
|
-
|
|
182
|
+
writeQueryTracked(ch, respReg, corrId, payloadCodec, payloadData)
|
|
134
183
|
end
|
|
135
184
|
end)
|
|
136
185
|
|
|
186
|
+
-- Resolve the waiting thread with nil after `timeout` seconds.
|
|
137
187
|
local function scheduleSingleTimeout(corrId: number, running: thread): ()
|
|
138
188
|
task.delay(timeout, function()
|
|
139
189
|
if _pending[corrId] == running then
|
|
@@ -143,10 +193,10 @@ function Query.define<Req, Resp>(
|
|
|
143
193
|
end)
|
|
144
194
|
end
|
|
145
195
|
|
|
146
|
-
|
|
196
|
+
-- Issue a single-target query on `ch` and yield until reply or timeout.
|
|
197
|
+
local function requestOnChannel(ch: Types.ChannelState, data: any): any
|
|
147
198
|
local corrId = nextCorr()
|
|
148
|
-
|
|
149
|
-
Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
|
|
199
|
+
writeQueryTracked(ch, reqReg, corrId, requestCodec, data)
|
|
150
200
|
|
|
151
201
|
local running = coroutine.running()
|
|
152
202
|
_pending[corrId] = running
|
|
@@ -155,30 +205,41 @@ function Query.define<Req, Resp>(
|
|
|
155
205
|
end
|
|
156
206
|
|
|
157
207
|
local function requestMulti(targets: { Player }, data: any): { [Player]: any }
|
|
158
|
-
|
|
208
|
+
local targetCount = #targets
|
|
209
|
+
if targetCount == 0 then
|
|
159
210
|
return {}
|
|
160
211
|
end
|
|
161
212
|
|
|
162
213
|
local results: { [Player]: any } = {}
|
|
163
|
-
local remaining =
|
|
214
|
+
local remaining = targetCount
|
|
164
215
|
local running = coroutine.running()
|
|
165
216
|
local resolved = false
|
|
217
|
+
local corrIds = table.create(targetCount)
|
|
166
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
|
+
]]
|
|
167
224
|
local function resolveOnce(): ()
|
|
168
225
|
if resolved then
|
|
169
226
|
return
|
|
170
227
|
end
|
|
171
228
|
resolved = true
|
|
229
|
+
for i = 1, targetCount do
|
|
230
|
+
_pendingGather[corrIds[i]] = nil
|
|
231
|
+
end
|
|
172
232
|
task.spawn(running, results)
|
|
173
233
|
end
|
|
174
234
|
|
|
175
235
|
local Server = Transport.server()
|
|
176
|
-
for
|
|
236
|
+
for i, p in targets do
|
|
177
237
|
local pCorr = nextCorr()
|
|
178
|
-
|
|
179
|
-
|
|
238
|
+
corrIds[i] = pCorr
|
|
239
|
+
local ch = Server.getChannel(p, false)
|
|
240
|
+
writeQueryTracked(ch, reqReg, pCorr, requestCodec, data)
|
|
180
241
|
|
|
181
|
-
local pRef =
|
|
242
|
+
local pRef = p
|
|
182
243
|
_pendingGather[pCorr] = function(response: any)
|
|
183
244
|
results[pRef] = response
|
|
184
245
|
remaining -= 1
|
|
@@ -192,54 +253,28 @@ function Query.define<Req, Resp>(
|
|
|
192
253
|
return coroutine.yield()
|
|
193
254
|
end
|
|
194
255
|
|
|
195
|
-
local function collectTargets(target: any): { Player }
|
|
196
|
-
local targets: { Player } = {}
|
|
197
|
-
if typeof(target) ~= "table" then
|
|
198
|
-
return targets
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
if target._lyncAll then
|
|
202
|
-
for _, p in Players:GetPlayers() do
|
|
203
|
-
table.insert(targets, p)
|
|
204
|
-
end
|
|
205
|
-
elseif target._lyncGroup then
|
|
206
|
-
for player in target._members do
|
|
207
|
-
table.insert(targets, player)
|
|
208
|
-
end
|
|
209
|
-
else
|
|
210
|
-
for _, p in target do
|
|
211
|
-
if isPlayer(p) then
|
|
212
|
-
table.insert(targets, p :: Player)
|
|
213
|
-
end
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
return targets
|
|
217
|
-
end
|
|
218
|
-
|
|
219
256
|
local function requestFromServer(data: any, target: any): any
|
|
220
257
|
if target == nil then
|
|
221
|
-
error(`
|
|
258
|
+
Log.error(`"{name}" server request requires a target`)
|
|
222
259
|
end
|
|
223
260
|
if isPlayer(target) then
|
|
224
|
-
return
|
|
261
|
+
return requestOnChannel(Transport.server().getChannel(target :: Player, false), data)
|
|
225
262
|
end
|
|
226
263
|
return requestMulti(collectTargets(target), data)
|
|
227
264
|
end
|
|
228
265
|
|
|
229
266
|
local function requestFromClient(data: any): any
|
|
230
|
-
|
|
231
|
-
local ch = Transport.client().getChannel(false)
|
|
232
|
-
Channel.writeQuery(ch, reqReg.id, corrId, requestCodec, data)
|
|
233
|
-
|
|
234
|
-
local running = coroutine.running()
|
|
235
|
-
_pending[corrId] = running
|
|
236
|
-
scheduleSingleTimeout(corrId, running)
|
|
237
|
-
return coroutine.yield()
|
|
267
|
+
return requestOnChannel(Transport.client().getChannel(false), data)
|
|
238
268
|
end
|
|
239
269
|
|
|
240
270
|
type Self = QueryHandle<Req, Resp>
|
|
241
271
|
|
|
242
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
|
+
]]
|
|
243
278
|
handle = function(
|
|
244
279
|
_self: Self,
|
|
245
280
|
fn: (request: Req, player: Player?) -> Resp?
|
|
@@ -248,17 +283,20 @@ function Query.define<Req, Resp>(
|
|
|
248
283
|
local myToken = handlerToken
|
|
249
284
|
activeHandler = fn :: any
|
|
250
285
|
|
|
251
|
-
local conn
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
|
262
300
|
end,
|
|
263
301
|
|
|
264
302
|
request = function(_self: Self, data: Req, target: any?): Resp?
|
package/src/api/Scope.luau
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
--!optimize 2
|
|
3
|
-
-- Batched connection lifecycle
|
|
4
|
-
-- RBXScriptConnection, and any object with :disconnect() / :Disconnect().
|
|
3
|
+
-- Batched connection lifecycle for Lync, RBXScriptConnection, and disconnect()-style objects.
|
|
5
4
|
|
|
6
5
|
local Types = require(script.Parent.Parent.Types)
|
|
7
6
|
|
|
@@ -25,21 +24,29 @@ local function pushConnection(self: Scope, conn: any): ()
|
|
|
25
24
|
self._connections[idx] = conn
|
|
26
25
|
end
|
|
27
26
|
|
|
27
|
+
-- Routes :on / :once through `methodName` so both call sites share insert + size bookkeeping.
|
|
28
|
+
local function attach(
|
|
29
|
+
self: Scope,
|
|
30
|
+
source: any,
|
|
31
|
+
fn: (...any) -> (),
|
|
32
|
+
methodName: string
|
|
33
|
+
): Types.Connection
|
|
34
|
+
local conn = source[methodName](source, fn)
|
|
35
|
+
pushConnection(self, conn)
|
|
36
|
+
return conn
|
|
37
|
+
end
|
|
38
|
+
|
|
28
39
|
-- Public -----------------------------------------------------------------
|
|
29
40
|
|
|
30
41
|
local ScopeMeta = {}
|
|
31
42
|
ScopeMeta.__index = ScopeMeta
|
|
32
43
|
|
|
33
44
|
function ScopeMeta.on(self: Scope, source: any, fn: (...any) -> ()): Types.Connection
|
|
34
|
-
|
|
35
|
-
pushConnection(self, conn)
|
|
36
|
-
return conn
|
|
45
|
+
return attach(self, source, fn, "on")
|
|
37
46
|
end
|
|
38
47
|
|
|
39
48
|
function ScopeMeta.once(self: Scope, source: any, fn: (...any) -> ()): Types.Connection
|
|
40
|
-
|
|
41
|
-
pushConnection(self, conn)
|
|
42
|
-
return conn
|
|
49
|
+
return attach(self, source, fn, "once")
|
|
43
50
|
end
|
|
44
51
|
|
|
45
52
|
ScopeMeta.add = pushConnection
|
|
@@ -48,6 +55,11 @@ function ScopeMeta.size(self: Scope): number
|
|
|
48
55
|
return self._count
|
|
49
56
|
end
|
|
50
57
|
|
|
58
|
+
--[[
|
|
59
|
+
Disconnect every tracked source. The shape sniff handles three kinds:
|
|
60
|
+
Roblox RBXScriptConnection (:Disconnect), Lync Connection (:disconnect),
|
|
61
|
+
and tables exposing either capitalization.
|
|
62
|
+
]]
|
|
51
63
|
function ScopeMeta.destroy(self: Scope): ()
|
|
52
64
|
local conns = self._connections
|
|
53
65
|
local count = self._count
|
|
@@ -56,8 +68,12 @@ function ScopeMeta.destroy(self: Scope): ()
|
|
|
56
68
|
conns[i] = nil
|
|
57
69
|
if typeof(conn) == "RBXScriptConnection" then
|
|
58
70
|
(conn :: RBXScriptConnection):Disconnect()
|
|
59
|
-
elseif typeof(conn) == "table"
|
|
60
|
-
conn
|
|
71
|
+
elseif typeof(conn) == "table" then
|
|
72
|
+
if conn.disconnect then
|
|
73
|
+
conn:disconnect()
|
|
74
|
+
elseif conn.Disconnect then
|
|
75
|
+
conn:Disconnect()
|
|
76
|
+
end
|
|
61
77
|
end
|
|
62
78
|
end
|
|
63
79
|
self._count = 0
|