GMS and Forced Frame Timeouts

xenoargh

Member
So, basically... I have some real problems I've experienced, repeatedly, with GMS's rather flexible concept of "now".

There appears to be <some mechanism> in GMS that, if a game-frame takes "too long", forces it to increment a frame. I've been fighting with this mechanism for months; it causes a lot of weird problems with my procedural world generator that I've had to work around via Global states, internal states within Instances, and a host of other things. Most of the time, it all "works" but occasionally, I see issues, even now that I've gotten used to it.

For example, if you create Instances and tell them their XY is now <something different> right after creating them, they won't move until the next frame. This is a standard (and annoying) rule. However, I've caught them taking many multiples of a frame to move, because <something else that takes time> is happening in the same game-frame.

I get why it's in GMS; it prevents hard-locks under certain circumstances that newbies might trigger, like creating infinite loops, etc.

Is there any possible way to turn this "feature" off, or govern it, so that during <some operations that take massive amounts of computational time> it is not allowed to move a frame forward until <some condition> has been reached?

This isn't a true show-stopper, but I've been fighting with it for a year now, and it keeps causing problems, because certain things should happen before anything else aren't always operating completely reliably.

Here's an example of what I'm talking about, in practice:

GML:
//someObjects create Instances, which in turn get a bunch of variables set, and, when the Global "blockingVar" is set, can proceed to do many, many complex operations.
with(someObject){
  <run an operation that creates a bunch of Instances>
  <Instances need to perform a bunch of operations upon Create>
  <instances created may include a new someObject>

  //Gets rid of this particular someObject, since it has done its work now.
  instance_destroy();
}

//If we've just created any someObjects, don't allow CrunchyMath
if(instance_exists(someObject)){
  Global.blockingVar = true;
}

//If we've run out of someObjects in the world, now we can start doing CrunchyMath.
if(Global.blockingVar = false){
  CrunchyMath();
}
 

TsukaYuriko

☄️
Forum Staff
Moderator
It feels like a part of this post talks about multithreading (specific code not blocking the execution of other code), which is not included in GMS' feature set, while another talks about loops blocking the execution of code, which is the standard unless you implement countermeasures.

There appears to be <some mechanism> in GMS that, if a game-frame takes "too long", forces it to increment a frame. I've been fighting with this mechanism for months; it causes a lot of weird problems with my procedural world generator that I've had to work around via Global states, internal states within Instances, and a host of other things. Most of the time, it all "works" but occasionally, I see issues, even now that I've gotten used to it.
I'm not so sure about the wording of "if a game-frame takes "too long", forces it to increment a frame" as I'm not sure what's supposed to be forced, or what incrementing a frame would entail.

Anyway, this mechanism is called the game loop, and it is what causes all continuous events of all instances in the game to run in a certain order, waiting for each of them to complete before moving on to the next one, repeated FPS times per second unless a frame takes longer to complete than 1/FPS seconds.

If you're running into issues with the latter, it either means you're processing more than your target device can handle and need to optimize your game, or you're expecting other code to run while a massive loop (e.g. the world generator) is running. In the latter case, you either have to stop expecting other code to run during this time, or to split up the massive loop to run over the course of multiple frames, allowing other code to run in the meantime as well, depending on what your overall intention is (should everything else stop executing or continue executing).

For example, if you create Instances and tell them their XY is now <something different> right after creating them, they won't move until the next frame. This is a standard (and annoying) rule. However, I've caught them taking many multiples of a frame to move, because <something else that takes time> is happening in the same game-frame.
It's not a standard rule. They will be told to move - and, subsequently, actually move - as soon as their Create event finishes running, or, in other words, as soon as the code of the instance that originally created that instance resumes running. Depending on when you are creating these instances, you may not see them until the next frame (e.g. if you create them after their Draw event normally would have run). If you're seeing this take "many multiples of a frame" to take place, then no frames are actually passing and your game is stuck in a blocking loop.

I get why it's in GMS; it prevents hard-locks under certain circumstances that newbies might trigger, like creating infinite loops, etc.
It doesn't prevent this.
It provides a structure to the order of execution of repeating events.

Is there any possible way to turn this "feature" off, or govern it, so that during <some operations that take massive amounts of computational time> it is not allowed to move a frame forward until <some condition> has been reached?
Run something in a loop that takes longer than a frame to complete and that code is the only code that will run during this time. If you're working with massive loops and this doesn't happen for you, your code does something to intentionally break out of that loop, interrupting the long process, then resuming the next frame, as infinite loops will never be interrupted by the engine itself. You can test this yourself with while (true) {} - that won't ever unfreeze without user interaction (read: killing the process).

Here's an example of what I'm talking about, in practice:
You're walking on thin ice there. The behavior of with when creating new instances of the object being iterated over in its body is undocumented, and this is the rough equivalent of modifying a list while iterating over it, which is among the easiest ways to cause a crash or unexpected behavior.

That aside, I'm not sure what exactly this is supposed to be an example of, as blockingVar is entirely unnecessary and I don't see anything that may or may not transcend the boundaries of a frame aside from CrunchyMath being implied to do so, which it will do without further ado and without interruption unless you intentionally stop it from doing so. This would lead to none of the someObjects being drawn until CrunchyMath finishes execution, as their Draw events won't have a chance to run - is this what you're talking about?
 
Last edited:

xenoargh

Member
Hmm. The issues I've seen aren't with drawing so much as "where is this thing, where is its bounds, can it collide, can it be detected by a ray-test" stuff.

Guess it's time to review the lowest-level code and walk my way through the state-machine for world-generation again; there's simply gotta be a reason why I'm seeing some of the weird stuff I see when testing. It's like, 99.9% right, but when it's thousands of Instances that all need to be in the right place, at the right time, with the right values, one tiny bit of bone-headed code could do it, lol.
 
Top