├── Logo.png ├── documentation ├── .gitbook │ └── assets │ │ ├── Cover.png │ │ └── Custom type example.png ├── SUMMARY.md ├── getting-started │ ├── introduction.md │ ├── custom-types.md │ ├── api-reference.md │ └── sharing-signals.md └── README.md ├── README.md ├── LICENSE └── SignalPlus.luau /Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexanderLindholt/SignalPlus/HEAD/Logo.png -------------------------------------------------------------------------------- /documentation/.gitbook/assets/Cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexanderLindholt/SignalPlus/HEAD/documentation/.gitbook/assets/Cover.png -------------------------------------------------------------------------------- /documentation/.gitbook/assets/Custom type example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AlexanderLindholt/SignalPlus/HEAD/documentation/.gitbook/assets/Custom type example.png -------------------------------------------------------------------------------- /documentation/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [Installation](README.md) 4 | 5 | ## Getting started 6 | 7 | * [Introduction](getting-started/introduction.md) 8 | * [API Reference](getting-started/api-reference.md) 9 | * [Generic types](getting-started/custom-types.md) 10 | * [Sharing signals](getting-started/sharing-signals.md) 11 | -------------------------------------------------------------------------------- /documentation/getting-started/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: 3 | width: default 4 | title: 5 | visible: true 6 | description: 7 | visible: false 8 | tableOfContents: 9 | visible: true 10 | outline: 11 | visible: true 12 | pagination: 13 | visible: true 14 | metadata: 15 | visible: true 16 | --- 17 | 18 | # Introduction 19 | 20 | When you require the module, it’ll return a function, which you can use to create signals: 21 | 22 | ```lua 23 | local Signal = require(script.SignalPlus) 24 | 25 | local signal = Signal() -- Creates a new signal. 26 | ``` 27 | 28 | You can then use methods to control and fire connections on the signal — just like BindableEvents and other signal alternatives. See the full API on the next page. 29 | -------------------------------------------------------------------------------- /documentation/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: box-archive 3 | --- 4 | 5 | # Installation 6 | 7 | {% stepper %} 8 | {% step %} 9 | ### Get the module 10 | 11 | {% tabs %} 12 | {% tab title="Creator Store" %} 13 | Get Roblox Asset 14 | 15 | * Click `Get Model`. 16 | * Open the ToolBox in Roblox Studio. 17 | * Go to the `Inventory` tab. 18 | * Click on `Signal+` to insert. 19 | {% endtab %} 20 | 21 | {% tab title="GitHub" %} 22 | See Latest Release 23 | 24 | * Download the `.rbxm` file. 25 | * Find the file in your file explorer. 26 | * Drag the file into Roblox Studio. 27 | {% endtab %} 28 | {% endtabs %} 29 | {% endstep %} 30 | 31 | {% step %} 32 | ### Place it 33 | 34 | Find a great place for the module, where other scripts can reference it. 35 | {% endstep %} 36 | {% endstepper %} 37 | -------------------------------------------------------------------------------- /documentation/getting-started/custom-types.md: -------------------------------------------------------------------------------- 1 | --- 2 | icon: gear-code 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 | # Generic types 20 | 21 | It’s super easy to define generic types for your signals with Signal+.\ 22 | This means you can have autocompletion for the [parameters](https://create.roblox.com/docs/tutorials/fundamentals/coding-2/use-parameters-and-events) in your signals: 23 | 24 |
25 | 26 | 27 | 28 | You can provide your own parameters as shown in the screenshot above: 29 | 30 | ```lua 31 | local mySignal = Signal() :: Signal.Signal 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 | --------------------------------------------------------------------------------