SignalX is an advanced event and messaging system for Roblox focused on developer experience, memory safety, and production debugging.
It improves on native signals by adding scoped cleanup, listener priorities, middleware, tags, replay-on-connect, and strong safety defaults.
SignalX is designed as a structured messaging layer:
- Better API ergonomics for large codebases
- Automatic cleanup patterns to prevent leaks
- Built-in debug visibility
- Extensible middleware pipeline
Design priorities:
- Developer experience
- Safety
- Debug visibility
- Performance
- Priority listeners (
Connect(fn, 100)) - One-shot listeners (
Once) - Safe execution (
pcallper listener) - Middleware chain (
Use) - Tagged listeners and bulk disconnect (
DisconnectTag) - Scope-based cleanup (
Signal.Scope.new()) - Replay last payload on connect (
Connect(fn, { replay = true })) - Auto disconnect when instance is destroyed (
AutoDisconnect) - Deferred firing (
FireDeferred) - Listener warning threshold (
SetMaxListeners) - Debug logging (
EnableDebug)
Install with Wally:
[dependencies]
SignalX = "nsawill1405/signalx@0.1.0"Then run:
wally installUse in Roblox:
local Packages = game:GetService("ReplicatedStorage").Packages
local Signal = require(Packages.SignalX)local Signal = require(Packages.SignalX)
local playerHit = Signal.new({ name = "PlayerHit" })
playerHit:Connect(function(player, damage)
print(player.Name, damage)
end)
playerHit:Fire(game.Players:GetPlayers()[1], 15)local sig = Signal.new()
sig:Connect(function() print("Low") end, 0)
sig:Connect(function() print("High") end, 100)
sig:Once(function() print("Only once") end)
sig:Fire()
sig:Fire()local roundScope = Signal.Scope.new()
local roundEnded = Signal.new()
roundScope:Connect(roundEnded, function()
print("Round ended")
end)
roundScope:Destroy() -- disconnects all tracked connectionslocal sig = Signal.new()
sig:Use(function(next, ...)
print("Before")
next(...)
print("After")
end)
sig:Connect(function(message)
print("Message:", message)
end)
sig:Fire("hello")local sig = Signal.new()
sig:Connect(function() end):Tag("UI")
sig:Connect(function() end):Tag("Combat")
sig:DisconnectTag("UI")local sig = Signal.new()
sig:Fire("cached payload")
sig:Connect(function(value)
print("Replayed:", value)
end, { replay = true })SignalX includes an example benchmark script at:
examples/Benchmark.server.lua
Benchmark guidance:
- Run benchmark in an empty place.
- Compare native
BindableEventand SignalX on your target device profile. - Measure connect cost, fire cost, and disconnect-all cost.
SignalX performance design choices:
- Array-based listener storage
- Dirty-sort only when needed
- Cached listener count
- Deferred compaction during active fire loops
SignalX is not trying to be the largest feature set. It is focused on predictable, safe behavior in real Roblox projects where many systems communicate concurrently.
It gives teams a better default event layer by making cleanup easy, failures isolated, and runtime behavior observable.
Create a signal.
config.name: string?config.maxListeners: number?
Connect a listener.
priorityOrOptionscan be:number(priority){ priority, replay, once, tags, autoDisconnect }
Returns a Connection.
Connect and auto-disconnect after first execution.
Runs middleware and listeners immediately.
Defers execution with task.defer.
Yields current coroutine until next fire and returns payload.
Returns a wait handle with:
:andThen(callback):await():cancel()
Disconnect every listener.
Disconnect listeners that have tag.
Add middleware (function(next, ...args)).
Enable debug logging.
Set signal display name in debug output.
Configure warning threshold for listener count.
Returns the active listener count.
connection:Disconnect()connection:Tag(tag)connection:HasTag(tag)connection:GetTag()connection:GetTags()connection:AutoDisconnect(instance)connection:IsConnected()connection:GetPriority()
local scope = Signal.Scope.new()scope:Track(connection)scope:Connect(signal, fn, priorityOrOptions?)scope:Destroy()
Scripts included:
test/Signal.unit.luatest/Signal.stress.lua
The unit tests cover:
- Connect / Fire
- Once behavior
- Priority ordering
- Disconnect logic
- Scope cleanup
- Replay behavior
The stress test covers:
- 1000+ listeners
- Rapid fire loop
- Listener cleanup validation
