@axpecter/lync 1.3.1 → 1.4.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 (41) hide show
  1. package/README.md +250 -68
  2. package/package.json +1 -1
  3. package/src/Types.luau +13 -2
  4. package/src/api/Group.luau +77 -46
  5. package/src/api/Namespace.luau +13 -6
  6. package/src/api/Packet.luau +52 -30
  7. package/src/api/Query.luau +116 -90
  8. package/src/api/Scope.luau +73 -0
  9. package/src/api/Signal.luau +10 -4
  10. package/src/codec/Base.luau +7 -3
  11. package/src/codec/composite/Array.luau +18 -15
  12. package/src/codec/composite/Map.luau +45 -31
  13. package/src/codec/composite/Optional.luau +3 -2
  14. package/src/codec/composite/Shared.luau +7 -5
  15. package/src/codec/composite/Struct.luau +6 -4
  16. package/src/codec/composite/Tagged.luau +6 -6
  17. package/src/codec/composite/Tuple.luau +9 -7
  18. package/src/codec/datatype/Buffer.luau +3 -2
  19. package/src/codec/datatype/CFrame.luau +2 -1
  20. package/src/codec/datatype/Instance.luau +3 -2
  21. package/src/codec/datatype/Sequence.luau +3 -4
  22. package/src/codec/datatype/String.luau +3 -6
  23. package/src/codec/meta/Auto.luau +77 -72
  24. package/src/codec/meta/Bitfield.luau +5 -4
  25. package/src/codec/meta/Enum.luau +6 -5
  26. package/src/codec/meta/Quantized.luau +5 -4
  27. package/src/codec/meta/Unknown.luau +5 -1
  28. package/src/codec/primitive/Float16.luau +5 -4
  29. package/src/codec/primitive/Varint.luau +2 -2
  30. package/src/index.d.ts +65 -26
  31. package/src/init.luau +60 -42
  32. package/src/internal/Baseline.luau +6 -2
  33. package/src/internal/Channel.luau +11 -6
  34. package/src/internal/Middleware.luau +18 -8
  35. package/src/internal/Pool.luau +5 -5
  36. package/src/internal/Registry.luau +4 -4
  37. package/src/transport/Bridge.luau +4 -3
  38. package/src/transport/Client.luau +4 -3
  39. package/src/transport/Gate.luau +5 -5
  40. package/src/transport/Reader.luau +3 -3
  41. package/src/transport/Server.luau +14 -16
@@ -11,16 +11,16 @@ type Connection = Types.Connection
11
11
  type PacketConfig<T> = Types.PacketConfig<T>
12
12
  type QueryConfig<R, S> = Types.QueryConfig<R, S>
13
13
 
14
- -- Constants --------------------------------------------------------------
14
+ -- Constants ----------------------------------------------------------
15
15
 
16
16
  local MAX_NAMESPACES = 64
17
17
 
18
- -- State ------------------------------------------------------------------
18
+ -- State --------------------------------------------------------------
19
19
 
20
20
  local _registered = {} :: { [string]: true }
21
21
  local _count = 0
22
22
 
23
- -- Private ----------------------------------------------------------------
23
+ -- Private ------------------------------------------------------------
24
24
 
