Resource Management
There have been some posts recently to GD-Algorithms concerning resource management. This happens to be an area I’ve investigated recently as part of my Sandbox project (which, btw, is becoming more of an excuse to experiment with different design patterns than actually doing game-code at the moment).
I’m going to define a resource manager as needing to have the following qualities:
- Provide universal access to all modules needing to retrieve externally persisted data (resources).
- Ensure that resources exists as a single, shared instance in memory.
- Ensure that resources are properly garbage-collected as necessary.
- Allow resources to be retrieved/referenced by unique key.
These requirements immediately evoke several classic design patterns that we should be leveraging. First, the universal access requirement means that the resource manager should be a singleton. Second, the two requirements of shared instance and garbage-collected means smart pointers. Finally, retrieving by unique key smells of some variation on the factory method.
The current implementation I’m playing with is based off of templates. The two key template parameters are the resource’s type and the unique key’s type. My implementation could also be thought of as a specialization of the std::map to automatically construct an object if it doesn’t exist in the map. Here’s an example of how a resource is retrieved:
Texture *texture = Resources<Texture>::Get("mytexture.tga");
Mesh *mesh = Resources<Mesh, int>::Get(37);
The first method is using a const char * as the unique key (this is the default type for the template) and the second is using an int as the unique key. Either can be useful. The only drawback to making this a template parameter is that you cannot mix them; in other words, resources retrieved by string are managed separately (and are considered unique from) resources retrieved by integer.
I’m also debating whether the resource manager should have its singleton aspect factored out of it. In this scenario, we could have local resource managers of the following form:
Resources<Texture> localResMgr;
Texture *texture = localResMgr.Get("mytexture.tga");
This may be desirable in reusing the code for internal resource management inside of particular sub-systems.
Another aspects of the resource manager is the desire to register resource creators. Creators are invoked when a resource is first accessed. The canoncial creator is one that uses an existing API to load a resource (for example, a text file creator simply creates a buffer filled with the contents of the text file, basically a giant string). My template above also allows the user to specify a creator functor to invoke when creating a specific type of resource (this is where the hints of the factory method come in). So far, I’ve not found a particularly good replacement creator, but building in that generalization is trivial (and free with templates).
A question that should likely be addressed in this scenario, and in most low-level system development, is whether we are creating a service or a type. When sitting down to write code, particularly code where you can envision most of its usage scenarios, this distinction may not be clear. But, having dealt with library code (RenderWare) for three years now, the difference between designing services and designing types seems pretty clear to me (I apologize if the terminology is wrong).
I see services as representing functional hooks for manipulating data, while types provide new classes of data (with their manipulations implicit to their definition). I guess this could be seen as the debate of functional vs. object-oriented programming. In the case of my resource manager, do I desire to have a resource management type that I can use throughout various systems, or do I desire a resource management service that I can use throughout various sytems.
The first invocation I showed at the top of this post would be an example of providing the resource manager as a service. It may be enclosed in a class, but this is actually just an implementation detail (there are differences between how template default parameters work for templated functions and templated classes, so I had to use a class). What the original resource manager does is define an interace for retrieving resources, and since it is essentially a single function, it is doing so globally (which was the original desire).
The second invocation of the resource manager shown demonstrates its definition as a type of object. If I desired for it to be globally accessible (like a service conceptually would be), then I can make it the specific type of a singleton template. This touches on my key point, I guess…
Decomposing this problem into its component pieces is the right path to go because it allows me to choose either service or type without changing the implementation details. For example:
Singleton<Resources<Texture> > ResourceManagerService;
Texture *texture = ResourceManagerService.theInstance().Get("mytexture.tga");
The above exposes my resource manager as a service. Using the exact same implementation of the Resources template, I can create a resource manager type to be used locally in a sub-system:
Resources<WaveData> AudioResourceManager;
WaveData *waves = AudioResourceManager.Get("mysound.wav");
So, what’s the point of this exercise? The point is this: go out right now and purchase Modern C++ Design by Andrei Alexandrescu. This book is devoted to maximizing your use of C++’s template facilities to get this type of easy, easy code construction. It’s the ultimate in generic programming.