/* // Full copyright information is available in the file ../doc/CREDITS // // Object cache routines. // // This code is based on code written by Marcus J. Ranum. That code, and // therefore this derivative work, are Copyright (C) 1991, Marcus J. Ranum, // all rights reserved. */ #define _cache_ #include "defs.h" #include "cdc_db.h" #include "util.h" #include "execute.h" /* // Store dummy objects for chain heads and tails. This is a little storage- // intensive, but it simplifies and speeds up the list operations. */ Obj * active; Obj * inactive; #if DEBUG_CACHE Int _acounter = 0; Int _icounter = 0; #endif /* // ---------------------------------------------------------------------- // // Requires: Shouldn't be called twice. // Modifies: active, inactive. // Effects: Builds an array of object chains in inactive, and an array of // empty object chains in active. // */ void init_cache(void) { Obj *obj; Int i, j; active = EMALLOC(Obj, cache_width); inactive = EMALLOC(Obj, cache_width); for (i = 0; i < cache_width; i++) { /* Active list starts out empty. */ active[i].next = active[i].prev = &active[i]; /* Inactive list begins as a chain of empty objects. */ inactive[i].next = inactive[i].prev = &inactive[i]; for (j = 0; j < cache_depth; j++) { obj = EMALLOC(Obj, 1); obj->objnum = INV_OBJNUM; obj->ucounter=0; obj->prev = &inactive[i]; obj->next = inactive[i].next; obj->prev->next = obj->next->prev = obj; } } } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Modifies: Contents of active, inactive, database files // Effects: Returns an object holder linked to the head of the appropriate // active chain. Gets the object holder from the tail of the inactive // chain, swapping out the object there if necessary. If the inactive // inactive chain is empty, then we create a new holder. // */ Obj * cache_get_holder(Long objnum) { Int ind = objnum % cache_width; Obj *obj; if (inactive[ind].next != &inactive[ind]) { /* Use the object at the tail of the inactive list. */ obj = inactive[ind].prev; /* Check if we need to swap anything out. */ if (obj->objnum != INV_OBJNUM) { if (obj->dirty) { if (!db_put(obj, obj->objnum)) panic("Could not store an object."); } object_free(obj); } /* Unlink it from the inactive list. */ obj->prev->next = obj->next; obj->next->prev = obj->prev; } else { /* Allocate a new object. */ obj = EMALLOC(Obj, 1); } /* Link the object a the head of the active chain. */ obj->prev = &active[ind]; obj->next = active[ind].next; obj->prev->next = obj->next->prev = obj; obj->search = START_SEARCH_AT; obj->dirty = 0; obj->dead = 0; obj->refs = 1; obj->ucounter = OBJECT_PERSISTANCE; /* we may actually have a connection or file, and when it is used these will get set correctly */ obj->conn = NULL; obj->file = NULL; #if DEBUG_CACHE _acounter++; #endif obj->objnum = objnum; return obj; } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Modifies: Contents of active, inactive, database files // Effects: Returns the object associated with objnum, getting it from the cache // or from disk. If the object is in the inactive chain or is on // disk, it will be linked into the active chain. Returns NULL if no // object exists with the given objnum. // */ Obj *cache_retrieve(Long objnum) { Int ind = objnum % cache_width; Obj *obj; if (objnum < 0) return NULL; /* Search active chain for object. */ for (obj = active[ind].next; obj != &active[ind]; obj = obj->next) { if (obj->objnum == objnum) { obj->refs++; obj->ucounter+=OBJECT_PERSISTANCE; return obj; } } /* Search inactive chain for object. */ for (obj = inactive[ind].next; obj != &inactive[ind]; obj = obj->next) { if (obj->objnum == objnum) { /* Remove object from inactive chain. */ obj->next->prev = obj->prev; obj->prev->next = obj->next; /* Install object at head of active chain. */ #if DEBUG_CACHE _icounter--; #endif obj->prev = &active[ind]; obj->next = active[ind].next; obj->prev->next = obj->next->prev = obj; obj->refs = 1; obj->ucounter+=OBJECT_PERSISTANCE; #if DEBUG_CACHE _acounter++; #endif return obj; } } /* Cache miss. Find an object to load in from disk. */ obj = cache_get_holder(objnum); /* Read the object into the place-holder, if it's on disk. */ if (db_get(obj, objnum)) { return obj; } else { /* Oops. Install holder at tail of inactive chain and return NULL. */ obj->objnum = INV_OBJNUM; obj->prev->next = obj->next; obj->next->prev = obj->prev; #if 1 obj->prev = inactive[ind].prev; obj->next = &inactive[ind]; obj->prev->next = obj->next->prev = obj; #else efree(obj); #endif return NULL; } } /* // ---------------------------------------------------------------------- */ Obj *cache_grab(Obj *obj) { obj->refs++; obj->ucounter+=OBJECT_PERSISTANCE; return obj; } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. obj should point to an active object. // Modifies: obj, contents of active and inactive, database files. // Effects: Decreases the refcount on obj, unlinking it from the active chain // if the refcount hits zero. If the object is marked dead, then it // is destroyed when it is unlinked from the active chain. // */ void cache_discard(Obj *obj) { Int ind; if (!obj) return; /* Decrease reference count. */ obj->refs--; if (obj->refs) return; #if DEBUG_CACHE _acounter--; #endif ind = obj->objnum % cache_width; /* Reference count hit 0; remove from active chain. */ obj->prev->next = obj->next; obj->next->prev = obj->prev; if (obj->dead) { /* The object is dead; remove it from the database, and install the holder at the tail of the inactive chain. Be careful about this, since object_destroy() can fiddle with the cache. We're safe as long as obj isn't in any chains at the time of db_del(). */ db_del(obj->objnum); object_destroy(obj); obj->objnum = INV_OBJNUM; obj->prev = inactive[ind].prev; obj->next = &inactive[ind]; obj->prev->next = obj->next->prev = obj; } else { /* Install at head of inactive chain. */ obj->prev = &inactive[ind]; obj->next = inactive[ind].next; obj->prev->next = obj->next->prev = obj; #if DEBUG_CACHE _icounter++; #endif } } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Effects: Returns nonzero if an object exists with the given objnum. // */ Int cache_check(Long objnum) { Int ind = objnum % cache_width; Obj *obj; if (objnum < 0) return 0; /* Search active chain. */ for (obj = active[ind].next; obj != &active[ind]; obj = obj->next) { if (obj->objnum == objnum) return 1; } /* Search inactive chain. */ for (obj = inactive[ind].next; obj != &inactive[ind]; obj = obj->next) { if (obj->objnum == objnum) return 1; } /* Check database on disk. */ return db_check(objnum); } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Modifies: Database files. // Effects: Writes out all objects in the cache which are marked dirty. // */ void cache_sync(void) { Int i; Obj *obj; /* Traverse all the active and inactive chains. */ for (i = 0; i < cache_width; i++) { /* Check active chain. */ for (obj = active[i].next; obj != &active[i]; obj = obj->next) { if (obj->dirty) { if (!db_put(obj, obj->objnum)) panic("Could not store an object."); obj->dirty = 0; } } /* Check inactive chain. */ for (obj = inactive[i].next; obj != &inactive[i]; obj = obj->next) { if (obj->objnum != INV_OBJNUM && obj->dirty) { if (!db_put(obj, obj->objnum)) panic("Could not store an object."); obj->dirty = 0; } } } db_flush(); } /* // ---------------------------------------------------------------------- */ Obj *cache_first(void) { Long objnum; cache_sync(); objnum = lookup_first_objnum(); if (objnum == INV_OBJNUM) return NULL; return cache_retrieve(objnum); } /* // ---------------------------------------------------------------------- */ Obj *cache_next(void) { Long objnum; objnum = lookup_next_objnum(); if (objnum == INV_OBJNUM) return NULL; return cache_retrieve(objnum); } /* // ---------------------------------------------------------------------- // // Called during main loop to verify that no objects are active, // or if they are, it is only because they are paused or suspended. // */ void cache_sanity_check(void) { #if DISABLED /* need to do some more work here */ Int i; Obj * obj; VMState * task; /* using labels was the best way I could come up with, I'm sorry... */ for (i = 0; i < cache_width; i++) { for (obj = active[i].next; obj != &active[i]; obj = obj->next) { /* check suspended tasks */ for (task = tasks; task != NULL; task = task->next) { if (task->cur_frame->object->objnum == obj->objnum) goto end; } /* check paused tasks */ for (task = paused; task != NULL; task = task->next) { if (task->cur_frame->object->objnum == obj->objnum) goto end; } /* ack, panic */ panic("Active object #%d at start of main loop.", (Int) obj->objnum); /* label both for loops can jump to, skipping the panic */ end: } } #endif #if 0 if (active[i].next != &active[i]) panic("Active objects at start of main loop."); #endif } /* // ---------------------------------------------------------------------- // // Called during main loop to clean inactive objects from the cache // */ #ifdef CLEAN_CACHE void cache_cleanup(void) { Obj * obj; Int i; for (i = 0; i < cache_width; i++) { for (obj = inactive[i].next; obj != &inactive[i]; obj = obj->next) { obj->ucounter >>= 1; if (obj->ucounter > 0) continue; if (obj->objnum != INV_OBJNUM && obj->dirty) { if (!db_put(obj, obj->objnum)) panic("Could not store an object."); obj->dirty = 0; } if(obj->objnum != INV_OBJNUM) { #if DEBUG_CACHE _icounter--; fprintf(errfile,"<%d\n",_icounter); #endif object_free(obj); obj->objnum = INV_OBJNUM; continue; } } } } #endif #undef _cache_