3

I am working on a video game in Unity and at some point I'm facing a tough problem to solve:

The game freezes while loading level data.

Let me lay down what's happening in that process that takes a few seconds:

  • load track mesh data consisting of sections, faces, vertices
  • load track textures, build an atlas out of them for the mesh
  • build track mesh using aforementioned data

This loading process takes a few seconds and I expect it to be even longer since I will have to load scenery which is bigger by a magnitude (not yet done).

Considerations:

Some of the steps in this loading process cannot be run in a background thread as they instantiate objects from Unity, which can only be done from its main thread, e.g. create a texture, mesh etc.

Attempts and ideas at solving the problem:

  • start a coroutine, this simply doesn't work since they are expected to run within a single frame, the loading process take hundreds of frames, a frame being 1/60th of a second

  • split the loading into chunks, process each at every frame, likely to be the solution but tricky

    • when required, generate engine objects using a dispatcher, i.e. an async call executing in the appropriate thread, e.g. create the final texture out of raw pixels

I will focus on the second approach as it seems the right one but I am open for another approach.

Question:

What approach or pattern I could use to split a long running operation into smaller ones?

(Hope that's clear enough to you, let me know otherwise.)

Edit: Clarifications after the comments:

Basically my problem is simple but difficult to fix, simple as UI freezes because a task running in it since it requires access to it takes longer than usual; complex as on how to refactor this without rewriting the whole thing. I believe there is a simpler and equally effective approach to tackle this.

Two other ideas emerge from this:

  • a Dispatcher approach, I'd run the whole thing in a background thread, wrapping engine calls as needed, the UI would only freeze a bit; this is fine as it happens even on AAA games

  • a static loading screen, perceptually hides the problem but doesn't solve it at all

  • Are you talking about the "Level loading screen" or are you trying to load things as the game is being played? In any case if I were you I would get the whole thing working first, and then worry about optimisation. If you can find the best solution in the meantime, great, but otherwise you may get multiple ideas (and a more complete question) as you have the big picture of everything. – async Mar 28 at 20:00
  • @async: I think it is pretty clear from the question "the thing" is already working and shows the freezing behaviour. The "not yet done" seems to refer to the upcoming scenery, not the code. – Doc Brown Mar 28 at 20:17
  • Can't you create "placeholder objects" for those "engine objects", and convert them to engine objects afterwards in the right thread? This may work when you don't need any special behavioural methods from those engine objects in your loading process. You may need something like a UnitOfWork object - an analogy from P of EAA. Instead of persisting to a DB, you "persist" your placeholder objects to the engine by converting them to engine objects. – Doc Brown Mar 28 at 20:30
  • @async The game is running, the main menu is visible, and the user has decided to start another level by clicking some UI. Getting the whole thing finished first to get a better picture doesn't really makes sense as it's already visible; I will only find that loading time is even longer. Hence why I am trying to find a solution early in the process so I can code the next parts according this fact. The loading process of the track is quite intricate already, I can let you imagine how tedious it'd be to refactor something bigger than that :) – Aybe Mar 28 at 23:01
  • 1
1

Hard constraints

You need to assign the data to a Mesh. That needs to happen.
There's no way around it.

The Unity API isn't thread safe, so it must happen in the main thread.
There's no way around it.

So what we can already establish here is, if assigning any of the data to the mesh takes (too) long, I'm afraid that this can't be changed; it takes how long it takes. However, it is probably reading and loading the data which takes the lion share of processing time here.

Coroutines

Your 2 ideas, "start a coroutine" and "split the loading into chunks" are actually the same. This is because what a coroutine allows you to do in terms of performance management is to say "OK, that's enough for this frame, let's continue this in the next".

public IEnumerator ExecuteSomethingThatTakesLong()
    {
        for (int i = 0; i < 200; i++)
        {
            // A function that takes time to execute. Doing this 200 times
            // in a row takes about a second, causing a lag.
            TakeTime(10000);

            // By waiting after each step, it will definitely take 200 frames
            // to complete, but no noticable lag occurs.
            yield return null; 
        }
    }

So, in order to get what you want from coroutines, you will have to "split the loading into chunks" anyway. The issue with that is that you'll have to find a good size for the chunks. If you find a chunk size that works well, it might be too much for a machine with less performance - and then that slower machine will stutter because every single frame is just a liiittle bit too much.

Sure, that's not an unsolvable problem; you can just leave a decent enough margin etc. However, splitting a 3D model file is not without its own challenge and just further complicates this.

If you e.g. need to build for the WebGL platform, you don't have threads and this is the only angle to attack this issue. However, from your question it seems like you're working on a platform that allows threads (= pretty much all other platforms).

What can we work with here

If you can use threads, they would be the easier option here. They say

You have a problem. You solve it with threads. You now have 2 problems.

And I agree, but in this case threads really seem to be the easier alternative.

Luckily (or probably rather because somebody thought about this) Mesh works with really primitive data; We're talking about Vector3s and int[]s here. Vector3 is part of the Unity API, but is an exception in that it is thread safe.

In order to squeeze the maximum performance gain out of this, I'd advise to make a class that mirrors Mesh's data. Well, the parts you need at least. That way, you have a class that you can populate with all the data you need in another thread. Then when you're done, you can just copy every single property over one by one on the main thread.

If it is really necessary, you can then also use a coroutine to split that data assigning further (first assign the vertices, the next frame the triangles, the next frame the UVs...).

  • Thank you, I think I'll just go with the 2 problems approach and @Robert Harvey helper! It's a bit convoluted to be honest but I guess the general idea is here so now I need to code it and cross my fingers it'll be worth the trouble :) – Aybe Mar 30 at 5:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.