Running a giant loop without freezing?

Is there any good practice for running a huge loop that does some calculations without freezing the game?
In my case it’s happening at the start of the game, but I think any kind of freeze is a bad player experience. I could split the loop into chunks, but I have no idea how long each chunk will take.

Is a loading screen an option? You could overlay the view with a canvas that you use as loading screen. The loop adds up ist progress to the value of a non-interactable slider object. As soon as calculation is done, deactivate the canvas.

1 Like

Use a coroutine and display a loading screen/progress bar until the routine is complete.

2 Likes

So would I just run a chunk of the loop in the coroutine and use WaitTillNextFrame? I think in that case the game wouldn’t freeze but might lag quite heavily if I chose the chunk size wrong.

Show your loop code.

3 Likes

There’s coroutines, threads, the Job system (which is a kind of thread), or just restructuring the code overall to avoid loading too many things on one frame; which one is appropriate depends on what exactly you’re doing in this loop.

If the work done in this loop doesn’t need to directly interact with a bunch of Unity classes (which can often only be accessed on the main thread), or can be written to do so, then putting the work into a thread will generally not cause any lag at all. It’ll take advantage of another core on the CPU instead of blocking the main core.

You could use the system clock to avoid having to choose a chunk; and instead, choose a certain number of milliseconds per frame you want to spend on this work. Something like this:

IEnumerator BigProcessingCoroutine() {
int millisecondsOfWorkPerFrame = 20; //With this, best possible FPS is 50. Adjust as desired; a low value will reduce lag but make the work take longer, a high value will make it lag a bit but get through it faster.
while (true) {
long thisFrameStartTime = System.DateTime.Now.Ticks;
while (System.DateTime.Now.Ticks < thisFrameStartTime + millisecondsOfWorkPerFrame * 10000) {
// HERE: Do one "unit" of work, whatever this is. If you finish the work, set processingDone to true.
if (processingDone) break;
}
yield return null;


if (processingDone) break;
}
}
3 Likes

Depends on what you’re actually doing.

Running a bunch of complex calculations and returning the result without blocking the main thread is exactly the use case Unity claims the Job system was designed for.

If you’re doing a bunch of calculations that are always the same on every play through, you might want to do them once yourself and take the result and store it as an asset in your build . Then when the player runs the game they just load the result like any other asset instead of running the calculations.

If it is something you can do during a loading scene, I’d consider doing it there since players are more tolerant of pauses in a loading scene than they are during game play.

If it is something that doesn’t use any Unity API’s then you could consider doing it on a separate thread.

There’s always coroutines to do the calculations spread over any number of frames.

2 Likes

Thanks for all the replies everyone!

Thanks, your example is exactly what I was looking for. I don’t have the code since I was asking mostly out of curiosity, in my game I can avoid having to use a loop. I’m working on Idle Games though where the offline progress is calculated at the start of the game and depending on the mechanics a loop might be unavoidable.

I’m going to look into multi threading, I never used it and never came across it in tutorials(aside from general ECS stuff).

Consider using jobs and Burst, for such cases.
Or even ECS from DOTS.

surely you could skip most of those loops though? (since its just to calculate production of something or so)

For the most part, yes, but picture a flow chart with generators that need resources to produce others, some have to wait before others are finished, some share and snatch resources away from each other, some might even need resources that they had contributed in generating, and the user might have different stock piles before going offline… and that’s just a straightforward example, I have wicked ideas :slight_smile:

In theory it can always be done without a loop, but at some point it becomes impractical. Depending on the size and complexity, the system is prone to errors, and those errors won’t show in the console, it would take a lot of testing, comparing etc. So I guess at some point it makes sense to use a loop, and I’d like to be prepared :slight_smile: