In this tutorial, I'm going to go over a technique I used to do load content on multiple threads. Doing this is definitely going to get more tricky than the normal content loading that we've talked about before. The only reason I'd suggest ever going this route is when load times become problematic. When this happens, you'll be really happy to have something like this to fall back on to.
In the game that I was making that required this, I ran into a significant problem loading content. Honestly, it didn't feel like I was loading all that much. Just a few models. Yet it was bringing my load times to a screeching halt. When deployed as a release mode package, it was perhaps 10 seconds. That may easily be tolerable, except there were two problems. One, I'm eventually going to need to load a whole lot more content (I haven't even started with audio or particle effects, and I've probably only begun with shaders.)
And two, perhaps the bigger problem is that in debug mode (which I really need when developing the game) it was taking over two minutes! As I was going through my game, finding and fixing bugs, I'd see things and be like, well that's going to take a two minute restart to fix! I'll have to take care of that another time. (Because, as everybody knows, if a bug takes longer than two minutes to fix, we just pretend it's a feature instead.)
Anyway, so this tutorial describes how I used multiple threads to address this issue.
Wrapping the ContentManager
To start, it is worth pointing out that instead of always going to the ContentManager class to load content, I wrapped the ContentManager class in a separate class that I happened to call AssetManager. In creating this wrapper class, I had intended to offload a bunch of the thinking about content loading and management (when to unload, etc.) to here, but at the current moment in time, it's actually really stragihtforward.
The only real thing that my AssetManager class does is make content loading available in a static fashion. In other words, no one needs a reference to a ContentManager to get access to assets like 3D models and textures.
Since the class is a static class, as long as the AssetManager class is configured correctly, you can get your hands on content without needing any instances of any special objects:
A second thing that the AssetManager class did was store a copy of the loaded content so that the second time it was asked for, it didn't try to load it again. I'll be honest though, I think the basic ContentManager class already does this. (I haven't seen it documented anywhere, but the performance of subsequent calls really seems to support this.) So perhaps this caching thing is overkill.
Wrapping the ContentManager class in another class to control it is not a requirement of doing multi-threaded content loading, it's just the path that I chose.
One thing you need to know before starting multi-threaded content loading is that the ContentManager class is not thread safe.
I assumed that it was. Or rather, I assumed that it might be, and it would be easier to pretend it was and see what happens, rather than try to deal with the complications if it wasn't, until it caused problems.
Sure enough, though, it's not thread safe, and I ran into all sorts of problems loading content. I saw things like IndexOutOfBoundsExceptions and models with triangles that had been loaded funky.
But there's a fairly simple solution to this. Since ContentManager isn't thread safe, just create a separate ContentManager object for each thread. To be clear, since I was using a ThreadPool, I had no clue what threads were going to be trying to load content. So I just created a new ContentManager each time I wanted to load content. It's probably not the most efficient, but at the moment, it seems to be enough in my particular game. This will mean, though, that you've got a lot of ContentManager objects sitting around taking space, and hanging on to assets on the graphics card. (Usually, you only have the one.)
To keep things simple, I create a new ContentManager object based on the original one that was passed in from my main game class (e.g., Game1) on initialization of my AssetManager class:
ContentManager temporaryContentManager = new ContentManager(ContentManager.ServiceProvider, ContentManager.RootDirectory);
Using the ThreadPool
|Having problems with this tutorial? Try the troubleshooting page!|