@quenty/signal 7.6.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 CHANGED
@@ -3,6 +3,22 @@
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
+
6
22
  # [7.6.0](https://github.com/Quenty/NevermoreEngine/compare/@quenty/signal@7.5.0...@quenty/signal@7.6.0) (2024-09-25)
7
23
 
8
24
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quenty/signal",
3
- "version": "7.6.0",
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.5.0"
33
+ "@quenty/loader": "^10.6.0"
34
34
  },
35
- "gitHead": "9b17fe79cddd071f0f06a9d35184e76b44bd6fe6"
35
+ "gitHead": "035abfa088c854a73e1c65b350267eaa17669646"
36
36
  }
@@ -44,7 +44,7 @@
44
44
  ]=]
45
45
 
46
46
  -- The currently idle thread to run the next handler on
47
- local freeRunnerThreadLookup = {}
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
@@ -52,13 +52,11 @@ local freeRunnerThreadLookup = {}
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
54
  local function acquireRunnerThreadAndCallEventHandler(memoryCategory, fn, ...)
55
- debug.setmemorycategory(memoryCategory)
56
-
57
- local acquiredRunnerThread = freeRunnerThreadLookup[memoryCategory]
58
- freeRunnerThreadLookup[memoryCategory] = nil
55
+ local acquiredRunnerThread = weakFreeRunnerThreadLookup[memoryCategory]
56
+ weakFreeRunnerThreadLookup[memoryCategory] = nil
59
57
  fn(...)
60
58
  -- The handler finished running, this runner thread is free again.
61
- freeRunnerThreadLookup[memoryCategory] = acquiredRunnerThread
59
+ weakFreeRunnerThreadLookup[memoryCategory] = acquiredRunnerThread
62
60
  end
63
61
 
64
62
  -- Coroutine runner that we create coroutines of. The coroutine can be
@@ -83,40 +81,56 @@ end
83
81
 
84
82
  -- Connection class
85
83
  local Connection = {}
84
+ Connection.ClassName = "Connection"
86
85
  Connection.__index = Connection
87
86
 
88
87
  function Connection.new(signal, fn)
89
88
  return setmetatable({
90
- _connected = true,
89
+ -- selene: allow(incorrect_standard_library_use)
90
+ _memoryCategory = debug.getmemorycategory(),
91
91
  _signal = signal,
92
92
  _fn = fn,
93
- _next = false,
94
93
  }, Connection)
95
94
  end
96
95
 
96
+ function Connection:IsConnected()
97
+ return rawget(self, "_signal") ~= nil
98
+ end
99
+
97
100
  function Connection:Disconnect()
98
- self._connected = false
99
-
100
- -- Unhook the node, but DON'T clear it. That way any fire calls that are
101
- -- currently sitting on this node will be able to iterate forwards off of
102
- -- it, but any subsequent fire calls will not hit it, and it will be GCed
103
- -- when no more fire calls are sitting on it.
104
- if self._signal._handlerListHead == self then
105
- self._signal._handlerListHead = self._next
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
106
116
  else
107
- local prev = self._signal._handlerListHead
108
- while prev and prev._next ~= self do
109
- prev = prev._next
117
+ local prev = signal._handlerListHead
118
+ while prev and rawget(prev, "_next") ~= self do
119
+ prev = rawget(prev, "_next")
110
120
  end
111
121
  if prev then
112
- prev._next = self._next
122
+ rawset(prev, "_next", ourNext)
113
123
  end
114
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)
115
129
  end
116
130
 
117
131
  Connection.Destroy = Connection.Disconnect
118
132
 
119
- -- Make Connection strict
133
+ -- Make signal strict
120
134
  setmetatable(Connection, {
121
135
  __index = function(_, key)
122
136
  error(string.format("Attempt to get Connection::%s (not a valid member)", tostring(key)), 2)
@@ -161,7 +175,7 @@ end
161
175
  function Signal:Connect(fn)
162
176
  local connection = Connection.new(self, fn)
163
177
  if self._handlerListHead then
164
- connection._next = self._handlerListHead
178
+ rawset(connection, "_next", self._handlerListHead)
165
179
  self._handlerListHead = connection
166
180
  else
167
181
  self._handlerListHead = connection
@@ -194,20 +208,26 @@ end
194
208
  @param ... T -- Variable arguments to pass to handler
195
209
  ]=]
196
210
  function Signal:Fire(...)
197
- -- selene: allow(incorrect_standard_library_use)
198
- local memoryCategory = debug.getmemorycategory()
199
-
200
- local item = self._handlerListHead
201
- while item do
202
- if item._connected then
203
- if not freeRunnerThreadLookup[memoryCategory] then
204
- freeRunnerThreadLookup[memoryCategory] = coroutine.create(runEventHandlerInFreeThread)
205
- -- Get the freeRunnerThread to the first yield
206
- coroutine.resume(freeRunnerThreadLookup[memoryCategory], memoryCategory)
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)
207
225
  end
208
- task.spawn(freeRunnerThreadLookup[memoryCategory], memoryCategory, item._fn, ...)
226
+
227
+ task.spawn(weakFreeRunnerThreadLookup[memoryCategory], memoryCategory, connection._fn, ...)
209
228
  end
210
- item = item._next
229
+
230
+ connection = nextNode
211
231
  end
212
232
  end
213
233
 
@@ -224,11 +244,13 @@ end
224
244
  ]=]
225
245
  function Signal:Wait()
226
246
  local waitingCoroutine = coroutine.running()
227
- local cn;
228
- cn = self:Connect(function(...)
229
- cn:Disconnect()
247
+
248
+ local connection
249
+ connection = self:Connect(function(...)
250
+ connection:Disconnect()
230
251
  task.spawn(waitingCoroutine, ...)
231
252
  end)
253
+
232
254
  return coroutine.yield()
233
255
  end
234
256
 
@@ -244,14 +266,12 @@ end
244
266
  @return RBXScriptConnection
245
267
  ]=]
246
268
  function Signal:Once(fn)
247
- local cn;
248
- cn = self:Connect(function(...)
249
- if cn._connected then
250
- cn:Disconnect()
251
- end
269
+ local connection
270
+ connection = self:Connect(function(...)
271
+ connection:Disconnect()
252
272
  fn(...)
253
273
  end)
254
- return cn
274
+ return connection
255
275
  end
256
276
 
257
277
  --[=[