Introducing Signal Class 2: A new way to create custom events

UPDATE:

This module will no longer receive updates as there is a new module released by me with various improvements. Please check it out here:

(Details on the post itself)

Original Post

As a developer, it has been difficult to find a way to create custom, modular, and efficient events in Roblox. The best solution I could find was @Quenty 's Signal class, which unfortunately has no documentation or tutorial here on the DevForum. Also, it used BindableEvents, and was not very easy to make cross-script compatible. That is why I decided to recreate this and call it Signal Class 2.

LINKS:
Download Module
Github (Still relatively new to Github, lemme know if there are issues)
Updates and Releases

What is different from this and the original Signal Class:

  • It does not use BindableEvents to make connections to a signal
  • It includes more functions that you may need
  • It was designed to be cross-script compatible, and is easy to use when making modules of your own (ex. gun module, cash module, etc.)

BENCHMARKS
For a brand new event on a server with one function connected to it, it takes an average of:
<0.0001seconds.
For a brand new event with 10 connections that perform basic mathematical calculations (The one I used was pi/2), it takes an average of <0.0001 seconds.
For a brand new event with 50 connections that perform basic mathematical calculations (The one I used was pi/2), it takes an average of <0.0002seconds.
Firing or connecting cross-script would have the same exact benchmarks, because of the way it is programmed.

I believe the numbers for the first 2 are smaller, but unfortunately there is no way to know, as benchmark numbers only have so many decimal digits.

DOCUMENTATION

Signal.new(name optional)
This creates a new signal which can be accessed cross-script. It has one parameter which is a name, which you will need if you plan on making it cross-script compatible. If you only plan on using it in the script you make it in, it is unnecessary to fill this parameter. This function returns one value, which is a new and blank custom signal.
Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.new("example") -- Again, the name is optional, and can only be used when making it cross script-compatible.

Signal.Get(name)
This gets a signal with the given name if one exists. If none exist, it returns nil. This is cross-script compatible, so creating a signal in one script and retrieving it in another will work.
Think of it the same way you would the :FindFirstChild() function
Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.Get("example") -- Will attempt to get a signal if one exists. You can use this to get signals from different scripts

Signal.WaitFor(name,timeOut optional)
This is nearly the same as the Get function, except it yields until it finds a signal with the given name or when the timeOut is reached, whichever comes first. It will release a warning if no signal is found in the given timeOut.

The timeOut value is optional, and the default value is 5.
This function is more reliable than the Get function, but should only be used at the beginning of scripts. the Get function should be used when returning nil is intended behavior.

Think of it the same way you would the :WaitForChild() function
Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.WaitFor("example",10) -- Yields the thread until it finds a signal or time runs out

Signal:Connect(function)
Connects the given function to the signal the function is ran on. Whenever the signal is fired, the function runs. This is a non-yielding function, similar to the real :Connect function, so it can be called in the middle of a thread. The function that is given as a parameter can accept multiple parameters.

Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.new() -- Creates a signal

ExampleSignal:Connect(function()
    print("This is an example of the connect function!")
end)

Signal:Fire(extra parameters optional)
Fires the given signal, which runs all functions that are connected to it using the :Connect() function. This fires cross-script.

Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.new() -- Creates a signal

ExampleSignal:Connect(function(param)
    print(param)
end)

ExampleSignal:Fire("This is an example of passing a paramter")

Signal:Wait(function)
Yields the thread it is called in until the signal that is given is fired. This does yield.

Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.new() -- Creates a signal

ExampleSignal:Wait() -- Waits until the signal is called

print("It was called!") -- Of course this runs when the signal is called. This can be cross-script.

Signal:Destroy()
Destroys the signal, which prevents it from being fired and prevents connected functions from running. This does not error, so attempting to fire a destroyed signal will not cause an error.

Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.new() -- Creates a signal

wait(5) -- Waits a bit...

ExampleSignal:Destroy() -- Destroys the signal

Connection:Disconnect()
Disconnects the function from the connection from the signal it is attached to. This function can only be run on a connection, so doing Signal:Disconnect() will error.

Example:

local Signal = require(script.Signal) -- Or wherever your module is located
local ExampleSignal = Signal.new() -- Creates a signal

local ExampleConnection = ExampleSignal:Connect(function()
    print("This is an example of the connect function!")
end)

wait(5) -- Waits a bit

ExampleConnection:Disconnect() -- Disconnects the function from the ExampleSignal

