How to do asynchronous loading/initialization

Currently, my game has to sit and think for a bit as it initializes before the update/draw loop starts, but during this process, nothing is shown. I’d like to be able to show a loading screen of some sort until the initialization is done, but I don’t really know how to go about doing that.

The best I could currently manage would be throwing up a static texture that says “Loading” and then starting initialization, thus blocking the update/draw loop until the initialization is done. It would be cool if I could use asynchronous programming to show some sort of animated loading screen until the initialization is done, though.

If anyone has any experience doing that, I’d welcome any advice.

I think most folks kick off a thread to load in all the assets while your game enters a loop waiting for that thread to set some sentinel to say it’s loaded.

I googled around (lots of hits on this) and it looks like you can also use a Task, or load a small amount of assets synchronously and then just load a few more whenever you have some spare cycles. Like, load no more than 10 assets (or whatever) every frame until they’re all done instead of loading 1000 in a single frame (or startup).

1 Like

I do a mix of this. Textures are loaded on the main thread, one texture per frame in the update loop. All other assets are loaded on a different thread. I can show a loading screen that way with a status message with what’s going on.

1 Like

Thanks for the replies. I think I’ll try out doing the per-frame thing and see how well that works out. My project has very few textures to load, but it does have to do quite a bit of initial processing.

This worked out great, thanks! I was able to get a window to show up with a progress bar, and I didn’t have to do any asynchronous or multi-threading stuff!

For anyone who comes across this, if you want to do synchronous loading of small chunks like this, you’ll want to disable fixed time step so that the draw calls aren’t skipped over when the update calls take longer than the target elapsed time.

Here’s some sample code showing how I’m doing it:

public class Game1 : Game
{
    // These can be whatever values you want
    const int loadingRectangleHeight = 40;
    const int loadingRectangleWidth = 800;

    GraphicsDeviceManager graphics;

    bool loading = true;
    bool stopLoading = false;
    int loadingSection = 0;
    Rectangle progressBarCurrent;
    Rectangle progressBarComplete;
    Color progressBarColor = new(0, 255, 0);
    Color progressBarBackgroundColor = new(50, 50, 50);
    Texture2D blankTexture;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";

        // Disable fixed time step so that draw calls aren't skipped
        IsFixedTimeStep = false;
        graphics.SynchronizeWithVerticalRetrace = false; // This might not be necessary
        // Can be re-enabled after loading is done
    }

    protected override void LoadContent()
    {
        // Create a blank texture for drawing the progress bar
        Color[] textureData = new Color[blankTexture.Width * blankTexture.Height];
        for (int i = 0; i < textureData.Length; i++)
            textureData[i] = new Color(255, 255, 255);
        blankTexture.SetData(textureData);

        progressBarComplete = new Rectangle(0, 0, loadingRectangleWidth, loadingRectangleHeight);
        progressBarCurrent = progressBarComplete;
        progressBarCurrent.Width = 0;
    }

    protected override void Update(GameTime gameTime)
    {
        if (loading)
            UpdateLoading(gameTime);
        else
            UpdateDoneLoading(gameTime);

        base.Update(gameTime);
    }

    void UpdateLoading(GameTime gameTime)
    {
        const float totalLoadingSections = 20f; // Put the number of loading sections here

        bool doneLoading = LoadSection(ref loadingSection);

        progressBarCurrent.Width = (int)(progressBarComplete.Width * (loadingSection / totalLoadingSections));

        if (doneLoading)
            // Rather than immediately setting loading to false, set a flag
            // and then use it to set loading to false after the next draw call is done.
            stopLoading = true;
    }

    bool LoadSection(ref int loadSection)
    {
        switch (loadSection)
        {
            case 0:
                /*load chunk here*/
                break;
            case 1:
                /*load chunk here*/
                break;
            case 2:
                /*load chunk here*/
                break;
            case 3:
                /*load chunk here*/
                break;
            // ...
            // Add more case statements as necessary

            // In the last case statement, when all loading is done, return true
            case 20:
                /*load chunk here*/
                return true;
        }
        loadSection++;
        return false;
    }

    void UpdateDoneLoading(GameTime gameTime)
    {
        // Update game here
    }

    protected override void Draw(GameTime gameTime)
    {
        if (loading)
            DrawLoading(gameTime);
        else
            DrawDoneLoading(gameTime);
        base.Draw(gameTime);
    }

    void DrawLoading(GameTime gameTime)
    {
        spriteBatch.Begin();

        spriteBatch.Draw(blankTexture, progressBarComplete, progressBarBackgroundColor);
        spriteBatch.Draw(blankTexture, progressBarCurrent, progressBarColor);

        spriteBatch.End();

        if (stopLoading)
            loading = false;
    }

    void DrawDoneLoading(GameTime gameTime)
    {
        // Draw game here
    }
}
1 Like