Thoughts on my custom events

Module: https://www.roblox.com/library/2985743347/event
I haven’t had any issues with it, but if you have any, please mention them.
If there is anything I can improve on, please let me know.

I’ve copied and pasted the code from Halalaluyafail3’s ModuleScript below for easier viewing.

Code
return {
	new = function()
		local Event = {}
		local ConnectedFunctions = {}
		local YieldedThreads = {}
		local HiddenFunctions = {}
		local HiddenDefaults = {}
		local Current
		local EventIndex = {}
		local mt = {}
		function EventIndex:Connect(func,...)
			if type(func) == "function" then
				local connection = {}
				local connectionindex = {Connected=true}
				local mt2 = {}
				function connectionindex:Disconnect()
					if connectionindex.Connected then
						connectionindex.Connected = false
						HiddenFunctions[connection] = nil
						HiddenDefaults[connection] = nil
						for i=1,#ConnectedFunctions do
							if ConnectedFunctions[i] == connection then
								table.remove(ConnectedFunctions,i)
								break
							end
						end
						connectionindex.Disconnect = nil
						connectionindex = nil
						connection = nil
						mt2 = nil
					end
				end
				mt2.__index = connectionindex
				mt2.__newindex = function()
					error("Attempt to modify a non existant/readonly value")
				end
				mt2.__metatable = "This metatable is hidden"
				setmetatable(connection,mt2)
				table.insert(ConnectedFunctions,connection)
				HiddenFunctions[connection] = func
				HiddenDefaults[connection] = {...}
				return connection
			else
				error("Can only connect functions")
			end
		end
		function EventIndex:Wait()
			table.insert(YieldedThreads,coroutine.running())
			coroutine.yield()
			return unpack(Current)
		end
		function EventIndex:Fire(...)
			Current = {...}
			for i=#YieldedThreads,1,-1 do
				coroutine.resume(YieldedThreads[i])
				table.remove(YieldedThreads,i)
			end
			for i=1,#ConnectedFunctions do
				local con = ConnectedFunctions[i]
				local f = HiddenFunctions[con]
				local d = HiddenDefaults[con]
				local tab = {}
				if #d == 0 then
					tab = {...}
				else
					tab = {unpack(d),...}
				end
				coroutine.resume(coroutine.create(function()
					local success,msg = pcall(f,unpack(tab))
					if not success then
						local timestamp = os.date("*t")
						print(timestamp.hour..":"..timestamp.min..":"..timestamp.sec.." - "..tostring(msg))
					end
				end))
			end
			Current = nil
		end
		function EventIndex:Destroy()
			EventIndex.Fire = nil
			EventIndex.Wait = nil
			EventIndex.Connect = nil
			for i=1,#ConnectedFunctions do
				HiddenFunctions[ConnectedFunctions[i]] = nil
				HiddenDefaults[ConnectedFunctions[i]] = nil
				ConnectedFunctions[i] = nil
			end
			Current = {"Destroy called on event"}
			for i=1,#YieldedThreads do
				coroutine.resume(YieldedThreads[i])
				YieldedThreads[i] = nil
			end
			Event = nil
			ConnectedFunctions = nil
			YieldedThreads = nil
			HiddenFunctions = nil
			HiddenDefaults = nil
			Current = nil
			EventIndex = nil
			mt = nil
		end
		mt.__index = EventIndex
		mt.__newindex = function()
			error("Attempt to modify a non existant/readonly value")
		end
		mt.__metatable = "This metatable is hidden"
		setmetatable(Event,mt)
		return Event
	end
}
2 Likes

I’ve improved this script a great deal. (threads re used whenever possible, O(1) disconnection, etc.)

Code
local TOKEN = newproxy and newproxy() or {}
local warn = warn or print
local function call(f,...) 
    f(...)
end
local function resumefunc(f,...)
	f(...)
	while true do
		f(coroutine.yield(TOKEN))
	end
end
local function CONNECT(event,f)
    local nxt = event[2]
    local connection = {f,nil,nxt,nil,true,event,debug.traceback("| Connection Point: ",2),false,false}
    if nxt then
        nxt[4] = connection
    end
    event[2] = connection
    return connection
