envy22/
envy22/log/
envy22/player/
envy22/src/Contributions/
/******************************************************************************
 *  SSM v2.2 (shared string manager)                                          *
 *                                                                            *
 *  Copyright(C) 1996 Melvin Smith (Fusion) for EnvyMUD 2.2                   *
 *                                                                            *
 *  Due to alignment differences on 32 bit and 64 bit machines, memory        *
 *  usage is now virtually identical to standard Merc on 32-bit               *
 *  architecture, but slightly larger on 64-bit. Memory usage is still        *
 *  smaller than SSM 2.0 or earlier. The manager now uses short ints for      *
 *  the count and size of chunks, so to increase MAX_STRING you must          *
 *  increase MAX_CHUNKS instead. String have a max reference count of         *
 *  +32766 and max size of CHUNK_SIZE (0xfff0). Fragmentation is also         *
 *  handled more efficiently by marking failed chunks with -1 to temporarily  *
 *  disable them until a defrag_heap() recycles them. This helps when a       *
 *  4 byte chunk is freed low in the heap, so string_dup() doesn't walk       *
 *  the whole heap every time.                                                *
 *                                                                            *
 *  <msmith@falcon.mercer.peachnet.edu>                                       *
 *                                                                            *
 *  ROM2.4 modifications by Tom Adriaenssen (Jan 1996) -- Wreck               *
 *                                                                            *
 *  <tadriaen@zorro.ruca.ua.ac.be>                                            *
 *                                                                            *
 *  Removed ROM 2.4 modifications as Envy doesnt need *fread_string_eol -Kahn *
 *                                                                            *
 *****************************************************************************/

#include <sys/types.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"

#if !defined( ultrix )
#include <memory.h>
#endif

#define intType        short int
#define uintType       unsigned intType
#define intTypeSize  ( sizeof( intType ) )
#define addrType       void * 
#define addrTypeSize ( sizeof( addrType ) )
#define addrSizeMask ( sizeof( addrType ) - 1 )

#if !defined( macintosh )
extern  int     _filbuf         args( (FILE *) );
#endif

typedef struct BE BufEntry;

struct BE
{
    BufEntry *next;
    uintType  size;   /* size of the chunk (regardless of NULL CHAR) */ 
    intType   usage;  /* how many pointers to the string */ 
    char      buf[1]; /* chunk starts here */
};

/*
 * This is for the temporary hashing of strings at bootup to speedup
 * comparison/crunching of the string space. The temp_string_hash will
 * be freed after boot_done() is called.
 */
typedef struct TH TempHash;

struct TH
{
    TempHash *next;
    uintType  len;
    char     *str;
};

TempHash **temp_string_hash; 

/* These are the original Merc vars in db.c */
extern bool  fBootDb;
char         str_empty[1];
char        *string_space;
char        *top_string;
long         nAllocString;
long         sAllocString;
long         nOverFlowString;
long         sOverFlowString;

int          numFree;
bool         Full;

char         *str_dup        ( const char * );
void          free_string    ( char * );
char         *fread_string   ( FILE *, int * );
void          temp_hash_add  ( char * );
char         *temp_hash_find ( const char * );

/*
 * ssm_buf_head points to start of shared space,
 * ssm_buf_free points to next free block
 */ 
BufEntry *ssm_buf_head, *ssm_buf_free;

/* To allocate more memory increase MAX_CHUNKS in merc.h. */
#define CHUNK_SIZE   0xfff0                  /* DO NOT mess with this! */ 
long    MAX_STRING = MAX_CHUNKS * CHUNK_SIZE;
int     HEADER_SIZE;

/*
 * Not sure what is a good value for MAX_FREE 
 * If a dup fails str_dup will not defrag unless the number
 * of numFree >= MAX_FREE. numFree is NOT the current number of free blocks,
 * it is just a counter so defrag doesnt start dragging the game in the
 * case of a lot of failed dups.
 */
#define MAX_FREE     1000

