@axpecter/lync 2.3.2 → 3.0.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.
- package/LICENSE +21 -0
- package/README.md +302 -299
- package/Types.luau +215 -0
- package/api/Define.luau +154 -0
- package/api/Lifecycle.luau +119 -0
- package/api/Packet.luau +108 -0
- package/api/Query.luau +149 -0
- package/api/Set.luau +189 -0
- package/channel/Blocks.luau +124 -0
- package/channel/Group.luau +118 -0
- package/channel/Link.luau +135 -0
- package/channel/Outbox.luau +261 -0
- package/channel/Recipients.luau +129 -0
- package/channel/Transport.luau +138 -0
- package/codec/Base.luau +236 -0
- package/codec/Composites.luau +128 -0
- package/codec/Datatypes.luau +162 -0
- package/codec/Legality.luau +61 -0
- package/codec/Scalars.luau +72 -0
- package/codec/Text.luau +89 -0
- package/codec/init.luau +39 -0
- package/index.d.ts +285 -0
- package/init.luau +91 -0
- package/kernel/Bits.luau +16 -0
- package/kernel/Catalog.luau +121 -0
- package/kernel/Heap.luau +80 -0
- package/kernel/Listeners.luau +95 -0
- package/kernel/Log.luau +197 -0
- package/kernel/Tally.luau +128 -0
- package/kernel/Threads.luau +44 -0
- package/kernel/Wide.luau +267 -0
- package/package.json +15 -32
- package/runtime/Boundary.luau +180 -0
- package/runtime/Dispatch.luau +91 -0
- package/runtime/Handles.luau +114 -0
- package/runtime/Handshake.luau +90 -0
- package/runtime/Pending.luau +196 -0
- package/runtime/Router.luau +379 -0
- package/runtime/Session.luau +731 -0
- package/shape/Compile.luau +340 -0
- package/shape/Folds.luau +332 -0
- package/shape/Frames.luau +170 -0
- package/shape/Grid.luau +169 -0
- package/shape/Ir.luau +540 -0
- package/shape/Joins.luau +125 -0
- package/shape/Legality.luau +273 -0
- package/shape/Lower.luau +419 -0
- package/shape/Order.luau +64 -0
- package/shape/Regions.luau +113 -0
- package/shape/Rotation.luau +472 -0
- package/shape/Serial.luau +258 -0
- package/shape/Words.luau +89 -0
- package/shape/emit/Block.luau +453 -0
- package/shape/emit/Column.luau +338 -0
- package/shape/emit/Leaf.luau +462 -0
- package/shape/emit/Word.luau +234 -0
- package/shape/emit/init.luau +222 -0
- package/shape/init.luau +78 -0
- package/state/Apply.luau +330 -0
- package/state/Audience.luau +126 -0
- package/state/Catchup.luau +218 -0
- package/state/Coalesce.luau +43 -0
- package/state/Fieldset.luau +177 -0
- package/state/Newest.luau +339 -0
- package/state/Plan.luau +441 -0
- package/state/Sections.luau +468 -0
- package/state/Store.luau +188 -0
- package/state/Sync.luau +762 -0
- package/wire/Bytes.luau +118 -0
- package/wire/Clock.luau +37 -0
- package/wire/Protocol.luau +110 -0
- package/wire/Sequence.luau +40 -0
- package/wire/Varint.luau +124 -0
- package/src/Types.luau +0 -180
- package/src/api/Group.luau +0 -156
- package/src/api/Packet.luau +0 -319
- package/src/api/Query.luau +0 -338
- package/src/api/Scope.luau +0 -90
- package/src/api/Signal.luau +0 -188
- package/src/codec/Base.luau +0 -136
- package/src/codec/composite/Array.luau +0 -456
- package/src/codec/composite/Map.luau +0 -469
- package/src/codec/composite/Optional.luau +0 -54
- package/src/codec/composite/Shared.luau +0 -291
- package/src/codec/composite/Struct.luau +0 -494
- package/src/codec/composite/Tagged.luau +0 -103
- package/src/codec/composite/Tuple.luau +0 -143
- package/src/codec/datatype/Buffer.luau +0 -66
- package/src/codec/datatype/CFrame.luau +0 -109
- package/src/codec/datatype/Color.luau +0 -32
- package/src/codec/datatype/Instance.luau +0 -52
- package/src/codec/datatype/IntVector.luau +0 -39
- package/src/codec/datatype/NumberRange.luau +0 -39
- package/src/codec/datatype/Ray.luau +0 -36
- package/src/codec/datatype/Rect.luau +0 -31
- package/src/codec/datatype/Region.luau +0 -62
- package/src/codec/datatype/Sequence.luau +0 -184
- package/src/codec/datatype/String.luau +0 -94
- package/src/codec/datatype/UDim.luau +0 -46
- package/src/codec/datatype/Vector.luau +0 -130
- package/src/codec/meta/Auto.luau +0 -318
- package/src/codec/meta/Bitfield.luau +0 -147
- package/src/codec/meta/Custom.luau +0 -33
- package/src/codec/meta/DeltaScalar.luau +0 -390
- package/src/codec/meta/Enum.luau +0 -50
- package/src/codec/meta/Float.luau +0 -38
- package/src/codec/meta/Nothing.luau +0 -13
- package/src/codec/meta/Unknown.luau +0 -41
- package/src/codec/primitive/Bool.luau +0 -26
- package/src/codec/primitive/Float16.luau +0 -99
- package/src/codec/primitive/Int.luau +0 -59
- package/src/codec/primitive/Number.luau +0 -24
- package/src/codec/primitive/Signed.luau +0 -32
- package/src/codec/primitive/Varint.luau +0 -165
- package/src/codec/primitive/Zint.luau +0 -99
- package/src/index.d.ts +0 -423
- package/src/init.luau +0 -362
- package/src/internal/Baseline.luau +0 -57
- package/src/internal/Channel.luau +0 -390
- package/src/internal/Middleware.luau +0 -100
- package/src/internal/Pool.luau +0 -65
- package/src/internal/Registry.luau +0 -119
- package/src/internal/Transport.luau +0 -28
- package/src/transport/Bridge.luau +0 -110
- package/src/transport/Client.luau +0 -97
- package/src/transport/Gate.luau +0 -488
- package/src/transport/Reader.luau +0 -331
- package/src/transport/Server.luau +0 -211
- package/src/util/Array.luau +0 -18
- package/src/util/Buffer.luau +0 -90
- package/src/util/Constants.luau +0 -30
- package/src/util/Log.luau +0 -68
- package/src/util/Player.luau +0 -14
- package/src/util/Quantize.luau +0 -84
- package/src/util/Quat.luau +0 -124
package/src/api/Query.luau
DELETED
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Query definition: handle, request with correlation IDs and timeout.
|
|
4
|
-
|
|
5
|
-
local Players = game:GetService("Players")
|
|
6
|
-
local RunService = game:GetService("RunService")
|
|
7
|
-
|
|
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)
|
|
11
|
-
local Registry = require(script.Parent.Parent.internal.Registry)
|
|
12
|
-
local Transport = require(script.Parent.Parent.internal.Transport)
|
|
13
|
-
local Types = require(script.Parent.Parent.Types)
|
|
14
|
-
|
|
15
|
-
-- Constants --------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
local IS_SERVER = RunService:IsServer()
|
|
18
|
-
local DEFAULT_TIMEOUT = 5
|
|
19
|
-
|
|
20
|
-
local EMPTY_OPTIONS: Types.QueryOptions = table.freeze({})
|
|
21
|
-
|
|
22
|
-
-- State ------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
local _nextCorrId = 0
|
|
25
|
-
-- Correlation -> waiting thread / completion closure. Cleared on resolve or timeout.
|
|
26
|
-
local _pending: { [number]: thread } = {}
|
|
27
|
-
local _pendingGather: { [number]: (any) -> () } = {}
|
|
28
|
-
|
|
29
|
-
-- Private ----------------------------------------------------------------
|
|
30
|
-
|
|
31
|
-
local isPlayer = Player.is
|
|
32
|
-
|
|
33
|
-
local function nextCorr(): number
|
|
34
|
-
_nextCorrId += 1
|
|
35
|
-
return _nextCorrId
|
|
36
|
-
end
|
|
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
|
-
|
|
79
|
-
-- Public types -----------------------------------------------------------
|
|
80
|
-
|
|
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,
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
-- Public -----------------------------------------------------------------
|
|
97
|
-
|
|
98
|
-
local Query = {}
|
|
99
|
-
|
|
100
|
-
function Query.define<Req, Resp>(
|
|
101
|
-
name: string,
|
|
102
|
-
requestCodec: Types.InternalCodec<Req>,
|
|
103
|
-
responseCodec: Types.InternalCodec<Resp>,
|
|
104
|
-
options: Types.QueryOptions?
|
|
105
|
-
): QueryHandle<Req, Resp>
|
|
106
|
-
local opts = options or EMPTY_OPTIONS
|
|
107
|
-
local timeout = opts.timeout or DEFAULT_TIMEOUT
|
|
108
|
-
|
|
109
|
-
local reqOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, name)
|
|
110
|
-
-- Embedded "\0resp" suffix stays uniquely paired with the request reg.
|
|
111
|
-
local respName = `{name}\0resp`
|
|
112
|
-
local respOpenFn = Channel.resolveOpenFn(Channel.TS_NONE, respName)
|
|
113
|
-
|
|
114
|
-
local reqReg = Registry.register({
|
|
115
|
-
name = name,
|
|
116
|
-
kind = Registry.KIND_REQUEST,
|
|
117
|
-
codec = requestCodec,
|
|
118
|
-
isUnreliable = false,
|
|
119
|
-
partner = nil,
|
|
120
|
-
needsGate = IS_SERVER,
|
|
121
|
-
headerSize = 1,
|
|
122
|
-
timestampMode = Channel.TS_NONE,
|
|
123
|
-
openFn = reqOpenFn,
|
|
124
|
-
rateLimit = opts.rateLimit or { maxPerSecond = 30 },
|
|
125
|
-
validate = opts.validate,
|
|
126
|
-
maxPayloadBytes = nil,
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
local respReg = Registry.register({
|
|
130
|
-
name = respName,
|
|
131
|
-
kind = Registry.KIND_RESPONSE,
|
|
132
|
-
codec = responseCodec,
|
|
133
|
-
isUnreliable = false,
|
|
134
|
-
partner = reqReg.id,
|
|
135
|
-
needsGate = false,
|
|
136
|
-
headerSize = 1,
|
|
137
|
-
timestampMode = Channel.TS_NONE,
|
|
138
|
-
openFn = respOpenFn,
|
|
139
|
-
rateLimit = nil,
|
|
140
|
-
validate = nil,
|
|
141
|
-
maxPayloadBytes = nil,
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
reqReg.partner = respReg.id
|
|
145
|
-
|
|
146
|
-
-- Single active handler; replacing it disconnects the prior one via handlerToken.
|
|
147
|
-
local activeHandler: ((any, Player?) -> any)? = nil
|
|
148
|
-
local handlerToken = 0
|
|
149
|
-
|
|
150
|
-
respReg.signal:connect(function(value: any, _sender: Player?, corrId: number)
|
|
151
|
-
local single = _pending[corrId]
|
|
152
|
-
if single then
|
|
153
|
-
_pending[corrId] = nil
|
|
154
|
-
task.spawn(single, value)
|
|
155
|
-
return
|
|
156
|
-
end
|
|
157
|
-
local gather = _pendingGather[corrId]
|
|
158
|
-
if gather then
|
|
159
|
-
_pendingGather[corrId] = nil
|
|
160
|
-
gather(value)
|
|
161
|
-
end
|
|
162
|
-
end)
|
|
163
|
-
|
|
164
|
-
reqReg.signal:connect(function(value: any, sender: Player?, corrId: number)
|
|
165
|
-
local handler = activeHandler
|
|
166
|
-
if not handler then
|
|
167
|
-
return
|
|
168
|
-
end
|
|
169
|
-
|
|
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).
|
|
175
|
-
local payloadCodec = if ok then responseCodec else nil
|
|
176
|
-
local payloadData = if ok then response else nil
|
|
177
|
-
|
|
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)
|
|
181
|
-
if ch then
|
|
182
|
-
writeQueryTracked(ch, respReg, corrId, payloadCodec, payloadData)
|
|
183
|
-
end
|
|
184
|
-
end)
|
|
185
|
-
|
|
186
|
-
-- Resolve the waiting thread with nil after `timeout` seconds.
|
|
187
|
-
local function scheduleSingleTimeout(corrId: number, running: thread): ()
|
|
188
|
-
task.delay(timeout, function()
|
|
189
|
-
if _pending[corrId] == running then
|
|
190
|
-
_pending[corrId] = nil
|
|
191
|
-
task.spawn(running, nil)
|
|
192
|
-
end
|
|
193
|
-
end)
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
-- Issue a single-target query on `ch` and yield until reply or timeout.
|
|
197
|
-
local function requestOnChannel(ch: Types.ChannelState, data: any): any
|
|
198
|
-
local corrId = nextCorr()
|
|
199
|
-
writeQueryTracked(ch, reqReg, corrId, requestCodec, data)
|
|
200
|
-
|
|
201
|
-
local running = coroutine.running()
|
|
202
|
-
_pending[corrId] = running
|
|
203
|
-
scheduleSingleTimeout(corrId, running)
|
|
204
|
-
return coroutine.yield()
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
local function requestMulti(targets: { Player }, data: any): { [Player]: any }
|
|
208
|
-
local targetCount = #targets
|
|
209
|
-
if targetCount == 0 then
|
|
210
|
-
return {}
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
local results: { [Player]: any } = {}
|
|
214
|
-
local remaining = targetCount
|
|
215
|
-
local running = coroutine.running()
|
|
216
|
-
local resolved = false
|
|
217
|
-
local corrIds = table.create(targetCount)
|
|
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
|
-
]]
|
|
224
|
-
local function resolveOnce(): ()
|
|
225
|
-
if resolved then
|
|
226
|
-
return
|
|
227
|
-
end
|
|
228
|
-
resolved = true
|
|
229
|
-
for i = 1, targetCount do
|
|
230
|
-
_pendingGather[corrIds[i]] = nil
|
|
231
|
-
end
|
|
232
|
-
task.spawn(running, results)
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
local Server = Transport.server()
|
|
236
|
-
for i, p in targets do
|
|
237
|
-
local pCorr = nextCorr()
|
|
238
|
-
corrIds[i] = pCorr
|
|
239
|
-
local ch = Server.getChannel(p, false)
|
|
240
|
-
writeQueryTracked(ch, reqReg, pCorr, requestCodec, data)
|
|
241
|
-
|
|
242
|
-
local pRef = p
|
|
243
|
-
_pendingGather[pCorr] = function(response: any)
|
|
244
|
-
results[pRef] = response
|
|
245
|
-
remaining -= 1
|
|
246
|
-
if remaining == 0 then
|
|
247
|
-
resolveOnce()
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
end
|
|
251
|
-
|
|
252
|
-
task.delay(timeout, resolveOnce)
|
|
253
|
-
return coroutine.yield()
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
local function requestFromServer(data: any, target: any): any
|
|
257
|
-
if target == nil then
|
|
258
|
-
Log.error(`"{name}" server request requires a target`)
|
|
259
|
-
end
|
|
260
|
-
if isPlayer(target) then
|
|
261
|
-
return requestOnChannel(Transport.server().getChannel(target :: Player, false), data)
|
|
262
|
-
end
|
|
263
|
-
return requestMulti(collectTargets(target), data)
|
|
264
|
-
end
|
|
265
|
-
|
|
266
|
-
local function requestFromClient(data: any): any
|
|
267
|
-
return requestOnChannel(Transport.client().getChannel(false), data)
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
type Self = QueryHandle<Req, Resp>
|
|
271
|
-
|
|
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
|
-
]]
|
|
278
|
-
handle = function(
|
|
279
|
-
_self: Self,
|
|
280
|
-
fn: (request: Req, player: Player?) -> Resp?
|
|
281
|
-
): Types.Connection
|
|
282
|
-
handlerToken += 1
|
|
283
|
-
local myToken = handlerToken
|
|
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
|
|
300
|
-
end,
|
|
301
|
-
|
|
302
|
-
request = function(_self: Self, data: Req, target: any?): Resp?
|
|
303
|
-
if IS_SERVER then
|
|
304
|
-
return requestFromServer(data, target)
|
|
305
|
-
end
|
|
306
|
-
return requestFromClient(data)
|
|
307
|
-
end,
|
|
308
|
-
|
|
309
|
-
name = function(_self: Self): string
|
|
310
|
-
return name
|
|
311
|
-
end,
|
|
312
|
-
|
|
313
|
-
stats = function(_self: Self): Types.PacketStats
|
|
314
|
-
return {
|
|
315
|
-
bytesSent = reqReg.bytesSent + respReg.bytesSent,
|
|
316
|
-
bytesReceived = reqReg.bytesReceived + respReg.bytesReceived,
|
|
317
|
-
fires = reqReg.fires + respReg.fires,
|
|
318
|
-
recvFires = reqReg.recvFires + respReg.recvFires,
|
|
319
|
-
drops = reqReg.drops + respReg.drops,
|
|
320
|
-
}
|
|
321
|
-
end,
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return table.freeze(handle) :: QueryHandle<Req, Resp>
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
function Query.pendingCount(): number
|
|
328
|
-
local count = 0
|
|
329
|
-
for _ in _pending do
|
|
330
|
-
count += 1
|
|
331
|
-
end
|
|
332
|
-
for _ in _pendingGather do
|
|
333
|
-
count += 1
|
|
334
|
-
end
|
|
335
|
-
return count
|
|
336
|
-
end
|
|
337
|
-
|
|
338
|
-
return table.freeze(Query)
|
package/src/api/Scope.luau
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- Batched connection lifecycle for Lync, RBXScriptConnection, and disconnect()-style objects.
|
|
4
|
-
|
|
5
|
-
local Types = require(script.Parent.Parent.Types)
|
|
6
|
-
|
|
7
|
-
-- Public types -----------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
export type ScopeHandle = {
|
|
10
|
-
on: (self: ScopeHandle, source: any, fn: (...any) -> ()) -> Types.Connection,
|
|
11
|
-
once: (self: ScopeHandle, source: any, fn: (...any) -> ()) -> Types.Connection,
|
|
12
|
-
add: (self: ScopeHandle, connection: any) -> (),
|
|
13
|
-
size: (self: ScopeHandle) -> number,
|
|
14
|
-
destroy: (self: ScopeHandle) -> (),
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
type Scope = ScopeHandle & { _connections: { any }, _count: number }
|
|
18
|
-
|
|
19
|
-
-- Private ----------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
local function pushConnection(self: Scope, conn: any): ()
|
|
22
|
-
local idx = self._count + 1
|
|
23
|
-
self._count = idx
|
|
24
|
-
self._connections[idx] = conn
|
|
25
|
-
end
|
|
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
|
-
|
|
39
|
-
-- Public -----------------------------------------------------------------
|
|
40
|
-
|
|
41
|
-
local ScopeMeta = {}
|
|
42
|
-
ScopeMeta.__index = ScopeMeta
|
|
43
|
-
|
|
44
|
-
function ScopeMeta.on(self: Scope, source: any, fn: (...any) -> ()): Types.Connection
|
|
45
|
-
return attach(self, source, fn, "on")
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
function ScopeMeta.once(self: Scope, source: any, fn: (...any) -> ()): Types.Connection
|
|
49
|
-
return attach(self, source, fn, "once")
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
ScopeMeta.add = pushConnection
|
|
53
|
-
|
|
54
|
-
function ScopeMeta.size(self: Scope): number
|
|
55
|
-
return self._count
|
|
56
|
-
end
|
|
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
|
-
]]
|
|
63
|
-
function ScopeMeta.destroy(self: Scope): ()
|
|
64
|
-
local conns = self._connections
|
|
65
|
-
local count = self._count
|
|
66
|
-
for i = 1, count do
|
|
67
|
-
local conn = conns[i]
|
|
68
|
-
conns[i] = nil
|
|
69
|
-
if typeof(conn) == "RBXScriptConnection" then
|
|
70
|
-
(conn :: RBXScriptConnection):Disconnect()
|
|
71
|
-
elseif typeof(conn) == "table" then
|
|
72
|
-
if conn.disconnect then
|
|
73
|
-
conn:disconnect()
|
|
74
|
-
elseif conn.Disconnect then
|
|
75
|
-
conn:Disconnect()
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
self._count = 0
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
table.freeze(ScopeMeta)
|
|
83
|
-
|
|
84
|
-
local ScopeModule = {}
|
|
85
|
-
|
|
86
|
-
function ScopeModule.create(): ScopeHandle
|
|
87
|
-
return (setmetatable({ _connections = {}, _count = 0 }, ScopeMeta) :: any) :: ScopeHandle
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
return table.freeze(ScopeModule)
|
package/src/api/Signal.luau
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
--!strict
|
|
2
|
-
--!optimize 2
|
|
3
|
-
-- O(1) disconnect signal with thread reuse and reentrancy-safe fire.
|
|
4
|
-
|
|
5
|
-
local Types = require(script.Parent.Parent.Types)
|
|
6
|
-
|
|
7
|
-
-- Constants --------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
local STATE_DISCONNECTED = 0
|
|
10
|
-
local STATE_CONNECTED = 1
|
|
11
|
-
local STATE_ONCE = 2
|
|
12
|
-
|
|
13
|
-
-- State ------------------------------------------------------------------
|
|
14
|
-
|
|
15
|
-
-- Single recyclable thread for async dispatch; saves coroutine.create per fire.
|
|
16
|
-
local _freeThread: thread? = nil
|
|
17
|
-
|
|
18
|
-
-- Private ----------------------------------------------------------------
|
|
19
|
-
|
|
20
|
-
local function passer(fn: (...any) -> (), ...): ()
|
|
21
|
-
local thread = _freeThread
|
|
22
|
-
_freeThread = nil
|
|
23
|
-
fn(...)
|
|
24
|
-
_freeThread = thread
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
local function yielder(): ()
|
|
28
|
-
while true do
|
|
29
|
-
passer(coroutine.yield())
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
local function spawnCallback(fn: (...any) -> (), ...: any): ()
|
|
34
|
-
if not _freeThread then
|
|
35
|
-
_freeThread = coroutine.create(yielder)
|
|
36
|
-
coroutine.resume(_freeThread :: thread)
|
|
37
|
-
end
|
|
38
|
-
task.spawn(_freeThread :: thread, fn, ...)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
type Entry = {
|
|
42
|
-
fn: (...any) -> (),
|
|
43
|
-
_state: number,
|
|
44
|
-
connected: boolean,
|
|
45
|
-
_signal: SignalFields,
|
|
46
|
-
_index: number,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type SignalFields = {
|
|
50
|
-
_entries: { Entry },
|
|
51
|
-
_count: number,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
local EntryImpl = {}
|
|
55
|
-
EntryImpl.__index = EntryImpl
|
|
56
|
-
|
|
57
|
-
-- Swap-with-last for O(1) disconnect; the moved entry's _index is patched too.
|
|
58
|
-
local function disconnectEntry(entry: Entry): ()
|
|
59
|
-
if entry._state == STATE_DISCONNECTED then
|
|
60
|
-
return
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
entry._state = STATE_DISCONNECTED
|
|
64
|
-
entry.connected = false
|
|
65
|
-
|
|
66
|
-
local signal = entry._signal
|
|
67
|
-
local entries = signal._entries
|
|
68
|
-
local idx = entry._index
|
|
69
|
-
local last = signal._count
|
|
70
|
-
|
|
71
|
-
if idx < last then
|
|
72
|
-
local moved = entries[last]
|
|
73
|
-
entries[idx] = moved
|
|
74
|
-
moved._index = idx
|
|
75
|
-
end
|
|
76
|
-
entries[last] = nil
|
|
77
|
-
signal._count = last - 1
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
EntryImpl.disconnect = disconnectEntry
|
|
81
|
-
table.freeze(EntryImpl)
|
|
82
|
-
|
|
83
|
-
-- Auto-disconnects ONCE entries before invoking; chooses sync vs spawned thread.
|
|
84
|
-
local function invokeEntry(entry: Entry, sync: boolean, ...: any): ()
|
|
85
|
-
local state = entry._state
|
|
86
|
-
if state == STATE_DISCONNECTED then
|
|
87
|
-
return
|
|
88
|
-
end
|
|
89
|
-
if state == STATE_ONCE then
|
|
90
|
-
disconnectEntry(entry)
|
|
91
|
-
end
|
|
92
|
-
if sync then
|
|
93
|
-
entry.fn(...)
|
|
94
|
-
else
|
|
95
|
-
spawnCallback(entry.fn, ...)
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
local SignalImpl = {}
|
|
100
|
-
SignalImpl.__index = SignalImpl
|
|
101
|
-
|
|
102
|
-
local function newEntry(self: SignalFields, callback: (...any) -> (), state: number): Entry
|
|
103
|
-
local idx = self._count + 1
|
|
104
|
-
self._count = idx
|
|
105
|
-
|
|
106
|
-
local entry: Entry = setmetatable({
|
|
107
|
-
fn = callback,
|
|
108
|
-
_state = state,
|
|
109
|
-
connected = true,
|
|
110
|
-
_signal = self,
|
|
111
|
-
_index = idx,
|
|
112
|
-
}, EntryImpl) :: any
|
|
113
|
-
|
|
114
|
-
self._entries[idx] = entry
|
|
115
|
-
return entry
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
function SignalImpl.connect(self: SignalFields, callback: (...any) -> ()): Types.Connection
|
|
119
|
-
return newEntry(self, callback, STATE_CONNECTED) :: any
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
function SignalImpl.once(self: SignalFields, callback: (...any) -> ()): Types.Connection
|
|
123
|
-
return newEntry(self, callback, STATE_ONCE) :: any
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
function SignalImpl.wait(self: SignalFields): ...any
|
|
127
|
-
local running = coroutine.running()
|
|
128
|
-
local conn: Types.Connection
|
|
129
|
-
conn = SignalImpl.connect(self, function(...: any)
|
|
130
|
-
conn:disconnect()
|
|
131
|
-
task.spawn(running, ...)
|
|
132
|
-
end)
|
|
133
|
-
return coroutine.yield()
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
local function dispatch(self: SignalFields, sync: boolean, ...: any): ()
|
|
137
|
-
local count = self._count
|
|
138
|
-
if count == 0 then
|
|
139
|
-
return
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
local entries = self._entries
|
|
143
|
-
|
|
144
|
-
-- Single-listener fast path skips the snapshot allocation.
|
|
145
|
-
if count == 1 then
|
|
146
|
-
invokeEntry(entries[1], sync, ...)
|
|
147
|
-
return
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
--[[
|
|
151
|
-
Re-entrancy: a handler may disconnect or add others mid-fire.
|
|
152
|
-
Snapshot the entry list so the iteration view is stable.
|
|
153
|
-
]]
|
|
154
|
-
local snapshot = table.create(count)
|
|
155
|
-
table.move(entries, 1, count, 1, snapshot)
|
|
156
|
-
|
|
157
|
-
for i = 1, count do
|
|
158
|
-
invokeEntry(snapshot[i], sync, ...)
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
function SignalImpl.fire(self: SignalFields, ...: any): ()
|
|
163
|
-
dispatch(self, false, ...)
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
function SignalImpl.fireSync(self: SignalFields, ...: any): ()
|
|
167
|
-
dispatch(self, true, ...)
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
function SignalImpl.count(self: SignalFields): number
|
|
171
|
-
return self._count
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
function SignalImpl.hasListeners(self: SignalFields): boolean
|
|
175
|
-
return self._count > 0
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
table.freeze(SignalImpl)
|
|
179
|
-
|
|
180
|
-
-- Public -----------------------------------------------------------------
|
|
181
|
-
|
|
182
|
-
local SignalModule = {}
|
|
183
|
-
|
|
184
|
-
function SignalModule.create(): Types.SignalLike
|
|
185
|
-
return (setmetatable({ _entries = {}, _count = 0 }, SignalImpl) :: any) :: Types.SignalLike
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
return table.freeze(SignalModule)
|