Binder - Intelligent Connection Management System…
Introduction
Binder is a strictly-typed, metatable heavy connection management lib for Luau. It is designed to be easy to use, and familiar with the default connection system.
Why You Need this
-
Memory leaks:
RBXScriptConnection and Signals are an extremely integral part of game development. e.g., PlayerAdded, PlayerRemoving, etc. If you forget to disconnect any connections - which is easy to forget, you add up a small performance hit.
Lots of developers handle this with arrays, with repeatedDisconnectcalls scattered everywhere. This gets overwhelming quite fast; especially with larger projects. -
Binder solves this for you:
Wrap any
SignalLikeonce, and every single connection made through it gets tracked automatically and intelligently, with minimal effort. -
Built and tested:
Binder was built from the ground up, and thoroughly unit-tested using TestEZ by Roblox.
How it works
Binder uses metatables and proxies to intercept calls and an intelligent and optimized ConnectionPoller. The main advantage of this is it requires little to no effort. You wrap a SignalLike once, and that signal is now tracked.
ConnectionPoller uses an optimized system to constantly check connections (incase of an external disconnection) over multiple frames. It also includes a cleanup interval (def: 60s) that cleans up all connections that may have made it through.
When no connections need cleanup, ConnectionPoller detects this and disconnects from RunService.
Performance Characteristics
-
Connection overhead: Near-zero. Proxy creation uses cached metatable detection and single table lookups.
-
Cleanup Detection: RBXScriptConnections are polled every few frames with optimized single-pass array compaction with the
ConnectionPoller. Table-based connections like GoodSignal use instant metatable-based detection. Unfortunately, there’s no way currently to track when RBXScriptConnections get disconnected externally instantly - as you cannot attach a metatable to a RBXScriptConnection. -
Memory Efficiency: Swapping array elements during removal maintains O(1) access patterns. No memory allocation during cleanup operations
-
Scaling behavior: Linear with active connection count, not total connections ever created. Disconnected connections are removed from tracking immediately
API
Basic Usage
Here’s a basic setup of Binder
local Binder = require(path.to.Binder)
local Signal = require(path.to.Signal)
local binder = Binder.new()
local mySignal = Signal.new()
-- Here's how we can register connections into binder...
local con = binder:bind(workspace.ChildAdded):Connect(function(child: Instance)
print(child)
end)
-- Now 'con' is contained!
-- Even if it is disconnected externally, that change gets reflected due to the intelligent behavior of Binder
-- Also supports custom Signals...
binder:bind(mySignal):Connect(function(...)
print(...)
end)
-- Note, ConnectParallel will error with GoodSignal. (Not supported- but included in the type)
-- We later fire the signal:
task.delay(5, function()
mySignal:Fire("string", 5)
end)
-- We have options for cleanup...
binder:unbind(con) -- Cleans up the specific connection, in this case 'con'
-- This unbinds everything...
binder:unbindAll() -- Everything
-- A complete teardown, unbinds everything, cleans up the object. Cannot be used
binder:destroy() -- Complete teardown
-- binder:bind() -- This will error!
Ensure you are using the correct connection method provided by your signal implementation.
It is super easy to register connections as you can see above, and it supports custom signals like GoodSignal!
We can also create groups for unit control:
local Binder = require(game.ReplicatedStorage.SharedModules.Libraries.Binder)
local Signal = require(game.ReplicatedStorage.SharedModules.Libraries.Signal)
local binder = Binder.new()
local mySignal = Signal.new()
-- We can use `binder:bindGroup` to construct a group...
local group = binder:bindGroup({
-- Construct ConnectionLike's returned from `bind` here...
binder:bind(workspace.ChildAdded):Connect(function(child)
print(child)
end),
-- Add a custom signal...
binder:bind(mySignal):Connect(function(...)
print(...)
end)
})
-- We can add ConnectionLike's
local addedIndex = group:add(binder:bind(workspace.ChildRemoved):Connect(function(child)
print(child)
end))
-- Removing ConnectionLike's
local removedCon = group:remove(1)
-- Options for cleanup...
-- Individual control:
group[addedIndex]:Disconnect() -- Disconnects the addedIndex connection from earlier
group[2]:Disconnect() -- Disconnects mySignal connection!
-- Mass control
group:disconnect() -- Disconnects EVERYTHING in the group! (Aside from removedCon, as it was removed!)
-- Full cleanup...
group:destroy()
GroupHandle objects provide dual access patterns: use
getConnections()for the full array, or index directly withgroup[1],group[2], etc. to access individual ConnectionLike objects.
Binder
Constructor
Binder.new() -> Binder: Creates a new Binder object for managing signal connections.
Methods
-
bind: <T...>(self: Binder, signal: SignalLike<T...>) -> SignalLike<T...>: Returns a proxy around the signal that tracks all connections made through it. -
bindGroup: (self: Binder, connections: {ConnectionLike}) -> GroupHandle: Creates a GroupHandle object for batch management of multiple connections. -
unbind: (self: Binder, connection: ConnectionLike) -> (): Disconnects and removes a specific connection from tracking. Most efficient cleanup method for ConnectionLike objects.
This is the optimal way of disconnecting ConnectionLike objects! -
unbindGroup: (self: Binder, group: GroupHandle) -> (): Disconnects all connections in a GroupHandle. This is equivalent togroup:disconnect. -
unbindAll: (self: Binder) -> (): Disconnects every tracked connection in the Binder instance. -
destroy: (self: Binder) -> (): Complete cleanup - disconnects everything and marks instance as destroyed.
After calling destroy, the Binder object should not be used anymore as it will no longer function properly.
GroupHandle
Returned by binder:bindGroup(). Manages collections of connections
GroupHandle is not directly exposed from Binder
Methods
-
add: (self: GroupHandle, connection: ConnectionLike) -> number: Adds a connection to the group, returns insertion index. -
remove: (self: GroupHandle, index: number) -> ConnectionLike: Removes connection from group without disconnecting it. Returns the removed connection. -
disconnect: (self: GroupHandle) -> (): Disconnects all connections in the group and clears the array.
The GroupHandle object is still usable after calling this method. If you have no use of the object, call destroyto free up memory -
getConnections: (self: GroupHandle) -> {ConnectionLike}: Returns internal connection array.
Returned array is mutable - treat as read-only -
destroy: (self: GroupHandle) -> (): Disconnects all connections and destroys the GroupHandle object.
After calling destroy, the GroupHandle object should not be used anymore as it will no longer function properly.
Signal Compatibility
- RBXScriptSignals:
Connect,ConnectParallel,Once - GoodSignal:
Connect,Once - Custom Signals: Any signal with compatible methods/properties
Binder is thoroughly tested with both RBXScriptSignals and GoodSignal implementations. It is not guaranteed to work with other custom signal implementations other than GoodSignal. Other custom implementations could work though.
Warnings
-
Timing
RBXScriptConnections have a small delay (~0.33ms) before removal from internal tracking when disconnected externally. Table-based connections are removed instantly. This doesn’t affect functionality, but may appear in debugging scenarios. This is intentional as to preserve the low performance overhead of Binder. -
GroupHandle Arrays Should be Treated as Readonly
getConnections()returns the actual internal array, not a copy. Modifying it directly bypasses Binder’s tracking and will cause issues. Treat the array as read-only. -
Destroyed Objects Should not be Used
Attempting to use Binder or GroupHandle methods after callingdestroy()will assert and throw. This is intentional - destroyed objects should not be reused. -
External Disconnections are Supported
External disconnection is supported, but suboptimal. You can callDisconnect()directly on connections, but usingbinder:unbind()is faster, and more performance friendly for cleanup.
Changelog
V1.0.1 (Current)
- Added support for Rojo via the Github releases page
License
Binder is under the MIT license. Feel free to use and modify for any Roblox projects.
Installation
Github page for Rojo users: GitHub - chumnyman/binder
You can download here on Roblox Creator Store: Binder - Intelligent Connection Management
You can also get the rbxm file:
binder.rbxm (13.6 KB)
Last Updated: 11/17/25
Author - RobloxJrTrainer
Current Version: 1.0.1
If you encounter bugs, let me know!
Made with
~ RobloxJrTrainer