Custom Replacement for RBXScriptSignals

hey guys, I have been working with OOP a lot lately and have found myself making custom events a lot, like Car.Stopped, for example. As it is now, the usual approach for this sort of thing is to just make a bindable event. I don’t like the idea of making a new bindable event and having the responsibility of managing it, so I created a custom signal class that does not use bindable events. It has a few other upsides:

  • It does not use any sort of repeat loops to yield, it uses coroutines and the task library.
  • metatables can be passed, making it more flexible than current RBXScriptSignals
  • room for new features
  • in my testing, it has perfect performance compared to the usual bindable event method

you can find it here:

it has everything current RBXScriptSignals have, including :Wait(), :Connect(), and connections can be disconnected with :Disconnect().

I would love feedback on memory management here, I don’t know the best approaach for a dedicated :Destroy() function. the current one sets the entire signal to an empty table, which is good enough for me considering it will be gc’ed anyways, but I know some people might not be happy with that.

edit:
the great thing about this is that I can add features that I’ve always felt were missing from RBXScriptSignals. for example, a timeout to :Wait(). ive actually just added that because I feel that its an important thing to have

Github repo:

6 Likes

Can you upload the source code into GitHub (either gists or a repo) or make a pastebin or include it in your post? So that peopl who cannot access Studio will be able to read the source code.

-- CreateSignal.lua

--[[

                __  __        __              
   ____  __  __/ /_/ /_____  / /_  ______ ___ 
  / __ \/ / / / __/ __/ __ \/ / / / / __ `__ \
 / / / / /_/ / /_/ /_/ /_/ / / /_/ / / / / / /
/_/ /_/\__,_/\__/\__/\____/_/\__,_/_/ /_/ /_/ 
                                              

RBXScriptSignal Replacement

functionally identical!

~EXCEPT~

this can pass metatables too

not SUPER useful but maybe you might find a use for it

to install:

put somewhere you can use it (usually ReplicatedStorage is the best place for this)

use:
	local signalClass = require(game.ReplicatedStorage.signalClass)
	
make a new signal:
	local itHappenedSignal = signalClass.new()
	
wait for the signal to fire:
	itHappenedSignal:Wait()
	print("it happened!")
	
wait returns arguments:

	itHappenedSignal:Fire("hi")
	
	~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	
	print(itHappenedSignal:Wait())
	
	(will print "hi")
	
connect and disconnect:
	
	function happen()
		print("it happened")
	end
	
	local con = itHappenedSignal:Connect(happen)

	con:Disonnect()


the main use for this is for things like OOP classes:
	
	function Car.new()
		return setmetatable){
			Stopped = signalClass.new()
		}, Car)
	
	
	end
	
	function Car:Stop()
		self.Stopped:Fire()
	end
	
	~~~~~~~~~~~~~~~~~~~~~~~~~
	
	myCar.Stopped:Connect(function()
		print("my car stopped!")
	end)


]]

local Signal = {}
Signal.__index = Signal

local Connection = require(script.Connection)

function Signal.new()
	return setmetatable({
		_Connections = {},
		_Yielding = {},
		Firing = false
	}, Signal)
end

function Signal:Fire(...)
	for i,Connection in ipairs(self._Connections) do
		if Connection._Callback == nil then table.remove(self._Connections,i) else
			spawn(function(...)
				Connection:Fire(...)
			end)(...)
		end
	end
	for i,thread in pairs(self._Yielding) do
		table.remove(self._Yielding, i)
		task.spawn(thread, ...)
	end
end

function Signal:Wait(duration)
	local Running = coroutine.running()
	table.insert(self._Yielding, Running)
	if duration then
		task.delay(duration, function()
			local index = table.find(self._Yielding, Running)
			if index then
				table.remove(self._Yielding, index)
				task.spawn(Running)
			end
		end)
	end
	return coroutine.yield()
end

function Signal:Connect(callback)
	local connection = Connection.new(callback)
	table.insert(self._Connections, connection)
	return connection
end

function Signal:Destroy()

	self._Connections = nil
	self._Yielding = nil
	self.Firing = nil
	setmetatable(self,{__index = function() return nil end})

end


return Signal
-- Connection.lua

local Connection = {}
Connection.__index = Connection

function Connection.new(callback)
	return setmetatable({
		_Callback = callback
	}, Connection)
end

function Connection:Fire(...)
	self._Callback(...)
end

function Connection:Disconnect()
	self._Callback = nil
	setmetatable(self,nil)
end

return Connection
3 Likes

sure thing

What is the difference between this module, and the likes of GoodSignal?

i didnt know that existed ill delete this

edit: nvm cant delete

I got a question, how to make a custom type that accepts outputs from Signals.new()? Since right now for typechecking I am using RBXScriptSignals but I’m not sure that’ll work.

Edit: Nevermind. I solved it.

type CusSignal = typeof(setmetatable({}, Signal))