//*****************************************************************************
//
// bitvector.c
//
// One of the questions that came up when designing NakedMud is, how do we
// efficiently implement bitvectors while maintaining the modularity of the mud?
// For those unfamiliar with the purposes of a bitvector, let me elaborate:
// there are many instances in which we may want to know about the presence or
// absence of a property (e.g. is the room dark? is the character blind?). We
// could easily have a variable for each of these properties in the relevant
// datastructures, but this is often quite wasteful on memory. What would be
// really nice is if we could compress the space that it took to store one
// bit of information (in C, the smallest basic datatype is 8 bits ... i.e. we
// are wasting 7 bits to store a piece of information that be represented as
// on/off). In a nutshell, this is the purpose of a bitvector (there are more
// reasons why bitvectors are attractive, but this is perhaps the most important
// one in the context of muds).
//
// For anyone who has ever worked on a DIKU-derivative, they know that
// bits in a bitvector are typically declared on after another like this:
// #define BIT_ONE (1 << 0)
// #define BIT_TWO (1 << 1)
// #define BIT_THREE (1 << 2)
// ...
//
// Of course, this is hugely unattractive for us: If we had a module that wanted
// to add a new bit, we surely would not want to add it to some global header
// file; we would want to keep the definition within the module headers. And,
// most certainly we cannot do this because we have to assocciate a position
// with each bit name, and every other module has to know that position so that
// noone else adds a new bit with the same position. This module is an attempt
// to address this problem, allowing modules to use bits, keep their
// declarations in some place local to the module, and make sure there are no
// conflicting definitions. This also ensures that bits are persistent for all
// rooms, objects, and characters (ie. they save if we crash, or the player
// logs off, or whatnot).
//
//*****************************************************************************
#include "mud.h"
#include "utils.h"
#include "bitvector.h"
//*****************************************************************************
// local functions, datastructures, and defines
//*****************************************************************************
// a table of mappings between bitvector names, and the
// data assocciated with them (i.e. bit:value mappings)
HASHTABLE *bitvector_table = NULL;
typedef struct bitvector_data {
HASHTABLE *bitmap; // a mapping from bit name to bit number
char *name; // which bitvector is this?
} BITVECTOR_DATA;
struct bitvector {
BITVECTOR_DATA *data; // the data corresponding to this bitvector
char *bits; // the bits we have set/unset
};
BITVECTOR_DATA *newBitvectorData(const char *name) {
BITVECTOR_DATA *data = malloc(sizeof(BITVECTOR_DATA));
data->bitmap = newHashtable();
data->name = strdup(name);
return data;
}
//*****************************************************************************
// implementation of bitvector.h
//*****************************************************************************
void init_bitvectors() {
// create the bitvector table
bitvector_table = newHashtable();
// and also create some of the basic bitvectors and
// bits that come stock and are required for the core release
bitvectorCreate("char_prfs");
bitvectorCreate("obj_bits");
bitvectorAddBit("obj_bits", "notake");
bitvectorCreate("room_bits");
bitvectorCreate("user_groups");
bitvectorAddBit("user_groups", "admin");
bitvectorAddBit("user_groups", "scripter");
bitvectorAddBit("user_groups", "builder");
bitvectorAddBit("user_groups", "player");
bitvectorAddBit("user_groups", "playtester");
}
void bitvectorAddBit(const char *name, const char *bit) {
BITVECTOR_DATA *data = hashGet(bitvector_table, name);
if(data != NULL)
hashPut(data->bitmap, bit, (void *)(hashSize(data->bitmap) + 1));
}
void bitvectorCreate(const char *name) {
if(!hashGet(bitvector_table, name))
hashPut(bitvector_table, name, newBitvectorData(name));
}
BITVECTOR *newBitvector() {
BITVECTOR *v = calloc(1, sizeof(BITVECTOR));
return v;
}
BITVECTOR *bitvectorInstanceOf(const char *name) {
BITVECTOR_DATA *data = hashGet(bitvector_table, name);
BITVECTOR *vector = NULL;
if(data != NULL) {
int vector_len = hashSize(data->bitmap)/8 + 1;
vector = newBitvector();
vector->data = data;
vector->bits = calloc(vector_len, sizeof(char));
}
return vector;
}
void deleteBitvector(BITVECTOR *v) {
if(v->bits) free(v->bits);
free(v);
}
void bitvectorCopyTo(BITVECTOR *from, BITVECTOR *to) {
int bit_len = 1 + hashSize(from->data->bitmap)/8;
to->data = from->data;
if(to->bits) free(to->bits);
to->bits = malloc(sizeof(char) * bit_len);
to->bits = memcpy(to->bits, from->bits, bit_len);
}
BITVECTOR *bitvectorCopy(BITVECTOR *v) {
BITVECTOR *newvector = bitvectorInstanceOf(v->data->name);
bitvectorCopyTo(v, newvector);
return newvector;
}
bool bitIsSet(BITVECTOR *v, const char *bit) {
LIST *bits = parse_keywords(bit);
char *one_bit = NULL;
bool found = FALSE;
// check for one
while( !found && (one_bit = listPop(bits)) != NULL) {
found = bitIsOneSet(v, one_bit);
free(one_bit);
}
// clean up our mess
deleteListWith(bits, free);
return found;
}
bool bitIsAllSet(BITVECTOR *v, const char *bit) {
LIST *bits = parse_keywords(bit);
char *one_bit = NULL;
bool found = TRUE;
// check for each one
while( found && (one_bit = listPop(bits)) != NULL) {
found = bitIsOneSet(v, one_bit);
free(one_bit);
}
// clean up our mess
deleteListWith(bits, free);
return found;
}
bool bitIsOneSet(BITVECTOR *v, const char *bit) {
int val = (int)hashGet(v->data->bitmap, bit);
return IS_SET(v->bits[val/8], (1 << (val % 8)));
}
void bitSet(BITVECTOR *v, const char *name) {
LIST *bits = parse_keywords(name);
char *one_bit = NULL;
// set each one
while( (one_bit = listPop(bits)) != NULL) {
int val = (int)hashGet(v->data->bitmap, one_bit);
free(one_bit);
// 0 is a filler meaning 'this is not an actual name for a bit'
if(val == 0) continue;
SET_BIT(v->bits[val/8], (1 << (val % 8)));
}
// garbage collection
deleteListWith(bits, free);
}
void bitClear(BITVECTOR *v) {
int i;
for(i = 1; i <= hashSize(v->data->bitmap); i++)
REMOVE_BIT(v->bits[i/8], (1 << (i % 8)));
}
void bitRemove(BITVECTOR *v, const char *name) {
LIST *bits = parse_keywords(name);
char *one_bit = NULL;
// remove each one
while( (one_bit = listPop(bits)) != NULL) {
int val = (int)hashGet(v->data->bitmap, one_bit);
REMOVE_BIT(v->bits[val/8], (1 << (val % 8)));
free(one_bit);
}
// garbage collection
deleteListWith(bits, free);
}
void bitToggle(BITVECTOR *v, const char *name) {
LIST *bits = parse_keywords(name);
char *one_bit = NULL;
// toggle each one
while( (one_bit = listPop(bits)) != NULL) {
int val = (int)hashGet(v->data->bitmap, one_bit);
free(one_bit);
// 0 is a filler meaning 'this is not an actual name for a bit'
if(val == 0) continue;
TOGGLE_BIT(v->bits[val/8], (1 << (val % 8)));
}
// garbage collection
deleteListWith(bits, free);
}
const char *bitvectorGetBits(BITVECTOR *v) {
static char bits[MAX_BUFFER];
HASH_ITERATOR *hash_i = newHashIterator(v->data->bitmap);
const char *key = NULL;
void *val = NULL;
int bit_i = 0;
*bits = '\0';
// add each set bit to our list to store
ITERATE_HASH(key, val, hash_i) {
if(bitIsOneSet(v, key)) {
bit_i += snprintf(bits+bit_i, MAX_BUFFER-bit_i, "%s%s",
(bit_i == 0 ? "" : ", "), key);
}
}
deleteHashIterator(hash_i);
return bits;
}
int bitvectorSize(BITVECTOR *v) {
return hashSize(v->data->bitmap);
}
LIST *bitvectorListBits(BITVECTOR *v) {
return hashCollect(v->data->bitmap);
}