Event Systems in Roblox Lua (Part 2 – Simple Solution)

Click here for part 1 of this tutorial series. You should be familiar with the content discussed in my Object-Oriented Programming tutorial.

In part 1, I discussed the pattern of an event system and the need for it in Roblox code. We frequently find ourselves needing to define our own gameplay defined events. Let’s dive into a simple way we can implement this pattern. Let’s use the OOP tricks we know in Lua to create the shell of an Event class.

-- A shell of an event class
local Event = {}
Event.__index = Event

function Event.new()
	local self = setmetatable({
		-- We'll put any variables (fields) we need here
	}, Event)
	return Event
end

function Event:connect(func)
	-- Somehow keep track of the functions connected here
	-- Provide a means to disconnect this function from this event
end

function Event:fire(...)
	-- Call all the functions somehow
end

So, at surface level we need a way to store some number of functions. We should be able to add/remove those functions. Crazy idea: let’s just use a numerically-indexed table of functions. We’ll use table.insert  to connect a function, and to disconnect we’ll iterate over each index and remove the first we find that matches. When we fire the event, just iterate over the list of functions, calling them in order. Let’s code it up.

First, let’s add a connections  table to our event. This will be our table of functions.

function Event.new()
	local self = setmetatable({
		connections = {};
	}, Event)
	return self
end

Next, let’s write the fire function. Simple iteration using a numeric for-loop:

function Event:fire(...)
	for i = 1, #self.connections do
		self.connections[i](...)
	end
end

That oughta do it! Finally, let’s write the connect function.

function Event:connect(func)
	if not func then error("connect(nil)", 2) end
	table.insert(self.connections, func)
end

Notice how I added a line that will throw an error if we sent no function to connect. Finally, we need to add a means to disconnect. Let’s return a table with a disconnect function as a key:

function Event:connect(func)
	if not func then error("connect(nil)", 2) end
	table.insert(self.connections, func)
	
	local function disconnectFunc()
		for i = 1, #self.connections do
			if self.connections[i] == func then
				table.remove(self.connections, i)
				break
			end
		end
	end
	return {disconnect = disconnectFunc}
end

And there we have it! We’ll soon find out that there are some issues with this. For simple uses cases, this works fine. However, if any connected functions yield (yielding essentially means waiting, like announceGameOver does to display the message), the next connected function will not run until the yielding function completes. This is a problem in the part 1 example because we might want our map to clean up first, then the game to announce the final score. It depends on the order in which we connected our events.

This event system calls connected functions in the order they were connected. In other words, first-in-first-out (FIFO). We could reverse this order (last-in-first-out, LIFO) by iterating backwards in the fire function:

function Event:fire(...)
	for i = #self.connections, 1, -1 do
		self.connections[i](...)
	end
end

Alternatively, we could just insert new functions at the beginning of the list in connect by specifying index 1 as the insertion point in the table.insert  call:

table.insert(self.connections, 1, func)

This is actually less efficient as Lua must move all the existing functions in the table over by 1 to make room for the one we’re inserting. For the computer science nerds in the audience, this is an O(n) operation. Simply inserting to the end of the list would be O(1). It goes without saying that making both of these changes (reverse iteration and insertion at the front of the list) would cancel each other out and we’d be back to original behavior of FIFO.

If this solves your problem, then I have good news: you can stop here. As for the rest of us, we have more options to explore in part 3. You can download the finished version of this Event class here.

Click here to advance to part 3 of this tutorial series.

Author: Ozzypig

Roblox developer and computer scientist. I love all things coding and game design!