/****** gbucket.c GameSpy Stats/Tracking SDK Copyright 1999-2007 GameSpy Industries, Inc devsupport@gamespy.com ******/ /******** INCLUDES ********/ #include "../common/gsCommon.h" #include "gbucket.h" #include "../hashtable.h" #include #ifdef __cplusplus extern "C" { #endif /******** TYPEDEFS ********/ struct bucketset_s { HashTable buckets; }; typedef struct bucket_s { char *name; BucketType type; int nvals; //for averaging union { int ival; double fval; char *sval; } vals; } bucket_t; typedef struct dumpdata_s { char *data; unsigned int maxlen; unsigned int len; } dumpdata_t; /******** PROTOTYPES ********/ static void DumpMap(void *elem, void *clientData); static void BucketFree(void *elem); static int BucketCompare(const void *entry1, const void *entry2); static int BucketHash(const void *elem, int numbuckets); static void *DoSet(bucket_t *pbucket, void *value); static void *DoGet(bucket_t *pbucket); static bucket_t *DoFind(bucketset_t set, char *name); /******** VARS ********/ bucketset_t g_buckets; /****************************************************************************/ /* PUBLIC FUNCTIONS */ /****************************************************************************/ bucketset_t NewBucketSet(void) { bucketset_t set; set = (bucketset_t)gsimalloc(sizeof (struct bucketset_s)); assert(set); set->buckets = TableNew(sizeof(bucket_t),32,BucketHash, BucketCompare, BucketFree); g_buckets = set; return set; } void FreeBucketSet(bucketset_t set) { assert(set); assert(set->buckets); TableFree(set->buckets); gsifree(set); } char *DumpBucketSet(bucketset_t set) { dumpdata_t data; if (set == NULL) set = g_buckets; assert(set); data.data = (char *)gsimalloc(128); //alloc an initial buffer data.data[0] = 0; data.len = 0; data.maxlen = 128; TableMap(set->buckets,DumpMap, &data); return data.data; } void *BucketNew(bucketset_t set, char *name, BucketType type, void *initialvalue) { bucket_t bucket; if (set == NULL) set = g_buckets; assert(set); bucket.name = goastrdup(name); bucket.type = type; bucket.vals.sval = NULL; bucket.nvals = 1; DoSet(&bucket, initialvalue); TableEnter(set->buckets,&bucket); return DoGet(DoFind(set, name)); } void *BucketSet(bucketset_t set, char *name,void *value) { bucket_t *pbucket = DoFind(set, name); if (!pbucket) return NULL; pbucket->nvals = 0; return DoSet(pbucket,value); } void *BucketGet(bucketset_t set, char *name) { return DoGet(DoFind(set,name)); } void *BucketAdd(bucketset_t set, char *name, void *value) { bucket_t *pbucket = DoFind(set, name); if (!pbucket) return NULL; if (pbucket->type == bt_int) return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) + (*(int *)value))); if (pbucket->type == bt_float) return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) + (*(double *)value))); //else, string -- just concat return BucketConcat(set, name, value); } void *BucketSub(bucketset_t set, char *name, void *value) { bucket_t *pbucket = DoFind(set, name); if (!pbucket) return NULL; if (pbucket->type == bt_int) return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) - (*(int *)value))); if (pbucket->type == bt_float) return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) - (*(double *)value))); //else, string -- just ignore return DoGet(pbucket); } void *BucketMult(bucketset_t set, char *name, void *value) { bucket_t *pbucket = DoFind(set, name); if (!pbucket) return NULL; if (pbucket->type == bt_int) return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) * (*(int *)value))); if (pbucket->type == bt_float) return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) * (*(double *)value))); //else, string -- just ignore return DoGet(pbucket); } void *BucketDiv(bucketset_t set, char *name, void *value) { bucket_t *pbucket = DoFind(set, name); if (!pbucket) return NULL; if (pbucket->type == bt_int) return DoSet(pbucket, bint( (*(int *)DoGet(pbucket)) / (*(int *)value))); if (pbucket->type == bt_float) return DoSet(pbucket, bfloat( (*(double *)DoGet(pbucket)) / (*(double *)value))); //else, string -- just ignore return DoGet(pbucket); } void *BucketConcat(bucketset_t set, char *name, void *value) { bucket_t *pbucket = DoFind(set, name); char *temp, *s; if (!pbucket) return NULL; assert(pbucket->type == bt_string); s = DoGet(pbucket); temp = (char *)gsimalloc(strlen(s) + strlen(value) + 1); strcpy(temp,s); strcat(temp, value); DoSet(pbucket, temp); gsifree(temp); return DoGet(pbucket); } #define AVG(cur, new, num) (((cur * num) + new) / (++num)) void *BucketAvg(bucketset_t set, char *name, void *value) { bucket_t *pbucket = DoFind(set, name); if (!pbucket) return NULL; if (pbucket->type == bt_int) return DoSet(pbucket, bint( AVG((*(int *)DoGet(pbucket)), (*(int *)value), pbucket->nvals))); if (pbucket->type == bt_float) return DoSet(pbucket, bfloat( AVG((*(double *)DoGet(pbucket)), (*(double *)value), pbucket->nvals))); //else, string -- just ignore return DoGet(pbucket); } /* Note: these are NOT thread safe! */ void *bint(int i) { static int j; j=i; return &j; } void *bfloat(double f) { static double g; g=f; return &g; } /*********** * UTILITY FUNCTIONS **********/ static void DumpMap(void *elem, void *clientData) { bucket_t *bucket = (bucket_t *)elem; dumpdata_t *data = (dumpdata_t *)clientData; unsigned int minlen; //find out if we need to resize! minlen = strlen(bucket->name) + 3; if (bucket->type == bt_int || bucket->type == bt_float) minlen += data->len + 16; else if (bucket->type == bt_string) minlen += data->len + strlen(bucket->vals.sval); if (data->maxlen <= minlen) { if (data->maxlen == 0) data->maxlen = minlen * 2; else data->maxlen *= 2; data->data = gsirealloc(data->data, data->maxlen); } switch (bucket->type) { case bt_int: data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%d",bucket->name,bucket->vals.ival); break; case bt_float: data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%f",bucket->name,bucket->vals.fval); break; case bt_string: data->len += (unsigned int)sprintf(data->data + data->len,"\\%s\\%s",bucket->name,bucket->vals.sval); break; } } static char *stripchars(char *s) { char *p = s; while (*s) { if (*s == '\\') *s = '/'; s++; } return p; } static void *DoSet(bucket_t *pbucket, void *value) { if (pbucket->type == bt_int) pbucket->vals.ival = *(int*)value; else if (pbucket->type == bt_float) pbucket->vals.fval = *(double *)value; else if (pbucket->type == bt_string) { if (pbucket->vals.sval != NULL) gsifree(pbucket->vals.sval); pbucket->vals.sval = (value == NULL ? NULL : stripchars(goastrdup((char *)value))); } return DoGet(pbucket); } static void *DoGet(bucket_t *pbucket) { if (!pbucket) return NULL; if (pbucket->type == bt_string) return pbucket->vals.sval; else //since it's a union, we can return any member return &pbucket->vals.sval; } static bucket_t *DoFind(bucketset_t set, char *name) { bucket_t tbucket; if (set == NULL) set = g_buckets; assert(set); tbucket.name = name; return (bucket_t *)TableLookup(set->buckets,&tbucket); } /* NonTermHash * ---------- * The hash code is computed using a method called "linear congruence." * This hash function has the additional feature of being case-insensitive, */ #define MULTIPLIER -1664117991 static int BucketHash(const void *elem, int numbuckets) { unsigned int i; unsigned int len; unsigned int hashcode = 0; char *s = ((bucket_t *)elem)->name; len = strlen(s); for (i = 0; i < len ; i++) { hashcode = (unsigned int)((int)hashcode * MULTIPLIER + tolower(s[i])); } return (int)(hashcode % numbuckets); } /* CaseInsensitiveCompare * ---------------------- * Comparison function passed to qsort to sort an array of * strings in alphabetical order. It uses strcasecmp which is * identical to strcmp, except that it doesn't consider case of the * characters when comparing them, thus it sorts case-insensitively. */ static int CaseInsensitiveCompare(const void *entry1, const void *entry2) { return strcasecmp(*(char **)entry1,*(char **)entry2); } /* keyval * Compares two buckets (case insensative) */ static int BucketCompare(const void *entry1, const void *entry2) { return CaseInsensitiveCompare(&((bucket_t *)entry1)->name, &((bucket_t *)entry2)->name); } /* KeyValFree * Frees the memory INSIDE a Bucket structure */ static void BucketFree(void *elem) { gsifree(((bucket_t *)elem)->name); if (((bucket_t *)elem)->type == bt_string && ((bucket_t *)elem)->vals.sval != NULL) gsifree(((bucket_t *)elem)->vals.sval); } #ifdef __cplusplus } #endif