Guide to event-based programming and why it's supreme

Greetings! I just felt like writing a small post that describes how event-based programming in Roblox works, and why it’s usually always king when deciding how to go about structuring your game.

So, what is event-based programming?

It’s exactly as it sounds! An event takes place, causing something to happen afterwards. Let’s take a quick look at the Touched event for example:

local myPart = workspace.Part

local function onPartTouched()
    workspace.Baseplate.BrickColor = BrickColor.Black()
end

myPart.Touched:Connect(onPartTouched)

When ever the specified part gets touched by another part, our baseplate will turn black. Now this might seem like scripting 101, but on a deeper level, could you imagine doing this instead of using the Touched event?

while true do
    local parts = myPart:GetTouchingParts()

    if #parts > 0 then --//A part was found in the table returned, so we know something is touching our part
        workspace.Baseplate.BrickColor = BrickColor.Black()

        break
    end

    wait()
end

In the second example, we do what’s known as “polling” (essentially just looping control flow every single frame, instead of waiting for an actual event to fire when it occurs.) Broken down even further, this is how our communication looks with the script we’re writing here:

Are there parts touching?
no
Are there parts touching?
no
Are there parts touching?
no

I bet you can start to see where I’m going with this. Obviously looking at this, this looks like horribly bad practice and I bet you’re thinking, Well I’m never going to do that, I’d always use the Touched event for when I need to detect collisions on parts.

But what about other use cases where a Roblox-supplied event might not be an option?

BindableEvents!

That’s where BindableEvents come into play. Essentially, you can use them to define and create your own events, which work similarly to Roblox-supplied events. Here’s how it looks when we set up a “listener” for our BindableEvent (a listener is essentially just a function that will run when the event gets fired)

myBindableEvent.Event:Connect(function()
    print("Something fired me!")
end)

As you can see, it looks quite similar to that of a Roblox-supplied event, with the main difference being the event is named ‘Event’.

One thing that makes BindableEvents great is that you can, much like with Roblox-supplied events, have multiple listeners (again, functions that run when the BindableEvent gets fired).

So now, here’s the big question. What do we do if we want to make something happen (like turning the baseplate black) when any of multiple conditions are met? Well, let’s see how we can implement this by using our BindableEvent instead of looping (or polling) for something to happen.

--//Script 1

local myBindable = workspace.BindableEvent

workspace.myPart.Touched:Connect(function()
--//We want our baseplate to change colors when this part gets touched, let's fire our BindableEvent

    myBindable:Fire("Touch") --//We can also send in arguments describing what type of condition was met!
end)

What if we also want the part to change colors if the user presses a specific key? We can fire our BindableEvent from a different script no problem!

--//Script 2, not the same script as the script above

local contextActionService = game:GetService("ContextActionService")
local myBindable = workspace.myBindable

local function onKeyPressed()
--//This function is called when ever the user presses F

--//We received user input! Let's make it so the baseplate changes color, much like before.
    myBindable:Fire("keyPressed")
end

--//We can use ContextActionService to easily handle user input for when the player presses the F key
contextActionService:BindAction("keyPressed", onKeyPressed, false, Enum.KeyCode.F)

Great! Now we can fire the same event, but when two different situations occur.

Say we have individual systems not affiliated with each other (two scripts for example) and for the one script, we need to run additional game logic for when this event fires; we’re not able to do this from the other listener. We can simply add another listener like the one we did before, for the script we need it to run in!

--//A new script
local myBindable = workspace.myBindable
local timesFired = 0

myBindable.Event:Connect(function()
--//This script needs to do something additionally when this event gets fired

--//Let's keep track of how many times this event gets fired!
    timesFired += 1
    print(timesFired)
end)

Have I convinced you yet?! A typical event breakdown might look like the following:

When the game round ends, we’ll fire a BindableEvent where all of its listeners will get called. We might need to do something in the lobby that handles turning lights on or off, replicating some local effects to all the players in the game telling them the game is over, and finally any other listeners that need to run when the event gets fired, if a specific script needs to do something when the game ends, like removing cached data in a table.

