wolf3d

The original open source release of Wolfenstein 3D
Log | Files | Refs

ID_MM.C (17183B)


      1 // NEWMM.C
      2 
      3 /*
      4 =============================================================================
      5 
      6 		   ID software memory manager
      7 		   --------------------------
      8 
      9 Primary coder: John Carmack
     10 
     11 RELIES ON
     12 ---------
     13 Quit (char *error) function
     14 
     15 
     16 WORK TO DO
     17 ----------
     18 MM_SizePtr to change the size of a given pointer
     19 
     20 Multiple purge levels utilized
     21 
     22 EMS / XMS unmanaged routines
     23 
     24 =============================================================================
     25 */
     26 
     27 #include "ID_HEADS.H"
     28 #pragma hdrstop
     29 
     30 #pragma warn -pro
     31 #pragma warn -use
     32 
     33 /*
     34 =============================================================================
     35 
     36 							LOCAL INFO
     37 
     38 =============================================================================
     39 */
     40 
     41 #define LOCKBIT		0x80	// if set in attributes, block cannot be moved
     42 #define PURGEBITS	3		// 0-3 level, 0= unpurgable, 3= purge first
     43 #define PURGEMASK	0xfffc
     44 #define BASEATTRIBUTES	0	// unlocked, non purgable
     45 
     46 #define MAXUMBS		10
     47 
     48 typedef struct mmblockstruct
     49 {
     50 	unsigned	start,length;
     51 	unsigned	attributes;
     52 	memptr		*useptr;	// pointer to the segment start
     53 	struct mmblockstruct far *next;
     54 } mmblocktype;
     55 
     56 
     57 //#define GETNEWBLOCK {if(!(mmnew=mmfree))Quit("MM_GETNEWBLOCK: No free blocks!")\
     58 //	;mmfree=mmfree->next;}
     59 
     60 #define GETNEWBLOCK {if(!mmfree)MML_ClearBlock();mmnew=mmfree;mmfree=mmfree->next;}
     61 
     62 #define FREEBLOCK(x) {*x->useptr=NULL;x->next=mmfree;mmfree=x;}
     63 
     64 /*
     65 =============================================================================
     66 
     67 						 GLOBAL VARIABLES
     68 
     69 =============================================================================
     70 */
     71 
     72 mminfotype	mminfo;
     73 memptr		bufferseg;
     74 boolean		mmerror;
     75 
     76 void		(* beforesort) (void);
     77 void		(* aftersort) (void);
     78 
     79 /*
     80 =============================================================================
     81 
     82 						 LOCAL VARIABLES
     83 
     84 =============================================================================
     85 */
     86 
     87 boolean		mmstarted;
     88 
     89 void far	*farheap;
     90 void		*nearheap;
     91 
     92 mmblocktype	far mmblocks[MAXBLOCKS]
     93 			,far *mmhead,far *mmfree,far *mmrover,far *mmnew;
     94 
     95 boolean		bombonerror;
     96 
     97 //unsigned	totalEMSpages,freeEMSpages,EMSpageframe,EMSpagesmapped,EMShandle;
     98 
     99 void		(* XMSaddr) (void);		// far pointer to XMS driver
    100 
    101 unsigned	numUMBs,UMBbase[MAXUMBS];
    102 
    103 //==========================================================================
    104 
    105 //
    106 // local prototypes
    107 //
    108 
    109 boolean		MML_CheckForEMS (void);
    110 void 		MML_ShutdownEMS (void);
    111 void 		MM_MapEMS (void);
    112 boolean 	MML_CheckForXMS (void);
    113 void 		MML_ShutdownXMS (void);
    114 void		MML_UseSpace (unsigned segstart, unsigned seglength);
    115 void 		MML_ClearBlock (void);
    116 
    117 //==========================================================================
    118 
    119 /*
    120 ======================
    121 =
    122 = MML_CheckForXMS
    123 =
    124 = Check for XMM driver
    125 =
    126 =======================
    127 */
    128 
    129 boolean MML_CheckForXMS (void)
    130 {
    131 	numUMBs = 0;
    132 
    133 asm {
    134 	mov	ax,0x4300
    135 	int	0x2f				// query status of installed diver
    136 	cmp	al,0x80
    137 	je	good
    138 	}
    139 
    140 	return false;
    141 good:
    142 	return true;
    143 }
    144 
    145 
    146 /*
    147 ======================
    148 =
    149 = MML_SetupXMS
    150 =
    151 = Try to allocate all upper memory block
    152 =
    153 =======================
    154 */
    155 
    156 void MML_SetupXMS (void)
    157 {
    158 	unsigned	base,size;
    159 
    160 asm	{
    161 	mov	ax,0x4310
    162 	int	0x2f
    163 	mov	[WORD PTR XMSaddr],bx
    164 	mov	[WORD PTR XMSaddr+2],es		// function pointer to XMS driver
    165 	}
    166 
    167 getmemory:
    168 asm	{
    169 	mov	ah,XMS_ALLOCUMB
    170 	mov	dx,0xffff					// try for largest block possible
    171 	call	[DWORD PTR XMSaddr]
    172 	or	ax,ax
    173 	jnz	gotone
    174 
    175 	cmp	bl,0xb0						// error: smaller UMB is available
    176 	jne	done;
    177 
    178 	mov	ah,XMS_ALLOCUMB
    179 	call	[DWORD PTR XMSaddr]		// DX holds largest available UMB
    180 	or	ax,ax
    181 	jz	done						// another error...
    182 	}
    183 
    184 gotone:
    185 asm	{
    186 	mov	[base],bx
    187 	mov	[size],dx
    188 	}
    189 	MML_UseSpace (base,size);
    190 	mminfo.XMSmem += size*16;
    191 	UMBbase[numUMBs] = base;
    192 	numUMBs++;
    193 	if (numUMBs < MAXUMBS)
    194 		goto getmemory;
    195 
    196 done:;
    197 }
    198 
    199 
    200 /*
    201 ======================
    202 =
    203 = MML_ShutdownXMS
    204 =
    205 ======================
    206 */
    207 
    208 void MML_ShutdownXMS (void)
    209 {
    210 	int	i;
    211 	unsigned	base;
    212 
    213 	for (i=0;i<numUMBs;i++)
    214 	{
    215 		base = UMBbase[i];
    216 
    217 asm	mov	ah,XMS_FREEUMB
    218 asm	mov	dx,[base]
    219 asm	call	[DWORD PTR XMSaddr]
    220 	}
    221 }
    222 
    223 //==========================================================================
    224 
    225 /*
    226 ======================
    227 =
    228 = MML_UseSpace
    229 =
    230 = Marks a range of paragraphs as usable by the memory manager
    231 = This is used to mark space for the near heap, far heap, ems page frame,
    232 = and upper memory blocks
    233 =
    234 ======================
    235 */
    236 
    237 void MML_UseSpace (unsigned segstart, unsigned seglength)
    238 {
    239 	mmblocktype far *scan,far *last;
    240 	unsigned	oldend;
    241 	long		extra;
    242 
    243 	scan = last = mmhead;
    244 	mmrover = mmhead;		// reset rover to start of memory
    245 
    246 //
    247 // search for the block that contains the range of segments
    248 //
    249 	while (scan->start+scan->length < segstart)
    250 	{
    251 		last = scan;
    252 		scan = scan->next;
    253 	}
    254 
    255 //
    256 // take the given range out of the block
    257 //
    258 	oldend = scan->start + scan->length;
    259 	extra = oldend - (segstart+seglength);
    260 	if (extra < 0)
    261 		Quit ("MML_UseSpace: Segment spans two blocks!");
    262 
    263 	if (segstart == scan->start)
    264 	{
    265 		last->next = scan->next;			// unlink block
    266 		FREEBLOCK(scan);
    267 		scan = last;
    268 	}
    269 	else
    270 		scan->length = segstart-scan->start;	// shorten block
    271 
    272 	if (extra > 0)
    273 	{
    274 		GETNEWBLOCK;
    275 		mmnew->useptr = NULL;
    276 
    277 		mmnew->next = scan->next;
    278 		scan->next = mmnew;
    279 		mmnew->start = segstart+seglength;
    280 		mmnew->length = extra;
    281 		mmnew->attributes = LOCKBIT;
    282 	}
    283 
    284 }
    285 
    286 //==========================================================================
    287 
    288 /*
    289 ====================
    290 =
    291 = MML_ClearBlock
    292 =
    293 = We are out of blocks, so free a purgable block
    294 =
    295 ====================
    296 */
    297 
    298 void MML_ClearBlock (void)
    299 {
    300 	mmblocktype far *scan,far *last;
    301 
    302 	scan = mmhead->next;
    303 
    304 	while (scan)
    305 	{
    306 		if (!(scan->attributes&LOCKBIT) && (scan->attributes&PURGEBITS) )
    307 		{
    308 			MM_FreePtr(scan->useptr);
    309 			return;
    310 		}
    311 		scan = scan->next;
    312 	}
    313 
    314 	Quit ("MM_ClearBlock: No purgable blocks!");
    315 }
    316 
    317 
    318 //==========================================================================
    319 
    320 /*
    321 ===================
    322 =
    323 = MM_Startup
    324 =
    325 = Grabs all space from turbo with malloc/farmalloc
    326 = Allocates bufferseg misc buffer
    327 =
    328 ===================
    329 */
    330 
    331 static	char *ParmStrings[] = {"noems","noxms",""};
    332 
    333 void MM_Startup (void)
    334 {
    335 	int i;
    336 	unsigned 	long length;
    337 	void far 	*start;
    338 	unsigned 	segstart,seglength,endfree;
    339 
    340 	if (mmstarted)
    341 		MM_Shutdown ();
    342 
    343 
    344 	mmstarted = true;
    345 	bombonerror = true;
    346 //
    347 // set up the linked list (everything in the free list;
    348 //
    349 	mmhead = NULL;
    350 	mmfree = &mmblocks[0];
    351 	for (i=0;i<MAXBLOCKS-1;i++)
    352 		mmblocks[i].next = &mmblocks[i+1];
    353 	mmblocks[i].next = NULL;
    354 
    355 //
    356 // locked block of all memory until we punch out free space
    357 //
    358 	GETNEWBLOCK;
    359 	mmhead = mmnew;				// this will allways be the first node
    360 	mmnew->start = 0;
    361 	mmnew->length = 0xffff;
    362 	mmnew->attributes = LOCKBIT;
    363 	mmnew->next = NULL;
    364 	mmrover = mmhead;
    365 
    366 
    367 //
    368 // get all available near conventional memory segments
    369 //
    370 	length=coreleft();
    371 	start = (void far *)(nearheap = malloc(length));
    372 
    373 	length -= 16-(FP_OFF(start)&15);
    374 	length -= SAVENEARHEAP;
    375 	seglength = length / 16;			// now in paragraphs
    376 	segstart = FP_SEG(start)+(FP_OFF(start)+15)/16;
    377 	MML_UseSpace (segstart,seglength);
    378 	mminfo.nearheap = length;
    379 
    380 //
    381 // get all available far conventional memory segments
    382 //
    383 	length=farcoreleft();
    384 	start = farheap = farmalloc(length);
    385 	length -= 16-(FP_OFF(start)&15);
    386 	length -= SAVEFARHEAP;
    387 	seglength = length / 16;			// now in paragraphs
    388 	segstart = FP_SEG(start)+(FP_OFF(start)+15)/16;
    389 	MML_UseSpace (segstart,seglength);
    390 	mminfo.farheap = length;
    391 	mminfo.mainmem = mminfo.nearheap + mminfo.farheap;
    392 
    393 //
    394 // allocate the misc buffer
    395 //
    396 	mmrover = mmhead;		// start looking for space after low block
    397 
    398 	MM_GetPtr (&bufferseg,BUFFERSIZE);
    399 }
    400 
    401 //==========================================================================
    402 
    403 /*
    404 ====================
    405 =
    406 = MM_Shutdown
    407 =
    408 = Frees all conventional, EMS, and XMS allocated
    409 =
    410 ====================
    411 */
    412 
    413 void MM_Shutdown (void)
    414 {
    415   if (!mmstarted)
    416 	return;
    417 
    418   farfree (farheap);
    419   free (nearheap);
    420 //  MML_ShutdownXMS ();
    421 }
    422 
    423 //==========================================================================
    424 
    425 /*
    426 ====================
    427 =
    428 = MM_GetPtr
    429 =
    430 = Allocates an unlocked, unpurgable block
    431 =
    432 ====================
    433 */
    434 
    435 void MM_GetPtr (memptr *baseptr,unsigned long size)
    436 {
    437 	mmblocktype far *scan,far *lastscan,far *endscan
    438 				,far *purge,far *next;
    439 	int			search;
    440 	unsigned	needed,startseg;
    441 
    442 	needed = (size+15)/16;		// convert size from bytes to paragraphs
    443 
    444 	GETNEWBLOCK;				// fill in start and next after a spot is found
    445 	mmnew->length = needed;
    446 	mmnew->useptr = baseptr;
    447 	mmnew->attributes = BASEATTRIBUTES;
    448 
    449 tryagain:
    450 	for (search = 0; search<3; search++)
    451 	{
    452 	//
    453 	// first search:	try to allocate right after the rover, then on up
    454 	// second search: 	search from the head pointer up to the rover
    455 	// third search:	compress memory, then scan from start
    456 		if (search == 1 && mmrover == mmhead)
    457 			search++;
    458 
    459 		switch (search)
    460 		{
    461 		case 0:
    462 			lastscan = mmrover;
    463 			scan = mmrover->next;
    464 			endscan = NULL;
    465 			break;
    466 		case 1:
    467 			lastscan = mmhead;
    468 			scan = mmhead->next;
    469 			endscan = mmrover;
    470 			break;
    471 		case 2:
    472 			MM_SortMem ();
    473 			lastscan = mmhead;
    474 			scan = mmhead->next;
    475 			endscan = NULL;
    476 			break;
    477 		}
    478 
    479 		startseg = lastscan->start + lastscan->length;
    480 
    481 		while (scan != endscan)
    482 		{
    483 			if (scan->start - startseg >= needed)
    484 			{
    485 			//
    486 			// got enough space between the end of lastscan and
    487 			// the start of scan, so throw out anything in the middle
    488 			// and allocate the new block
    489 			//
    490 				purge = lastscan->next;
    491 				lastscan->next = mmnew;
    492 				mmnew->start = *(unsigned *)baseptr = startseg;
    493 				mmnew->next = scan;
    494 				while ( purge != scan)
    495 				{	// free the purgable block
    496 					next = purge->next;
    497 					FREEBLOCK(purge);
    498 					purge = next;		// purge another if not at scan
    499 				}
    500 				mmrover = mmnew;
    501 				return;	// good allocation!
    502 			}
    503 
    504 			//
    505 			// if this block is purge level zero or locked, skip past it
    506 			//
    507 			if ( (scan->attributes & LOCKBIT)
    508 				|| !(scan->attributes & PURGEBITS) )
    509 			{
    510 				lastscan = scan;
    511 				startseg = lastscan->start + lastscan->length;
    512 			}
    513 
    514 
    515 			scan=scan->next;		// look at next line
    516 		}
    517 	}
    518 
    519 	if (bombonerror)
    520 	{
    521 
    522 extern char configname[];
    523 extern	boolean	insetupscaling;
    524 extern	int	viewsize;
    525 boolean SetViewSize (unsigned width, unsigned height);
    526 #define HEIGHTRATIO		0.50
    527 //
    528 // wolf hack -- size the view down
    529 //
    530 		if (!insetupscaling && viewsize>10)
    531 		{
    532 mmblocktype	far *savedmmnew;
    533 			savedmmnew = mmnew;
    534 			viewsize -= 2;
    535 			SetViewSize (viewsize*16,viewsize*16*HEIGHTRATIO);
    536 			mmnew = savedmmnew;
    537 			goto tryagain;
    538 		}
    539 
    540 //		unlink(configname);
    541 		Quit ("MM_GetPtr: Out of memory!");
    542 	}
    543 	else
    544 		mmerror = true;
    545 }
    546 
    547 //==========================================================================
    548 
    549 /*
    550 ====================
    551 =
    552 = MM_FreePtr
    553 =
    554 = Deallocates an unlocked, purgable block
    555 =
    556 ====================
    557 */
    558 
    559 void MM_FreePtr (memptr *baseptr)
    560 {
    561 	mmblocktype far *scan,far *last;
    562 
    563 	last = mmhead;
    564 	scan = last->next;
    565 
    566 	if (baseptr == mmrover->useptr)	// removed the last allocated block
    567 		mmrover = mmhead;
    568 
    569 	while (scan->useptr != baseptr && scan)
    570 	{
    571 		last = scan;
    572 		scan = scan->next;
    573 	}
    574 
    575 	if (!scan)
    576 		Quit ("MM_FreePtr: Block not found!");
    577 
    578 	last->next = scan->next;
    579 
    580 	FREEBLOCK(scan);
    581 }
    582 //==========================================================================
    583 
    584 /*
    585 =====================
    586 =
    587 = MM_SetPurge
    588 =
    589 = Sets the purge level for a block (locked blocks cannot be made purgable)
    590 =
    591 =====================
    592 */
    593 
    594 void MM_SetPurge (memptr *baseptr, int purge)
    595 {
    596 	mmblocktype far *start;
    597 
    598 	start = mmrover;
    599 
    600 	do
    601 	{
    602 		if (mmrover->useptr == baseptr)
    603 			break;
    604 
    605 		mmrover = mmrover->next;
    606 
    607 		if (!mmrover)
    608 			mmrover = mmhead;
    609 		else if (mmrover == start)
    610 			Quit ("MM_SetPurge: Block not found!");
    611 
    612 	} while (1);
    613 
    614 	mmrover->attributes &= ~PURGEBITS;
    615 	mmrover->attributes |= purge;
    616 }
    617 
    618 //==========================================================================
    619 
    620 /*
    621 =====================
    622 =
    623 = MM_SetLock
    624 =
    625 = Locks / unlocks the block
    626 =
    627 =====================
    628 */
    629 
    630 void MM_SetLock (memptr *baseptr, boolean locked)
    631 {
    632 	mmblocktype far *start;
    633 
    634 	start = mmrover;
    635 
    636 	do
    637 	{
    638 		if (mmrover->useptr == baseptr)
    639 			break;
    640 
    641 		mmrover = mmrover->next;
    642 
    643 		if (!mmrover)
    644 			mmrover = mmhead;
    645 		else if (mmrover == start)
    646 			Quit ("MM_SetLock: Block not found!");
    647 
    648 	} while (1);
    649 
    650 	mmrover->attributes &= ~LOCKBIT;
    651 	mmrover->attributes |= locked*LOCKBIT;
    652 }
    653 
    654 //==========================================================================
    655 
    656 /*
    657 =====================
    658 =
    659 = MM_SortMem
    660 =
    661 = Throws out all purgable stuff and compresses movable blocks
    662 =
    663 =====================
    664 */
    665 
    666 void MM_SortMem (void)
    667 {
    668 	mmblocktype far *scan,far *last,far *next;
    669 	unsigned	start,length,source,dest;
    670 	int			playing;
    671 
    672 	//
    673 	// lock down a currently playing sound
    674 	//
    675 	playing = SD_SoundPlaying ();
    676 	if (playing)
    677 	{
    678 		switch (SoundMode)
    679 		{
    680 		case sdm_PC:
    681 			playing += STARTPCSOUNDS;
    682 			break;
    683 		case sdm_AdLib:
    684 			playing += STARTADLIBSOUNDS;
    685 			break;
    686 		}
    687 		MM_SetLock(&(memptr)audiosegs[playing],true);
    688 	}
    689 
    690 
    691 	SD_StopSound();
    692 
    693 	if (beforesort)
    694 		beforesort();
    695 
    696 	scan = mmhead;
    697 
    698 	last = NULL;		// shut up compiler warning
    699 
    700 	while (scan)
    701 	{
    702 		if (scan->attributes & LOCKBIT)
    703 		{
    704 		//
    705 		// block is locked, so try to pile later blocks right after it
    706 		//
    707 			start = scan->start + scan->length;
    708 		}
    709 		else
    710 		{
    711 			if (scan->attributes & PURGEBITS)
    712 			{
    713 			//
    714 			// throw out the purgable block
    715 			//
    716 				next = scan->next;
    717 				FREEBLOCK(scan);
    718 				last->next = next;
    719 				scan = next;
    720 				continue;
    721 			}
    722 			else
    723 			{
    724 			//
    725 			// push the non purgable block on top of the last moved block
    726 			//
    727 				if (scan->start != start)
    728 				{
    729 					length = scan->length;
    730 					source = scan->start;
    731 					dest = start;
    732 					while (length > 0xf00)
    733 					{
    734 						movedata(source,0,dest,0,0xf00*16);
    735 						length -= 0xf00;
    736 						source += 0xf00;
    737 						dest += 0xf00;
    738 					}
    739 					movedata(source,0,dest,0,length*16);
    740 
    741 					scan->start = start;
    742 					*(unsigned *)scan->useptr = start;
    743 				}
    744 				start = scan->start + scan->length;
    745 			}
    746 		}
    747 
    748 		last = scan;
    749 		scan = scan->next;		// go to next block
    750 	}
    751 
    752 	mmrover = mmhead;
    753 
    754 	if (aftersort)
    755 		aftersort();
    756 
    757 	if (playing)
    758 		MM_SetLock(&(memptr)audiosegs[playing],false);
    759 }
    760 
    761 
    762 //==========================================================================
    763 
    764 /*
    765 =====================
    766 =
    767 = MM_ShowMemory
    768 =
    769 =====================
    770 */
    771 
    772 void MM_ShowMemory (void)
    773 {
    774 	mmblocktype far *scan;
    775 	unsigned color,temp,x,y;
    776 	long	end,owner;
    777 	char    scratch[80],str[10];
    778 
    779 	temp = bufferofs;
    780 	bufferofs = displayofs;
    781 	scan = mmhead;
    782 
    783 	end = -1;
    784 
    785 	while (scan)
    786 	{
    787 		if (scan->attributes & PURGEBITS)
    788 			color = 5;		// dark purple = purgable
    789 		else
    790 			color = 9;		// medium blue = non purgable
    791 		if (scan->attributes & LOCKBIT)
    792 			color = 12;		// red = locked
    793 		if (scan->start<=end)
    794 			Quit ("MM_ShowMemory: Memory block order currupted!");
    795 		end = scan->length-1;
    796 		y = scan->start/320;
    797 		x = scan->start%320;
    798 		VW_Hlin(x,x+end,y,color);
    799 		VW_Plot(x,y,15);
    800 		if (scan->next && scan->next->start > end+1)
    801 			VW_Hlin(x+end+1,x+(scan->next->start-scan->start),y,0);	// black = free
    802 
    803 		scan = scan->next;
    804 	}
    805 
    806 	VW_FadeIn ();
    807 	IN_Ack();
    808 
    809 	bufferofs = temp;
    810 }
    811 
    812 //==========================================================================
    813 
    814 /*
    815 =====================
    816 =
    817 = MM_DumpData
    818 =
    819 =====================
    820 */
    821 
    822 void MM_DumpData (void)
    823 {
    824 	mmblocktype far *scan,far *best;
    825 	long	lowest,oldlowest;
    826 	unsigned	owner;
    827 	char	lock,purge;
    828 	FILE	*dumpfile;
    829 
    830 
    831 	free (nearheap);
    832 	dumpfile = fopen ("MMDUMP.TXT","w");
    833 	if (!dumpfile)
    834 		Quit ("MM_DumpData: Couldn't open MMDUMP.TXT!");
    835 
    836 	lowest = -1;
    837 	do
    838 	{
    839 		oldlowest = lowest;
    840 		lowest = 0xffff;
    841 
    842 		scan = mmhead;
    843 		while (scan)
    844 		{
    845 			owner = (unsigned)scan->useptr;
    846 
    847 			if (owner && owner<lowest && owner > oldlowest)
    848 			{
    849 				best = scan;
    850 				lowest = owner;
    851 			}
    852 
    853 			scan = scan->next;
    854 		}
    855 
    856 		if (lowest != 0xffff)
    857 		{
    858 			if (best->attributes & PURGEBITS)
    859 				purge = 'P';
    860 			else
    861 				purge = '-';
    862 			if (best->attributes & LOCKBIT)
    863 				lock = 'L';
    864 			else
    865 				lock = '-';
    866 			fprintf (dumpfile,"0x%p (%c%c) = %u\n"
    867 			,(unsigned)lowest,lock,purge,best->length);
    868 		}
    869 
    870 	} while (lowest != 0xffff);
    871 
    872 	fclose (dumpfile);
    873 	Quit ("MMDUMP.TXT created.");
    874 }
    875 
    876 //==========================================================================
    877 
    878 
    879 /*
    880 ======================
    881 =
    882 = MM_UnusedMemory
    883 =
    884 = Returns the total free space without purging
    885 =
    886 ======================
    887 */
    888 
    889 long MM_UnusedMemory (void)
    890 {
    891 	unsigned free;
    892 	mmblocktype far *scan;
    893 
    894 	free = 0;
    895 	scan = mmhead;
    896 
    897 	while (scan->next)
    898 	{
    899 		free += scan->next->start - (scan->start + scan->length);
    900 		scan = scan->next;
    901 	}
    902 
    903 	return free*16l;
    904 }
    905 
    906 //==========================================================================
    907 
    908 
    909 /*
    910 ======================
    911 =
    912 = MM_TotalFree
    913 =
    914 = Returns the total free space with purging
    915 =
    916 ======================
    917 */
    918 
    919 long MM_TotalFree (void)
    920 {
    921 	unsigned free;
    922 	mmblocktype far *scan;
    923 
    924 	free = 0;
    925 	scan = mmhead;
    926 
    927 	while (scan->next)
    928 	{
    929 		if ((scan->attributes&PURGEBITS) && !(scan->attributes&LOCKBIT))
    930 			free += scan->length;
    931 		free += scan->next->start - (scan->start + scan->length);
    932 		scan = scan->next;
    933 	}
    934 
    935 	return free*16l;
    936 }
    937 
    938 //==========================================================================
    939 
    940 /*
    941 =====================
    942 =
    943 = MM_BombOnError
    944 =
    945 =====================
    946 */
    947 
    948 void MM_BombOnError (boolean bomb)
    949 {
    950 	bombonerror = bomb;
    951 }
    952 
    953