I have a resource manager that manages the lifetime of resources in memory. A "resource" is something like a texture, mesh, et cetera. I also have an entity class that, the base class for things in my game world, which has pointers to individual resources.
I'm thinking about giving my resource manager a memory quota so it can free and reload resources as it needs to in order to satisfy this quota, and I'm wondering how I could go about this without ending up with dangling pointers in my entity objects.
I'll need the ability to free up a mesh or texture at any time in the resource manager.
I could use shared_ptr
in the resource manager and weak_ptr
in the entities (as long as I don't expose a shared_ptr
out side of the resource manager, I'd still get the ability to free them on demand), but I am more interested in how you would do it without those classes, for example before C++11 existed or before Boost's smart pointer library existed.
Answer
Instead of holding raw pointers, hold some other lightweight reference to the resource. A shared_ptr
and corresponding weak_ptr
instances are one such mechanism, but you can easily implement your own if you don't have access to those types (or their earlier Boost equivalents) or there are other reasons why those types would not be appropriate for your scenario (such as being too heavyweight).
This kind of 'handle' system can be as simple as handing out integers which refer to internal slots in the resource system, which can then be used to dynamically reload a resource if access to it is requested while it's been unloaded.
In practice you probably want something a little more robust, at least a way to treat the handle "like" a pointer and provide operator ->
overloads, for example, by wrapping it in a Handle
type. That Handle
type can just contain a pointer to a "control block" stored within the resource manager. The control block has the raw resource reference and a boolean flag that indicates if the resource is alive or not, and some kind of key that can be used to reload the resource if needed (I assume a string in the example below). A very simple example of this might look like:
template
struct Handle {
Resource * operator -> () }
return m_owner->retrieve_resource(m_resource_id);
}
private:
int m_resource_id;
resource_manager * m_owner;
};
namespace detail {
struct control_block {
Resource * resource;
bool is_alive;
std::string key;
};
};
struct resource_manager {
Resource * retrieve_resource (int id) {
auto block = m_resources[id];
if (block->is_alive) {
return block->resource;
} else {
block->resource = reload_resource(key);
block->is_alive = true;
return block->resource;
}
}
private:
std::vector m_resources;
};
In this implementation, the resource manager just stores a flat array of control blocks which can be referenced by an integer, and provides a method to recover a resource pointer by ID, reloading that resource if needed. The Handle
class is just a useful wrapper around that behavior. Obviously this is a very simple example, though. If you want something more complete, consider Scott Bilas' article on a generic handle-based resource manager. It takes a somewhat different approach, but is well worth the read.
(Be warned that a need your question implies may mean your resource utilization is very chaotic in nature, and that isn't necessarily a good thing for performance, so you might want to consider that.)
No comments:
Post a Comment