Is it possible to make a custom RBXScriptSignal with OOP?

Here’s the one I used in one of my projects. No dependencies, unlike the above snippet. Note that it’s not possible to implement wait() without the use of BindableEvents or similar constructs.

local Connection = {}
Connection.__index = Connection

function Connection.new(signal, func)
	local self = {
		signal = signal,
		func = func,
	}
	setmetatable(self, Connection)

	return self
end

function Connection:disconnect()
	if self.signal then
		self.signal.connections[self.func] = nil
		self.signal = nil
		self.func = nil
	end
end

local Signal = {}
Signal.__index = Signal

function Signal.new()
	local self = {
		connections = {},
	}
	setmetatable(self, Signal)

	return self
end

function Signal:fire(...)
	for func,_ in pairs(self.connections) do
		func(...)
	end
end

function Signal:connect(func)
	self.connections[func] = true
	return Connection.new(self, func)
end

return Signal

Edit (August 2021): I made this post more than 3 years ago. Since then, I’ve learned that this approach has the following disadvantages:

  • Any errors in the callbacks will propagate to the thread firing the signal, and stop all handlers from being processed.
  • Any callback that yields will also yield the thread firing the signal. Using coroutine.wrap() will fix this.
  • Incorrect handling of reentrancy (the signal gets fired inside of a callback from firing the signal).

Additionally, since I wrote this the coroutine.running() API was introduced, making it possible to implement :wait().

Recently, the task.spawn and task.defer APIs were introduced, which helps make this even better. It’s possible to make a Signal class now that avoids all these downsides.

Check out this post for more info, as well as a fully featured signal library.

28 Likes