/****************************************************************************** * ssm.c (shared string manager) * * * * Copyright(C) 1995 Melvin Smith - Mercer University, Macon GA. * * * * This code was scrapped from MUD++ in favor of a String class * * I converted it over to drop into Merc/Envy code by request of * * Jason Dinkel to go with OLC. * * --Fusion * * <msmith@falcon.mercer.peachnet.edu> * ******************************************************************************/ #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 typedef struct B BufEntry; /* There is a temptation to change the 'long usage' to short or standard * int but this causes problems on at least 2 platforms that I know * of ( Sun and MIPS/Ultrix ) because of 32-bit alignment. The compiler * will pad the struct anyway so it probably woudln't make a difference. */ struct B { BufEntry *next; long int size; /* size of the chunk (regardless of NULL CHAR) */ long int usage; /* keeps track of 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 Hash table will * be freed after boot_done() is called. */ typedef struct T TempHash; struct T { TempHash *next; unsigned int len; char *str; }; TempHash **temp_string_hash; /* These are the original Merc vars in db.c */ extern char str_empty[]; extern char *string_space; extern char *top_string; extern int nAllocString; extern int sAllocString; extern bool fBootDb; int numFree; int Full; /* These are the replacement functions, * comment them out in db.c */ char *str_dup ( const char * ); void free_string ( char * ); char *fread_string ( FILE * ); char *fread_word_dup ( FILE * ); /* Implement later to check words also */ void temp_hash_add ( char * ); char *temp_hash_find ( const char * ); /* * buf_head points to start of shared space, * buf_free points to next free block */ BufEntry *buf_head, *buf_free; int MAX_STRING = 1700000; 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 ton of failed dups */ #define MAX_FREE 1000 void init_string_space() { 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; buf_head = (BufEntry *)string_space; /* * Would be nice to use sizeof()-1 here but word alignment * adds some nice padding that puts it off. The REAL size of * the struct is probably 16 bytes, 8 words but since we are overlaying * the struct on the character space we need the exact offset. * * Oct 24, 1995 - This is still true but probably has no effect now that * I am using a long (32-bit) for both ints, the struct should * line up nicely. Talen reports that his runs fine now after the * mod. - MS */ HEADER_SIZE = (int)((char*)buf_head->buf-(char*)buf_head); buf_head->usage = 0; buf_head->size = MAX_STRING - HEADER_SIZE; buf_head->next = 0; buf_free = 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; buf_free = 0; for( walk=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( walk > buf_free ) buf_free = walk; continue; } else { /* previous block is free so merge walk into last_free and move on */ merges++; last_free->size += walk->size + HEADER_SIZE; last_free->next = walk->next; } } if( merges ) bug( "[SSM] defrag_heap() : made %d block merges. Good.", 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; int len; int rlen; char *str_new; 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 = strlen( str ) + 1; /* * Round up to word boundary if not already. * Don't remove this, because when the BufEntry struct is overlaid * the struct must be aligned on word boundarys. */ if( len & 3 ) len += 4-(len&3); if( !buf_free ) { /* 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 ); return str_new; } else { RETRY: for( ptr = 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 ); 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; if( ptr == buf_free ) buf_free = temp; } ptr->usage = 1; str_new = ptr->buf; if( ptr == buf_free ) for( buf_free = buf_head; buf_free; buf_free = buf_free->next ) if( buf_free->usage <= 0 ) break; strcpy( str_new, str ); nAllocString++; sAllocString += len + HEADER_SIZE; } 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 bug.", 0 ); bug( ptr->buf, 0 ); return; } numFree++; sAllocString -= (ptr->size + HEADER_SIZE); nAllocString--; if( ptr->next && ptr->next->usage <= 0 ) { ptr->size += ptr->next->size + HEADER_SIZE; ptr->next = ptr->next->next; } if( buf_free > ptr ) buf_free = ptr; /* This written if you ever need to free strings at bootup. Without this, there would be invalid pointers in the temp_hash_table. I have no need for it now but felt like writing it. 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; } free( str ); } /* * Read and allocate space for a string from a file. * This replaces db.c fread_string */ char *fread_string( FILE *fp ) { char buf[ MAX_STRING_LENGTH*4 ]; char *ptr = buf; char c; /* * Skip blanks. * Read first char. */ do { c = getc( fp ); } while ( isspace( c ) ); if ( ( *ptr++ = c ) == '~' ) return &str_empty[0]; for ( ;; ) { /* * Back off the char type lookup, * it was too dirty for portability. * -- Furey */ 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'; 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 ); } } } void temp_fread_string( FILE *fp, char *buf ) { char *ptr = buf; char c; /* * Skip blanks. * Read first char. */ do { c = getc( fp ); } while ( isspace( c ) ); if ( ( *ptr++ = c ) == '~' ) { *buf = '\0'; return; } for ( ;; ) { /* * Back off the char type lookup, * it was too dirty for portability. * -- Furey */ 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 ) { int len; int ihash; TempHash *add; 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 ); } } }