smoothing framedrop. How can I get yield("idle_frame") to work ?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Inces


My AI processing function is quite extensive, and it causes visual frame drop. It iterates every tile character can reach, and for every reachable tile it iterates tiles, that can be targeted by all his spells. When I put yield("idle_frame")in the first iteration, frame drop is completely decreased and everything runs smooth. The problem occures again when I equip character with one spell, that has large range, and as such - iterates more targets than walkable tiles were iterated. Frame drop happens again. However, when I place yield("idle_frame") in both loops, it eliminates framedrop, but prolongs duration of whole function by over 300 %.

Is there any way I can control yielding for idle frame, so it eliminates framedrop, yet lets function run at maximum speed ? I am thinking of some way to yield idle frame on condition some frametime variable is over certain value. Can You advise me on something like this ?

:bust_in_silhouette: Reply From: HyperlinkYourHeart

Yielding for “idle_frame” means waiting until the next frame to continue the work. As such it inherently means preventing the function from running at full speed. I don’t think there’s any way around that when trying to do the work on the main thread.

If the slowdown is too much you might be able to do it on a separate thread. This should allow you to do the work as fast as possible without interfering with the main thread.

Thank You for your answer !
I was working with threads in my former project and it proved to be dangerous, I was getting a lot of inconsistent crashes. If there really is no way around it, I will have to give it another chance. But maybe You can tell me more about framedrops so I can think of something around. Like what determines, that a frame will drop ? Profiler can show variables like frametime, as it reaches above 100% on framedrops. Can I get it early from OS or something, and only yield for idle frame when it is close to 100% ? Would that work ? Any ideas of this kind ?

Inces | 2023-06-25 14:45

Yeah multi-threading can be tricky alright.

A frame will appear “dropped” if all of the processing for it takes too long, as far as I know - or rather, subsequent frames will not get processed in a timely manner.

I had been thinking about this a bit since answering, and had the thought of running the second loop 2 or 3 times before yielding - or however many times you could get away with. I was thinking to decide a number of loops that works by trial and error, but actually watching the execution time before yielding seems like a much better idea! I thought there was a method for getting how long the current frame has been processing, but I can’t find it. However, you could calculate it yourself using the Time singleton.

Remember that everything else in your game also needs to be processed in a reasonable amount of time. If you want to be really sure that everything gets done on time you should make sure that this work is done by a node with a high process_priority (i.e. it gets executed last). And if you want to be sure you know how long the frame has been processed for you would need to record the current time in a node with a low priority (gets executed first). You have 16ms to work with for a 60FPS game.

Also, I think having a yield in your first loop when you have one in the second might be wasting time, leaving frames where basically no work is done?

HyperlinkYourHeart | 2023-06-25 15:51

These are great ideas, thank You, I will try it out and update here :slight_smile:
However I already can see a problem - measuring Time will only prevent framedrops on the machine it was tested on. I will need some independent variable, or can I calculate it myself using Time and something else ?

Inces | 2023-06-25 16:05

The idea of measuring the time that has passed processing the current frame is so that it can work well on different computers, because they will be able to figure out how much work to do before yielding based on their own capabilities. You would need to calculate:

  1. The amount of time passed since the beginning of the frame when starting your calculation, and from this the time remaining.
  2. The amount of time it takes to perform one loop.

With those you can figure out how many more times you can loop before yielding. For example if one loop takes 5ms and you have 15ms remaining then you can loop three more times, then yield. When you return from the yield you have to calculate the timings again. Maybe you could calculate the loop time once and it will be consistent, or maybe you will want to calculate an average (and be conservative with how many more loops you do) if it is not, you will have to determine that by experimenting probably.

To calculate the time it takes to do something, just record the value from Time.get_ticks_msec() before you do it, then again afterwards, and subtract one from the other. Or use Time.get_ticks_usec() if you want to be really precise.

HyperlinkYourHeart | 2023-06-26 15:21

Hey, I have encountered what seems like inpenetrable issue. It looks like it is impossible to design conditional yield. Every function that yields for completion of another function, expects this function to have yield inside of it as well, so it returns GDScriptFunctionState. But if yield for idle frame only happens under certain conditions, it will not always return GDScriptFunctionState and force a crash :frowning:

Inces | 2023-07-01 10:10