Signal Certifications & Classes
[1] What are Signals?
It’s a custom module meant to improve-upon and replace BindableEvents as an event-dispatcher, they usually take up less memory and often include additional features for versatility such as :DisconnectAll().
Terminology Clarification
Event-dispatcher= The Signal module itselfDispatch=:Fire()Listeners= connectionsMutations= new connections & disconnections, midway through a dispatch.Reentrancy= A function that calls itself in a recursive-like manner.:Fire()inside of:Fire()to the same event.Deferred= NOTtask.defer, I just mean the literal concept of inserting an action to the very end of a list/queue/stack.
[2] What are Certifications?
Certifications are badges to serve as proof of capability that they’re guaranteed to be reliable in some form of specified functionality.
List of Certifications
Scheduler Certification
If the Signal has the minimum working features expected of any event-dispatcher on Roblox.
Details
:Connect()→ Event Listener that can be dispatched multiple times until disconnected.:Once()→ Event Listener that will disconnect itself upon/after its first dispatch.:Wait()→ Event Listener that yields the current cooperative thread (coroutine thread).:Disconnect()→ Method called by connections to disconnect themselves.:DisconnectAll()→ Method called by the signal to disconnect all connections.
Speed Certification
If all minimum features of the Signal are faster or fast enough to replace BindableEvents without perceptibly harming existing systems implemented by developers. Results are compared to the baseline speeds (BindableEvents) within a margin of acceptable speed.
Details
< +0.1µs means that the Signal is allowed to be up to +0.1µs slower or faster than BindableEvents, measured in microseconds or 0.000001 of a second. Comparison values are retrieved from the 50th percentile of samples, 1_000 → 100_000 samples depending on the metric.
Create→< +2.0µs, to create the Signal object.Connect→< +0.1µsOnce→< +0.1µsFire_None→< +2.0µs, to dispatch with 0 listenersFire_One→< +3.0µs, to dispatch with 1 listenerFire_Many→< +3.0µs, to dispatch with 100 listenersFire_OneYield→< +5.0µs, to dispatch with 1 listener that yield usingtask.wait()Fire_ManyYield→< +5.0µs, to dispatch with 100 listeners that yield usingtask.wait()Disconnect→< +0.1µs, to disconnect 1 listenerDisconnectAll→< +0.1µs, to disconnect 100 listeners at once
Connected Certification
If the Signal includes full support for the .Connected value/property for connections. Readability of a connection’s state is important for cleanup systems/modules to effectively handle them.
Details
It must be accounted for by the following:
:Connect()→ true:Once()→ true:Disconnect()→ false:DisconnectAll()→ false.Connected→ boolean
Snapshot Certification
If midway-mutations are processed only after listeners are ran and reentrant dispatches are deferred.
Details
Ideal Stack Order:
:Fire() [START]Run connections→ ALL connections are ran even if they’re disconnected midway through.Process mutationsRun/Continue deferred reentrant dispatch queue→:Fire() within a :Fire() is deferred so mutations are respected.[END]
Synchronous Certification
If event-dispatches are ran-to-completion before it returns/synchronous. If all connections are ran, mutations processed, and reentrant dispatches ran & completed before the initial :Fire() returns. The code will wait for it to finish before it moves on.
Details
Note: Connections being ran in a dispatch will exit the “stack” (but remain connected) if they become asynchronous/encounter a yield such as task.wait or coroutine.yield(), this is to preserve the dispatcher’s synchronous behavior.
Order Certification
If connections are ran and maintained in the order of Oldest → Newest inserted.
Details
Preserved behaviors with Oldest → Newest
-
Causality
In order to respect how time works, each new listener should only extend or observe older listeners. It’s easier to work with what has already happened than to predict what might happen. This is non-negotiable in complex systems where you can’t keep track of the timing of every connection. -
Composability
Exploring deeper in causality and how it affects systems that stack on one another, this ordering allows for assumptions to remain stable without the need for side-effect safety due to new systems modifying values afterwards rather than beforehand. -
Determinism
Causality and composability affects how independent handlers determine actions and results based on what has already happened rather than through pure prediction. -
Debuggability
Determinism except for the specialized purpose of debugging/searching for potential issues. Debuggers should only ever observe what has already happened, this is the only point that truly necessitates Oldest → Newest.
^ Although the above behaviors can similarly be done in Newest → Oldest, it will require an unnecessary amount of extra effort depending on application complexity.
[3] What are Classes?
When certain certifications are combined, they qualify for one of three levels of classification representing their utility-capability and favorability over BindableEvents. Although it’s strongly encouraged to use Class 3 signals, it’s ultimately not necessary depending on your needs. Any Signal can be considered “good enough” as long as it isn’t Class 0.
List of Classes
Class 0
The signal lacks one or all necessary certifications to be safe or even worth using over BindableEvents.
Class 1
The signal is safe and fast enough for specialized use outside of needing the .Connected property, a potential suitable replacement to BindableEvents.
Requirements
Scheduler Certified
Speed Certified
Class 2
The signal is a complete suitable replacement to BindableEvents for all situations outside of absolute speed.
Requirements
Scheduler Certified
Speed Certified
Connected Certified
Note: Absolute speed is only required in outlier-heavy workloads that deals with 100_000+ connections at once which most developers will never need nor encounter.
Class 3
The signal follows the modern standard that best fits Roblox game development, Synchronous FRP (Functional Reactive Programming) with Atomic Propagation. Signals of this classification should always be used over BindableEvents and other classifications due to these superior event-dispatcher behaviors:
- Snapshot/Queue-based dispatch ordering
It’ll run all connections uninterrupted first, apply mutations afterwards, then finally resume deferred reentrant event dispatches (:Fire() within a :Fire()). This is awesome because it entirely eliminates mutation-related bugs caused by dependence on connection timing or insertion order, which is crucial for complex projects where you can’t easily keep track of connection-execution timing. - Synchronous timing
It’ll run all connections, mutations, and nested dispatches to completion prior to returning the :Fire(). This guarantees that the event’s effects are applied exactly when you wanted it and never after (not asynchronous). - Connections are ran and maintained in the order of “Oldest → Newest”
This direction is far superior because it preserves:- Causality, respects time.
- Composability, no side-effect safety needed for stacking systems.
- Determinism, easier to determine based on what has already happened than what might happen.
- Debuggability, this REQUIRES Oldest → Newest if attempting to debug older connections.
It’s still completely backwards compatible to reeimplement some or all of BindableEvent’s quirks at your discretion at minimal to no human-perceptible speed cost.
The only things that BindableEvents do right are order maintenance (but in Newest → Oldest) and reentrance deferral.
Requirements
Scheduler Certified
Speed Certified
Connected Certified
Snapshot Certification
Synchronous Certification
Order Certification
Rant Yappathon
Thanks to BindableEvents (and all pre-existing Signals) lacking one or multiple of those vital behaviors, it should be easy to understand the canon events that it forced every developer designing a complex system to encounter. Such as, finding out that there’s an order-based bug that required you to implement side-effect safety. Or even worse (happened to me MULTIPLE times), you were forced to design your ENTIRE event handling process around order dependence because you had no choice. That is the PRIMARY reason I was spiteful enough to spend 2 months of my life researching and developing Signal Certifications & Classes for the best-possible event-dispatcher standard in the context of Roblox game development. Class 3 signals finally exist on Roblox not only to prevent those canon events from happening in the first place, but they also finally give you a CHOICE.
[4] How to test a Signal?
By following these 5 easy steps:
- Download the Grader (either download works)
- Insert the grader to ServerScriptService
- Set the
SignalReference’s value to the Signal module you want to test
- Ensure that all of the configurations are to your liking.
- Run/Playtest to view your results in the output
[5] What do existing Signals score as?
This is of all current Signals that I could find on the developer forum and niche development servers.
Downloads and images of certifications are present below, feel free to test them out yourself.
If you do happen to pick a Signal to use, here are my personal recommendations:
🏆 Class 3: SomeSignal🏆 Class 3: NewSignal🏆 Class 3: NamedSignal
🥉 Class 0
CuteSignal
A synchronous-only signal that runs connections one at a time to completion, where you need to manually wrap connections in a thread (task.spawn | task.defer) to be able to have asynchronicity support. The only reason it’s Class 0 is because it doesn’t have built-in asynchronous error-catching, so an entire main-thread that calls Fire will break if there’s a single error in any of the connections. If you do wish to use this signal, I recommend wrapping connections in a pcall/xpcall where-ever applicable.
Download: Devforum Page
DProSignal
Download: Github Page
SignalPlus
Download: Devforum Page
SimpleSignal
Download: Devforum Page
Suphi’s Signal Module
Download: Creator Store Page
ZeroSignal
Another synchronous-only signal (you need to manually wrap connections in a thread for asynchronicity). This signal is archived and should never be used in a live project.
Archive: Devforum Page
🥇 Class 2
LemonSignal
Download: Github Page
MadSignal
Creator: GohanDucis
The Signal of my own design during the development of the Signal Certifications & Class system, abandoned after the release of the
Order Certification in favor of entrusting the first Class 3 Signal efforts to the awesome creator of SomeSignal.
Download: MadSignal.lua (23.9 KB)
Wire
A signal where you can create “Wires”, which are unique Ids that you use to manually pass through to wire-functions.
NOTE: Use [5] instead of .Connected to check if a connection is still connected (example in Code Sample).
Code Sample
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()
-- Check that it disconnected
print(connection[5]) -- false
Download: Github Page
Grader Adapter: WireAdapter.lua (636 Bytes)
Zignal
Creator: Z3SC
Download: Zignal.lua (2.9 KB)
🏆 Class 3
NamedSignal
A Luau signal implementation that lets you name and define variadic parameters — conveniently.
Download: Devforum Page
NewSignal
New Gen Signal implementation.
NOTE: Use [1] instead of .Connected to check if a connection is still connected (example in Code Sample).
Code Sample
local signalMD = require(path.to.NewSignal)
-- Create signal
local signal = signalMD.new()
-- Connect a listener
local connection = signal:Connect(function()
print("Signal fired!")
end)
-- Fire the signal
signal:Fire()
-- Disconnect when done
connection:Disconnect()
-- Check that it disconnected
print(connection[1]) -- false
Download: Github Page
SomeSignal
Creator: 6inch9inch
SomeSignal used and even built upon this guide to also offer extra features at no compromise such as:
Download: Devforum Page
❔ Honorable Mentions
Signals that cannot be tested by the SIgnal Certifications Grader without significant modifications due to the non-standard API that they offer to developers. (I haven’t made an adapter to test them yet)
There’s nothing here…
























