Zdecydowałem, że chcę napisać centralną klasę ResourceManager / ResourceCache dla mojego silnika gry hobby, ale mam problem z zaprojektowaniem schematu buforowania.
Chodzi o to, że ResourceManager ma miękki cel dla całkowitej pamięci używanej przez wszystkie zasoby gry łącznie. Inne klasy utworzą obiekty zasobów, które będą w stanie rozładowanym, i przekażą je do menedżera zasobów. Następnie ResourceManager decyduje, kiedy załadować / rozładować dane zasoby, pamiętając o miękkim limicie.
Gdy zasób jest potrzebny innej klasie, do ResourceManager wysyłane jest żądanie (za pomocą identyfikatora ciągu lub unikalnego identyfikatora). Jeśli zasób jest załadowany, wówczas odwołanie tylko do odczytu do zasobu jest przekazywane do funkcji wywołującej, (zawinięte w liczony jako referencyjny słaby_ptr) Jeśli zasób nie zostanie załadowany, menedżer oznaczy obiekt do załadowania przy następnej okazji (zwykle na końcu rysowania ramki).
Zauważ, że chociaż mój system wykonuje pewne odliczanie referencji, liczy się tylko podczas odczytywania zasobu (więc liczba referencji może wynosić 0, ale jednostka może nadal śledzić swój identyfikator użytkownika).
Możliwe jest również oznaczenie zasobów do załadowania z dużym wyprzedzeniem przed pierwszym użyciem. Oto trochę szkic klas, z których korzystam:
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();
};
Problem polega na tym, że aby utrzymać całkowite wykorzystanie danych w pobliżu / poniżej miękkiego limitu, menedżer musi mieć sprytny sposób określania, które obiekty należy rozładować.
Zastanawiam się nad użyciem pewnego rodzaju systemu priorytetów (np. Priorytet tymczasowy, Często używany priorytet, Stały priorytet), w połączeniu z czasem ostatniej dereferencji i wielkością zasobu, aby określić, kiedy należy go usunąć. Ale nie mogę wymyślić przyzwoitego schematu do użycia ani odpowiednich struktur danych wymaganych do szybkiego zarządzania nimi.
Czy ktoś, kto wdrożył taki system, może przedstawić przegląd działania swojego systemu? Czy brakuje mi oczywistego wzorca projektowego? Czy uczyniłem to zbyt skomplikowanym? Idealnie potrzebuję sprawnego i trudnego do nadużyć systemu. Jakieś pomysły?