end
local function DISCONNECT(connection)
    if connection[5] then
        if connection[8] then
            connection[9] = true
            return
        end
        local event,nxt,prev = connection[6],connection[3],connection[4]
        if not prev then
            if nxt then
                nxt[4] = nil
            end
            event[2],connection[3],connection[5] = nxt,nil,false
        elseif nxt then
            prev[3],nxt[4],connection[3],connection[4],connection[5] = connection[3],connection[4],nil,nil,false
        else
            prev[3],connection[4],connection[5] = nil,nil,false
        end
    end
end
local function RECONNECT(connection)
    if not connection[5] then
        local event = connection[6]
        local nxt = event[2]
        if nxt then
            nxt[4] = connection
        end
        event[2],connection[3],connection[5] = connection,nxt,true
    elseif connection[9] then
        connection[9] = false
    end
end
local function ISCONNECTED(connection)
    return connection[5] and not connection[9]
end
local function NEWEVENT()
    return {{},nil,false}
end
local function WAIT(event)
    local threads = event[1]
    threads[#threads+1] = coroutine.running()
    return coroutine.yield()
end
local function FIRE(event,...)
    local isntfiring = not event[3]
    if isntfiring then
        event[3] = true
    end
    local firepoint,threads = debug.traceback("| Fire Point: ",2),event[1]
    local con = event[2]
    while con do
        if con[5] and not con[9] then
            if con[8] then
                local thread = coroutine.create(call)
                local success,err = coroutine.resume(thread,con[1],...)
                if not success then
                    warn(string.format("| Error Message (Connection):\n%s\n%s\n%s\n%s\n| End",tostring(err),debug.traceback(thread,"| Traceback:"),firepoint,con[7]))
                end
            else
                con[8] = true
                local thread = con[2]
                if thread then
                    local status = coroutine.status(thread)
                    if status == "suspended" then
                        local success,err = coroutine.resume(thread,...)
                        if not success then
                            con[2] = nil
                            warn(string.format("| Error Message (Connection):\n%s\n%s\n%s\n%s\n| End",tostring(err),debug.traceback(thread,"| Traceback:"),firepoint,con[7]))
                        elseif err ~= TOKEN then
                            con[2] = nil
                        end
                    elseif status == "running" or status == "normal" then
                        thread = coroutine.create(call)
                        local success,err = coroutine.resume(thread,con[1],...)
                        if not success then
                            warn(string.format("| Error Message (Connection):\n%s\n%s\n%s\n%s\n| End",tostring(err),debug.traceback(thread,"| Traceback:"),firepoint,con[7]))
                        end
                    else
                        thread = coroutine.create(resumefunc)
                        con[2] = thread
                        local success,err = coroutine.resume(thread,con[1],...)
                        if not success then
                            con[2] = nil
                            warn(string.format("| Error Message (Connection):\n%s\n%s\n%s\n%s\n| End",tostring(err),debug.traceback(thread,"| Traceback:"),firepoint,con[7]))
                        elseif err ~= TOKEN then
                            con[2] = nil
                        end
                    end
                else
                    thread = coroutine.create(resumefunc)
                    con[2] = thread
                    local success,err = coroutine.resume(thread,con[1],...)
                    if not success then
                        con[2] = nil
                        warn(string.format("| Error Message (Connection):\n%s\n%s\n%s\n%s\n| End",tostring(err),debug.traceback(thread,"| Traceback:"),firepoint,con[7]))
                    elseif err ~= TOKEN then
                        con[2] = nil
                    end
                end
                con[8] = false
                if con[9] then
                    con[9] = false
                    local oldcon = con
                    con = con[3]
                    DISCONNECT(oldcon)
                else
                    con = con[3]
                end
            end
        else
            con = con[3]
        end
    end
    for i=#threads,1,-1 do
        local thread = threads[i]
        local success,err = coroutine.resume(thread,...)
        if not success then
            warn(string.format("| Error Message (Wait Resume):\n%s\n%s\n%s\n| End",tostring(err),debug.traceback(thread,"| Traceback:"),firepoint))
        end
        table.remove(threads,i)
    end
    if isntfiring then
        event[3] = false
    end
end

Example use

local event = NEWEVENT()
local connection = CONNECT(event,print)
FIRE(event,1,2,3) --> 1  2  3
FIRE(event,nil)   --> nil
DISCONNECT(connection)
FIRE(event,4)     -- nothing prints

I assume there is still a lot I can improve in this script, so feel free to mention what I can improve.

4 Likes