void init_string_space()
{
    BufEntry *walk;
    int i;

    string_space = (char *)malloc( MAX_STRING );
    if( !string_space )
    {
        bug( "[SSM] Cant allocate shared string space.", 0 );
        exit(1);
    }

    top_string   = string_space + MAX_STRING-1;
    ssm_buf_head = (BufEntry *)string_space;
    HEADER_SIZE  = (int)( (char*)&ssm_buf_head->buf[0] - (char*)ssm_buf_head );

    walk = ssm_buf_head;
    for( i = 0; ;i++ )
    {
	walk->usage = 0;
	walk->size  = CHUNK_SIZE - HEADER_SIZE;
	if( i < MAX_CHUNKS - 1 )
	{
	    walk->next = (BufEntry *)( (char*)walk + CHUNK_SIZE );
	    walk       = walk->next;
	    continue;
	}

	walk->next = 0;
	break;
    }

    ssm_buf_free        = ssm_buf_head;
    temp_string_hash    = (TempHash **)calloc( sizeof(TempHash *),
					      MAX_KEY_HASH );
}

int defrag_heap()
{
    /*
     * Walk through the shared heap and merge adjacent free blocks.
     * Free blocks are merged in str_free if free->next is free but
     * if the block preceding free is free, it stays unmerged. I would
     * rather not have the heap as a DOUBLE linked list for 2 reasons...
     *  (1) Extra 4 bytes per struct uses more mem
     *  (2) Speed - don't want to bog down str_ functions with heap management
     * The "orphaned" blocks will eventually be either merged or reused.
     * The str_dup function will call defrag if it cant allocate a buf.
     */

    BufEntry *walk, *last_free, *next;
    int merges   = 0;
    ssm_buf_free = 0;

    for( walk=ssm_buf_head,last_free=0; walk; walk = next )
      {
	next = walk->next;
	if( walk->usage > 0 )
	{
	    /* this block is in use so set last_free to NULL */
	    last_free = 0;
	    continue;
	}
	else if( !last_free )
	{
	    /* OK found a NEW free block, set last_free and move to next */
	    last_free = walk;
	    if( !ssm_buf_free )
	        ssm_buf_free = walk; 

	    continue; 
	}
	else
	{
	    /* previous block free so merge walk into last_free and move on */
	    if( (long)last_free->size + (long)walk->size <= CHUNK_SIZE )
	    {
		merges++;
		last_free->size += walk->size + HEADER_SIZE;
		last_free->next  = walk->next;
		last_free->usage = 0;
	    }
	    else
	        last_free = walk;
	}
    }

    if( merges )
        bug( "[SSM] defrag_heap : made %d block merges.", merges );
    else
        bug( "[SSM] defrag_heap : resulted in 0 merges.", 0 ); 

    /* Start count over again */ 
    numFree = 0;
    return merges;
}


/*
 * Dup a string into shared space. If string exists, the usage count
 * gets incremented and the reference is returned. If the string does
 * not exist in heap, space is allocated and usage is 1.
 * This is a linked list first fit algorithm, so strings can be
 * freed. Upon bootup, there is a seperate hash table constructed in order
 * to do crunching, then the table is destroyed.
 */
