@quenty/signal 7.5.0 → 7.7.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/CHANGELOG.md +32 -0
- package/package.json +3 -3
- package/src/Shared/GoodSignal.lua +71 -40
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
# [7.7.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/signal@7.6.0...@quenty/signal@7.7.0) (2024-10-04)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* GoodSignal now uses the connection memory category instead of the original items memory category, resulting in even more accurate tracking ([c18353d](https://github.com/Quenty/NevermoreEngine/commit/c18353d61a4b4966ad4025c3b7e58b895dcb16a8))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Performance Improvements
|
|
15
|
+
|
|
16
|
+
* Connection clears references, and avoids storing _connected and _next, which reduces memory usage of signal ([8738269](https://github.com/Quenty/NevermoreEngine/commit/8738269c457b8075b89dd18e7371a103413879d6))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# [7.6.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/signal@7.5.0...@quenty/signal@7.6.0) (2024-09-25)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* Selene doesn't know debug.getmemorycategory() ([0c613a3](https://github.com/Quenty/NevermoreEngine/commit/0c613a3ae0b6ba6a4cda511f572220bfa951c70d))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### Features
|
|
31
|
+
|
|
32
|
+
* MemoryCategory is tracked properly in signal (Not sure what the perf implications of this are) ([4d43b0c](https://github.com/Quenty/NevermoreEngine/commit/4d43b0c0c07fd5d24335b1801ca96c58d37ba149))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
6
38
|
# [7.5.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/signal@7.4.0...@quenty/signal@7.5.0) (2024-09-25)
|
|
7
39
|
|
|
8
40
|
**Note:** Version bump only for package @quenty/signal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quenty/signal",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.7.0",
|
|
4
4
|
"description": "A simple signal implementation for Roblox",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Roblox",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@quenty/loader": "^10.
|
|
33
|
+
"@quenty/loader": "^10.6.0"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "035abfa088c854a73e1c65b350267eaa17669646"
|
|
36
36
|
}
|
|
@@ -44,25 +44,31 @@
|
|
|
44
44
|
]=]
|
|
45
45
|
|
|
46
46
|
-- The currently idle thread to run the next handler on
|
|
47
|
-
local
|
|
47
|
+
local weakFreeRunnerThreadLookup = setmetatable({}, {__mode = "kv"})
|
|
48
48
|
|
|
49
49
|
-- Function which acquires the currently idle handler runner thread, runs the
|
|
50
50
|
-- function fn on it, and then releases the thread, returning it to being the
|
|
51
51
|
-- currently idle one.
|
|
52
52
|
-- If there was a currently idle runner thread already, that's okay, that old
|
|
53
53
|
-- one will just get thrown and eventually GCed.
|
|
54
|
-
local function acquireRunnerThreadAndCallEventHandler(fn, ...)
|
|
55
|
-
local acquiredRunnerThread =
|
|
56
|
-
|
|
54
|
+
local function acquireRunnerThreadAndCallEventHandler(memoryCategory, fn, ...)
|
|
55
|
+
local acquiredRunnerThread = weakFreeRunnerThreadLookup[memoryCategory]
|
|
56
|
+
weakFreeRunnerThreadLookup[memoryCategory] = nil
|
|
57
57
|
fn(...)
|
|
58
58
|
-- The handler finished running, this runner thread is free again.
|
|
59
|
-
|
|
59
|
+
weakFreeRunnerThreadLookup[memoryCategory] = acquiredRunnerThread
|
|
60
60
|
end
|
|
61
61
|
|
|
62
62
|
-- Coroutine runner that we create coroutines of. The coroutine can be
|
|
63
63
|
-- repeatedly resumed with functions to run followed by the argument to run
|
|
64
64
|
-- them with.
|
|
65
|
-
local function runEventHandlerInFreeThread()
|
|
65
|
+
local function runEventHandlerInFreeThread(memoryCategory)
|
|
66
|
+
if #memoryCategory == 0 then
|
|
67
|
+
debug.setmemorycategory("signal_unknown")
|
|
68
|
+
else
|
|
69
|
+
debug.setmemorycategory(memoryCategory)
|
|
70
|
+
end
|
|
71
|
+
|
|
66
72
|
-- Note: We cannot use the initial set of arguments passed to
|
|
67
73
|
-- runEventHandlerInFreeThread for a call to the handler, because those
|
|
68
74
|
-- arguments would stay on the stack for the duration of the thread's
|
|
@@ -75,40 +81,56 @@ end
|
|
|
75
81
|
|
|
76
82
|
-- Connection class
|
|
77
83
|
local Connection = {}
|
|
84
|
+
Connection.ClassName = "Connection"
|
|
78
85
|
Connection.__index = Connection
|
|
79
86
|
|
|
80
87
|
function Connection.new(signal, fn)
|
|
81
88
|
return setmetatable({
|
|
82
|
-
|
|
89
|
+
-- selene: allow(incorrect_standard_library_use)
|
|
90
|
+
_memoryCategory = debug.getmemorycategory(),
|
|
83
91
|
_signal = signal,
|
|
84
92
|
_fn = fn,
|
|
85
|
-
_next = false,
|
|
86
93
|
}, Connection)
|
|
87
94
|
end
|
|
88
95
|
|
|
96
|
+
function Connection:IsConnected()
|
|
97
|
+
return rawget(self, "_signal") ~= nil
|
|
98
|
+
end
|
|
99
|
+
|
|
89
100
|
function Connection:Disconnect()
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
--
|
|
96
|
-
|
|
97
|
-
|
|
101
|
+
local signal = rawget(self, "_signal")
|
|
102
|
+
if not signal then
|
|
103
|
+
return
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
-- Unhook the node. Originally the good signal would not clear this signal and
|
|
107
|
+
-- rely upon GC. However, this means that connections would keep themselves and other
|
|
108
|
+
-- disconnected nodes in the chain alive, keeping the function closure alive, and in return
|
|
109
|
+
-- keeping the signal alive. This means a `Maid` could keep full object trees alive if a
|
|
110
|
+
-- connection was made to them.
|
|
111
|
+
|
|
112
|
+
local ourNext = rawget(self, "_next")
|
|
113
|
+
|
|
114
|
+
if signal._handlerListHead == self then
|
|
115
|
+
signal._handlerListHead = ourNext or false
|
|
98
116
|
else
|
|
99
|
-
local prev =
|
|
100
|
-
while prev and prev
|
|
101
|
-
prev = prev
|
|
117
|
+
local prev = signal._handlerListHead
|
|
118
|
+
while prev and rawget(prev, "_next") ~= self do
|
|
119
|
+
prev = rawget(prev, "_next")
|
|
102
120
|
end
|
|
103
121
|
if prev then
|
|
104
|
-
prev
|
|
122
|
+
rawset(prev, "_next", ourNext)
|
|
105
123
|
end
|
|
106
124
|
end
|
|
125
|
+
|
|
126
|
+
-- Clear all member variables that aren't _next so keeping a connection
|
|
127
|
+
-- indexed allows for GC of other components
|
|
128
|
+
table.clear(self)
|
|
107
129
|
end
|
|
108
130
|
|
|
109
131
|
Connection.Destroy = Connection.Disconnect
|
|
110
132
|
|
|
111
|
-
-- Make
|
|
133
|
+
-- Make signal strict
|
|
112
134
|
setmetatable(Connection, {
|
|
113
135
|
__index = function(_, key)
|
|
114
136
|
error(string.format("Attempt to get Connection::%s (not a valid member)", tostring(key)), 2)
|
|
@@ -153,7 +175,7 @@ end
|
|
|
153
175
|
function Signal:Connect(fn)
|
|
154
176
|
local connection = Connection.new(self, fn)
|
|
155
177
|
if self._handlerListHead then
|
|
156
|
-
connection
|
|
178
|
+
rawset(connection, "_next", self._handlerListHead)
|
|
157
179
|
self._handlerListHead = connection
|
|
158
180
|
else
|
|
159
181
|
self._handlerListHead = connection
|
|
@@ -186,17 +208,26 @@ end
|
|
|
186
208
|
@param ... T -- Variable arguments to pass to handler
|
|
187
209
|
]=]
|
|
188
210
|
function Signal:Fire(...)
|
|
189
|
-
local
|
|
190
|
-
while
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
211
|
+
local connection = self._handlerListHead
|
|
212
|
+
while connection do
|
|
213
|
+
-- capture our next node, which could after this be cleared or disconnected.
|
|
214
|
+
-- any connections occuring during fire will be added to the _handerListHead and not be fired
|
|
215
|
+
-- in this round. Any disconnections in the chain will still work here.
|
|
216
|
+
local nextNode = rawget(connection, "_next")
|
|
217
|
+
|
|
218
|
+
if rawget(connection, "_signal") ~= nil then -- isConnected
|
|
219
|
+
local memoryCategory = connection._memoryCategory
|
|
220
|
+
|
|
221
|
+
-- Get the freeRunnerThread to the first yield
|
|
222
|
+
if not weakFreeRunnerThreadLookup[memoryCategory] then
|
|
223
|
+
weakFreeRunnerThreadLookup[memoryCategory] = coroutine.create(runEventHandlerInFreeThread)
|
|
224
|
+
coroutine.resume(weakFreeRunnerThreadLookup[memoryCategory], memoryCategory)
|
|
196
225
|
end
|
|
197
|
-
|
|
226
|
+
|
|
227
|
+
task.spawn(weakFreeRunnerThreadLookup[memoryCategory], memoryCategory, connection._fn, ...)
|
|
198
228
|
end
|
|
199
|
-
|
|
229
|
+
|
|
230
|
+
connection = nextNode
|
|
200
231
|
end
|
|
201
232
|
end
|
|
202
233
|
|
|
@@ -213,11 +244,13 @@ end
|
|
|
213
244
|
]=]
|
|
214
245
|
function Signal:Wait()
|
|
215
246
|
local waitingCoroutine = coroutine.running()
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
247
|
+
|
|
248
|
+
local connection
|
|
249
|
+
connection = self:Connect(function(...)
|
|
250
|
+
connection:Disconnect()
|
|
219
251
|
task.spawn(waitingCoroutine, ...)
|
|
220
252
|
end)
|
|
253
|
+
|
|
221
254
|
return coroutine.yield()
|
|
222
255
|
end
|
|
223
256
|
|
|
@@ -233,14 +266,12 @@ end
|
|
|
233
266
|
@return RBXScriptConnection
|
|
234
267
|
]=]
|
|
235
268
|
function Signal:Once(fn)
|
|
236
|
-
local
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
cn:Disconnect()
|
|
240
|
-
end
|
|
269
|
+
local connection
|
|
270
|
+
connection = self:Connect(function(...)
|
|
271
|
+
connection:Disconnect()
|
|
241
272
|
fn(...)
|
|
242
273
|
end)
|
|
243
|
-
return
|
|
274
|
+
return connection
|
|
244
275
|
end
|
|
245
276
|
|
|
246
277
|
--[=[
|