Closing Notes

Also keep in mind, BindableEvents are not like RemoteEvents/RemoteFunctions. They do not communicate across the client-server model. You can use them both server-sided or client-sided, though. And depending on where you place them, like ReplicatedStorage, you can utilize the same BindableEvent on both the client and server.

Hopefully this clears the air a little bit. I’ve recently dug more into BindableEvents for a more recent project of mine, and I’ve yet to be disappointed with them. Generally, there’s a ton of ways you can get the same end result, but from my experience BindableEvents helps a ton with code structure and keeping your code flexible; it’s super easy to just add another listener for the same event to do something from within a different script, rather than using polling or other means to wait until something happens.

Couldn’t I just use modules?

ModuleScripts are great options as well, and in some cases you definitely could. What I’ve found personally for me is that adding new BindableEvent listeners from within the scope of your ModuleScript is a little tidier than having say, a list of ModuleScript function calls that get ran when X takes place:

--//End of game
moduleA.onEvent()
moduleB.onEvent()
moduleC.onEvent()

Hope this helps!

Don’t hesitate to inform me of any typos or misinformation, I spent a decent amount of time on this so it should be pretty validated, but definitely let me know if not!

21 Likes

Cool and all, but why are the codeblocks so badly formatted?

Because Roblox won’t let me indent when making a post

while true do
    local parts = myPart:GetTouchingParts()

    if #parts > 0 then --//A part was found in the table returned, so we know something is touching our part
        workspace.Baseplate.BrickColor = BrickColor.Black()

        break
    end

    wait()
end

Doesn’t stop you from pressing space or using a tool such as

Just a suggestion because new programmers look at these and they should be written in proper code syntax as to ease readability.

That tool doesn’t work for me, and yeah it’s weird how you can’t use tab on here. I also don’t feel like spamming my space bar 10 times per line.

5 Likes

I’m a little confused by this, could you possibly show a code example of what you mean? Thanks!

1 Like

Is this right? According to the wiki, a BindableEvent can only be subscribed to by one script:

I couldn’t find any clarification of what “Subscription” means in this context, but I took it to mean a connection to the event, so you can only connect one function to a BindableEvent at a time

Ahem ahem. BindableEvents can be connected by unlimited scripts.

BindableFunctions only by one.

1 Like

yeah I tested it out and I could connect as many scripts as I wanted. do you know what it meant by the subscription limitation then?

Perhaps I didn’t do a great job of wording that.

Basically like, I’ve found it being better practice (for me personally) to put something like this within your ModuleScript’s environment:

function myModule:Init()

someBindableEvent.Event:Connect(function()
--//Do stuff within this module
end)
end

Versus explicitly running a bunch of module methods when an event occurs:

--//Regular script

someBindableEvent.Event:Connect(function()
moduleA:Cleanup()
moduleB:Cleanup()
moduleC:Cleanup()
end)

Both work obviously, I just personally prefer the first option because it feels like you have more control.

1 Like

Probably just how many scripts can connect

Great tutorial, but bindableEvente are outdated because they can cause memory leaks if not used correctly.

1 Like

You’re using them wrong then. A event wrapper instance (aka bindableevent) cannot cause a memory leak on it’s own.

1 Like

It can, if you pass in any value that isn’t pure Lua, garbage collector wouldn’t be able to collect it.

At least provide valid stats if you want people to not use bindables all together. I’m not reading a long post that doesn’t mention memory leaks you mentioned.

It does.

RobloxSignal uses BindableEvents to make custom events.

Cool but you still don’t show any stats showing that it does indeed memory leak in that way.

I don’t understand how that is related to bindables memory leaking – it doesnt describe how they leak, nor does it provide stats that I’m literally begging for you to show.

I am new to this so I can be wrong unless you can correct my flaws.