Concept
The Cache currently shares it's memory space with the Hunk, and is a rather nasty-looking mess of code with functions to move objects around and maintain least-recently-used and most-recently used lists.
We're going to have an unlimited-sized cache that is completely decoupled from the Hunk system at the end of this one, and the code in the file will really start looking a lot cleaner and more maintainable.
Sometimes it's better to fine tune code by tweaking a little here and a little there until you get where you want with it. Other times you need to rip it up and start again.
Code
Guess which one I did? Replace everything between the "CACHE SYSTEM comment header and the Memory_Init function with this:
Code: Select all
typedef struct cache_system_s
{
int size;
cache_user_t *user;
char name[MAX_OSPATH];
struct cache_system_s *next;
struct cache_system_s *prev;
} cache_system_t;
cache_system_t *cache_head = NULL;
cache_system_t *cache_tail = NULL;
void Cache_FreeLow (int new_low_hunk) {/* used by hunk */}
void Cache_FreeHigh (int new_high_hunk) {/* used by Hunk */}
void Cache_Report (void) {/* used in cl_main.c */}
void *Cache_Check (cache_user_t *c) {return c->data;}
void Cache_Free (cache_user_t *c)
{
cache_system_t *cs = ((cache_system_t *) c) - 1;
if (cs->prev)
cs->prev->next = cs->next;
else cache_head = cs->next;
if (cs->next)
cs->next->prev = cs->prev;
cache_tail = cs->prev;
// prevent Cache_Check from blowing up
cs->user->data = NULL;
free (cs);
}
void *Cache_Alloc (cache_user_t *c, int size, char *name)
{
cache_system_t *cs = NULL;
for (cs = cache_head; cs; cs = cs->next)
if (!strcmp (cs->name, name))
return cs->user->data;
if (c->data) Sys_Error ("Cache_Alloc: allready allocated");
if (size <= 0) Sys_Error ("Cache_Alloc: size %i", size);
cs = (cache_system_t *) malloc (sizeof (cache_system_t) + size);
cs->next = cache_head;
if (!cache_head)
{
cache_head = cs;
cs->prev = NULL;
}
else
{
cache_tail->next = cs;
cs->prev = cache_tail;
}
cache_tail = cs;
cs->next = NULL;
strcpy (cs->name, name);
cs->size = size;
cs->user = c;
c->data = (cs + 1);
return c->data;
}
What's happening here is that I've completely reworked the system to live in it's own free memory space totally removed from the Hunk, and managed by standard malloc and free. It's still a double-linked list because we potentially need to remove items from the middle of the Cache (in the software model.c) and I wanted to make it easier to do so.
The only tricksy thing here is that I allocate the cache_system_t struct and the actual data in a single contiguous block. This makes it easier to retrieve the original cache_system_t struct from a user data pointer when freeing memory, although I think it's a security weakness as it allows code outside of the memory system to be able to access the system struct. It's the way ID Quake did it however, and changing it would need changes outside of zone.c, which I want to avoid.
The original Quake Cache system had a function for flushing the entire cache, but it was unused (most likely because it shouldn't be done while a server is active!) and so I didn't bother implementing it. Doubly-linked lists are Computer Science 101, so look at your favourite textbook or article if you want to implement it yourself.
Cleaning Up
I've been a bit more brutal this time, removing functions that weren't used outside of zone.c and tidying up a bit more myself. There shouldn't be much cleaning up to do this time.
The Next Part
And so on to the Hunk. Quake actually maintains two Hunks, one at the upper-end and one at the lower-end of it's memory block. The changes needed here are already written and working, but I'm going to review them and decide if I want to split the Hunk into two separate blocks or keep things as they are, and also see what potential for cleaning up there is.