NormalSignal: Fast Signal Library - Fork of a BadSignal

NormalSignal is a fork of a BadSignal whose purpose is to simplify BadSignal for begginers while NOT losing any perfomance.

Getonmarketplace or normalsignalgetongithub

:question:Why using NormalSignal?

The exact same Reason as to why people use custom signal libraries:

  • Immediate execution unlike in BindableEvent with defered signal
  • better perfomance compare to BindableEvent
    NormalSignal does replicate behavior of BindableEvent and allows you to do more specific optimizations such as :ConnectMain() (:Push(false) of a BadSignal)
    In general its a very fast Signal library that is able to surpass most of signal libraries like GoodSignal by A LOT! (look at benchmarks bellow) thanks to its optimization methods.

:page_with_curl:Documentation:

Creating a Signal:

For the best perfomance (as you see on benchmarks) put a number with ammount of connections you want to have in this Signal.

local signal = NormalSignal(1)

:memo:Note: order in which you connect connections will affect the order on which connections will resume!

Connect

Connect works just like in BindableEvent which will connect a function to a Signal and upon firing will run a function inside a new thread!
image

local signal = NormalSignal(1)
signal:Connect(function()
	
end)

ConnectMain

ConnectMain works a little different to Connect becouse upon firing it will simply run this function without creating a new thread; VERY GOOD FOR PERFOMANCE AND YOU MUST PRIORITIZE USING IT INSTEAD OF CONNECT hower if function does have any yielding such as wait()/task.wait() it will cause a huge problems and in this case you must resort to using Connect instead!
image

local signal = NormalSignal(1)
signal:ConnectMain(function()
	
end)

Once

Once works similar to Once in a BindableEvent hower here it does return you a thread; Once will ALWAYS create a thread like Connect, you must avoid using :Disconnect if you are using this method and instead you will have to do it manually:
image

local signal = NormalSignal(1)
local thread = signal:Once(function()
	
end)
local needle:number? = table.find(signal._tsk,thread)
if needle then
	table.remove(signal._tsk,needle)
end
--Once is now disconnected

Disconnect

As you can see (not meant to be used for threads​:warning::memo:) refers that you must NOT pass thread into Disconnect from :Once or :Wait
image

Disconnect is used to disconnect a function from Signal, which does mean that you previously should’ve had cached referance to a function and should’ve avoided using anonimous function!Althrough that not a big deal becouse in BindableEvent for example you have to cache connections instead.
image

local signal = NormalSignal(1)
local function Hello()
	
end
signal:ConnectMain(Hello)
signal:Disconnect(Hello)

Fire

Fire works just like in BindableEvent, allowing you to pass arguments into all connected functions!

local signal = NormalSignal(1)
signal:ConnectMain(function(Action,Item)
	print(`Im {Action} {Item}`)
end)
signal:Fire("Eating","Apple") -- print("Im Eating Apple")

Wait

Wait works the exact same way it works in BindableEvent Yielding untill you fire Signal from elsewhere and does return arguments Fire signal gave!
image

local signal = NormalSignal(1)
task.delay(10,function()
	signal:Fire("You are now free!")
end)
local hi = signal:Wait()
print("Im resumed:",hi)

DisconnectAll

Disconnects absolutelly ALL connections in Signal and it doesn’t require you to pass any parameters to do so at all!
image

local signal = NormalSignal(1)
signal:Connect(function()
	
end)
signal:DisconnectAll()

Destroy

Destroys Signal, making it ready to be garbage collected!
image

local signal = NormalSignal(1)
signal:Destroy()
signal=nil

:bar_chart:Benchmarks:

Creation

NormalSignal losing a tiny bit to BadSignal becouse NormalSignal has more methods.

t = 0
for ii=1,20 do
	local h = os.clock()
	for i=1,99999 do
		NormalSignal(0)
	end
	t+=os.clock()-h
