Way to implement :Wait() like roblox does in its event system?

Currently I’m making a event system that matches the roblox event system. I currently have this:

return function()
	local Event = BaseClass:new("Event")
	local Connection = BaseClass:new("Connection")

	local Log = import("org.log4lua.Log")()
	local EventLogger = Log:getLogger(Event)
	local Connection = Log:getLogger(Connection)

	Event.Event = function(self)
		self.functions = { }

		return self
	end

	Event.Connect = function(self, connectionFunction)
		if (not self.functions) then
			return EventLogger:atError()
				:log("Attempted to connect to a invalid event")
		end

		self.functions[connectionFunction] = connectionFunction

		return Connection(self, Connection)
	end

	Event.Fire = function(self, ...)
		for _, connectionFunction in pairs(self.functions) do
			connectionFunction(...)
		end
	end

	Connection.Connection = function(self, event, connectionFunction)
		self.event = event
		self.connectionFunction = connectionFunction

		return self
	end

	Connection.Disconnect = function(self)
		self.event.functions[self.connectionFunction] = nil
	end

	return Event
end

(Ignore all the class based stuff)
I have the async side of calling the function for events but I wanted to implement :wait() as well. I don’t want to have to go to something like:

repeat wait() until eventFired

as this creates unnecessary performance loss and hogs CPU. Any help is appreciated!

It’s not very easy to do. In fact, the way I’ve ended up doing it is by using the Event:Wait() method of a BindableEvent. Here’s my custom Event class:

https://github.com/Sleitnick/AeroGameFramework/blob/master/src/ReplicatedStorage/Aero/Shared/Event.lua

1 Like

Well thats one way to fix it! I would like to have testing with Lemur and since it doesn’t have support for this I would like to put this off to last resort. I’m now wondering if roblox is handling this C sided or do they have a pure lua implmentation of this?

I imagine it simply yields the current thread/coroutine behind the scenes. You can replicate this using coroutine yielding and such, but you will lose your stack traces in the process, which is a huge pain in regards to debugging.

To do this without touching BindableEvents: return a call to coroutine.yield from the Wait method. Before you yield, add the current thread (found with coroutine.running) to an array of threads associated with the event that need to be resumed upon the event being fired:

function Event:Wait()
    -- add current thread to array
    table.insert(self.Threads, coroutine.running())
    
    -- yield thread until it is resumed by Event:Fire, and return arguments passed to Fire
    return coroutine.yield()
end

function Event:Fire(...)
    -- invoke listeners here...
    -- then, iterate through each yielding thread and resume it:
    for _, thread in ipairs(self.Threads) do
        coroutine.resume(thread, ...)
    end
    -- clear/replace the Threads table
    self.Threads = {}
end
3 Likes