char *str_dup( const char *str )
{
    BufEntry *ptr;
    char     *str_new;
    int       len;
    int       rlen;

    if( !str || !*str )
        return &str_empty[0];

    if( str > string_space && str < top_string )
    {
	ptr = (BufEntry *)( str - HEADER_SIZE );
	if( ptr->usage <= 0 )
	{
	    bug( "str_dup : invalid str", 0 );
	    bug( str, 0 );
	}

	ptr->usage++;
	return (char *)str;
    }

    rlen = len = (int)strlen( str ) + 1;

    /* 
     * Round up to machine dependant address size.
     * Don't remove this, because when the BufEntry struct is overlaid
     * the struct must be aligned correctly.
     */

    if( ( len + HEADER_SIZE ) & addrSizeMask ) 
        len += addrTypeSize - ( ( len + HEADER_SIZE ) & addrSizeMask );

    if( ssm_buf_free )
    { 
        RETRY:
	for( ptr = ssm_buf_free; ptr; ptr = ptr->next )
	    if( ptr->usage == 0 && ptr->size >= len )
	        break;

	if( !ptr )
	{
	    if( numFree >= MAX_FREE )
	    {
		int merges;

		bug( "[SSM] Attempting to optimize shared string heap.", 0 );
		merges = defrag_heap();

		/* goto is fine because defrag will return 0 next time */
		if( merges )
		    goto RETRY;
	    }	

	    str_new = (char *)malloc( rlen );
	    strcpy( str_new, str ); 
	    sOverFlowString += rlen;
	    nOverFlowString++;
	    return str_new;
	}

	/* If there is at least header size excess break it up */
	else if( ptr->size - len >= HEADER_SIZE ) 
	{
	    BufEntry *temp;

	    /* WARNING! - DONT REMOVE THE CASTS BELOW! - Fusion */
	    temp = (BufEntry*)( (char *)ptr + HEADER_SIZE + len );
	    temp->size   = ptr->size - ( len + HEADER_SIZE );
	    temp->next   = ptr->next;
	    temp->usage  = 0;
	    ptr->size    = len;
	    ptr->next    = temp;
	    ptr->usage   = 1;
	    ssm_buf_free = temp;
	}
	else
	{
	    ptr->usage = 1;
	    if( ptr != ssm_buf_free )
	        ssm_buf_free->usage--; /* buf_free was skipped */

	    for( ssm_buf_free = ssm_buf_head; ssm_buf_free;
		ssm_buf_free = ssm_buf_free->next )
	        if( ssm_buf_free->usage == 0 )
		    break;
	}

	str_new = (char *)&ptr->buf[0];
	strcpy( str_new, str ); 
	nAllocString++;
	sAllocString += ptr->size + HEADER_SIZE;
    }
    else  
    {
	/* A one time toggle just for bugging purposes */
	if( !Full )
	{
	    bug( "[SSM] The shared string heap is full!", 0 );
	    Full = 1;
	}

	str_new = (char *)malloc( rlen );
	strcpy( str_new, str ); 
	sOverFlowString += rlen;
	nOverFlowString++;
    } 

    return str_new;
}

/*
 * If string is in shared space, decrement usage, if usage then is 0,
 * free the chunk and attempt to merge with next node. Other
 * strings are freed with standard free.
 * Never call free/delete externally on a shared string.
 */
void free_string( char *str )
{
    BufEntry *ptr;

    if( !str || str == &str_empty[0] )
        return;

    if( str > string_space && str < top_string )
    {
	ptr = (BufEntry *)( str - HEADER_SIZE );

	if( --ptr->usage > 0 )
	    return;
	else if( ptr->usage < 0 )
	{
	    bug( "SSM:free_string: multiple free or invalid string.", 0 );
	    bug( (char*)&ptr->buf[0], 0 );
	    return;
	}

	numFree++;
	sAllocString -= ( ptr->size + HEADER_SIZE );
	nAllocString--;

	if( ssm_buf_free > ptr )
	    ssm_buf_free = ptr;

	if( fBootDb )
	{
	    TempHash *ptr;
	    TempHash *walk;
	    int ihash = strlen( str ) % MAX_KEY_HASH;

	    for( ptr = temp_string_hash[ ihash ]; ptr; ptr = ptr->next )
	    {
		if( ptr->str != str )
		    continue;
		else if( ptr == temp_string_hash[ ihash ] )
		    temp_string_hash[ ihash ] = ptr->next;
		else
		    for( walk = temp_string_hash[ ihash ]; walk;
			walk = walk->next )
		    {
			if( walk->next == ptr )
			{
			    walk->next = ptr->next;
			    break;
			}
		    }

		free( ptr );
		break; 
	    }
	}
	return;
    }

    sOverFlowString -= strlen( str ) + 1;
    nOverFlowString--;
    if ( sOverFlowString < 0 || nOverFlowString < 0 )
    {
	bug( "SSM:free_string: string free from outside SS space.", 0 );
	bug( str, 0 );
    }
    free( str );
}