Please let me know if you have any reccomendations or if you encounter a problem and enjoy the module!

25 Likes

I have a few questions. Should I always be using this instead of Remote Events? Also, will this make it harder to fire a remote event or something? Like you need some scripting knowledge in order to try and fire a signal event.

1 Like

No, this is an alternative to making custom RBXScriptSignalEvents, such as Instance.Changed. You will probably need scripting knowledge to understand this and to utilize it for the needs it was intended to fulfill. Also, this is different from remote events. Remote events are for client-server communication and vice-versa. Also, this does not replace bindables because this is more modular and is meant for simple code for events, such as money changed or gun fired.

1 Like

Oh wow! This is very cool! I’m excited to try this on my projects! :slight_smile:

2 Likes

Have you ran any performance tests?
I don’t think you can write anything more efficient in Lua compared to the C++ equivalent

3 Likes

I could be wrong but I don’t think the purpose is to have any sort of performance boost, at least if that’s what you are implying - it wasn’t super clear. The purpose of it wouldn’t be for anything like networking, it’s to create an OOP object to act as a script signal. It was possible to actually create those in the past but that has since been deprecated. So this is really useful when working with OOP. Personally speaking I think this version of a signal class is overcomplicating things and I prefer Quenty’s.

2 Likes

Yes, @ForcyDorcy and @dispeller , the purpose of this was to make it easy to make custom event signals of your own. I will run performance tests. Also Quenty’s does not have documentation here and this provides cross-script support, which is most important when creating modules of your own. That is the main purpose of the functions such as WaitFor and Get. You can not really compare this to Quenty’s, as his goal and mine are different. Also, this just provides extra functions to use if you want, so it should not feel overcomplicated. If you feel that it is overcomplicated or hard to use, please tell me why and how so I can improve. Also, the goal of this one and the main difference between the key functions of mine and Quenty’s are the way they were written. Quenty’s uses BindableFunctions and mine is based around OOP programming and tables.

1 Like

I’ve just never come across a need to get the events in the way you are describing. I’ve always just used return statements and that’s worked just fine. That being said, if for whatever reason you need/want to get the events like this, there isn’t anything wrong with it. Just not my cup of tea.

1 Like

I wrote down some benchmarks, they seem pretty consistent, and also seem to be faster and more efficient when compared to bindable events.

1 Like

That makes sense. The most common use for the cross-script method is when writing modules or when you need to fire a signal from a different script, or make a new connection from a different script. Once again, the most common case for this is for modules. You may come across this in the future. Also, that being said, even if you do not want to use the extra functions, there is nothing stopping you from just not using them. They function differently from Quenty’s, and with the benchmarks, it works pretty fast, so you can still use this, even if you do not want to use the extra functions.

1 Like

Is there any difference between this and a BindableEvent?

1 Like

Yes, this is more modular and less “tacky”, also this more really replicates the behavior of custom events, which arguably are already similar to BindableEvents. Also, these are meant more for connecting code to a certain thing that may happen or occur, such as the pre-existing RenderStepped or Changed. Also, really, a BindableEvent is just an instance that holds an event, so it really is no different from an event itself.

2 Likes

Nice resource, seems useful. As far as I have tested it, it seems to work like advertised. Though, I don’t really see the point of the WaitFor function, but apart from that, nice work.

2 Likes

I think I used this in BadgeService3 and didn’t even realize? The way you do connections is the same I did manually a while ago.

You save a UUID and stuff and fire all functions, that’s also what I used to do a while back.

I might make my own signal API.

By the way, on one of the functions it seems as if there’s a variable not defined or something, “signal”, not sure. It was underlined when I looked at the code.

1 Like

This disconnects everything right? (And “garbage collects” it? Deleting everything and setting everything to nil, etc.) Currently I just made sure by looping through the connections and disconnecing them, then destroying all my connectors.

1 Like

Yes, it removes them completely, all trace of them

3 Likes

Thanks for sharing. BindableEvents are pretty limited when it comes to passing custom objects, so this is an easy solution! :happy1:

2 Likes

Just a suggestion, usually signal:Wait() returns the arguments that were fired as well, you may want to consider adding that feature.

1 Like

Has this been rectified? I will not use this module unless signal:Wait() actually returns the fired arguments.

1 Like

I am not sure, but you should be using the following module instead:

This one is made by me as well with various improvements

1 Like