openmohaa/code/gamespy/gstats/gbucket.c
2023-02-04 21:00:01 +01:00

396 lines
8.6 KiB
C

/******
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 <ctype.h>
#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