Asynchronous Asset Loading

I spent the past week porting the Choose Puzzle screen for my jigsaw puzzle game from the JavaScript prototype over to C++. This meant tackling asynchronous loading of assets (images, sound effects, and music) in a language that doesn't offer automatic gargage collection. JavaScript also offers a built-in Fetch API that the prototype was able to lean on.

For the C++ version of the game, I'm looking to build both a desktop version and the web-based version. The web version of the game will live on the Exploring Winnipeg Parks website, and still uses fetch to load assets. But in C++, that data has to be converted from the JavaScript environment into the C++ environment. Since I'm using SDL, that means they ultimately become SDL_Texture or Mix_Music objects.

Issues Surrounding Asynchronous Asset Loading in C++

There are a whole class of issues that come up in C++ that simply don't exist in JavaScript in the browser:

  1. Spawning and managing asynchronous loading threads.
  2. Assets such as SDL_Texture must be created on the game's main thread.
  3. Lifetime management.
  4. Safely shutting down the application while loading threads are running.
  5. Disk or network I/O saturation.

😬

My Game's Architecture

After several late nights, I managed to work through these issues in a manner that I'm, at the very least, at peace with. This is my approach.

AsyncTaskTracker

A simple mutex-guarded count of outstanding asynchronous threads. It has a waitForAllTasks method that can be called during application shut-down. This relies on std::condition_variable to wait until the count is zero.

For my game, I decided that each screen is managed as its own class, and there is one instance of the screen that lives on the game class. This allowed me to have a single AsyncTaskTracker for the application. It gets created before the game, and passed in to its constructor.

This solves the issue of dangling threads if the user closes the application while asynchronous loading is still ongoing.

ByteDataAsset

A thin wrapper around a std::vector<uint8_t>, with a loading status. These can be safely created off the main thread. I essentially create a queue of these, stored in a std::unordered_map<std::string, ByteDataAsset>. The main thread then polls this queue, looking for completed assets to be converted to their final asset type.

This ensures that objects like SDL_Texture are only created on the main thread.

AssetLoaderInterface

A pure virtual class with a loadAsync method that accepts a path list, plus onSuccess and onError functions as input arguments. The desktop build has a FileSystemAssetLoader / HttpAssetLoader implementation, and the Emscripten build has an EmscriptenAssetLoader implementation that just uses the JavaScript Fetch API.

Conclusion

Getting through asynchronous asset loading feels like a mini-project of its own. I attempted to tackle these things in the past with my Garden of Eating game. It didn't handle application shut-down safety.

Once I get through this game, I plan on building an open-source indie game dev library. I think I have some decent building-block primitives for asset loading that I'll be able to include.