/*
	Copyright (C) 1991, Marcus J. Ranum. All rights reserved.
*/
#ifndef	lint
static	char	RCSid[] = "$Header: /homes/hawkwind/u0/cks/src/MUD/umud/DB/RCS/dbchk_cache.c,v 1.0 91/08/15 23:11:45 cks Exp Locker: cks $";
#endif
/* configure all options BEFORE including system stuff. */
#include	"config.h"
#ifdef	NOSYSTYPES_H
#include	<types.h>
#else
#include	<sys/types.h>
#endif
#include	<stdio.h>
#include	<assert.h>
#include	"mud.h"
/*
This is by far the most complex and kinky code in UnterMUD. You should
never need to mess with anything in here - if you value your sanity.
*/
typedef	struct	cache	{
	char	*onm;
	Obj	*op;
	int	flg;
	struct	cache	*nxt;
	struct	cache	*prv;
} Cache;
/* flag states */
#define	C_NOFLG	00
#define	C_DIRTY	01
#define	C_DEAD	02
typedef	struct	{
	Cache	*ahead;
	Cache	*ohead;
	Cache	*atail;
	Cache	*otail;
} CacheLst;
/* initial settings for cache sizes */
static	int	cwidth = CACHE_WIDTH;
static	int	cdepth = CACHE_DEPTH;
/* ntbfw - main cache pointer and list of things to kill off */
static	CacheLst	*sys_c;
static	int	cache_initted = 0;
/* cache stats gathering stuff. you don't like it? comment it out */
static	time_t	cs_ltime;
static	int	cs_writes = 0;	/* total writes */
static	int	cs_reads = 0;	/* total reads */
static	int	cs_dbreads = 0;	/* total read-throughs */
static	int	cs_dbwrites = 0;	/* total write-throughs */
static	int	cs_dels = 0;		/* total deletes */
static	int	cs_checks = 0;	/* total checks */
static	int	cs_rhits = 0;	/* total reads filled from cache */
static	int	cs_ahits = 0;	/* total reads filled active cache */
static	int	cs_whits = 0;	/* total writes to dirty cache */
static	int	cs_fails = 0;	/* attempts to grab nonexistent */
static	int	cs_resets = 0;	/* total cache resets */
static	int	cs_syncs = 0;	/* total cache syncs */
static	int	cs_objects = 0;	/* total cache size */
int
cache_init()
{
	int	x;
	int	y;
	Cache	*np;
	static char	*ncmsg = "cache_init: cannot allocate cache: ";
	if(cache_initted || sys_c != (CacheLst *)0)
		return(0);
	sys_c = (CacheLst *)malloc((unsigned)cwidth * sizeof(CacheLst));
	if(sys_c == (CacheLst *)0) {
		fprintf(stderr, "dbchk: cache malloc failure\n");
		return(-1);
	}
	for(x = 0; x < cwidth; x++) {
		sys_c[x].ahead = sys_c[x].ohead = (Cache *)0;
		sys_c[x].atail = sys_c[x].otail = (Cache *)0;
		for(y = 0; y < cdepth; y++) {
			np = (Cache *)malloc(sizeof(Cache));
			if(np == (Cache *)0) {
				fprintf(stderr, "%s\n", ncmsg);
				return(-1);
			}
			if((np->nxt = sys_c[x].ohead) != (Cache *)0)
				np->nxt->prv = np;
			else
				sys_c[x].otail = np;
			np->prv = (Cache *)0;
			np->flg = C_NOFLG;
			np->onm = (char *)0;
			np->op = (Obj *)0;
			sys_c[x].ohead = np;
			cs_objects++;
		}
	}
	/* mark caching system live */
	cache_initted++;
	time(&cs_ltime);
	return(0);
}
void
cache_reset()
{
	int	x;
	if(!cache_initted)
		return;
	/* unchain and rechain each active list at head of old chain */
	for(x = 0; x < cwidth; x++) {
		if(sys_c[x].ahead != (Cache *)0) {
			assert(sys_c[x].otail || !sys_c[x].ohead);
			sys_c[x].atail->nxt = sys_c[x].ohead;
			if(sys_c[x].ohead != (Cache *)0)
				sys_c[x].ohead->prv = sys_c[x].atail;
			sys_c[x].ohead = sys_c[x].ahead;
			if (sys_c[x].otail == (Cache *)0)
				sys_c[x].otail = sys_c[x].atail;
			sys_c[x].ahead = (Cache *)0;
			sys_c[x].atail = (Cache *)0;
		}
	}
	cs_resets++;
}
/*
search the cache for an object, and if it is not found, thaw it.
this code is probably a little bigger than it needs be because I
had fun and unrolled all the pointer juggling inline.
*/
Obj	*
cache_get(nam)
char	*nam;
{
	Cache		*cp;
	int		hv = 0;
	Obj		*ret;
	static char	*nmmesg = "cache_get: cannot allocate memory ";
	/* firewall */
	if(nam == (char *)0 || *nam == '\0' || !cache_initted) {
#ifdef	CACHE_VERBOSE
		logf("cache_get: NULL object name - programmer error\n",(char *)0);
#endif
		abort();
		return((Obj *)0);
	}
#ifdef	CACHE_DEBUG
	printf("get %s\n",nam);
#endif
	/* is some fool trying to get "nowhere"? */
	if(!strcmp(nam,"nowhere"))
		return((Obj *)0);
	cs_reads++;
	hv = objid_hash(nam,cwidth);
	/* search active chain first */
	for(cp = sys_c[hv].ahead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam)) {
			cs_rhits++;
			cs_ahits++;
#ifdef	CACHE_DEBUG
			printf("return %s active cache %d\n",cp->onm,cp->op);
#endif
			return(cp->op);
		}
	}
	/* search in-active chain second */
	for(cp = sys_c[hv].ohead; cp != (Cache *)0; cp = cp->nxt) {
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam)) {
			/* dechain from in-active chain */
			if(cp->nxt == (Cache *)0)
				sys_c[hv].otail = cp->prv;
			else
				cp->nxt->prv = cp->prv;
			if(cp->prv == (Cache *)0)
				sys_c[hv].ohead = cp->nxt;
			else
				cp->prv->nxt = cp->nxt;
			/* insert at head of active chain */
			cp->nxt = sys_c[hv].ahead;
			if(sys_c[hv].ahead == (Cache *)0)
				sys_c[hv].atail = cp;
			cp->prv = (Cache *)0;
			if(cp->nxt != (Cache *)0)
				cp->nxt->prv = cp;
			sys_c[hv].ahead = cp;
			/* done */
			cs_rhits++;
#ifdef	CACHE_DEBUG
			printf("return %s old cache %d\n",cp->onm,cp->op);
#endif
			return(cp->op);
		}
	}
	/* DARN IT - at this point we have a certified, type-A cache miss */
	/* thaw the object from wherever. */
	if((ret = DB_GET(nam)) == (Obj *)0) {
		cs_fails++;
#ifdef	CACHE_DEBUG
		printf("%s not in db\n",nam);
#endif
		return((Obj *)0);
	}
	cs_dbreads++;
	/* if there are no old cache object holders left, allocate one */
	if(sys_c[hv].otail == (Cache *)0) {
		int	xnm;
		assert(sys_c[hv].ohead == (Cache *)0);
		if((cp = (Cache *)malloc(sizeof(Cache))) == (Cache *)0) {
			fprintf(stderr, "dbchk: no memory for new cache struct\n");
			abort();
		}
		xnm = strlen(nam) + 1;
		if((cp->onm = (char *)malloc((unsigned)xnm)) == (char *)0) {
			fprintf(stderr, "dbchk: no memory for new cache name\n");
			abort();
		}
		
		/* sanity chex */
		if(xnm > MAXOID)
			fprintf(stderr, "dbchk: cache_get: name %s too long!\n",
				nam);
		(void)strcpy(cp->onm,nam);
		cp->flg = C_NOFLG;
		/* linkit at head of active chain */
		cp->nxt = sys_c[hv].ahead;
		if(sys_c[hv].ahead == (Cache *)0)
			sys_c[hv].atail = cp;
		cp->prv = (Cache *)0;
		if(cp->nxt != (Cache *)0)
			cp->nxt->prv = cp;
		sys_c[hv].ahead = cp;
		cs_objects++;
#ifdef	CACHE_DEBUG
		printf("return %s loaded into cache %d\n",cp->onm,cp->op);
#endif
		return(cp->op = ret);
	}
	/* unlink old cache chain tail */
	cp = sys_c[hv].otail;
	if(cp->prv != (Cache *)0) {
		sys_c[hv].otail = cp->prv;
		cp->prv->nxt = cp->nxt;
	} else	/* took last one */
		sys_c[hv].ohead = sys_c[hv].otail = (Cache *)0;
	/* if there is a dirty object still in memory, write it */
	if((cp->flg & C_DIRTY) && cp->onm != (char *)0 && cp->op != (Obj *)0) {
#ifdef	CACHE_DEBUG
		printf("clean %s from cache %d\n",cp->onm,cp->op);
#endif
		fprintf(stderr, "dbchk: dirty object in cache to be flushed.\n");
		abort();
		cs_dbwrites++;
	}
	/* free object's data */
	if(cp->op != (Obj *)0)
		objfree(cp->op);
	cp->op = ret;
	/* free old name */
	if(cp->onm != (char *)0)
		deferfree((mall_t)cp->onm);
	/* if this fails, boy are we in trouble! */
	if((cp->onm = (char *)malloc((unsigned)strlen(nam) + 1)) == (char *)0) {
		fprintf(stderr, "dbchk: death point 1\n");
		abort();
	}
	/* sanity chex */
	if(strlen(nam) + 1 > MAXOID)
		fprintf(stderr, "dbchk: cache_get: name %s too long!\n",
			nam);
	(void)strcpy(cp->onm,nam);
	cp->flg = C_NOFLG;
	/* relink at head of active chain */
	cp->nxt = sys_c[hv].ahead;
	if(sys_c[hv].ahead == (Cache *)0)
		sys_c[hv].atail = cp;
	cp->prv = (Cache *)0;
	if(cp->nxt != (Cache *)0)
		cp->nxt->prv = cp;
	sys_c[hv].ahead = cp;
