When and Why to use a Signal Module?

I’ve been trying to implement signals into my code, but I’m not sure when to use them.

I know it is used in OOP, but outside of that, is there really a lot of use cases it should be used over something like a RemoteEvent?

It says that you can decouple your code with it, but I’m not entirely sure what this means. Is it something like this? Would I just use functions upon receiving the signal instead of just calling the module’s functions? Is there any real benefit to this?

SettingsModule.SettingsChanged:Fire(setting, value)

self:SettingsChanged:Connect(function()
do something
end

3 Likes

RemoteEvents and BindableEvents are two completely different things. One is for client/server communication and the other is to send information between client to client or server to server scripts.

An example where a signal might be useful would be an inventory module, where it fires a signal every time an item is added. You could connect it to your ui script so every time that signal fires, a new item needs to be added onto the ui

1 Like

Bindable events are client <-> client or server <-> server. They can be useful if you need to reuse functions or transfer data from a module to another or such similar. I am not too experienced at using them but it helps out sometimes.

2 Likes

Sorry, I should mention that I am using a signal module. Not RobloxScriptSignals or event objects

1 Like

Mind linking the module?

2 Likes

I cant believe i didnt know about these… From what i read, they are mainly quicker bindable events, not used like remote events (unless mistaken.) They run server to server or client to client just like the former

2 Likes

Can be found in here:

Look for “GoodSignal”

2 Likes

Yeah, it just looks like a BindableEvent wrapper which works like how I described in the first post. Just makes coding easier

1 Like

Do you know if it is better to call a module function directly, or use signals to run the function + decouple the code?

1 Like

I mean the difference is probably negligible tbh. I personally have my own wrapper that uses functions instead of bindable events but there’s not much of a difference.

1 Like

A major advantage to signals over calling the functions directly is that they fire into new threads. This is nice for avoiding yields or errors.

-- If any function errors, the remaining will not fire.
-- If any function yields, the remaining will be delayed in their fire.
for _, f in ipairs(bindedFunctions) do
	f()
end

vs

signal:Fire() -- any binded functions will not yield or error current thread
3 Likes

Code coupling refers to how intertwined your code is. In other words, how strong the connections between different modules are.

Typically, loose coupling (weaker connections between modules) is considered good design. However, strong coupling is considered bad design.

The reason for using signal modules is that they promote loose coupling and, in turn, good design.

Here’s an example of code that uses signals versus code that doesn’t:

Without Signals

PlayerDataHandler.lua

local PlayerDataHandler  = {
	Coins = 100
}

function PlayerDataHandler:SetCoins(NewValue)
	self.Coins = NewValue
	
	CoinGui.Coins.Text = NewValue
	Character.Overhead.Coins.Text = NewValue
end

...

This example allows us to set our coins to whatever we want. However, because we’re not using signals, we’re forced to add unrelated code into the PlayerDataHandler module that is purely meant for storing player data.

Say later down the line we have another chunk of code that needs to know when coins change. Because we’re not using signals, we’ll either need to setup a loop to detect when coins change, or we’ll have to add more unrelated code into the PlayerDataHandler module.

With Signals

PlayerDataHandler.lua

local PlayerDataHandler = {
	Coins = 100,
	CoinsChanged = Signal.new()
}

function PlayerDataHandler:SetCoins(NewValue)
	self.Coins = NewValue
	
	self.CoinsChanged:Fire(NewValue)
end

HudHandler.lua

local function UpdateCoinsLabel(NewValue)
	CoinGui.Coins.Text = NewValue
end

PlayerDataHandler.CoinsChanged:Connect(UpdateCoinsLabel)

...

OverheadHandler.lua

local function UpdateOverheadCoinCount(NewValue)
	Character.Overhead.Coins.Text = NewValue
end

PlayerDataHandler.CoinsChanged:Connect(UpdateOverheadCoinCount)

...

This would then allow you to copy your PlayerDataHandler module into another project if needed, without needing to modify it.

4 Likes

The main downside of using signals is that they’re boilerplate and don’t mesh well with other Luau features such as types and optimizations.

Generally, when you want code to ‘bind’ to a specific scenario (such as a change in settings), you can always just inline it into the scenario ‘invoker’. You already do that normally–it’s called procedural programming.

Coupling is also a good point to make. With my personal experience, writing code as decoupled as possible is the most efficient route, because instead of prematurely abstracting things before you know you need to, you can abstract when you need to on the way.

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.