tmuck2.4/
tmuck2.4/admin/scripts/
tmuck2.4/docs/
tmuck2.4/minimal-db/
tmuck2.4/minimal-db/data/
tmuck2.4/minimal-db/logs/
tmuck2.4/minimal-db/muf/
tmuck2.4/old/
tmuck2.4/src/
tmuck2.4/src/compile/
tmuck2.4/src/editor/
tmuck2.4/src/game/
tmuck2.4/src/interface/
tmuck2.4/src/scripts/
tmuck2.4/src/utilprogs/
/* Copyright (c) 1992 by David Moore.  All rights reserved. */
/* hashtab.c,v 2.7 1997/08/10 04:32:42 dmoore Exp */
#include "config.h"

#include <stdlib.h>		/* NULL */

#include "db.h"
#include "hashtab.h"
#include "externs.h"


struct hash_tab {
    const char *id;		/* ''Name'' of hashtable, for stat display. */

    compare_function cmpfn;	/* Function to compare two entries. */
    key_function     keyfn;	/* Function to generate a key for an entry. */
    delete_function  delfn;	/* Function called when freeing an entry. */

    unsigned long size;		/* Size of the hashtable. */
    struct hash_entry **table;	/* Actual hashtable array. */

    unsigned long walk_key;	/* Current bucket when walking hash table. */
    struct hash_entry *walk_loc; /* Current entry when walking hash table. */

    unsigned long entries;	/* Total number of entries. */
    unsigned long max_entries;	/* Max # of entries ever stored. */

    unsigned long hits;		/* Total hits. */
    unsigned long hits_visited;	/* Total number of entries visited on hits. */
    
    unsigned long misses;	/* total misses. */
    unsigned long misses_visited; /* Total number of entried visited misses. */

    unsigned long total_insert;	/* Total number of entries ever inserted. */

    struct hash_tab *next;	/* Keep all hashtables in linked list. */
    struct hash_tab **prev;
};


struct hash_entry {
    struct hash_entry *next;	/* Linked list buckets for collisions. */
    const void	      *data;	/* Actual entry we are storing. */
};


/* Linked list of all hash tables we are managing. */
static struct hash_tab *hash_table_list = NULL;

/* Linked list of reusable hash_entry structures. */
static struct hash_entry *available_hash_entries = NULL;
static long total_entries = 0;


/* These are the generic hashtable routines.  They _require_ that the first
   member of the entry be a string, and that string is taken as the key.
   The generic delete function simply free's it's argument. */
int compare_generic_hash(const void *entry1, const void *entry2)
{
    return muck_stricmp(*(const char **) entry1, *(const char **) entry2);
}


unsigned long make_key_generic_hash(const void *entry)
{
    return default_hash(*(const char **) entry);
}


void delete_generic_hash(void *entry)
{
    FREE(entry);
}


static unsigned long compute_hash(hash_tab *table, const void *data)
{
    return (table->keyfn(data) % table->size);
}


static void generate_hash_entries(long count)
{
    struct hash_entry *hunk;
    int num_4k;
    int i, j;
 
    /* Try allocating them in 4K sets.  The -4 is for some mallocs
       which want to stick some overhead in the space.  This keeps
       things on a single page (hopefully). */
    num_4k = (4096 - 4) / sizeof(struct hash_entry);
 
    count = (count / num_4k) + 1;
 
    for (i = 0; i < count; i++) {
        MALLOC(hunk, struct hash_entry, num_4k);
        for (j = 0; j < num_4k; j++) {
            hunk->next = available_hash_entries;
            available_hash_entries = hunk;
            hunk++;
        }
    }
}


static struct hash_entry *make_new_hash_entry(void)
{
    struct hash_entry *result;

    if (!available_hash_entries) {
	generate_hash_entries(1);
    }
    result = available_hash_entries;
    available_hash_entries = result->next;

    total_entries++;

    return result;
}


static void free_hash_entry(struct hash_entry *entry)
{
    entry->next = available_hash_entries;
    available_hash_entries = entry;

    total_entries--;
}



static struct hash_entry *find_hash_internal(hash_tab *table, const unsigned long hashval, const void *match)
{
    struct hash_entry *curr;
    struct hash_entry **prev;
    compare_function cmpfn;
    int visits = 0;
    
    if (!table) return NULL;

    cmpfn = table->cmpfn;

    prev = &(table->table[hashval]);
    for (curr = *prev; curr; prev = &(curr->next), curr = curr->next) {
	visits++;
	if (cmpfn(match, curr->data) == 0) {
	    /* Move this entry to the head of the bucket. */
	    *prev = curr->next;
	    curr->next = table->table[hashval];
	    table->table[hashval] = curr;

	    /* Mark this hit for statistics. */
	    table->hits_visited += visits;
	    table->hits++;

	    return curr;
	}
    }

    table->misses_visited += visits + 1;
    table->misses++;

    return NULL;                  /* not found */
}


const void *find_hash_entry(hash_tab *table, const void *match)
{
    struct hash_entry *entry;

    entry = find_hash_internal(table, compute_hash(table, match), match);

    return (entry) ? (entry->data) : NULL;
}


