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.