end
print(`NormalSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	for i=1,99999 do
		BadSignal:new(9999)
	end
	t+=os.clock()-h
end
print(`BadSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	for i=1,99999 do
		GoodSignal.new()
	end
	t+=os.clock()-h
end
print(`GoodSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	for i=1,99999 do
		Instance.new("BindableEvent")
	end
	t+=os.clock()-h
end
print(`BindableEvent Creation: {t/20}`)

Connecting

Same problem as in previous benchmark

t = 0
for ii=1,20 do
	local h = os.clock()
	local normal = NormalSignal(99999)
	for i=1,99999 do
		normal:ConnectMain(BenchmarkFunc)
	end
	t+=os.clock()-h
end
print(`NormalSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bad = BadSignal:new(99999)
	for i=1,99999 do
		bad:Push(false,BenchmarkFunc)
	end
	t+=os.clock()-h
end
print(`BadSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local good = GoodSignal.new()
	for i=1,99999 do
		good:Connect(BenchmarkFunc)
	end
	t+=os.clock()-h
end
print(`GoodSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bindable = Instance.new("BindableEvent")
	local event = bindable.Event
	for i=1,99999 do
		event:Connect(BenchmarkFunc)
	end
	t+=os.clock()-h
end
print(`BindableEvent Creation: {t/20}`)

Firing

Somehow NormalSignal won BadSignal wth :skull::skull::skull:

t = 0
for ii=1,20 do
	local h = os.clock()
	local normal = NormalSignal(99999)
	normal:ConnectMain(BenchmarkFunc)
	for i=1,99999 do
		normal:Fire()
	end
	t+=os.clock()-h
end
print(`NormalSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bad = BadSignal:new(99999)
	bad:Push(false,BenchmarkFunc)
	for i=1,99999 do
		bad:Fire()
	end
	t+=os.clock()-h
end
print(`BadSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local good = GoodSignal.new()
	good:Connect(BenchmarkFunc)
	for i=1,99999 do
		good:Fire()
	end
	t+=os.clock()-h
end
print(`GoodSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bindable = Instance.new("BindableEvent")
	bindable.Event:Connect(BenchmarkFunc)
	for i=1,99999 do
		bindable:Fire()
	end
	t+=os.clock()-h
end
print(`BindableEvent Creation: {t/20}`)

Benchmarking have been done with something like that:

--!optimize 2
--!strict
--!native
local NormalSignal = require(script:WaitForChild("NormalSignal"))
local BadSignal = require(script:WaitForChild("BadSignal"))
local GoodSignal = require(script:WaitForChild("GoodSignal"))

local function BenchmarkFunc()
	
end
local t:number=0

t = 0
for ii=1,20 do
	local h = os.clock()
	local normal = NormalSignal(99999)
	normal:ConnectMain(BenchmarkFunc)
	for i=1,99999 do
		normal:Fire()
	end
	t+=os.clock()-h
end
print(`NormalSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bad = BadSignal:new(99999)
	bad:Push(false,BenchmarkFunc)
	for i=1,99999 do
		bad:Fire()
	end
	t+=os.clock()-h
end
print(`BadSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local good = GoodSignal.new()
	good:Connect(BenchmarkFunc)
	for i=1,99999 do
		good:Fire()
	end
	t+=os.clock()-h
end
print(`GoodSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bindable = Instance.new("BindableEvent")
	bindable.Event:Connect(BenchmarkFunc)
	for i=1,99999 do
		bindable:Fire()
	end
	t+=os.clock()-h
end
print(`BindableEvent Creation: {t/20}`)
6 Likes

Do we really need that much signal libraries ?

5 Likes

I have another question for you: Are there any devs who still care about performance?
Well, according to your reply, then you are definitely not one of them.

1 Like

oh yes I am, but really SignalX, NormalSignal, BadSignal, BindableEvents, Signal+, SimpleSignal and much more. I didn’t mean to offend you maybe your lib is good but it seems that community resources just oversaturated with signaling libs :slightly_smiling_face:.

2 Likes

Yeah, it’s crazy to me too, but they all have different approaches to a problem with different solutions.SignalX (at least when it released) for example was literally a joke which was basically “class for my single YIELDABLE BY THE WAY function” :skull: .

3 Likes

not trying to be rude but theres no need for anymore signal modules, 99% of signal modules are already extremely fast (even goodsignal) the benchmarks used to run these tests mean absolutely nothing. any signal module works extremely fast, as long as your hooking up a working function up to it.

for reference, based off of your benchmarks, goodsignal fires in ~0.0000006. to put that into perspective, that’s about 1/170,000 of a blink. your module fires ~0.00000025 seconds. to put that into perspective, thats about 1/400,000 of a blink.

also I don’t see how this is more beginner friendly, every single signal module is beginner friendly, and only takes a couple of minutes to learn the ins and outs of it.

TL;DR: the “optimization” is so small it means almost nothing, every signal module is extremely optimized as long as u put in a working function.

imo, fusion would be better to learn than a signal module.

1 Like

Disagree with you completely.
If optimization can be made without any cost, then it shall be without second thinking. Also, some other modules are using no check type mode either because of author’s lack of education or simply because it didn’t exist back then (Like Good Signal for example) which will completely screw up your code if you want to do something complex because type errors will get you eventually. Either you are just a production based dev who is ignorant to your players on low end devices, or I don’t know, mate.
Yeah bro why do we need this computers?Lets revive mamonth and live like in a good old day ahhh response bro :skull:

1 Like

unless your running these on ENIAC the amount of time it takes to run these functions is practically nothing.

fair, but a lot of other already existing signal modules are type-checked, and just as fast, if not faster then yours.

1 Like

Other modules use metatable OOP structure that physically can’t be any faster than BadSignal or mine.
Also i want to have link to them and lets benchmark it to be sure?Note that slop such as: “Do yeilding yourself :nerd_face::hammer:” or “glorified wrap function inside other one:brain::x:” will not be tolerated becouse its equialent to calling function dirrectly and point of Signal behavior falls appart.

SimpleSignal doesnt use metatables :face_with_monocle: Also, I agree with @metatablesnow, theres no need for more signal modules. The only reason I even released SimpleSignal is because it was already there and I wanted to share the thing I made (+optimizations) that all of my “Simple{name}” resources used :V , and now I don’t update it because it doesn’t need more updates

This isnt a good look bro…

Anyways, remember, clenching for nanoseconds leaves you with less time to tackle bigger issues :wink:

3 Likes

I believe your post does violate multiple TOS of devforum; There is always a room for improvement and even tiny improvement does matter

What kind of ego does it need to have to use “kid” as an argument on the internet? Isn’t it ignorant to be in denial of improvement as well?

Sir I do think you went on a little different platform for forums, here we discuss development and development recourses not “life stories”

Denial counter: 2, this is a baseless claim “I have nothing to say, but I must win” if you are uneducated enough to not know OOP, type checking, OOP conventions and OOP approaches on Roblox then what does it have to do with my module?

Isn’t that bit hypocritical of you to say any of this?
More over according to my benchmarking your signal module loses to mine by a bit so information you provided is basically attempt to silence a module that is better than yours
image

Please note that unlike your claims i keep information open and i am transparent when it comes to benchmarks.
More over mine module even without native still wins compare to your module :skull:(no offense bro but accept defeat already in a game where you did cheated)

--!optimize 2

local NormalSignal = require(script:WaitForChild("NormalSignal"))
local BadSignal = require(script:WaitForChild("BadSignal"))
local SimpleSignal = require(script:WaitForChild("SimpleSignal"))

local function BenchmarkFunc()
	
end
local t:number=0

t = 0
for ii=1,20 do
	local h = os.clock()
	local normal = NormalSignal(99999)
	normal:ConnectMain(BenchmarkFunc)
	for i=1,99999 do
		normal:Fire()
	end
	t+=os.clock()-h
end
print(`NormalSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bad = BadSignal:new(99999)
	bad:Push(false,BenchmarkFunc)
	for i=1,99999 do
		bad:Fire()
	end
	t+=os.clock()-h
end
print(`BadSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local simple = SimpleSignal.new()
	simple:Connect(BenchmarkFunc)
	for i=1,99999 do
		simple:Fire()
	end
	t+=os.clock()-h
end
print(`SimpleSignal Creation: {t/20}`)
t = 0
for ii=1,20 do
	local h = os.clock()
	local bindable = Instance.new("BindableEvent")
	bindable.Event:Connect(BenchmarkFunc)
	for i=1,99999 do
		bindable:Fire()
	end
	t+=os.clock()-h
end
print(`BindableEvent Creation: {t/20}`)

Quite surprised to see my module being forked, I initially made it mostly for myself to avoid performance issues other modules have. I guess making it beginner-friendly and performant was possible

1 Like

How well does it do on constant firing to clients? What if I want to fire the same Signal twice in the same row to all clients?

1 Like

You may want to look at networking modules like Packet, this is for signaling on the same context, not cross context

I do use Packet but I keep running into issues where the event might not even fire to the clients causing it to be dropped, I tried to recreate a Packet where its for messages only (most used type) yet it still gets dropped due to throttle and limits, I had to use NetRay as an alternative while keeping Packet as a backup

If your usecase for the remote event is sending a relatively small payload you can try just using regular remote events/unreliable remote events w/ a buffer serde like BufferEncoder, maybe make your own wrapper that does it for you :V

Well the thing is the Event is used as a replicator, sometimes its used for messages, sometimes its used to replicate particles, normal events cause a lot of lag to clients in my use case

Itd probably be best if you made these purposes into their own events so you can use schemas and also maintain and label them better :V

1 Like

Hypocrisy and too many excuses: “I can make the own signal module, but you aren’t allowed to do so too” will be the exact same thing as your response.
Say it right awey: you are scared of competition or you don’t want other people to succeed better than you (like am i)
image