/*
=============================================================================
ID software memory manager
--------------------------
Primary coder: John Carmack
RELIES ON
---------
Quit (char *error) function
WORK TO DO
----------
mm_SizePtr to change the size of a given pointer
Multiple purge levels utilized
EMS / XMS unmanaged routines
=============================================================================
*/
#include "ID_HEADS.H"
#pragma hdrstop
#pragma warn -pro
#pragma warn -use
/*
=============================================================================
LOCAL INFO
=============================================================================
*/
#define LOCKBIT 0x80 // if set in attributes, block cannot be moved
#define PURGEBITS 3 // 0-3 level, 0= unpurgable, 3= purge first
#define PURGEMASK 0xfffc
#define BASEATTRIBUTES 0 // unlocked, non purgable
#define MAXUMBS 10
typedef struct mmblockstruct
{
unsigned start,length;
unsigned attributes;
memptr *useptr; // pointer to the segment start
struct mmblockstruct far *next;
} mmblocktype;
//#define GETNEWBLOCK {if(!(mmnew=mmfree))Quit("mm_GETNEWBLOCK: No free blocks!")\
// ;mmfree=mmfree->next;}
#define GETNEWBLOCK {if(!mmfree)mmL_ClearBlock();mmnew=mmfree;mmfree=mmfree->next;}
#define FREEBLOCK(x) {*x->useptr=NULL;x->next=mmfree;mmfree=x;}
/*
=============================================================================
GLOBAL VARIABLES
=============================================================================
*/
mminfotype mminfo;
memptr bufferseg;
boolean mmerror;
void (* beforesort) (void);
void (* aftersort) (void);
/*
=============================================================================
LOCAL VARIABLES
=============================================================================
*/
boolean mmstarted;
void far *farheap;
void *nearheap;
mmblocktype far mmblocks[MAXBLOCKS]
,far *mmhead,far *mmfree,far *mmrover,far *mmnew;
boolean bombonerror;
//unsigned totalEMSpages,freeEMSpages,EMSpageframe,EMSpagesmapped,EMShandle;
void (* XMSaddr) (void); // far pointer to XMS driver
unsigned numUMBs,UMBbase[MAXUMBS];
//==========================================================================
//
// local prototypes
//
boolean mmL_CheckForEMS (void);
void mmL_ShutdownEMS (void);
void mm_MapEMS (void);
boolean mmL_CheckForXMS (void);
void mmL_ShutdownXMS (void);
void mmL_UseSpace (unsigned segstart, unsigned seglength);
void mmL_ClearBlock (void);
//==========================================================================
/*
======================
=
= mmL_CheckForXMS
=
= Check for Xmm driver
=
=======================
*/
boolean mmL_CheckForXMS (void)
{
numUMBs = 0;
asm {
mov ax,0x4300
int 0x2f // query status of installed diver
cmp al,0x80
je good
}
return false;
good:
return true;
}
/*
======================
=
= mmL_SetupXMS
=
= Try to allocate all upper memory block
=
=======================
*/
void mmL_SetupXMS (void)
{
unsigned base,size;
asm {
mov ax,0x4310
int 0x2f
mov [WORD PTR XMSaddr],bx
mov [WORD PTR XMSaddr+2],es // function pointer to XMS driver
}
getmemory:
asm {
mov ah,XMS_ALLOCUMB
mov dx,0xffff // try for largest block possible
call [DWORD PTR XMSaddr]
or ax,ax
jnz gotone
cmp bl,0xb0 // error: smaller UMB is available
jne done;
mov ah,XMS_ALLOCUMB
call [DWORD PTR XMSaddr] // DX holds largest available UMB
or ax,ax
jz done // another error...
}
gotone:
asm {
mov [base],bx
mov [size],dx
}
mmL_UseSpace (base,size);
mminfo.XMSmem += size*16;
UMBbase[numUMBs] = base;
numUMBs++;
if (numUMBs MAXUMBS)
goto getmemory;
done:;
}
/*
======================
=
= mmL_ShutdownXMS
=
======================
*/
void mmL_ShutdownXMS (void)
{
int i;
unsigned base;
for (i=0;i numUMBs;i++)
{
base = UMBbase[i];
asm mov ah,XMS_FREEUMB
asm mov dx,[base]
asm call [DWORD PTR XMSaddr]
}
}
//==========================================================================
/*
======================
=
= mmL_UseSpace
=
= Marks a range of paragraphs as usable by the memory manager
= This is used to mark space for the near heap, far heap, ems page frame,
= and upper memory blocks
=
======================
*/
void mmL_UseSpace (unsigned segstart, unsigned seglength)
{
mmblocktype far *scan,far *last;
unsigned oldend;
long extra;
scan = last = mmhead;
mmrover = mmhead; // reset rover to start of memory
//
// search for the block that contains the range of segments
//
while (scan->start+scan->length segstart)
{
last = scan;
scan = scan->next;
}
//
// take the given range out of the block
//
oldend = scan->start + scan->length;
extra = oldend - (segstart+seglength);
if (extra 0)
Quit ("mmL_UseSpace: Segment spans two blocks!");
if (segstart == scan->start)
{
last->next = scan->next; // unlink block
FREEBLOCK(scan);
scan = last;
}
else
scan->length = segstart-scan->start; // shorten block
if (extra > 0)
{
GETNEWBLOCK;
mmnew->useptr = NULL;
mmnew->next = scan->next;
scan->next = mmnew;
mmnew->start = segstart+seglength;
mmnew->length = extra;
mmnew->attributes = LOCKBIT;
}
}
//==========================================================================
/*
====================
=
= mmL_ClearBlock
=
= We are out of blocks, so free a purgable block
=
====================
*/
void mmL_ClearBlock (void)
{
mmblocktype far *scan,far *last;
scan = mmhead->next;
while (scan)
{
if (!(scan->attributes&LOCKBIT) && (scan->attributes&PURGEBITS) )
{
mm_FreePtr(scan->useptr);
return;
}
scan = scan->next;
}
Quit ("mm_ClearBlock: No purgable blocks!");
}
//==========================================================================
/*
===================
=
= mm_Startup
=
= Grabs all space from turbo with malloc/farmalloc
= Allocates bufferseg misc buffer
=
===================
*/
static char *ParmStrings[] = {"noems","noxms",""};
void mm_Startup (void)
{
int i;
unsigned long length;
void far *start;
unsigned segstart,seglength,endfree;
if (mmstarted)
mm_Shutdown ();
mmstarted = true;
bombonerror = true;
//
// set up the linked list (everything in the free list;
//
mmhead = NULL;
mmfree = &mmblocks[0];
for (i=0;i MAXBLOCKS-1;i++)
mmblocks[i].next = &mmblocks[i+1];
mmblocks[i].next = NULL;
//
// locked block of all memory until we punch out free space
//
GETNEWBLOCK;
mmhead = mmnew; // this will allways be the first node
mmnew->start = 0;
mmnew->length = 0xffff;
mmnew->attributes = LOCKBIT;
mmnew->next = NULL;
mmrover = mmhead;
//
// get all available near conventional memory segments
//
length=coreleft();
start = (void far *)(nearheap = malloc(length));
length -= 16-(FP_OFF(start)&15);
length -= SAVENEARHEAP;
seglength = length / 16; // now in paragraphs
segstart = FP_SEG(start)+(FP_OFF(start)+15)/16;
mmL_UseSpace (segstart,seglength);
mminfo.nearheap = length;
//
// get all available far conventional memory segments
//
length=farcoreleft();
start = farheap = farmalloc(length);
length -= 16-(FP_OFF(start)&15);
length -= SAVEFARHEAP;
seglength = length / 16; // now in paragraphs
segstart = FP_SEG(start)+(FP_OFF(start)+15)/16;
mmL_UseSpace (segstart,seglength);
mminfo.farheap = length;
mminfo.mainmem = mminfo.nearheap + mminfo.farheap;
//
// allocate the misc buffer
//
mmrover = mmhead; // start looking for space after low block
mm_GetPtr (&bufferseg,BUFFERSIZE);
}
//==========================================================================
/*
====================
=
= mm_Shutdown
=
= Frees all conventional, EMS, and XMS allocated
=
====================
*/
void mm_Shutdown (void)
{
if (!mmstarted)
return;
farfree (farheap);
free (nearheap);
// mmL_ShutdownXMS ();
}
//==========================================================================
/*
====================
=
= mm_GetPtr
=
= Allocates an unlocked, unpurgable block
=
====================
*/
void mm_GetPtr (memptr *baseptr,unsigned long size)
{
mmblocktype far *scan,far *lastscan,far *endscan
,far *purge,far *next;
int search;
unsigned needed,startseg;
needed = (size+15)/16; // convert size from bytes to paragraphs
GETNEWBLOCK; // fill in start and next after a spot is found
mmnew->length = needed;
mmnew->useptr = baseptr;
mmnew->attributes = BASEATTRIBUTES;
tryagain:
for (search = 0; search 3; search++)
{
//
// first search: try to allocate right after the rover, then on up
// second search: search from the head pointer up to the rover
// third search: compress memory, then scan from start
if (search == 1 && mmrover == mmhead)
search++;
switch (search)
{
case 0:
lastscan = mmrover;
scan = mmrover->next;
endscan = NULL;
break;
case 1:
lastscan = mmhead;
scan = mmhead->next;
endscan = mmrover;
break;
case 2:
mm_SortMem ();
lastscan = mmhead;
scan = mmhead->next;
endscan = NULL;
break;
}
startseg = lastscan->start + lastscan->length;
while (scan != endscan)
{
if (scan->start - startseg >= needed)
{
//
// got enough space between the end of lastscan and
// the start of scan, so throw out anything in the middle
// and allocate the new block
//
purge = lastscan->next;
lastscan->next = mmnew;
mmnew->start = *(unsigned *)baseptr = startseg;
mmnew->next = scan;
while ( purge != scan)
{ // free the purgable block
next = purge->next;
FREEBLOCK(purge);
purge = next; // purge another if not at scan
}
mmrover = mmnew;
return; // good allocation!
}
//
// if this block is purge level zero or locked, skip past it
//
if ( (scan->attributes & LOCKBIT)
|| !(scan->attributes & PURGEBITS) )
{
lastscan = scan;
startseg = lastscan->start + lastscan->length;
}
scan=scan->next; // look at next line
}
}
if (bombonerror)
{
extern char configname[];
extern boolean insetupscaling;
extern int viewsize;
boolean SetViewSize (unsigned width, unsigned height);
#define HEIGHTRATIO 0.50
//
// WOLF hack -- size the view down
//
if (!insetupscaling && viewsize>10)
{
mmblocktype far *savedmmnew;
savedmmnew = mmnew;
viewsize -= 2;
SetViewSize (viewsize*16,viewsize*16*HEIGHTRATIO);
mmnew = savedmmnew;
goto tryagain;
}
// unlink(configname);
Quit ("mm_GetPtr: Out of memory!");
}
else
mmerror = true;
}
//==========================================================================
/*
====================
=
= mm_FreePtr
=
= Deallocates an unlocked, purgable block
=
====================
*/
void mm_FreePtr (memptr *baseptr)
{
mmblocktype far *scan,far *last;
last = mmhead;
scan = last->next;
if (baseptr == mmrover->useptr) // removed the last allocated block
mmrover = mmhead;
while (scan->useptr != baseptr && scan)
{
last = scan;
scan = scan->next;
}
if (!scan)
Quit ("mm_FreePtr: Block not found!");
last->next = scan->next;
FREEBLOCK(scan);
}
//==========================================================================
/*
=====================
=
= mm_SetPurge
=
= Sets the purge level for a block (locked blocks cannot be made purgable)
=
=====================
*/
void mm_SetPurge (memptr *baseptr, int purge)
{
mmblocktype far *start;
start = mmrover;
do
{
if (mmrover->useptr == baseptr)
break;
mmrover = mmrover->next;
if (!mmrover)
mmrover = mmhead;
else if (mmrover == start)
Quit ("mm_SetPurge: Block not found!");
} while (1);
mmrover->attributes &= ~PURGEBITS;
mmrover->attributes |= purge;
}
//==========================================================================
/*
=====================
=
= mm_SetLock
=
= Locks / unlocks the block
=
=====================
*/
void mm_SetLock (memptr *baseptr, boolean locked)
{
mmblocktype far *start;
start = mmrover;
do
{
if (mmrover->useptr == baseptr)
break;
mmrover = mmrover->next;
if (!mmrover)
mmrover = mmhead;
else if (mmrover == start)
Quit ("mm_SetLock: Block not found!");
} while (1);
mmrover->attributes &= ~LOCKBIT;
mmrover->attributes |= locked*LOCKBIT;
}
//==========================================================================
/*
=====================
=
= mm_SortMem
=
= Throws out all purgable stuff and compresses movable blocks
=
=====================
*/
void mm_SortMem (void)
{
mmblocktype far *scan,far *last,far *next;
unsigned start,length,source,dest;
int playing;
//
// lock down a currently playing sound
//
playing = SD_SoundPlaying ();
if (playing)
{
switch (SoundMode)
{
case sdm_PC:
playing += STARTPCSOUNDS;
break;
case sdm_AdLib:
playing += STARTADLIBSOUNDS;
break;
}
mm_SetLock(&(memptr)audiosegs[playing],true);
}
SD_StopSound();
if (beforesort)
beforesort();
scan = mmhead;
last = NULL; // shut up compiler warning
while (scan)
{
if (scan->attributes & LOCKBIT)
{
//
// block is locked, so try to pile later blocks right after it
//
start = scan->start + scan->length;
}
else
{
if (scan->attributes & PURGEBITS)
{
//
// throw out the purgable block
//
next = scan->next;
FREEBLOCK(scan);
last->next = next;
scan = next;
continue;
}
else
{
//
// push the non purgable block on top of the last moved block
//
if (scan->start != start)
{
length = scan->length;
source = scan->start;
dest = start;
while (length > 0xf00)
{
movedata(source,0,dest,0,0xf00*16);
length -= 0xf00;
source += 0xf00;
dest += 0xf00;
}
movedata(source,0,dest,0,length*16);
scan->start = start;
*(unsigned *)scan->useptr = start;
}
start = scan->start + scan->length;
}
}
last = scan;
scan = scan->next; // go to next block
}
mmrover = mmhead;
if (aftersort)
aftersort();
if (playing)
mm_SetLock(&(memptr)audiosegs[playing],false);
}
//==========================================================================
/*
=====================
=
= mm_ShowMemory
=
=====================
*/
void mm_ShowMemory (void)
{
mmblocktype far *scan;
unsigned color,temp,x,y;
long end,owner;
char scratch[80],str[10];
temp = bufferofs;
bufferofs = displayofs;
scan = mmhead;
end = -1;
while (scan)
{
if (scan->attributes & PURGEBITS)
color = 5; // dark purple = purgable
else
color = 9; // medium blue = non purgable
if (scan->attributes & LOCKBIT)
color = 12; // red = locked
if (scan->start =end)
Quit ("mm_ShowMemory: Memory block order currupted!");
end = scan->length-1;
y = scan->start/320;
x = scan->start%320;
VW_Hlin(x,x+end,y,color);
VW_Plot(x,y,15);
if (scan->next && scan->next->start > end+1)
VW_Hlin(x+end+1,x+(scan->next->start-scan->start),y,0); // black = free
scan = scan->next;
}
VW_FadeIn ();
IN_Ack();
bufferofs = temp;
}
//==========================================================================
/*
=====================
=
= mm_DumpData
=
=====================
*/
void mm_DumpData (void)
{
mmblocktype far *scan,far *best;
long lowest,oldlowest;
unsigned owner;
char lock,purge;
FILE *dumpfile;
free (nearheap);
dumpfile = fopen ("mmDUMP.TXT","w");
if (!dumpfile)
Quit ("mm_DumpData: Couldn't open mmDUMP.TXT!");
lowest = -1;
do
{
oldlowest = lowest;
lowest = 0xffff;
scan = mmhead;
while (scan)
{
owner = (unsigned)scan->useptr;
if (owner && owner oldlowest)
{
best = scan;
lowest = owner;
}
scan = scan->next;
}
if (lowest != 0xffff)
{
if (best->attributes & PURGEBITS)
purge = 'P';
else
purge = '-';
if (best->attributes & LOCKBIT)
lock = 'L';
else
lock = '-';
fprintf (dumpfile,"0x%p (%c%c) = %u\n"
,(unsigned)lowest,lock,purge,best->length);
}
} while (lowest != 0xffff);
fclose (dumpfile);
Quit ("mmDUMP.TXT created.");
}
//==========================================================================
/*
======================
=
= mm_UnusedMemory
=
= Returns the total free space without purging
=
======================
*/
long mm_UnusedMemory (void)
{
unsigned free;
mmblocktype far *scan;
free = 0;
scan = mmhead;
while (scan->next)
{
free += scan->next->start - (scan->start + scan->length);
scan = scan->next;
}
return free*16l;
}
//==========================================================================
/*
======================
=
= mm_TotalFree
=
= Returns the total free space with purging
=
======================
*/
long mm_TotalFree (void)
{
unsigned free;
mmblocktype far *scan;
free = 0;
scan = mmhead;
while (scan->next)
{
if ((scan->attributes&PURGEBITS) && !(scan->attributes&LOCKBIT))
free += scan->length;
free += scan->next->start - (scan->start + scan->length);
scan = scan->next;
}
return free*16l;
}
//==========================================================================
/*
=====================
=
= mm_BombOnerror
=
=====================
*/
void mm_BombOnerror (boolean bomb)
{
bombonerror = bomb;
}