ifengine/memory.c
2018-09-08 19:51:49 -05:00

419 lines
10 KiB
C

/* $Id: memory.c,v 1.2 2000/05/25 22:28:56 jholder Exp $
* --------------------------------------------------------------------
* see doc/License.txt for License Information
* --------------------------------------------------------------------
*
* File name: $Id: memory.c,v 1.2 2000/05/25 22:28:56 jholder Exp $
*
* Description:
*
* Modification history:
* $Log: memory.c,v $
* Revision 1.2 2000/05/25 22:28:56 jholder
* changes routine names to reflect zmachine opcode names per spec 1.0
*
* Revision 1.1.1.1 2000/05/10 14:21:34 jholder
*
* imported
*
*
* --------------------------------------------------------------------
*/
/*
* memory.c
*
* Code and data caching routines
*
*/
#include "ztypes.h"
/* A cache entry */
typedef struct cache_entry
{
struct cache_entry *flink;
int page_number;
zbyte_t data[PAGE_SIZE];
}
cache_entry_t;
/* Cache chain anchor */
static cache_entry_t *cache = NULL;
/* Pseudo translation buffer, one entry each for code and data pages */
static unsigned int current_code_page = 0;
static cache_entry_t *current_code_cachep = NULL;
static unsigned int current_data_page = 0;
static cache_entry_t *current_data_cachep = NULL;
static unsigned int calc_data_pages( void );
static cache_entry_t *update_cache( int );
/*
* load_cache
*
* Initialise the cache and any other dynamic memory objects. The memory
* required can be split into two areas. Firstly, three buffers are required for
* input, output and status line. Secondly, two data areas are required for
* writeable data and read only data. The writeable data is the first chunk of
* the file and is put into non-paged cache. The read only data is the remainder
* of the file which can be paged into the cache as required. Writeable data has
* to be memory resident because it cannot be written out to a backing store.
*
*/
void load_cache( void )
{
unsigned long file_size;
unsigned int i, file_pages, data_pages;
cache_entry_t *cachep;
/* Allocate output and status line buffers */
line = ( char * ) malloc( screen_cols + 1 );
if ( line == NULL )
{
fatal( "load_cache(): Insufficient memory to play game" );
}
status_line = ( char * ) malloc( screen_cols + 1 );
if ( status_line == NULL )
{
fatal( "load_cache(): Insufficient memory to play game" );
}
/* Must have at least one cache page for memory calculation */
cachep = ( cache_entry_t * ) malloc( sizeof ( cache_entry_t ) );
if ( cachep == NULL )
{
fatal( "load_cache(): Insufficient memory to play game" );
}
cachep->flink = cache;
cachep->page_number = 0;
cache = cachep;
/* Calculate dynamic cache pages required */
if ( h_config & CONFIG_MAX_DATA )
{
data_pages = calc_data_pages( );
}
else
{
data_pages = ( h_data_size + PAGE_MASK ) >> PAGE_SHIFT;
}
data_size = data_pages * PAGE_SIZE;
file_size = ( unsigned long ) h_file_size *story_scaler;
file_pages = ( unsigned int ) ( ( file_size + PAGE_MASK ) >> PAGE_SHIFT );
/* Allocate static data area and initialise it */
datap = ( zbyte_t * ) malloc( data_size );
if ( datap == NULL )
{
fatal( "load_cache(): Insufficient memory to play game" );
}
for ( i = 0; i < data_pages; i++ )
{
read_page( i, &datap[i * PAGE_SIZE] );
}
/* Allocate memory for undo */
undo_datap = ( zbyte_t * ) malloc( data_size );
/* Allocate cache pages and initialise them */
for ( i = data_pages; cachep != NULL && i < file_pages; i++ )
{
cachep = ( cache_entry_t * ) malloc( sizeof ( cache_entry_t ) );
if ( cachep != NULL )
{
cachep->flink = cache;
cachep->page_number = i;
read_page( cachep->page_number, cachep->data );
cache = cachep;
}
}
} /* load_cache */
/*
* unload_cache
*
* Deallocate cache and other memory objects.
*
*/
void unload_cache( void )
{
cache_entry_t *cachep, *nextp;
/* Make sure all output has been flushed */
z_new_line( );
/* Free output buffer, status line and data memory */
free( line );
free( status_line );
free( datap );
free( undo_datap );
/* Free cache memory */
for ( cachep = cache; cachep->flink != NULL; cachep = nextp )
{
nextp = cachep->flink;
free( cachep );
}
} /* unload_cache */
/*
* read_code_word
*
* Read a word from the instruction stream.
*
*/
zword_t read_code_word( void )
{
zword_t w;
w = ( zword_t ) read_code_byte( ) << 8;
w |= ( zword_t ) read_code_byte( );
return ( w );
} /* read_code_word */
/*
* read_code_byte
*
* Read a byte from the instruction stream.
*
*/
zbyte_t read_code_byte( void )
{
unsigned int page_number, page_offset;
/* Calculate page and offset values */
page_number = ( unsigned int ) ( pc >> PAGE_SHIFT );
page_offset = ( unsigned int ) pc & PAGE_MASK;
/* Load page into translation buffer */
if ( page_number != current_code_page )
{
current_code_cachep = update_cache( page_number );
current_code_page = page_number;
}
/* Update the PC */
pc++;
/* Return byte from page offset */
if ( !current_code_cachep )
{
fatal
( "read_code_byte(): read from non-existant page!\n\t(Your dynamic memory usage _may_ be over 64k in size!)" );
}
return ( current_code_cachep->data[page_offset] );
} /* read_code_byte */
/*
* read_data_word
*
* Read a word from the data area.
*
*/
zword_t read_data_word( unsigned long *addr )
{
zword_t w;
w = ( zword_t ) read_data_byte( addr ) << 8;
w |= ( zword_t ) read_data_byte( addr );
return ( w );
} /* read_data_word */
/*
* read_data_byte
*
* Read a byte from the data area.
*
*/
zbyte_t read_data_byte( unsigned long *addr )
{
unsigned int page_number, page_offset;
zbyte_t value;
/* Check if byte is in non-paged cache */
if ( *addr < ( unsigned long ) data_size )
{
value = datap[*addr];
}
else
{
/* Calculate page and offset values */
page_number = ( int ) ( *addr >> PAGE_SHIFT );
page_offset = ( int ) *addr & PAGE_MASK;
/* Load page into translation buffer */
if ( page_number != current_data_page )
{
current_data_cachep = update_cache( page_number );
current_data_page = page_number;
}
/* Fetch byte from page offset */
if ( current_data_cachep )
{
value = current_data_cachep->data[page_offset];
}
else
{
fatal( "read_data_byte(): Fetching data from invalid page!" );
}
}
/* Update the address */
( *addr )++;
return ( value );
} /* read_data_byte */
/*
* calc_data_pages
*
* Compute the best size for the data area cache. Some games have the data size
* header parameter set too low. This causes a write outside of data area on
* some games. To alleviate this problem the data area size is set to the
* maximum of the restart size, the data size and the end of the dictionary. An
* attempt is made to put the dictionary in the data area to stop paging during
* a dictionary lookup. Some games have the dictionary end very close to the
* 64K limit which may cause problems for machines that allocate memory in
* 64K chunks.
*
*/
static unsigned int calc_data_pages( void )
{
unsigned long offset, data_end, dictionary_end;
int separator_count, word_size, word_count;
unsigned int data_pages;
/* Calculate end of data area, use restart size if data size is too low */
if ( h_data_size > h_restart_size )
{
data_end = h_data_size;
}
else
{
data_end = h_restart_size;
}
/* Calculate end of dictionary table */
offset = h_words_offset;
separator_count = read_data_byte( &offset );
offset += separator_count;
word_size = read_data_byte( &offset );
word_count = read_data_word( &offset );
dictionary_end = offset + ( word_size * word_count );
/* If data end is too low then use end of dictionary instead */
if ( dictionary_end > data_end )
{
data_pages = ( unsigned int ) ( ( dictionary_end + PAGE_MASK ) >> PAGE_SHIFT );
}
else
{
data_pages = ( unsigned int ) ( ( data_end + PAGE_MASK ) >> PAGE_SHIFT );
}
return ( data_pages );
} /* calc_data_pages */
/*
* update_cache
*
* Called on a code or data page cache miss to find the page in the cache or
* read the page in from disk. The chain is kept as a simple LRU chain. If a
* page cannot be found then the page on the end of the chain is reused. If the
* page is found, or reused, then it is moved to the front of the chain.
*
*/
static cache_entry_t *update_cache( int page_number )
{
cache_entry_t *cachep, *lastp;
/* Search the cache chain for the page */
for ( lastp = cache, cachep = cache;
cachep->flink != NULL && cachep->page_number && cachep->page_number != page_number;
lastp = cachep, cachep = cachep->flink )
;
/* If no page in chain then read it from disk */
if ( cachep->page_number != page_number )
{
/* Reusing last cache page, so invalidate cache if page was in use */
if ( cachep->flink == NULL && cachep->page_number )
{
if ( current_code_page == ( unsigned int ) cachep->page_number )
{
current_code_page = 0;
}
if ( current_data_page == ( unsigned int ) cachep->page_number )
{
current_data_page = 0;
}
}
/* Load the new page number and the page contents from disk */
cachep->page_number = page_number;
read_page( page_number, cachep->data );
}
/* If page is not at front of cache chain then move it there */
if ( lastp != cache )
{
lastp->flink = cachep->flink;
cachep->flink = cache;
cache = cachep;
}
return ( cachep );
} /* update_cache */