#ifdef	CACHE_DEBUG
	printf("return %s loaded into cache %d\n",cp->onm,cp->op);
#endif
	return(ret);
}
int
cache_sync()
{
	int	x;
	Cache	*cp;
	cs_syncs++;
	if(!cache_initted)
		return(1);
	for(x = 0; x < cwidth; x++) {
		for(cp = sys_c[x].ahead; cp != (Cache *)0; cp = cp->nxt) {
			if(cp->flg & C_DIRTY) {
				fprintf(stderr, "dbchk: panic: dirty cache\n");
				abort();
			}
		}
		for(cp = sys_c[x].ohead; cp != (Cache *)0; cp = cp->nxt) {
			if(cp->flg & C_DIRTY) {
				fprintf(stderr, "dbchk: panic: dirty cache\n");
				abort();
			}
		}
	}
	return(0);
}
/*
probe the cache and the database (if needed) for the existence of an
object. return nonzero if the object is in cache or database
*/
int
cache_check(nam)
char	*nam;
{
	Cache	*cp;
	int	hv = 0;
	if(nam == (char *)0 || !cache_initted)
		return(0);
	cs_checks++;
	hv = objid_hash(nam,cwidth);
	for(cp = sys_c[hv].ahead; cp != (Cache *)0; cp = cp->nxt)
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam))
			return(1);
	for(cp = sys_c[hv].ohead; cp != (Cache *)0; cp = cp->nxt)
		if(cp->onm != (char *)0 && !(cp->flg & C_DEAD) && !strcmp(cp->onm,nam))
			return(1);
	/* no ? */
	return(DB_CHECK(nam));
}
cache_stats()
{
	printf("cache stats, last %d sec.:\n",time(0) - cs_ltime);
	printf("writes=%d reads=%d\n",cs_writes,cs_reads);
	printf("dbwrites=%d dbreads=%d\n",cs_dbwrites,cs_dbreads);
	printf("read hits=%d active hits=%d\n",cs_rhits,cs_ahits);
	printf("write hits to dirty cache=%d\n",cs_whits);
	printf("deletes=%d checks=%d\n",cs_dels,cs_checks);
	printf("resets=%d syncs=%d objects=%d\n",	cs_resets,cs_syncs,cs_objects);
}