/*
 * Read and allocate space for a string from a file.
 * This replaces db.c fread_string
 * This is modified version of Furey's fread_string from Merc
 */
char *fread_string( FILE *fp, int *status )
{
    char buf[ MAX_STRING_LENGTH*4 ];
    char *ptr = buf;
    char  c;

    *status = 0;

    do { c = getc( fp ); }
    while( isspace( c ) );

    if( ( *ptr++ = c ) == '~' )
        return &str_empty[0];

    for ( ;; )
    {
	switch ( *ptr = getc( fp ) )
	{
	    default:
	        ptr++;
		break;

	    case EOF:
		bug( "Fread_string: EOF", 0 );
		*status = 1;
		return NULL;
		break;

	    case '\n':
		ptr++;
		*ptr++ = '\r';
		break;

	    case '\r':
		break;

	    case '~':
		*ptr = '\0';
		if( fBootDb )
		{ 
		    ptr = temp_hash_find( buf ); 
		    if( ptr )
		        return str_dup( ptr ); 
		    
		    ptr = str_dup( buf );
		    temp_hash_add( ptr );
		    return ptr;
		}

		return str_dup( buf );
	}
    }
}


/*
 * Read string into user supplied buffer.
 * Modified version of Furey's fread_string
 */
void temp_fread_string( FILE *fp, char *buf )
{
    char *ptr = buf;
    char  c;

    do { c = getc( fp ); }
    while ( isspace( c ) );

    if ( ( *ptr++ = c ) == '~' )
    {   
	*buf = '\0';
	return;
    }

    for ( ;; )
    {
	switch ( *ptr = getc( fp ) )
	{
	    default:
	        ptr++;
		break;

	    case EOF:
		bug( "Fread_string: EOF", 0 );
		exit( 1 );
		break;

	    case '\n':
		ptr++;
		*ptr++ = '\r';
		break;

	    case '\r':
		break;

	    case '~':
		*ptr = '\0';
		return;
	}
    }
}


/* Lookup the string in the boot-time hash table. */
char *temp_hash_find( const char *str )
{
    TempHash *ptr;
    int       len;
    int       ihash;

    if( !fBootDb || !*str )
        return 0;

    len   = strlen( str );
    ihash = len % MAX_KEY_HASH;

    for( ptr = temp_string_hash[ ihash ]; ptr; ptr = ptr->next )
    {
	if( *ptr->str != *str )
	    continue;
	else if( strcmp( ptr->str, str ) )
	    continue;
	else return ptr->str;
    }

    return 0;
}


/*
 * Add a reference in the temporary hash table.
 * String is still in the linked list structure but
 * reference is kept here for quick lookup at boot time;
 */
void temp_hash_add( char *str )
{
    TempHash *add;
    int      len;
    int      ihash;

    if( !fBootDb || !*str || ( str <= string_space && str >= top_string ) )
        return;

    len   = strlen( str );
    ihash = len % MAX_KEY_HASH;
    add   = (TempHash *)malloc( sizeof( TempHash ) );
    add->next = temp_string_hash[ ihash ];
    temp_string_hash[ ihash ] = add;
    add->len = len;
    add->str = str;
}


/* Free the temp boot string hash table */
void boot_done( void )
{
    TempHash *ptr, *next;
    int       ihash;

    for( ihash = 0; ihash < MAX_KEY_HASH; ihash++ )
    {
	for( ptr = temp_string_hash[ ihash ]; ptr; ptr = next )
	{
	    next = ptr->next;
	    free( ptr );
	}
    }

    free( temp_string_hash );
    temp_string_hash = 0; /* Bug check in case someone accesses later */
}