32 | ```
33 |
34 | {% hint style="success" %}
35 | All methods (`Connect`, `Once`, `Wait`, and `Fire`) will display your desired parameters.
36 | {% endhint %}
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 | An exceptionally fast, lightweight, and elegant open-source signal
6 | library for Luau — with generic types and detailed documentation.
7 |
8 | [

](https://create.roblox.com/store/asset/118793070598362) [

](https://devforum.roblox.com/t/3552231)
9 |
10 |
11 |
12 |
13 |
14 |
15 | # ⚡ Speed. Reliability. Elegance.
16 | Signal+ is built for developers who demand performance without compromise.
17 | It’s fast, reliable, and equipped with every feature that matters.
18 |
19 | **It easily outperforms leading alternatives like
20 | Good-, Lemon-, and FastSignal — in both design and execution.**
21 |
22 |
23 |
24 | ## Visit the [documentation](https://alexxander.gitbook.io/signalplus) to learn more!
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Alexander Lindholt
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/documentation/getting-started/api-reference.md:
--------------------------------------------------------------------------------
1 | ---
2 | icon: list
3 | layout:
4 | width: default
5 | title:
6 | visible: true
7 | description:
8 | visible: false
9 | tableOfContents:
10 | visible: true
11 | outline:
12 | visible: true
13 | pagination:
14 | visible: true
15 | metadata:
16 | visible: true
17 | ---
18 |
19 | # API Reference
20 |
21 | ## `Signal`:
22 |
23 | | Name | Type | Description |
24 | | ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- |
25 | | Connect | Method | Connects a function.
Returns: Connection
|
26 | | Once | Method | Connects a function, then auto-disconnects after the first call.
Returns: Connection
|
27 | | Wait | Method | Yields the calling thread until the next fire. |
28 | | Fire | Method | Runs all connected functions and resumes all waiting threads. |
29 | | DisconnectAll | Method | Erases all connections.
Much faster than calling Disconnect on each.
|
30 | | Destroy | Method | Erases all connections and methods.
To fully erase, also remove all references to the signal.
|
31 |
32 | ## `Connection`:
33 |
34 | | Name | Type | Description |
35 | | ---------- | -------- | -------------------------------------------------------------------------------------------------------- |
36 | | Disconnect | Method | Removes the connection from the signal.
The connection’s data remains.
|
37 | | Connected | Property | A boolean representing the connection state. |
38 |
--------------------------------------------------------------------------------
/documentation/getting-started/sharing-signals.md:
--------------------------------------------------------------------------------
1 | ---
2 | icon: globe
3 | layout:
4 | width: default
5 | title:
6 | visible: true
7 | description:
8 | visible: false
9 | tableOfContents:
10 | visible: true
11 | outline:
12 | visible: true
13 | pagination:
14 | visible: true
15 | metadata:
16 | visible: true
17 | ---
18 |
19 | # Sharing signals
20 |
21 | Should you be in doubt, here are the main ways to share signals between scripts:
22 |
23 |
24 |
25 | Modules
26 |
27 | You can return a table of signals in a ModuleScript, which multiple other scripts can then require and thereby access the same signals and communicate.
28 |
29 | Scripts can also add or remove signals from the ModuleScript at any time.
30 |
31 |
32 |
33 | Example:
34 |
35 | {% code title="Signals (ModuleScript)" %}
36 | ```lua
37 | local Signal = require(script.SignalPlus)
38 |
39 | return {
40 | CoolSignal = Signal()
41 | SuperCoolSignal = Signal() :: Signal.Signal -- Custom type :D
42 | }
43 | ```
44 | {% endcode %}
45 |
46 | {% code title="Some script" %}
47 | ```lua
48 | local signals = require(script.Signals)
49 | local coolSignal = signals.CoolSignal
50 |
51 | task.wait(5)
52 |
53 | coolSignal:Fire()
54 | ```
55 | {% endcode %}
56 |
57 | {% code title="Some other script" %}
58 | ```lua
59 | local signals = require(script.Signals)
60 | local coolSignal = signals.CoolSignal
61 |
62 | coolSignal:Connect(function()
63 | print("CoolSignal fired!")
64 | end)
65 | ```
66 | {% endcode %}
67 |
68 |
69 |
70 |
71 |
72 | Built-in shared table (Roblox-specific)
73 |
74 | You can store signals in the built-in shared table, which multiple other scripts can then access and thereby access the same signals and communicate.
75 |
76 | Scripts can also add or remove signals from the shared table at any time.
77 |
78 |
79 |
80 | Example:
81 |
82 | {% code title="Some script" %}
83 | ```lua
84 | local Signal = require(script.SignalPlus)
85 | shared.CoolSignal = Signal()
86 |
87 | task.wait(5)
88 |
89 | shared.CoolSignal:Fire()
90 | ```
91 | {% endcode %}
92 |
93 | {% code title="Some other script" %}
94 | ```lua
95 | -- Just make sure that this part runs after
96 | -- the signal has been added to the shared table.
97 | shared.CoolSignal:Connect(function()
98 | print("CoolSignal fired!")
99 | end)
100 | ```
101 | {% endcode %}
102 |
103 |
104 |
--------------------------------------------------------------------------------
/SignalPlus.luau:
--------------------------------------------------------------------------------
1 | --!optimize 2
2 |
3 | --[[
4 |
5 | +++
6 | ++++++++ ===
7 | ++++++++++ ==== ====
8 | ++++++ ====
9 | +++++ ==== ====== ===== ==== ======= ======== ==== ====
10 | +++++ ==== ============= ============= =========== ==== ====
11 | ++++ ==== ==== ===== ===== ==== ==== ==== ====
12 | ++++ ==== ==== ===== ===== ==== ========== ==== =============
13 | ++++ ==== ==== ===== ===== ==== ====== ==== ==== =============
14 | ++++++ ==== ===== ====== ===== ==== ==== ==== ==== ==== +++++++++
15 | ++++++++++ ==== ============= ===== ==== ============ ==== ++++ ==== ++++++++++++
16 | +++++++ ==== ===== ==== ==== + ==== ==== ==== ++++++++ ++++++++
17 | +++++ ==== +++ ==== +++++++++++++++++++++++++++++++++++++++++++++++++++++
18 | ++++ +++++++++++ =========== +++++++++++++++++++++++++++++++++++++++ ++++++
19 | +++++++++++++++++++++++++++ +
20 | +++++++++++++++++++++++++
21 | +++++
22 |
23 | v3.6.1
24 |
25 | An exceptionally fast, lightweight, and elegant open-source signal
26 | library for Luau — with generic types and detailed documentation.
27 |
28 |
29 | GitHub (repository):
30 | https://github.com/AlexanderLindholt/SignalPlus
31 |
32 | GitBook (documentation):
33 | https://alexxander.gitbook.io/SignalPlus
34 |
35 | DevForum (topic):
36 | https://devforum.roblox.com/t/3552231
37 |
38 |
39 | --------------------------------------------------------------------------------
40 | MIT License
41 |
42 | Copyright (c) 2025 Alexander Lindholt
43 |
44 | Permission is hereby granted, free of charge, to any person obtaining a copy
45 | of this software and associated documentation files (the "Software"), to deal
46 | in the Software without restriction, including without limitation the rights
47 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
48 | copies of the Software, and to permit persons to whom the Software is
49 | furnished to do so, subject to the following conditions:
50 |
51 | The above copyright notice and this permission notice shall be included in all
52 | copies or substantial portions of the Software.
53 |
54 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
55 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
56 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
57 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
58 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
59 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
60 | SOFTWARE.
61 | --------------------------------------------------------------------------------
62 |
63 | ]]--
64 |
65 | -- Types.
66 | export type Connection = {
67 | Connected: boolean,
68 | Disconnect: typeof(
69 | -- Removes the connection from the signal.
70 | -- The connection’s data remains.
71 | function(connection: Connection) end
72 | )
73 | }
74 | export type Signal = {
75 | Connect: typeof(
76 | -- Connects a function.
77 | function(signal: Signal, callback: (Parameters...) -> ()): Connection end
78 | ),
79 | Once: typeof(
80 | -- Connects a function, then auto-disconnects after the first call.
81 | function(signal: Signal, callback: (Parameters...) -> ()): Connection end
82 | ),
83 | Wait: typeof(
84 | -- Yields the calling thread until the next fire.
85 | function(signal: Signal): Parameters... end
86 | ),
87 |
88 | Fire: typeof(
89 | --✨
90 | function(signal: Signal, ...: Parameters...) end
91 | ),
92 |
93 | DisconnectAll: typeof(
94 | -- Erases all connections.
95 | -- Much faster than calling Disconnect on each.
96 | function(signal: Signal) end
97 | ),
98 | Destroy: typeof(
99 | -- Erases all connections and methods.
100 | -- To fully erase, also remove all references to the signal.
101 | function(signal: Signal) end
102 | )
103 | }
104 | type CreateSignal = typeof(
105 | -- Creates a new signal.
106 | function(): Signal end
107 | )
108 |
109 | -- Spawn function.
110 | local spawnThread = if script:GetAttribute("Deferred") then task.defer else task.spawn
111 |
112 | -- Setup callback threads.
113 | local threads = {}
114 |
115 | local function reusableThreadCall(callback, thread, ...)
116 | callback(...)
117 | table.insert(threads, thread)
118 | end
119 | local function reusableThread()
120 | while true do
121 | reusableThreadCall(coroutine.yield())
122 | end
123 | end
124 |
125 | -- Connection methods.
126 | local Connection = {}
127 | Connection.__index = Connection
128 |
129 | Connection.Disconnect = function(connection)
130 | -- Verify connection.
131 | if not connection.Connected then return end
132 |
133 | -- Remove connection.
134 | connection.Connected = nil
135 |
136 | local signal = connection.Signal
137 | local previous = connection.Previous
138 | local next = connection.Next
139 | if previous then
140 | previous.Next = next
141 | else
142 | signal.Tail = next
143 | end
144 | if next then
145 | next.Previous = previous
146 | else
147 | signal.Head = previous
148 | end
149 | end
150 |
151 | -- Signal methods.
152 | local Signal = {}
153 | Signal.__index = Signal
154 |
155 | Signal.Connect = function(signal, callback)
156 | -- Linked list head.
157 | local head = signal.Head
158 |
159 | -- Create connection.
160 | local connection = {
161 | Signal = signal,
162 | Previous = head,
163 |
164 | Callback = callback,
165 |
166 | Connected = true
167 | }
168 |
169 | -- Add connection.
170 | if head then
171 | head.Next = connection
172 | else
173 | signal.Tail = connection
174 | end
175 | signal.Head = connection
176 |
177 | -- Return connection.
178 | return setmetatable(connection, Connection)
179 | end
180 | Signal.Once = function(signal, callback)
181 | -- Linked list head.
182 | local head = signal.Head
183 |
184 | -- Create connection.
185 | local connection = nil
186 | connection = {
187 | Signal = signal,
188 | Previous = head,
189 |
190 | Callback = function(...)
191 | -- Verify connection.
192 | if not connection.Connected then return end
193 |
194 | -- Remove connection.
195 | connection.Connected = false
196 |
197 | local previous = connection.Previous
198 | local next = connection.Next
199 | if previous then
200 | previous.Next = next
201 | else
202 | signal.Tail = next
203 | end
204 | if next then
205 | next.Previous = previous
206 | else
207 | signal.Head = previous
208 | end
209 |
210 | -- Fire callback.
211 | callback(...)
212 | end,
213 |
214 | Connected = true
215 | }
216 |
217 | -- Add connection.
218 | if head then
219 | head.Next = connection
220 | else
221 | signal.Tail = connection
222 | end
223 | signal.Head = connection
224 |
225 | -- Return connection.
226 | return setmetatable(connection, Connection)
227 | end
228 | Signal.Wait = function(signal)
229 | -- Save this thread to resume later.
230 | local thread = coroutine.running()
231 |
232 | -- Linked list head.
233 | local head = signal.Head
234 |
235 | -- Create connection.
236 | local connection = nil
237 | connection = {
238 | Previous = head,
239 |
240 | Callback = function(...)
241 | -- Remove connection.
242 | connection.Connected = false
243 |
244 | local previous = connection.Previous
245 | local next = connection.Next
246 | if previous then
247 | previous.Next = next
248 | else
249 | signal.Tail = next
250 | end
251 | if next then
252 | next.Previous = previous
253 | else
254 | signal.Head = previous
255 | end
256 |
257 | -- Resume the thread.
258 | if coroutine.status(thread) == "suspended" then -- To avoid creating new threads.
259 | task.spawn(thread, ...)
260 | end
261 | end
262 | }
263 |
264 | -- Add connection.
265 | if head then
266 | head.Next = connection
267 | else
268 | signal.Tail = connection
269 | end
270 | signal.Head = connection
271 |
272 | -- Yield until the next fire, then return the arguments on resume.
273 | return coroutine.yield()
274 | end
275 |
276 | Signal.Fire = function(signal, ...)
277 | local connection = signal.Tail -- Start from the tail (back) of the list.
278 | while connection do
279 | -- Find or create a thread, then run the callback in it.
280 | local length = #threads
281 | if length == 0 then
282 | local thread = coroutine.create(reusableThread)
283 | coroutine.resume(thread) -- Initialize.
284 | spawnThread(thread, connection.Callback, thread, ...)
285 | else
286 | local thread = threads[length]
287 | threads[length] = nil -- Remove from free threads list.
288 | spawnThread(thread, connection.Callback, thread, ...)
289 | end
290 | -- Traverse.
291 | connection = connection.Next
292 | end
293 | end
294 |
295 | Signal.DisconnectAll = function(signal)
296 | -- Remove all connections.
297 | local connection = signal.Tail
298 | while connection do
299 | connection.Connected = nil
300 | -- Traverse.
301 | connection = connection.Next
302 | end
303 | -- Remove signal’s references.
304 | signal.Tail = nil
305 | signal.Head = nil
306 | end
307 | Signal.Destroy = function(signal)
308 | -- Remove all connections.
309 | local connection = signal.Tail
310 | while connection do
311 | connection.Connected = nil
312 | -- Traverse.
313 | connection = connection.Next
314 | end
315 | -- Remove signal’s references.
316 | signal.Tail = nil
317 | signal.Head = nil
318 |
319 | -- Unlink signal methods.
320 | setmetatable(signal, nil)
321 | end
322 |
323 | -- Signal creation function.
324 | return function()
325 | return setmetatable({}, Signal) -- New blank metatable with signal methods attached.
326 | end :: CreateSignal
327 |
--------------------------------------------------------------------------------