FastSignal API - For all your event needs!

FastSignal is used in BadgeService3 for stuff like :onUpdate(), etc.

FastSignal allows you to create signals, aka: events, fire them, get stuff from them, etc.
It has everything RBXScriptSignals/Connections have with some extra stuff here and there.

Unlike other solutions, FastSignal does NOT use BindableEvents. BindableEvents are more expensive and can cause more problems. I went against them as they’re not only expensive, they also don’t handle multiple scripts well, and blah blah blah.

When using this, you shoudn’t care too much about tecnical details, just create a signal, connect, disconnect, fire the signal, destroy it, done.


Good:

  • It doesn’t use BindableEvents, makes it way faster and optimized.
  • Support for :Wait() function on Signals;
  • Simple to use and understand;

Download it here:

If anyone can do some benchmarking to help me out I would love it :D

4 Likes

Added some documentation for the functions this module has on GitHub here.


What’s the point of this? We have FastSpawn and Signal modules already

1 Like

Ok thanks. Removed from the model.

Don’t be one of those people;

Also I’m pretty sure FastSpawn is not related, most signal modules handle signals using BindableEvents, like you showed, which are slower, and less optimized.

3 Likes

Do you have reliable sources to back up that information? I’ve ran a quick benchmark firing standard BindableEvents, your signal module, and NevermoreEngine’s signal module.


Nevermore’s Signal does 0.0035295000416227

Your signal does 0.005104299983941

Standard BindableEvent does 0.0046025000046939
My specs are:
CPU: Intel i3-9100F 3.6 GHz
GPU: AMD Radeon RX 550 2GB
RAM: HyperX Fury 8GB 2400 MHz

1 Like

This:

btw, for benchmarks you should wait about 5 seconds or more before running code, start up makes benchmarking weird.

Anyways thanks for giving me an idea on benchmarks (I was trying to calculate those stupid numbers…)

Can you try also connecting some functions? If you just use :Fire() with no connected functions nothing will be really calculated, since it’s just a for loop.

3 Likes

Hey, I tried with these scripts:

local Signal = require(game.ServerScriptService.Signal)

local signaler = Signal.new()

signaler:Connect(function()

end)

wait(10)

local start = os.clock()

for i = 1, 100000 do
    signaler:Fire()
end

print(os.clock() - start)

local Signal = require(game.ServerScriptService.NeverMore)

wait(10)

local signaler = Signal.new()
signaler:Connect(function()
	
end)

local start = os.clock()

for i = 1, 100000 do 
	signaler:Fire()
end

print(os.clock() - start)

And the results for me were:

In this case, I ran both twice, and got their average. I only did it twice, but I don’t think more would change much.

I didn’t try with “pure” BindableEvents here, how ever, because NeverMore’s signal api is already small enough, I don’t think that would have impacted it.

1 Like

Here it is waiting 5 seconds and connecting the event:


Nevermore does 0.066512100049295

Your signal does 0.068769700010307

BindableEvent does 0.04701770003885

If I haven’t messed up anything, BindableEvent is faster than both while Nevermore’s signal’s faster than yours.
Also I’ve read through your code and you’ve kept the coroutine concept, but the rest you’ve changed (2 tables for assigning __index?)

???
Yeah um, Connections run in “Coroutine” just like normal events. Of course it’s a bit different internally, but it is a form of coroutine.

Hm, and what are you talking about assigning __index twice? Can you show me that so I can fix it?

2 Likes

I think this is very dependant of hardware. Mine had completely different results like I showed.

My specs are:

AMD Athlon 3000G with Radeon Vega Graphics 3.50GHZ, 4 cores.
8 gigs of ram, 2 dedicated to integrated graphics, which leaves 6.
idk aaaaa 128GB SSD with a 1TB HDD, I guess.

1 Like

I meant having 3 tables (I thought it was 2) for OOP:
image
image
image
You could’ve done

Signal._index = Signal

then just assign to the metatable in the constructor:

return setmetatable({
	_connections = {}
}, Signal)

and for the rest you could’ve kept that information in self
Also, table.clear() doesn’t free memory, and most likely (not sure) doesn’t get garbage collected
image
You can assign it to nil.
I can make a PR if you’d like to :DD

1 Like

shoreee do it I kind of want to cuz because I wanna test it out :joy:

Bindables are slow, no doubt about that, but using coroutines is not much better. There are a few other ways to write a signal class, like integrating an intrinsic set for connections, but let’s not reiterate!

https://devforum.roblox.com/t/when-to-use-bindableevents/828609/4?u=ukendio

tl;dr: use bindables because they are safer, or apply correct data structure implementation for optimal time complexity. Psst here’s a cheat sheet: https://www.bigocheatsheet.com/

2 Likes

I don’t like using Bindables in my opnion. They’re slower, and for what I’m using it for, coroutines are fine. The only problems with coroutines being that they don’t error if you don’t wrap it in a pcall.

I don’t see any purpose of utilizing data-structures other than the standard dynamic arrays or dictionaries when making a custom signal, but good advice I guess.

1 Like

I did. The other link I’m too dumb to understand, sorry.

If we are talking about optimal performance and security? All of these signals will break under certain conditions due to how data structures work. Imagine disconnecting within a signal, what would happen? I go a lot more in depth in my first link.
@LucasTutoriaisSaimo you didnt read the first link.

That’s because I did this.

If you want to optimize your code with data-structures, I would recommend only doing so when handling a large amount of input or when your code is handling some heavy work. Otherwise, it would just lead to overcomplication and a negligible speed boost.

With a custom signal module it is very unlikely to have to do any heavy work internally.

1 Like