Decidi que desejo escrever uma classe central ResourceManager / ResourceCache para o meu mecanismo de jogos por hobby, mas estou tendo problemas para projetar um esquema de cache.
A idéia é que o ResourceManager tenha um objetivo flexível para a memória total usada por todos os recursos do jogo combinados. Outras classes criarão objetos de recursos, que estarão em um estado descarregado, e os passarão para o ResourceManager. O ResourceManager decide quando carregar / descarregar os recursos fornecidos, mantendo o limite flexível em mente.
Quando um recurso é necessário por outra classe, uma solicitação é enviada ao ResourceManager para ele (usando um ID de string ou um identificador exclusivo). Se o recurso for carregado, uma referência somente leitura ao recurso será passada para a função de chamada (envolvida em um fraco_ptr contado referenciado). Se o recurso não for carregado, o gerente marcará o objeto a ser carregado na próxima oportunidade (geralmente no final do desenho do quadro).
Observe que, embora meu sistema faça alguma contagem de referência, ele só conta quando o recurso está sendo lido (portanto, a contagem de referência pode ser 0, mas uma entidade ainda pode estar acompanhando seu uid).
Também é possível marcar recursos para carregamento bem antes do primeiro uso. Aqui está um esboço das classes que estou usando:
typedef unsigned int ResourceId;
// Resource is an abstract data type.
class Resource
{
Resource();
virtual ~Resource();
virtual bool load() = 0;
virtual bool unload() = 0;
virtual size_t getSize() = 0; // Used in determining how much memory is
// being used.
bool isLoaded();
bool isMarkedForUnloading();
bool isMarkedForReload();
void reference();
void dereference();
};
// This template class works as a weak_ptr, takes as a parameter a sub-class
// of Resource. Note it only hands give a const reference to the Resource, as
// it is read only.
template <class T>
class ResourceGuard
{
public:
ResourceGuard(T *_resource): resource(_resource)
{
resource->reference();
}
virtual ~ResourceGuard() { resource->dereference();}
const T* operator*() const { return (resource); }
};
class ResourceManager
{
// Assume constructor / destructor stuff
public:
// Returns true if resource loaded successfully, or was already loaded.
bool loadResource(ResourceId uid);
// Returns true if the resource could be reloaded,(if it is being read
// it can't be reloaded until later).
bool reloadResource(ResourceId uid)
// Returns true if the resource could be unloaded,(if it is being read
// it can't be unloaded until later)
bool unloadResource(ResourceId uid);
// Add a resource, with it's named identifier.
ResourceId addResource(const char * name,Resource *resource);
// Get the uid of a resource. Returns 0 if it doesn't exist.
ResourceId getResourceId(const char * name);
// This is the call most likely to be used when a level is running,
// load/reload/unload might get called during level transitions.
template <class T>
ResourceGuard<T> &getResource(ResourceId resourceId)
{
// Calls a private method, pretend it exits
T *temp = dynamic_cast<T*> (_getResource(resourceId));
assert(temp != NULL);
return (ResourceGuard<T>(temp));
}
// Generally, this will automatically load/unload data, and is called
// once per frame. It's also where the caching scheme comes into play.
void update();
};
O problema é que, para manter o uso total de dados pairando em torno do limite flexível, o gerente precisará ter uma maneira inteligente de determinar quais objetos descarregar.
Estou pensando em usar algum tipo de sistema de prioridade (por exemplo: Prioridade Temporária, Prioridade Freqüentemente Usada, Prioridade Permanente), combinado com o tempo da última desreferência e o tamanho do recurso, para determinar quando removê-lo. Mas não consigo pensar em um esquema decente para usar ou nas estruturas de dados corretas necessárias para gerenciá-las rapidamente.
Alguém que tenha implementado um sistema como esse pode fornecer uma visão geral de como os seus trabalhos funcionam. Existe um padrão de design óbvio que estou perdendo? Eu fiz isso muito complicado? Idealmente, preciso de um sistema eficiente e difícil de abusar. Alguma ideia?