Wait vs State machines

Recently, I was told multiple times that using state machines instead of wait() I would save a lot of resource and time later on. Does anyone know where I could go to find out more about this? I don’t even know the very basics of state machines so any form of documentation would be nice.

2 Likes

I’m kinda interested in hearing the reasoning behind state machines vs wait() that you were told.

But from my understanding, I get the basic connection behind why state machines and “wait” are related topics. And why one might argue to use the one over the other in some cases.

Right off the bat, keep in mind that each script in your game runs indipendently of the other. So if you have two scripts with their own seperate code, there is no guarantee that the code in each script is going to execute in the same order, in relation to eachother.

So for example, if “Script 1” had 3 function calls (function A, B, and C).
And “Script 2” had 3 function calls (function X, Y, and Z).
Ignoring any scheduling-logic-nuances that people would use to tell me “I’m technically wrong”, the order in which those functions would be called could be:
A,X,B,Y,C,Z
or
A,B,X,Y,C,Z
or
A,B,C,X,Y,Z

Now, I do believe that in reality, with Roblox, the order would actually be more like “A,B,C,X,Y,Z”.
Because I think each script will run until it reaches a “yield point”.

But my point with what I wrote above is that each script does not care about when it was executed relative to other scripts.


But this is where the wait functions come in. What wait (and task.wait) do is tell the script that it doesn’t have to reach the end of the code immediately. So that script will pause at the point where the wait() happened, and will resume when the “Script Scheduler” says “ok, you can resume”.

But the problem is, when the script resumes, how does it know where to resume? That might sound like a strange question, but somone did have to solve this problem at Roblox. There needs to be information stored somewhere about where in the script you paused. Especially if you have multiple different points in your code where wait() is being used.

This is where state machines come into play.
One solution is state machines. In many other places outside of Roblox, similar systems use state machines to keep track of your place in an executing system.

The one example of a state machine is when it comes to 2d platformers. The player can be standing on the ground, they can be in the air, they could be in water, they could be sliding on a wall, etc.

So if the player is in the “standing on ground state” the player is allowed to jump and enter the “is in air” state. But can’t re-enter that state until after they landed on the ground. So you can imagine crating a sort of map (or graph, as it’s also called) of all of your player’s action states, and where the player is allowed to transition to at any given time.

And so in the case of wait() functions, a state machine can be used as an alternative to wait() for representing your current state in a transitioning system.
But there are still some scenarios where wait()'s might be necesary. Because Roblox doesn’t like it if a script executes for too long. So it might be necesary to call task.wait(). To let your script sleep for a short time before resuming on the next frame.
But keep in mind, this will mean that your code will execute over multiple frames, rather than just 1 frame.


But I am curious if your question applies to a specific piece of code you are looking at.
Because like I said, depending on the situation, a state machine might be good, but it depends.

2 Likes

" * Do not use task.wait(), often we use task.wait() for cooldowns and stuff, but trust me when I say overtime this will cause you lots of headaches, You are better off stepping the cooldowns yourself through a state machine."

^ I had some questions on cooldowns and was told I was better off running them through state machines but didn’t know how they work.

Also thank you for briefly explaining a state machine and its use, but I don’t see how I would apply it to my cooldowns like I was previously advised to.

Yea… I’m wondering the same thing.
I’m wondering what example this person had in mind, when it comes to cooldowns.

For one thing, if you let a script run long eanough, like if you are doing a lot of data processing in a loop, then the script will eventually stop running because Roblox doesn’t want scripts running for too long before either reaching the end or yielding (via task.wait)

I know I could just show some infinite loop code as an example, but this probably would resemble somthing closer to an actual real-world scenario.

wait(4)

num = 0

for y=1,10000 do
	for x=1,10000 do
		num = math.random()
		num = num + math.random()
		num = num * math.random()
		num = num / math.random()
	end
end

The game will freeze for a few seconds.
Eventually the console will say:

Script timeout: exhausted allowed execution time
Stack Begin
Script ‘Workspace.Script’, Line 6
Stack End

So you would have to integrate a task.wait() in there somehow.
But with that being said, I think I know what they are talking about now.


So when they say “stepping the cooldowns yourself through a state machine”. I think what they are essentially talking about is having your code run over multiple frames, like what would happen when you use task.wait()

The difference is that you would use somthing like RunService with the Heartbeat event (or another event) to have code that executes over multiple frames of the game.

So for example, I will take the code I wrote above, and make it so it executes over multiple frames in a “state machine esque” fasion.

local run = game:GetService("RunService")

function CodeFinished()
	print("Code finished running!")
end

num = 0

local y = 1
local finished = false

local connection: RBXScriptConnection = nil
connection = run.Heartbeat:Connect(function(deltaTime)
	
	-- This part is redundant, but still nice to have to make sure it doesn't continue running
	if finished then return end 
	
	for x=1,10000 do
		-- the main logic we were doing before
		num = math.random()
		num = num + math.random()
		num = num * math.random()
		num = num / math.random()
	end
	
	y = y+1
	
	if y == 10000 then -- End condition
		finished = true
		connection:Disconnect()
		CodeFinished()
	end
end)

The above code would do more or less the same as what I showed higher up in this reply.
The difference is that I broke a part the nested for loop. So now the outer for loop essentially happens once per frame (via the Heartbeat event).
The inner loop is basically the same, and this means that the code will execute over multiple frames, and you won’t get a message that says “script timeout”.

Before, your nested loop would execute the main logic 100 million times in a single frame. It took way too long. So now we are only doing 10,000 operaions per frame, which is a lot better (Though, I noticed that it still took about 3 miliseconds per frame to execute on my pc, so it’s still expensive).

But hopefully this gives you an idea of what that person probably meant.
And what “state machine structured” code roughly looks like.
This coding strategy has it’s pros and cons, it depends on if doing it this way is both beneficial for performance or for code organization.

Personally, in most cases. I still use task.wait() if I need a specific delay, or even just to yield for a frame.
But I know there are cases where state machines are better.

( Also, task.wait() may already be using state machines internally to know where to resume in the code anyway, which makes me wonder why they would want to write their own state machine for performance. But I am sure I could be wrong in saying that, and there’s a reason they were telling you what they did. )

I think the difference they are talking about, is for like abilities.
Say you use a fireball, and there needs to be a cooldown before you can fire another one.
Instead of using a wait to wait for the fireball to be available again, they are saying to use a
state machine that will switch states between ‘can fire’ and ‘waiting to fire’ or something like that.

I actually think you are better off just using a wait.
Have whatever ability have a server script, that after you use the ability, it waits for the cooldown before you are allowed to do it agian.

2 Likes

That’s a good point, yea.
I’m surprised I didn’t think of that actually.

In other game engines, this is typically what I do.
I have a bool for “canFire”, and I hook into the game loop and decrement a timer variable until it reaches zero, and then set canFire = true

Same, in other engines, I have states that run in a server loop.
However in Roblox I think in terms of ‘gear’, where you have gear with a server and client script.
The server script can just set a flag when the gear is used, and then unset the flag after a wait.
New attempts to activate the gear, check that flag.

So I guess I can see it both ways.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.