If there is already a thread for this with a solution, please direct me to it.
I’m getting into OOP in lua, and I understand that you can almost create your own class with functions and properties. However, I haven’t found any way to create events for your class. Is it possible, and if so how?
Maybe something like this so you don’t have to use a BindableEvent
local class = {
Events = {"SomeEvent1"}
}
--Create Events
function class:Init(self)
--Events
for _, eventName in pairs(self.Events) do
local event = {Functions = {}}
function event:Connect(func)
table.insert(self.Functions, func)
end
function event:Fire()
for _, func in pairs(event.Functions) do
spawn(func)
end
end
self[eventName] = event
end
end
--Some other place
local object = ObjectService.new("TestObject")
object.SomeEvent1:Connect(function()
print(1+2)
end)
object.SomeEvent1:Fire()
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.
I was referring to gillern’s snippet. It appears to rely on some unspecified OOP framework. The Nevermore one is good, I mainly linked mine as an alternative version which didn’t use BindableEvent.
My game Shard Seekers can create thousands of signal-related objects, so I tried to avoid packing in unneeded features and went for something simple and lightweight. The resulting object is a list of functions with a metatable, which uses the least amount of memory possible for a Lua-based signal.
local setmetatable = setmetatable
local table_remove = table.remove
local Class = {}
local Me = {} -- 'Me' is short for 'Methods'
local Mt = {__index = Me} -- 'Mt' is short for 'Metatable'
-- Although hacky, it's possible to have the metatable change based on the number of arguments in the list for better firing performance. I left that out for simplicity.
-- 'cn' is short for 'connection'
-- I use 'connection()' instead of 'connection:Disconnect()'. This means I can use functions and connections interchangeably
local CnMt = {__call = function(cn)
local self, method = cn[1], cn[2]
for i = 1, #self do
if self[i] == method then
local sync = self[0]
if sync then
sync(i)
end
table_remove(self, i)
break
end
end
end}
-- ':Cn' means the method returns a callable connection
function Me:Cn(method) -- I use 'event:Cn()' instead of 'event:Connect()' to distinguish it from roblox methods that return RBXScriptConnections
self[#self + 1] = method
return setmetatable({self, method}, CnMt)
end
-- ':Cns' means the method uses a connection list
function Me:Cns(cns, method) -- 'cns' is short for 'connections', and is my lightweight version of a Maid class
self[#self + 1] = method
cns[#cns + 1] = setmetatable({self, method}, CnMt)
end
function Mt:__call(...) -- I use 'event(...)' instead of 'event:Fire(...)'
local i0, i1 = 0, #self
-- I have other implementations that "lock" the list instead of creating functions (which is very expensive), but they're more complex.
-- This implementation creates unneeded functions for 0 or 1 methods, but I'm leaving those out for the sake of simplicity.
local syncPrev = self[0]
self[0] = syncPrev and function(i) -- Supports recursive calling, as well as disconnecting arbitrary methods during other methods
if i<=i1 then i1=i1-1 end
if i<=i0 then i0=i0-1 end
syncPrev(i)
end or function(i) -- use fewer upvalues
if i<=i1 then i1=i1-1 end
if i<=i0 then i0=i0-1 end
end
-- Calling backwards is better for some cases, but calling in order of connection is more intuitive
while i0 < i1 do
i0 = i0 + 1
-- This should never yield. If a method needs a coroutine, it should create one itself.
-- This is not protected from errors, but I find this useful because they are often caused by a bad input.
self[i0](...)
end
self[0] = syncPrev
end
function Class.new()
return setmetatable({}, Mt)
end
do -- Usage:
local event = Class.new()
local cn = event:Cn(function(...)
print("Event firing:", ...)
end)
event(1, 2, 3)
cn()
event(4, 5, 6)
end
return Class
@Tiffblocks has a simple implementation as well. I like it because disconnecting has low complexity (doesn’t require searching through a list for the method), but the pairs iterator can behave unexpectedly when connecting/disconnecting during the fire.