Search Unity

Procedural generation locks up UI thread

Discussion in 'Scripting' started by ChrisJohnson, Aug 28, 2019.

  1. ChrisJohnson

    ChrisJohnson

    Joined:
    Feb 20, 2013
    Posts:
    64
    Hello,

    I have a problem that I can’t figure out. I’m working on a game that is a procedurally generated rogue-like. At the start of the game, I generate the world, but it takes a while to generate, and ends up locking up the main UI thread.

    In order to fix this, I tried to use a background thread, but I’m using Resources.Load and some other Unity APIs in the generation code. Apparently these api’s can’t be used from a background thread.

    I also tried putting the generation code in a separate scene, and tried loading it asynchronously. But, the main thread still locked up even doing this. Also this seems like a really hacky way of generating the world anyways.

    I’m also looking at using the job system, but I’m not sure if that will work either, because my generation code is using a lot of reference types, and non-blittable types. And I don’t want to rewrite all my generation code to use DOTs.

    So I’m wondering, is there an easy way to generate a world without locking up the main UI thread.
     
  2. CurtisMcGill

    CurtisMcGill

    Joined:
    Aug 7, 2012
    Posts:
    67
    There a few assets on the store that handles using GPU to generate worlds. They use video GPU from computer to make level and some work with newer phones, read docs.

    I have not created these assets, I do own them and I am not fully endorsing them. You can check them out and see if they will work for you.

    Terrain Composer 2 $22.5
    https://assetstore.unity.com/packages/tools/terrain/terrain-composer-2-65563

    Mercator – Rapid Terrain Prototyping and Iteration $28
    https://assetstore.unity.com/packag...apid-terrain-prototyping-and-iteration-119518

    As for making your own, there might be a starter package @ GitHub package that does this? There are GPU tutorials on youtube, etc.
     
    Last edited: Aug 28, 2019
  3. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    You probably want to refactor your code so that you don't need Unity APIs in the main worker thread of your generation algorithm. This might involve preloading key resources in the main thread or finding substitutes for Unity utility functions (e.g. using System.Random instead of UnityEngine.Random).

    Hypothetically you could also try using a coroutine. A coroutine executes on the main Unity thread, but you can make it "pause" and wait for a frame (or a specified period of time) at a point of your choosing.
     
    Joe-Censored likes this.
  4. ChrisJohnson

    ChrisJohnson

    Joined:
    Feb 20, 2013
    Posts:
    64
    Yeah, I was thinking about trying to get all the Unity APIs out of the generation code. But, I’m not sure how much time that will take me. Because I have a lot of code, and I’m even creating textures, as well as the map for the world, plus a lot of other stuff.

    I also tried coroutines, and that also locks up the main thread. But, I haven't tried the pausing thing. So that might be something that I will try.
     
  5. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    The magic in the coroutine is when you yield. That's the pausing part. If you don't ever yield then it is really no different than just calling any other function which executes all the way to the end before anything else on that thread gets to run.

    See the 2nd code example from the manual:
    https://docs.unity3d.com/Manual/Coroutines.html
     
    ChrisJohnson likes this.
  6. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    Yeah, the difficulty with using a coroutine for this is that the computer won't split up your work automatically; you have to decide for yourself when you yield control back to the main thread.

    (The other difficulty is that you can't take advantage of any extra cores your machine might have available.)
     
    Joe-Censored and ChrisJohnson like this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    Cutting up procedural generation routines by making them coroutines is possible, but in my experience it tends to seriously complicate and convolute what is often already a tricky thing to get right.

    For instance if you are making 10,000 GameObjects, you could write your coroutine to "yield return null;" every 1000 objects or so.

    You could also make each stage of the generation be its own loop, separated by "yield return null;" For instance, make the ground terrain, yield, sprinkle the trees on, yield, sprinkle the rocks on, etc.

    But be aware that making code into a coroutine will complicate it and make it harder to refactor.

    I recommend putting such a step off as long as possible, which can be assisted by putting up a clear "LOADING... This may take a while" screen, as well as giving the user a rough estimate of what "a while" is: "This may take up to ten seconds" for instance.

    My reasons for putting such a refactoring off is that procedural generation code generally is a Hard Thing(tm) to get right, and when used effectively it will make up the lion's share of your engineering effort, so you don't want to do anything that complicates it until you are confident of the entire problem space, ie., you are almost done.
     
    ChrisJohnson and Ryiah like this.
  8. ChrisJohnson

    ChrisJohnson

    Joined:
    Feb 20, 2013
    Posts:
    64
    Yeah, right now I do have a loading screen. And that helps some, but it still isn't the best user experience. Because the music stops playing, and it still kind of looks like it is locking up. Also, I want to show some story text, and images while the world is loading, but I can only show one screen before the main thread locks up.

    For now, I think I might just leave it for a while. I might try to get all the Unity api’s out of the generation code, and if I can’t do that then I will probably resort to using coroutines. Unless, I can figure out some other way of dealing with this problem.