Multithreaded resource manager

Started by
3 comments, last by Telastyn 18 years, 1 month ago
I've been designing a multithreaded resource manager over the past couple days. The basic way that it works is that my main game thread uses a ResourceManager class to manage all the resources (which would be things loaded from the disk, like models, sounds, etc). The ResourceManager uses a ResourceLoader class (which the rest of the game isn't aware of) to load resources that the game requests. The RM communicates with the RL through message passing, and all the resource loading is done in the RL class in a seperate thread. The result is that (hopefully) the game can stream resources on the fly and remain nice and fluid even as stuff is busy being loaded (especially, I hope, on new dual core machines). Now let's say there's these "doodad" things. These doodad things are like props that populate the game world. My example game world is full of tree doodads. In order to create a tree doodad the doodad must refer to a tree model, which contains graphical information as well as other fun stuff like collision info, etc. In other words, to fully create a tree doodad the doodad has to be able to refer to the proper tree model resource. But here's an issue. Let's say I want to create a tree doodad which uses a particular tree model, perhaps Tree01.obj. Inside the doodad's constructor it requests a pointer to the Tree01.obj model, which isn't loaded yet, but the RM sends a message to the RL that it needs to be loaded. Meanwhile the doodad is... doing what? I guess the RM could just return immediately with a NULL pointer when the doodad requests the model, which the doodad understands to mean that the resource is in the process of being loaded. But then what? How does the doodad find out when the model is finally loaded? Polling? Some kind of callback? OR, should doodad creation be done over on the resource loading thread, so that instead of a "resource loading thread" it becomes an "entity creation thread" that handles both loading resources and creating game objects? My game says "put a tree over there," sends a message somewhere, the other thread gets the message, loads the required resources, creates the doodads, and sends a "here's that tree you wanted" message to the game thread. This could work, but it means that so much more stuff is going on in the loading/creation thread that there's a lot more conflicts that could come up that I'd have to put locks around (for instance, my loading thread shouldn't be adding new geometries to the physics engine when the physics engine is busy calculating a timestep) and that could hurt performance. Plus if a resource is already loaded you're wasting a lot of time waiting for messages to be passed around. To summarize, how does World of Warcraft do it?
I like the DARK layout!
Advertisement
I have no idea how WoW does it.

How I did it was to immediately return a default image [or as someone in another thread recently commented, it would be largely trivial to load low res images of all resources at all times and return that instead] which is replaced via callback once loaded. The callback isn't a direct replacement, but usually something to insert 'replace that texture with this texture' into some other threadsafe message or delegate queue.

Personally, my resource manager actually creates the doodads rather than just loading the resources. Either way though, the immediate return and callback approach works the same.
I'm a little fuzzy about what you mean. How does the callback work? Does the RM have to keep up with which doodad requested the resource and then inform that doodad (and any others that requested the same resource in the meantime) that the resource is ready? Or do you mean you're using something where the RM creates a new copy of a "still loading" resource and gives that back to whatever requested it, then when the real resource is loaded it just replaces the data inside the resource so that whatever is referencing that resource gets the new data automatically?

If it's the latter, how would you handle a doodad needing data from a resource in order to set up the doodad properly? For instance, the doodad might need data from a model resource in order to build its collision geometry. How would the doodad know it needs to rebuild when the resource is updated? It still seems like you'd need an explicit callback for each doodad using that resource.
I like the DARK layout!
You don't need threads to let the CPU work while the file is loading; you can use overlapped I/O on Windows, and async I/O on most UNIX-es (even Linux gets it in 2.6).

That being said, whether the Doodad instance is created on your worker thread, or in the main thread, is probably most dependent on the rest of your APIs. If you're using Direct3D, then it becomes much less efficient if you access the same device from more than one thread, so creating the vertex buffers and whatnot should probably be done in the main thread.

Typically, what you'll do with multi-threaded resource load is to return an object that's of type "resource" but that's in an unloaded state. The user can then subscribe to notifications of when the loaded state changes. As the data comes in and is parsed, the user gets a message that the loaded state changed (and can go grab the data out of the resource as needed). The nice thing with this is that you can then un-load the resource if the data on disk changes; notify the user; re-load the new data and re-notify the user of the new data. In-place asset updates without re-starting!

Typically, if your main game loop is single-threaded, and objects aren't generally re-entrant, you will want the subscription notification to come on the main thread, so you need to have a message queue of some sort which gets pumped through the main thread; ideally when PeekMessage() returns false in your Windows message loop.
enum Bool { True, False, FileNotFound };
Well let's see...

Quote:
Or do you mean you're using something where the RM creates a new copy of a "still loading" resource and gives that back to whatever requested it, then when the real resource is loaded it just replaces the data inside the resource so that whatever is referencing that resource gets the new data automatically?


This way, simply put.

Quote:
Does the RM have to keep up with which doodad requested the resource and then inform that doodad (and any others that requested the same resource in the meantime) that the resource is ready?


Yes.

Quote:
If it's the latter, how would you handle a doodad needing data from a resource in order to set up the doodad properly?


Currently all I have threaded is texture loading, and entities don't really care what texture they're using. If they did though, a simple OnChange event or 'dirty' flag or even a nicely done Setter would allow for re-initialization on update.

Quote:
How does the callback work?


Programmer calls directly or indirectly a texture to be loaded via thread. An entry is added to a List indicating that that texture is loaded via thread. Thread is started, with a callback to alert the RM when complete.

When a renderable wants to load a texture [either as the primary request of the 'indirect' load above, or later] the loader checks to see

A)if the texture is already loaded, doing standard faire reference counting on it and returning a new renderable.

and B)if the texture is loading in thread. If it is, it creates the 'loading' renderable, adds that renderable to a Dictionary/std::map <textureID,List<renderable>>. It then returns the renderable.

C) If neither, it currently non-threadingly loads the texture, adds it to the 'loaded' list via weakptr/WeakReference and returns a new renderable with it set.

So, if 'B', the RM eventually gets a callback indicating the texture is loaded, and where it can be found. RM adds the texture to the 'loaded' list, and runs through the 'waiting for the texture' Dictionary/std::map and replaces the texture in each object. If there are no entries, since the Textures are stored as weakptr/WeakReference, it makes a temporary renderable with a solid reference to it, and a timer to destroy the temporary after a set time; just to give the app time to pickup the pre-loaded texture. The List of textures currently loading, and the Dictionary entry for renderables waiting on a texture are then cleared of the entries for the now loaded texture.

This topic is closed to new replies.

Advertisement