25
25
  type NamespaceConfig = {
26
26
  packets: { [string]: PacketConfig<any> }?,
@@ -47,8 +47,6 @@ local function scopedHandler (
47
47
  end
48
48
  end
49
49
 
50
- -- Namespace --------------------------------------------------------------
51
-
52
50
  local NamespaceImpl = {}
53
51
  NamespaceImpl.__index = NamespaceImpl
54
52
 
@@ -173,7 +171,9 @@ function NamespaceImpl.queryNames (self: any): { string }
173
171
  return result
174
172
  end
175
173
 
176
- -- Public -----------------------------------------------------------------
174
+ table.freeze (NamespaceImpl)
175
+
176
+ -- Public -------------------------------------------------------------
177
177
 
178
178
  local Namespace = {}
179
179
 
@@ -194,6 +194,8 @@ function Namespace.define (name: string, config: NamespaceConfig): any
194
194
 
195
195
  local packets = {} :: { [string]: any }
196
196
  local queries = {} :: { [string]: any }
197
+ local packetsByShort = {} :: { [string]: any }
198
+ local queriesByShort = {} :: { [string]: any }
197
199
  local fields = {} :: { [string]: any }
198
200
 
199
201
  fields._name = name
@@ -207,6 +209,7 @@ function Namespace.define (name: string, config: NamespaceConfig): any
207
209
  local fullName = prefixName (name, shortName)
208
210
  local packet = Packet.define (fullName, packetConfig)
209
211
  packets[fullName] = packet
212
+ packetsByShort[shortName] = packet
210
213
  fields[shortName] = packet
211
214
  end
212
215
  end
@@ -216,10 +219,14 @@ function Namespace.define (name: string, config: NamespaceConfig): any
216
219
  local fullName = prefixName (name, shortName)
217
220
  local query = Query.define (fullName, queryConfig)
218
221
  queries[fullName] = query
222
+ queriesByShort[shortName] = query
219
223
  fields[shortName] = query
220
224
  end
221
225
  end
222
226
 
227
+ fields.packets = table.freeze (packetsByShort)
228
+ fields.queries = table.freeze (queriesByShort)
229
+
223
230
  return setmetatable (fields, NamespaceImpl)
224
231
  end
225
232
 
@@ -11,9 +11,10 @@ local Signal = require (script.Parent.Signal)
11
11
  local Types = require (script.Parent.Parent.Types)
12
12
 
13
13
  type Connection = Types.Connection
14
+ type InternalCodec<T> = Types.InternalCodec<T>
14
15
  type PacketConfig<T> = Types.PacketConfig<T>
15
16
 
16
- -- Constants --------------------------------------------------------------
17
+ -- Constants ----------------------------------------------------------
17
18
 
18
19
  local IS_SERVER = RunService:IsServer ()
19
20
 
@@ -21,10 +22,13 @@ local serverWriteTo = Server.writeTo
21
22
  local serverWriteToAll = Server.writeToAll
22
23
  local serverWriteToList = Server.writeToList
23
24
  local serverWriteToAllExcept = Server.writeToAllExcept
24
- local serverWriteToGroup = Server.writeToGroup
25
+ local serverWriteToSet = Server.writeToSet
25
26
  local clientWrite = Client.write
26
27
 
27
- -- Private ----------------------------------------------------------------
28
+ local DELTA_TARGETED = 1
29
+ local DELTA_BROADCAST = 2
30
+
31
+ -- Private ------------------------------------------------------------
28
32
 
29
33
  -- Shared methods (both contexts)
30
34
  local SharedImpl = {}
@@ -45,13 +49,10 @@ function SharedImpl.disconnectAll (self: any): ()
45
49
  self._signal:disconnectAll ()
46
50
  end
47
51
 
48
- -- Server-only metatable . Has sendTo/sendToAll, no send().
52
+ -- Server-only metatable. Single send() dispatches on target type.
49
53
  local ServerImpl = setmetatable ({}, { __index = SharedImpl })
50
54
  ServerImpl.__index = ServerImpl
51
55
 
52
- local DELTA_TARGETED = 1
53
- local DELTA_BROADCAST = 2
54
-
55
56
  local function checkDeltaMode (self: any, mode: number): ()
56
57
  if not self._isDelta then
57
58
  return
@@ -64,34 +65,55 @@ local function checkDeltaMode (self: any, mode: number): ()
64
65
  end
65
66
  end
66
67
 
67
- function ServerImpl.sendTo (self: any, data: any, player: Player): ()
68
- checkDeltaMode (self, DELTA_TARGETED)
69
- serverWriteTo (player, self._id, self._name, self._codec, data, self._isUnreliable)
70
- end
68
+ function ServerImpl.send (self: any, data: any, target: any?): ()
69
+ if target == nil then
70
+ error ("[Lync] Server packet:send requires a target")
71
+ end
71
72
 
72
- function ServerImpl.sendToAll (self: any, data: any): ()
73
- checkDeltaMode (self, DELTA_BROADCAST)
74
- serverWriteToAll (self._id, self._name, self._codec, data, self._isUnreliable)
75
- end
73
+ local id = self._id
74
+ local name = self._name
75
+ local codec = self._codec
76
+ local isUnreliable = self._isUnreliable
76
77
 
77
- function ServerImpl.sendToList (self: any, data: any, players: { Player }): ()
78
- checkDeltaMode (self, DELTA_TARGETED)
79
- serverWriteToList (players, self._id, self._name, self._codec, data, self._isUnreliable)
80
- end
78
+ -- Single player
79
+ if typeof (target) == "Instance" then
80
+ checkDeltaMode (self, DELTA_TARGETED)
81
+ serverWriteTo (target :: Player, id, name, codec, data, isUnreliable)
82
+ return
83
+ end
81
84
 
82
- function ServerImpl.sendToAllExcept (self: any, data: any, except: Player): ()
83
- checkDeltaMode (self, DELTA_BROADCAST)
84
- serverWriteToAllExcept (except, self._id, self._name, self._codec, data, self._isUnreliable)
85
- end
85
+ -- Must be a table from here
86
+ local tag = target._tag
87
+
88
+ -- Lync.all sentinel
89
+ if tag == "all" then
90
+ checkDeltaMode (self, DELTA_BROADCAST)
91
+ serverWriteToAll (id, name, codec, data, isUnreliable)
92
+ return
93
+ end
94
+
95
+ -- Lync.except(player, ...) descriptor
96
+ if tag == "except" then
97
+ checkDeltaMode (self, DELTA_BROADCAST)
98
+ serverWriteToAllExcept (target._set, id, name, codec, data, isUnreliable)
99
+ return
100
+ end
101
+
102
+ -- Group object
103
+ if tag == "group" then
104
+ checkDeltaMode (self, DELTA_BROADCAST)
105
+ serverWriteToSet (target:getSet (), id, name, codec, data, isUnreliable)
106
+ return
107
+ end
86
108
 
87
- function ServerImpl.sendToGroup (self: any, data: any, groupName: string): ()
88
- checkDeltaMode (self, DELTA_BROADCAST)
89
- serverWriteToGroup (groupName, self._id, self._name, self._codec, data, self._isUnreliable)
109
+ -- Player list (array)
110
+ checkDeltaMode (self, DELTA_TARGETED)
111
+ serverWriteToList (target :: { Player }, id, name, codec, data, isUnreliable)
90
112
  end
91
113
 
92
114
  table.freeze (ServerImpl)
93
115
 
94
- -- Client-only metatable . Has send(), no sendTo/sendToAll
116
+ -- Client-only metatable. Has send(), no target needed.
95
117
  local ClientImpl = setmetatable ({}, { __index = SharedImpl })
96
118
  ClientImpl.__index = ClientImpl
97
119
 
@@ -101,7 +123,7 @@ end
101
123
 
102
124
  table.freeze (ClientImpl)
103
125
 
104
- -- Public -----------------------------------------------------------------
126
+ -- Public -------------------------------------------------------------
105
127
 
106
128
  local Packet = {}
107
129
 
@@ -115,7 +137,7 @@ function Packet.define (name: string, config: PacketConfig<any>): any
115
137
 
116
138
  local isUnreliable = config.unreliable or false
117
139
 
118
- if (config.value :: any)._isDelta and isUnreliable then
140
+ if (config.value :: InternalCodec<any>)._isDelta and isUnreliable then
119
141
  error (`[Lync] Delta codec requires reliable delivery: "{name}"`)
120
142
  end
121
143
 
@@ -131,7 +153,7 @@ function Packet.define (name: string, config: PacketConfig<any>): any
131
153
  config.maxPayloadBytes
132
154
  )
133
155
 
134
- local isDelta = (config.value :: any)._isDelta == true
156
+ local isDelta = (config.value :: InternalCodec<any>)._isDelta == true
135
157
 
136
158
  return setmetatable ({
137
159
  _id = reg.id,
@@ -6,7 +6,6 @@ local Players = game:GetService ("Players")
6
6
  local RunService = game:GetService ("RunService")
7
7
 
8
8
  local Client = require (script.Parent.Parent.transport.Client)
9
- local Group = require (script.Parent.Group)
10
9
  local Registry = require (script.Parent.Parent.internal.Registry)
11
10
  local Server = require (script.Parent.Parent.transport.Server)
12
11
  local Signal = require (script.Parent.Signal)
@@ -15,14 +14,14 @@ local Types = require (script.Parent.Parent.Types)
15
14
  type Connection = Types.Connection
16
15
  type QueryConfig<R, S> = Types.QueryConfig<R, S>
17
16
 
18
- -- Constants --------------------------------------------------------------
17
+ -- Constants ----------------------------------------------------------
19
18
 
20
19
  local IS_SERVER = RunService:IsServer ()
21
20
 
22
21
  -- Wire ceiling: correlationId is u16 in Channel.writeQuery / Reader.process.
23
22
  local POOL_SIZE = 65536
24
23
 
25
- -- State ------------------------------------------------------------------
24
+ -- State --------------------------------------------------------------
26
25
 
27
26
  type PendingQuery = {
28
27
  thread: thread?,
@@ -34,7 +33,7 @@ local _pending = {} :: { [number]: PendingQuery }
34
33
  local _nextId = 0
35
34
  local _activeCount = 0
36
35
 
37
- -- Private ----------------------------------------------------------------
36
+ -- Private ------------------------------------------------------------
38
37
 
39
38
  local function allocCorrelation (): number
40
39
  if _activeCount >= POOL_SIZE then
@@ -54,7 +53,9 @@ local function allocCorrelation (): number
54
53
  return id
55
54
  end
56
55
 
57
- local function freeCorrelation (): ()
56
+ -- Decrements the active correlation counter. Does not free a specific slot;
57
+ -- that is handled by nil-ing _pending[id] at the call site.
58
+ local function releaseCorrelation (): ()
58
59
  _activeCount -= 1
59
60
  end
60
61
 
@@ -65,7 +66,7 @@ local function completeQuery (id: number, data: any): ()
65
66
  end
66
67
 
67
68
  _pending[id] = nil
68
- freeCorrelation ()
69
+ releaseCorrelation ()
69
70
 
70
71
  if entry.timeout then
71
72
  task.cancel (entry.timeout)
@@ -82,90 +83,35 @@ local function onTimeout (correlation: number): ()
82
83
  completeQuery (correlation, nil)
83
84
  end
84
85
 
85
- -- Query ------------------------------------------------------------------
86
-
87
- local QueryImpl = {}
88
- QueryImpl.__index = QueryImpl
89
-
90
- function QueryImpl.listen (self: any, callback: (...any) -> any): Connection
91
- local respId = self._respReg.id
92
- local respName = self._name
93
- local respCodec = self._respReg.codec
94
-
95
- if IS_SERVER then
96
- return self._reqReg.signal:connect (
97
- function (request: any, player: Player, correlation: number): ()
98
- local ok, response = pcall (callback, request, player)
99
- if not ok then
100
- warn (`[Lync] Query handler error on "{respName}": {response}`)
101
- end
102
-
103
- if ok and response ~= nil then
104
- Server.writeQuery (player, respId, respName, correlation, respCodec, response)
105
- else
106
- Server.writeQueryNil (player, respId, respName, correlation)
107
- end
108
- end
109
- )
110
- else
111
- return self._reqReg.signal:connect (
112
- function (request: any, _player: Player?, correlation: number): ()
113
- local ok, response = pcall (callback, request)
114
- if not ok then
115
- warn (`[Lync] Query handler error on "{respName}": {response}`)
116
- end
117
-
118
- if ok and response ~= nil then
119
- Client.writeQuery (respId, respName, correlation, respCodec, response)
120
- else
121
- Client.writeQueryNil (respId, respName, correlation)
122
- end
123
- end
124
- )
86
+ -- Send a query to multiple players and yield. Returns { [Player]: response? }.
87
+ local function requestMulti (self: any, players: { Player }, data: any): { [Player]: any? }
88
+ if not IS_SERVER then
89
+ error ("[Lync] Multi-request is server-only")
125
90
  end
126
- end
127
91
 
128
- function QueryImpl.invoke (self: any, request: any, player: Player?): any
129
- if IS_SERVER and not player then
130
- error ("[Lync] Query:invoke on server requires a player argument")
92
+ local count = #players
93
+ if count == 0 then
94
+ return {}
131
95
  end
132
96
 
133
- local correlation = allocCorrelation ()
134
- local thread = coroutine.running ()
135
-
136
- local entry: PendingQuery = { thread = thread, timeout = nil }
137
- _pending[correlation] = entry
97
+ if count == 1 then
98
+ -- Delegate to single-player path
99
+ local correlation = allocCorrelation ()
100
+ local thread = coroutine.running ()
101
+ local entry: PendingQuery = { thread = thread, timeout = nil }
102
+ _pending[correlation] = entry
138
103
 
139
- if IS_SERVER then
140
104
  Server.writeQuery (
141
- player :: Player,
105
+ players[1],
142
106
  self._reqReg.id,
143
107
  self._name,
144
108
  correlation,
145
109
  self._reqReg.codec,
146
- request
110
+ data
147
111
  )
148
- else
149
- Client.writeQuery (self._reqReg.id, self._name, correlation, self._reqReg.codec, request)
150
- end
151
-
152
- entry.timeout = task.delay (self._timeout, onTimeout, correlation)
153
- return coroutine.yield ()
154
- end
155
-
156
- -- Send a query to multiple players and yield. Returns { [Player]: response? }.
157
- local function invokeMulti (self: any, players: { Player }, request: any): { [Player]: any? }
158
- if not IS_SERVER then
159
- error ("[Lync] Multi-invoke is server-only")
160
- end
161
-
162
- local count = #players
163
- if count == 0 then
164
- return {}
165
- end
166
112
 
167
- if count == 1 then
168
- local result = QueryImpl.invoke (self, request, players[1])
113
+ entry.timeout = task.delay (self._timeout, onTimeout, correlation)
114
+ local result = coroutine.yield ()
169
115
  return { [players[1]] = result }
170
116
  end
171
117
 
@@ -187,8 +133,8 @@ local function invokeMulti (self: any, players: { Player }, request: any): { [Pl
187
133
 
188
134
  local entry: PendingQuery = {
189
135
  thread = nil,
190
- callback = function (data: any): ()
191
- results[player] = data
136
+ callback = function (resp: any): ()
137
+ results[player] = resp
192
138
  remaining -= 1
193
139
  if remaining == 0 and not finished then
194
140
  finished = true
@@ -200,7 +146,7 @@ local function invokeMulti (self: any, players: { Player }, request: any): { [Pl
200
146
 
201
147
  _pending[correlation] = entry
202
148
 
203
- Server.writeQuery (player, reqId, reqName, correlation, reqCodec, request)
149
+ Server.writeQuery (player, reqId, reqName, correlation, reqCodec, data)
204
150
  end
205
151
 
206
152
  local timeoutThread = task.delay (timeout, function (): ()
@@ -214,7 +160,7 @@ local function invokeMulti (self: any, players: { Player }, request: any): { [Pl
214
160
  local entry = _pending[cid]
215
161
  if entry then
216
162
  _pending[cid] = nil
217
- freeCorrelation ()
163
+ releaseCorrelation ()
218
164
  end
219
165
  end
220
166
 
@@ -232,25 +178,105 @@ local function invokeMulti (self: any, players: { Player }, request: any): { [Pl
232
178
  return coroutine.yield ()
233
179
  end
234
180
 
235
- function QueryImpl.invokeAll (self: any, request: any): { [Player]: any? }
181
+ local QueryImpl = {}
182
+ QueryImpl.__index = QueryImpl
183
+
184
+ function QueryImpl.listen (self: any, callback: (...any) -> any): Connection
185
+ local respId = self._respReg.id
186
+ local respName = self._name
187
+ local respCodec = self._respReg.codec
188
+
189
+ if IS_SERVER then
190
+ return self._reqReg.signal:connect (
191
+ function (request: any, player: Player, correlation: number): ()
192
+ local ok, response = pcall (callback, request, player)
193
+ if not ok then
194
+ warn (`[Lync] Query handler error on "{respName}": {response}`)
195
+ end
196
+
197
+ if ok and response ~= nil then
198
+ Server.writeQuery (player, respId, respName, correlation, respCodec, response)
199
+ else
200
+ Server.writeQueryNil (player, respId, respName, correlation)
201
+ end
202
+ end
203
+ )
204
+ else
205
+ return self._reqReg.signal:connect (
206
+ function (request: any, _player: Player?, correlation: number): ()
207
+ local ok, response = pcall (callback, request)
208
+ if not ok then
209
+ warn (`[Lync] Query handler error on "{respName}": {response}`)
210
+ end
211
+
212
+ if ok and response ~= nil then
213
+ Client.writeQuery (respId, respName, correlation, respCodec, response)
214
+ else
215
+ Client.writeQueryNil (respId, respName, correlation)
216
+ end
217
+ end
218
+ )
219
+ end
220
+ end
221
+
222
+ -- Client: send request to server, yield until response.
223
+ function QueryImpl.request (self: any, data: any): any
224
+ if IS_SERVER then
225
+ error ("[Lync] query:request is client-only. Use query:requestFrom on server")
226
+ end
227
+
228
+ local correlation = allocCorrelation ()
229
+ local thread = coroutine.running ()
230
+ local entry: PendingQuery = { thread = thread, timeout = nil }
231
+ _pending[correlation] = entry
232
+
233
+ Client.writeQuery (self._reqReg.id, self._name, correlation, self._reqReg.codec, data)
234
+
235
+ entry.timeout = task.delay (self._timeout, onTimeout, correlation)
236
+ return coroutine.yield ()
237
+ end
238
+
239
+ -- Server: send request to one player, yield until response.
240
+ function QueryImpl.requestFrom (self: any, player: Player, data: any): any
241
+ if not IS_SERVER then
242
+ error ("[Lync] query:requestFrom is server-only. Use query:request on client")
243
+ end
244
+
245
+ local correlation = allocCorrelation ()
246
+ local thread = coroutine.running ()
247
+ local entry: PendingQuery = { thread = thread, timeout = nil }
248
+ _pending[correlation] = entry
249
+
250
+ Server.writeQuery (player, self._reqReg.id, self._name, correlation, self._reqReg.codec, data)
251
+
252
+ entry.timeout = task.delay (self._timeout, onTimeout, correlation)
253
+ return coroutine.yield ()
254
+ end
255
+
256
+ -- Server: send request to all players, yield until all respond or timeout.
257
+ function QueryImpl.requestAll (self: any, data: any): { [Player]: any? }
236
258
  local players = Players:GetPlayers ()
237
- return invokeMulti (self, players, request)
259
+ return requestMulti (self, players, data)
238
260
  end
239
261
 
240
- function QueryImpl.invokeList (self: any, request: any, players: { Player }): { [Player]: any? }
241
- return invokeMulti (self, players, request)
262
+ -- Server: send request to a list of players.
263
+ function QueryImpl.requestList (self: any, players: { Player }, data: any): { [Player]: any? }
264
+ return requestMulti (self, players, data)
242
265
  end
243
266
 
244
- function QueryImpl.invokeGroup (self: any, request: any, groupName: string): { [Player]: any? }
245
- local set = Group.getSet (groupName)
267
+ -- Server: send request to all players in a group.
268
+ function QueryImpl.requestGroup (self: any, group: any, data: any): { [Player]: any? }
269
+ local set = group:getSet ()
246
270
  local players = {} :: { Player }
247
271
  for player in set do
248
272
  table.insert (players, player)
249
273
  end
250
- return invokeMulti (self, players, request)
274
+ return requestMulti (self, players, data)
251
275
  end
252
276
 
253
- -- Public -----------------------------------------------------------------
277
+ table.freeze (QueryImpl)
278
+
279
+ -- Public -------------------------------------------------------------
254
280
 
255
281
  local Query = {}
256
282
 
@@ -0,0 +1,73 @@
1
+ --!strict
2
+ --!optimize 2
3
+ -- Batched connection lifecycle management.
4
+
5
+ -- Private ------------------------------------------------------------
6
+
7
+ local ScopeImpl = {}
8
+ ScopeImpl.__index = ScopeImpl
9
+
10
+ function ScopeImpl.add (self: any, conn: any): ()
11
+ if not conn then
12
+ return
13
+ end
14
+
15
+ local entries = self._entries
16
+ local count = self._count + 1
17
+ self._count = count
18
+ entries[count] = conn
19
+ end
20
+
21
+ function ScopeImpl.listen (self: any, source: any, callback: (...any) -> ()): ()
22
+ local conn = source:listen (callback)
23
+ local entries = self._entries
24
+ local count = self._count + 1
25
+ self._count = count
26
+ entries[count] = conn
27
+ end
28
+
29
+ function ScopeImpl.once (self: any, source: any, callback: (...any) -> ()): ()
30
+ local conn = source:once (callback)
31
+ local entries = self._entries
32
+ local count = self._count + 1
33
+ self._count = count
34
+ entries[count] = conn
35
+ end
36
+
37
+ function ScopeImpl.listenAll (self: any, namespace: any, callback: (...any) -> ()): ()
38
+ local conn = namespace:listenAll (callback)
39
+ local entries = self._entries
40
+ local count = self._count + 1
41
+ self._count = count
42
+ entries[count] = conn
43
+ end
44
+
45
+ function ScopeImpl.destroy (self: any): ()
46
+ local entries = self._entries
47
+ local count = self._count
48
+
49
+ for i = 1, count do
50
+ local conn = entries[i]
51
+ entries[i] = nil
52
+
53
+ if typeof (conn) == "RBXScriptConnection" then
54
+ conn:Disconnect ()
55
+ elseif conn.disconnect then
56
+ conn:disconnect ()
57
+ end
58
+ end
59
+
60
+ self._count = 0
61
+ end
62
+
63
+ table.freeze (ScopeImpl)
64
+
65
+ -- Public -------------------------------------------------------------
66
+
67
+ local Scope = {}
68
+
69
+ function Scope.create (): any
70
+ return setmetatable ({ _entries = {}, _count = 0 }, ScopeImpl)
71
+ end
72
+
73
+ return table.freeze (Scope)
@@ -6,12 +6,13 @@ local Types = require (script.Parent.Parent.Types)
6
6
 
7
7
  type Connection = Types.Connection
8
8
 
9
- -- Listener state: single numeric field for both connected and once checks.
9
+ -- Constants ----------------------------------------------------------
10
+
10
11
  local STATE_DISCONNECTED = 0
11
12
  local STATE_CONNECTED = 1
12
13
  local STATE_ONCE = 2
13
14
 
14
- -- Private ----------------------------------------------------------------
15
+ -- Private ------------------------------------------------------------
15
16
 
16
17
  local _freeThread: thread? = nil
17
18
 
@@ -36,7 +37,9 @@ local function spawn (fn: (...any) -> (), ...: any): ()
36
37
  task.spawn (_freeThread :: thread, fn, ...)
37
38
  end
38
39
 
39
- -- Entry doubles as the Connection handle , 1 alloc per listener instead of 2.
40
+ -- Entry doubles as the Connection handle: 1 alloc per listener instead of 2.
41
+ -- self: any — metatable typing via typeof(setmetatable) is not practical here
42
+ -- because Entry and Signal reference each other circularly.
40
43
  local EntryImpl = {}
41
44
  EntryImpl.__index = EntryImpl
42
45
 
@@ -213,7 +216,10 @@ function SignalImpl.disconnectAll (self: any): ()
213
216
  self._count = 0
214
217
  end
215
218
 
216
- -- Public -----------------------------------------------------------------
219
+ table.freeze (EntryImpl)
220
+ table.freeze (SignalImpl)
221
+
222
+ -- Public -------------------------------------------------------------
217
223
 
218
224
  local Signal = {}
219
225
 
@@ -3,9 +3,13 @@
3
3
  -- Fixed-size codec factory. Every fixed-size codec calls Base.define once.
4
4
 
5
5
  local Channel = require (script.Parent.Parent.internal.Channel)
6
+ local Types = require (script.Parent.Parent.Types)
7
+
8
+ type ChannelState = Types.ChannelState
9
+
6
10
  local alloc = Channel.alloc
7
11
 
8
- -- Public -----------------------------------------------------------------
12
+ -- Public -------------------------------------------------------------
9
13
 
10
14
  local Base = {}
11
15
 
@@ -18,7 +22,7 @@ function Base.define <T>(
18
22
  _size = size,
19
23
  _directWrite = directWrite,
20
24
  _directRead = directRead,
21
- write = function (ch: any, value: T): ()
25
+ write = function (ch: ChannelState, value: T): ()
22
26
  local c = ch.cursor
23
27
  if c + size > ch.size then
24
28
  alloc (ch, size)
@@ -39,7 +43,7 @@ function Base.zero (): any
39
43
  _directRead = function (_b: buffer, _offset: number): any
40
44
  return nil
41
45
  end,
42
- write = function (_ch: any, _value: any): () end,
46
+ write = function (_ch: ChannelState, _value: any): () end,
43
47
  read = function (_src: buffer, _pos: number, _refs: { Instance }?): (any, number)
44
48
  return nil, 0
45
49
  end,