void add_hash_entry(hash_tab *table, const void *data)
{
    struct hash_entry *entry;
    unsigned long hashval;

    if (!table) return;
    
    hashval = compute_hash(table, data);
    
    entry = find_hash_internal(table, hashval, data);

    if (entry) {
	/* Free old entry. */
	table->delfn((void *) entry->data);
    } else {
	entry = make_new_hash_entry();
	entry->next = table->table[hashval];
	table->table[hashval] = entry;

	table->entries++;
	if (table->entries > table->max_entries)
	    table->max_entries = table->entries;
	table->total_insert++;
    }
    
    /* Either we just created it, or we already found a valid one. */
    entry->data = data;
}


void clear_hash_entry(hash_tab *table, const void *data)
{
    struct hash_entry *entry;
    unsigned long hashval;

    if (!table) return;

    hashval = compute_hash(table, data);
    
    entry = find_hash_internal(table, hashval, data);

    if (!entry) return;

    /* Found one.  It's now at front of bucket. */
    table->table[hashval] = entry->next;

    table->entries--;

    table->delfn((void *) entry->data);
    free_hash_entry(entry);
}


void clear_hash_table(hash_tab *table)
{
    struct hash_entry **temp;	/* In the table array. */
    struct hash_entry *entry, *next;
    unsigned long i, size;
    
    if (!table) return;

    size = table->size;
    temp = table->table;
    for (i = 0; i < size; i++, temp++) {
	for (entry = *temp; entry; entry = next) {
	    next = entry->next;	/* Save it, since we free next. */
	    table->delfn((void *) entry->data);
	    free_hash_entry(entry);
	}
    }

    /* Remove from our linked list of tables. */
    *(table->prev) = table->next;
    if (table->next)
	table->next->prev = table->prev;

    FREE(table->table);
    FREE(table);
}


hash_tab *init_hash_table(const char *id, compare_function cmpfn, key_function keyfn, delete_function delfn, unsigned long size)
{
    hash_tab *table;
    struct hash_entry **temp;
    unsigned long i;

    /* Malloc everything. */
    MALLOC(table, hash_tab, 1);
    MALLOC(table->table, struct hash_entry *, size);

    /* Clear out the actual table. */
    temp = table->table;
    for (i = 0; i < size; i++, temp++)
	*temp = NULL;

    /* Fill in everything. */
    table->id = id;
    table->cmpfn = cmpfn;
    table->keyfn = keyfn;
    table->delfn = delfn;
    table->size = size;
    table->entries = 0;
    table->max_entries = 0;
    table->hits = 0;
    table->hits_visited = 0;
    table->misses = 0;
    table->misses_visited = 0;
    table->total_insert = 0;

    /* Stick in our linked list of hash tables. */
    if (hash_table_list)
	hash_table_list->prev = &(table->next);
    table->next = hash_table_list;
    table->prev = &hash_table_list;
    hash_table_list = table;
    
    return table;
}


/* Don't do _anything_ else to this hash table when walking it.  This
   includes looking for other things, etc. */
void init_hash_walk(hash_tab *table)
{
    table->walk_key = 0;
    table->walk_loc = NULL;
}


const void *next_hash_walk(hash_tab *table)
{
    struct hash_entry **temp;
    const void *result;
    unsigned long i, size;

    if (table->walk_loc) {
	result = table->walk_loc->data;
	table->walk_loc = table->walk_loc->next;
	return result;
    }

    size = table->size;
    temp = table->table + table->walk_key;

    for (i = table->walk_key; i < size; i++, temp++) {
	if (*temp) break;
    }

    if (i == table->size) return NULL;

    result = (*temp)->data;
    table->walk_loc = (*temp)->next;
    table->walk_key = i + 1;

    return result;
}


void dump_hashtab_stats(hash_tab *table)
{
    int doing_all = 0;
    long total_ratio, hit_ratio, miss_ratio;
    double t;

    if (table == NULL) {
	/* Dump information on all of the hashtables. */
	doing_all = 1;
	table = hash_table_list;
    }

    for (; table; table = table->next) {
	if (table->hits_visited + table->misses_visited) {
	    t = 1000 * (((double) table->hits_visited + (double) table->misses_visited) / ((double) table->hits + (double) table->misses));
	    total_ratio = t;
	} else total_ratio = 0;
	if (table->hits_visited) {
	    t = 1000 * ((double) table->hits_visited / (double) table->hits);
	    hit_ratio = t;
	} else hit_ratio = 0;
	if (table->misses_visited) {
	    t = 1000 * ((double) table->misses_visited / (double) table->misses);
	    miss_ratio = t;
	} else miss_ratio = 0;

	log_info("");
	log_info("--- Statistics for: %s",
		 table->id);
	log_info("Table size: %i.  Entries: %i.",
		 table->size, table->entries);
	log_info("Total accesses: %i.",
		 table->hits + table->misses);
	log_info("Hit accesses: %i.  Miss accesses: %i.",
		 table->hits, table->misses);
	log_info("Bucket probes requiring comparison: %i",
		 table->hits_visited + table->misses_visited);
	log_info("Hit compares: %i.  Miss compares: %i.",
		 table->hits_visited, table->misses_visited);
	log_info("Total access ratio: %i (/1000)",
		 total_ratio);
	log_info("Hit ratio: %i.  Miss ratio: %i. (/1000)",
		 hit_ratio, miss_ratio); 
	log_info("Max entries stored: %i.",
		 table->max_entries);

	if (!doing_all) break;
    }
}


long total_hash_entries(void)
{
    return total_entries;
}


void init_hash_entries(const long count)
{
    generate_hash_entries(count);
}