A high-performance, signal and request library for Roblox. Wire provides lightweight alternatives to `BindableEvent` and `BindableFunction` with superior performance and a clean, functional API.
- Blazing Fast — Outperforms GoodSignal, SignalPlus, and FastSignal in benchmarks
- O(1) Disconnect — Doubly-linked list architecture enables constant-time connection removal
- Thread Pool Recycling — Efficient coroutine reuse for async operations
- Parallel Luau Support — First-class support for parallel execution with
connectParallel - Dual Paradigm — Both event-style signals and RPC-style requests
- Type-Safe — Full Luau strict mode with exported types
- Zero Dependencies — Pure Luau implementation
- Memory-Efficient — Instead of creating a table & using metatables for every signal object, it simply creates an ID and only stores head & tail connections when needed
[dependencies]
Wire = "elentium/wire@0.0.4"install the roblox-direct/Wire.rbxm and insert in studio
local Wire = require(path.to.Wire)
-- Create a signal
local PlayerDamaged = Wire.signal()
-- Connect a listener
local connection = Wire.connect(PlayerDamaged, function(player, damage)
print(player.Name .. " took " .. damage .. " damage!")
end)
-- Fire the signal
Wire.fire(PlayerDamaged, player, 25)
-- Disconnect when done
connection:disconnect()Creates a new signal and returns its entity ID.
local MySignal = Wire.signal()Creates a new request (similar to BindableFunction) and returns its entity ID.
local GetPlayerData = Wire.request(function(player)
return playerDataStore[player]
end)Connects a callback to a signal. Returns a connection object.
local connection = Wire.connect(MySignal, function(...)
print("Signal fired with:", ...)
end)Connects a callback that runs in parallel (for Parallel Luau).
Wire.connectParallel(HeavyComputation, function(data)
-- This runs desynchronized from the main thread
processData(data)
end)Connects a callback that automatically disconnects after the first fire.
Wire.once(GameStarted, function()
print("Game has started!")
end)Combines once and connectParallel — runs once in parallel, then disconnects.
Fires a signal synchronously. All callbacks execute sequentially in the current thread.
Wire.fire(MySignal, "arg1", "arg2", 123)Recommended for non-yielding callbacks. Most performant option.
Fires a signal synchronously. All callbacks are wrapped in pcall and are executed sequentially in the current thread.
Wire.fire(MySignal, "arg1", "arg2", 123)Recommended for non-yielding callbacks that can error and you do not want it to stop other connections.
Fires a signal asynchronously. Each callback runs in its own coroutine.
Wire.fireAsync(MySignal, data)Use when callbacks may yield (e.g., contain
task.wait, HTTP requests, etc.)
Yields the current thread until the signal fires, then returns the fired arguments.
local damage, attacker = Wire.await(PlayerDamaged)
print("Received damage:", damage, "from", attacker)Disconnects all connections from a signal. Can also serve as a signal destructor.
Wire.disconnectAll(MySignal)Sets or updates the callback for a request.
Wire.onInvoke(GetPlayerData, function(player)
return database:GetAsync(player.UserId)
end)Invokes a request and returns the result.
local data = Wire.invoke(GetPlayerData, player)Removes the request callback, freeing the reference.
Wire.destroyRequest(GetPlayerData)Disconnects the connection from its signal.
local connection = Wire.connect(MySignal, callback)
-- Later...
connection:disconnect()Benchmarks run with 10 connections per signal:
| Operation | Wire | GoodSignal | SignalPlus | FastSignal |
|---|---|---|---|---|
| Fire (100k iterations) | 0.490s | 0.552s | 0.587s | 0.677s |
| Disconnect (1k items) | 0.00007s | 0.0155s | 0.00008s | 0.00009s |
Wire achieves the fastest fire times and the fastest disconnect times thanks to its doubly-linked list architecture (O(1) removal vs O(n) for array-based implementations).
fire is significantly faster because it avoids coroutine overhead. Only use fireAsync when your callbacks yield.
-- ✅ Good: Non-yielding callback with fire
Wire.connect(DamageDealt, function(amount)
healthBar:Update(amount)
end)
Wire.fire(DamageDealt, 50)
-- ✅ Good: Yielding callback with fireAsync
Wire.connect(SaveData, function(player)
dataStore:SetAsync(player.UserId, getData(player))
end)
Wire.fireAsync(SaveData, player)With fire, an error in any callback will halt execution. Wrap risky code in pcall:
Wire.connect(RiskySignal, function(data)
local success, err = pcall(function()
processUnsafeData(data)
end)
if not success then
warn("Handler error:", err)
end
end)Always disconnect connections when they're no longer needed to prevent memory leaks:
local connections = {}
function module:Init()
table.insert(connections, Wire.connect(Signal1, handler1))
table.insert(connections, Wire.connect(Signal2, handler2))
end
function module:Destroy()
for _, conn in connections do
conn:disconnect()
end
table.clear(connections)
endApache-2.0 — See LICENSE for details.
- GitHub: https://github.com/Elentium/Wire
- Wally:
elentium/wire@0.0.4