- some work on a generic cache manager.

For main resource data this is probably unnecessary - most resources are never cached with the exception of sounds and textures, which are loaded permanently anyway.
But for hardware textures this is different. Due to the poor precaching it is impossible to selectively evict hardware textures that are not needed any longer, so for this an MRU cache is really needed so that they do not accumulate and congest the video RAM in the process.
This commit is contained in:
Christoph Oelckers 2019-11-03 10:00:19 +01:00
parent e24b1e8903
commit 94aa556953

View file

@ -1,125 +1,129 @@
CACHENODE Resource::purgeHead = { NULL, &purgeHead, &purgeHead, 0 };
void *Resource::Lock(DICTNODE *h)
struct CacheNode
{
dassert(h != NULL);
if (h->ptr)
{
if (h->lockCount == 0)
{
RemoveMRU(h);
}
}
else
{
h->ptr = Alloc(h->size);
Read(h);
}
size_t size;
CacheNode *next, *prev;
int lockCount;
int lastusetick; // This is to ensure that a node lives for the duration of the frame it is last axxessed on
h->lockCount++;
return h->ptr;
}
virtual ~CacheNode();
virtual void Purge() = 0; // needs to be implemented by the child class to allow different types of menory to be used.
};
void Resource::Unlock(DICTNODE *h)
class Cache
{
dassert(h != NULL);
dassert(h->ptr != NULL);
if (h->lockCount > 0)
{
h->lockCount--;
if (h->lockCount == 0)
{
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
}
}
size_t maxSize;
size_t currentSize;
CacheNode purgeHead;
int currenttick;
public:
Cache()
{
maxSize = 100'000'000;
currentSize = 0;
purgeHead = { 0, &purgeHead, &purgeHead, 0 };
}
SetSize(size_t newsize)
{
if (newsize < maxSize) Purge();
maxSize = newsize;
}
void AddToPurgeList(CacheNode *h)
{
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
void RemoveFromPurgeList(CacheNode *h)
{
h->prev->next = h->next;
h->next->prev = h->prev;
}
void Alloc(CacheNode *h)
{
currentSize += h->size;
if (currentSize > maxSize)
{
Purge();
}
AddToPurgeList(h);
}
void Release(CacheNode *h)
{
currentSize -= h->size;
RemoveFromPurgeList(h);
}
void Validata(CacheNode *h)
{
if (h->LockCount == 0)
{
// Move node to the top of the linked list.
RemoveFromPurgeList(h);
AddToPurgeList(h);
}
}
void *Lock(CacheNode *h)
{
// This merely locks the node. Allocation must be reported separately.
assert(h != NULL);
if (h->lockCount == 0)
{
RemoveFromPurgeList(h);
}
h->lockCount++;
return h->ptr;
}
void Unlock(CacheNode *h)
{
assert(h != NULL);
if (h->lockCount > 0)
{
h->lockCount--;
if (h->lockCount == 0)
{
AddToPurgeList();
if (currentSize > maxSize)
{
Release(h);
h->Purge();
}
}
}
}
void PurgeCache(bool all = false)
{
// Do not delete from the list while it's being iterated. Better store in a temporary list and delete from there.
TArray<CacheNode *> nodesToPurge(10);
int purgeSize = 0;
for (CacheNode *node = purgeHead.next; node != &purgeHead; node = node->next)
{
if (node->lastusetick < currenttick)
{
nodesToPurge.Push(npde);
purgeSize += node->size;
if (currentSize - purgeSize < maxSize && !all) break;
}
}
for (auto h : nodesToPurge)
{
Release(h);
h->Purge();
}
}
};
void Resource::Flush(CACHENODE *h)
{
if (h->ptr)
{
#ifdef USE_QHEAP
heap->Free(h->ptr);
#else
delete[] (char*)h->ptr;
#endif
h->ptr = NULL;
if (h->lockCount == 0)
{
RemoveMRU(h);
return;
}
h->lockCount = 0;
}
}
void Resource::Purge(void)
{
for (unsigned int i = 0; i < count; i++)
{
if (dict[i].ptr)
{
Flush((CACHENODE *)&dict[i]);
}
}
}
void Resource::PurgeCache(void)
{
#ifndef USE_QHEAP
for (CACHENODE *node = purgeHead.next; node != &purgeHead; node = node->next)
{
DICTNODE *pDict = (DICTNODE*)node;
if (!(pDict->flags & DICT_LOAD))
{
dassert(pDict->lockCount == 0);
dassert(pDict->ptr != NULL);
Free(pDict->ptr);
pDict->ptr = NULL;
RemoveMRU(pDict);
}
}
#endif
}
void *Resource::Load(DICTNODE *h)
{
dassert(h != NULL);
if (h->ptr)
{
if (!h->lockCount)
{
RemoveMRU(h);
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
}
else
{
h->ptr = Alloc(h->size);
Read(h);
h->prev = purgeHead.prev;
purgeHead.prev->next = h;
h->next = &purgeHead;
purgeHead.prev = h;
}
return h->ptr;
}
void Resource::RemoveMRU(CACHENODE *h)
{
h->prev->next = h->next;
h->next->prev = h->prev;
}