1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6 
7 This file is part of the OpenJK source code.
8 
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12 
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22 
23 // Created 2/3/03 by Brian Osman - split Zone code from common.cpp
24 
25 #include "q_shared.h"
26 #include "qcommon.h"
27 
28 #ifdef DEBUG_ZONE_ALLOCS
29 #include "sstring.h"
30 int giZoneSnaphotNum=0;
31 #define DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE 256
32 typedef sstring<DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE> sDebugString_t;
33 #endif
34 
35 static void Z_Details_f(void);
36 
37 // define a string table of all mem tags...
38 //
39 #ifdef TAGDEF	// itu?
40 #undef TAGDEF
41 #endif
42 #define TAGDEF(blah) #blah
43 static const char *psTagStrings[TAG_COUNT+1]=	// +1 because TAG_COUNT will itself become a string here. Oh well.
44 {
45 	#include "tags.h"
46 };
47 
48 // This handles zone memory allocation.
49 // It is a wrapper around malloc with a tag id and a magic number at the start
50 
51 #define ZONE_MAGIC			0x21436587
52 
53 // if you change ANYTHING in this structure, be sure to update the tables below using DEF_STATIC...
54 //
55 typedef struct zoneHeader_s
56 {
57 		int					iMagic;
58 		memtag_t			eTag;
59 		int					iSize;
60 struct	zoneHeader_s		*pNext;
61 struct	zoneHeader_s		*pPrev;
62 
63 #ifdef DEBUG_ZONE_ALLOCS
64 		char				sSrcFileBaseName[MAX_QPATH];
65 		int					iSrcFileLineNum;
66 		char				sOptionalLabel[DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE];
67 		int					iSnapshotNumber;
68 #endif
69 
70 } zoneHeader_t;
71 
72 typedef struct
73 {
74 	int iMagic;
75 
76 } zoneTail_t;
77 
ZoneTailFromHeader(zoneHeader_t * pHeader)78 static inline zoneTail_t *ZoneTailFromHeader(zoneHeader_t *pHeader)
79 {
80 	return (zoneTail_t*) ( (char*)pHeader + sizeof(*pHeader) + pHeader->iSize );
81 }
82 
83 #ifdef DETAILED_ZONE_DEBUG_CODE
84 std::map <void*,int> mapAllocatedZones;
85 #endif
86 
87 
88 typedef struct zoneStats_s
89 {
90 	int		iCount;
91 	int		iCurrent;
92 	int		iPeak;
93 
94 	// I'm keeping these updated on the fly, since it's quicker for cache-pool
95 	//	purposes rather than recalculating each time...
96 	//
97 	int		iSizesPerTag [TAG_COUNT];
98 	int		iCountsPerTag[TAG_COUNT];
99 
100 } zoneStats_t;
101 
102 typedef struct zone_s
103 {
104 	zoneStats_t				Stats;
105 	zoneHeader_t			Header;
106 } zone_t;
107 
108 cvar_t	*com_validateZone;
109 
110 zone_t	TheZone = {};
111 
112 
113 
114 
115 // Scans through the linked list of mallocs and makes sure no data has been overwritten
116 
Z_Validate(void)117 int Z_Validate(void)
118 {
119 	int ret=0;
120 	if(!com_validateZone || !com_validateZone->integer)
121 	{
122 		return ret;
123 	}
124 
125 	zoneHeader_t *pMemory = TheZone.Header.pNext;
126 	while (pMemory)
127 	{
128 		#ifdef DETAILED_ZONE_DEBUG_CODE
129 		// this won't happen here, but wtf?
130 		int& iAllocCount = mapAllocatedZones[pMemory];
131 		if (iAllocCount <= 0)
132 		{
133 			Com_Error(ERR_FATAL, "Z_Validate(): Bad block allocation count!");
134 			return ret;
135 		}
136 		#endif
137 
138 		if(pMemory->iMagic != ZONE_MAGIC)
139 		{
140 			Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone header!");
141 			return ret;
142 		}
143 
144 		// this block of code is intended to make sure all of the data is paged in
145 		if (pMemory->eTag != TAG_IMAGE_T
146 			&& pMemory->eTag != TAG_MODEL_MD3
147 			&& pMemory->eTag != TAG_MODEL_GLM
148 			&& pMemory->eTag != TAG_MODEL_GLA )	//don't bother with disk caches as they've already been hit or will be thrown out next
149 		{
150 			unsigned char *memstart = (unsigned char *)pMemory;
151 			int totalSize = pMemory->iSize;
152 			while (totalSize > 4096)
153 			{
154 				memstart += 4096;
155 				ret += (int)(*memstart); // this fools the optimizer
156 				totalSize -= 4096;
157 			}
158 		}
159 
160 
161 		if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC)
162 		{
163 			Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone tail!");
164 			return ret;
165 		}
166 
167 		pMemory = pMemory->pNext;
168 	}
169 	return ret;
170 }
171 
172 
173 
174 // static mem blocks to reduce a lot of small zone overhead
175 //
176 #pragma pack(push)
177 #pragma pack(1)
178 typedef struct
179 {
180 	zoneHeader_t	Header;
181 //	byte mem[0];
182 	zoneTail_t		Tail;
183 } StaticZeroMem_t;
184 
185 typedef struct
186 {
187 	zoneHeader_t	Header;
188 	byte mem[2];
189 	zoneTail_t		Tail;
190 } StaticMem_t;
191 #pragma pack(pop)
192 
193 const static StaticZeroMem_t gZeroMalloc  =
194 	{ {ZONE_MAGIC, TAG_STATIC,0,NULL,NULL},{ZONE_MAGIC}};
195 
196 #ifdef DEBUG_ZONE_ALLOCS
197 #define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL, "<static>",0,"",0},{_char,'\0'},{ZONE_MAGIC}
198 #else
199 #define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL			        },{_char,'\0'},{ZONE_MAGIC}
200 #endif
201 
202 const static StaticMem_t gEmptyString =
203 	{ DEF_STATIC('\0') };
204 
205 const static StaticMem_t gNumberString[] = {
206 	{ DEF_STATIC('0') },
207 	{ DEF_STATIC('1') },
208 	{ DEF_STATIC('2') },
209 	{ DEF_STATIC('3') },
210 	{ DEF_STATIC('4') },
211 	{ DEF_STATIC('5') },
212 	{ DEF_STATIC('6') },
213 	{ DEF_STATIC('7') },
214 	{ DEF_STATIC('8') },
215 	{ DEF_STATIC('9') },
216 };
217 
218 qboolean gbMemFreeupOccured = qfalse;
219 
220 #ifdef DEBUG_ZONE_ALLOCS
221 // returns actual filename only, no path
222 // (copes with either slash-scheme for names)
223 //
224 // (normally I'd call another function for this, but this is supposed to be engine-independent,
225 //	 so a certain amount of re-invention of the wheel is to be expected...)
226 //
_D_Z_Filename_WithoutPath(const char * psFilename)227 char *_D_Z_Filename_WithoutPath(const char *psFilename)
228 {
229 	static char sString[ MAX_QPATH ];
230 
231 	const char *psCopyPos = psFilename;
232 
233 	while (*psFilename)
234 	{
235 		if (*psFilename == PATH_SEP)
236 			psCopyPos = psFilename+1;
237 		psFilename++;
238 	}
239 
240 	strcpy(sString,psCopyPos);
241 
242 	return sString;
243 }
244 #endif
245 
246 #include "../rd-common/tr_public.h"	// sorta hack sorta not
247 extern refexport_t re;
248 
249 #ifdef DEBUG_ZONE_ALLOCS
_D_Z_Malloc(int iSize,memtag_t eTag,qboolean bZeroit,const char * psFile,int iLine)250 void *_D_Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, const char *psFile, int iLine)
251 #else
252 void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int /*unusedAlign*/)
253 #endif
254 {
255 	gbMemFreeupOccured = qfalse;
256 
257 	if (iSize == 0)
258 	{
259 		zoneHeader_t *pMemory = (zoneHeader_t *) &gZeroMalloc;
260 		return &pMemory[1];
261 	}
262 
263 	// Add in tracking info and round to a longword...  (ignore longword aligning now we're not using contiguous blocks)
264 	//
265 //	int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t) + 3) & 0xfffffffc;
266 	int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t));
267 
268 	// Allocate a chunk...
269 	//
270 	zoneHeader_t *pMemory = NULL;
271 	while (pMemory == NULL)
272 	{
273 		if (gbMemFreeupOccured)
274 		{
275 			Sys_Sleep(1000);	// sleep for a second, so Windows has a chance to shuffle mem to de-swiss-cheese it
276 		}
277 
278 		if (bZeroit) {
279 			pMemory = (zoneHeader_t *) calloc ( iRealSize, 1 );
280 		} else {
281 			pMemory = (zoneHeader_t *) malloc ( iRealSize );
282 		}
283 		if (!pMemory)
284 		{
285 			// new bit, if we fail to malloc memory, try dumping some of the cached stuff that's non-vital and try again...
286 			//
287 
288 			// ditch the BSP cache...
289 			//
290 			if (CM_DeleteCachedMap(qfalse))
291 			{
292 				gbMemFreeupOccured = qtrue;
293 				continue;		// we've just ditched a whole load of memory, so try again with the malloc
294 			}
295 
296 
297 			// ditch any sounds not used on this level...
298 			//
299 			extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel);
300 			if (SND_RegisterAudio_LevelLoadEnd(qtrue))
301 			{
302 				gbMemFreeupOccured = qtrue;
303 				continue;		// we've dropped at least one sound, so try again with the malloc
304 			}
305 
306 
307 			// ditch any image_t's (and associated GL texture mem) not used on this level...
308 			//
309 			if (re.RegisterImages_LevelLoadEnd())
310 			{
311 				gbMemFreeupOccured = qtrue;
312 				continue;		// we've dropped at least one image, so try again with the malloc
313 			}
314 
315 
316 			// ditch the model-binaries cache...  (must be getting desperate here!)
317 			//
318 			if (re.RegisterModels_LevelLoadEnd(qtrue))
319 			{
320 				gbMemFreeupOccured = qtrue;
321 				continue;
322 			}
323 
324 			// as a last panic measure, dump all the audio memory, but not if we're in the audio loader
325 			//	(which is annoying, but I'm not sure how to ensure we're not dumping any memory needed by the sound
326 			//	currently being loaded if that was the case)...
327 			//
328 			// note that this keeps querying until it's freed up as many bytes as the requested size, but freeing
329 			//	several small blocks might not mean that one larger one is satisfiable after freeup, however that'll
330 			//	just make it go round again and try for freeing up another bunch of blocks until the total is satisfied
331 			//	again (though this will have freed twice the requested amount in that case), so it'll either work
332 			//	eventually or not free up enough and drop through to the final ERR_DROP. No worries...
333 			//
334 			extern qboolean gbInsideLoadSound;
335 			extern int SND_FreeOldestSound(void);	// I had to add a void-arg version of this because of link issues, sigh
336 			if (!gbInsideLoadSound)
337 			{
338 				int iBytesFreed = SND_FreeOldestSound();
339 				if (iBytesFreed)
340 				{
341 					int iTheseBytesFreed = 0;
342 					while ( (iTheseBytesFreed = SND_FreeOldestSound()) != 0)
343 					{
344 						iBytesFreed += iTheseBytesFreed;
345 						if (iBytesFreed >= iRealSize)
346 							break;	// early opt-out since we've managed to recover enough (mem-contiguity issues aside)
347 					}
348 					gbMemFreeupOccured = qtrue;
349 					continue;
350 				}
351 			}
352 
353 			// sigh, dunno what else to try, I guess we'll have to give up and report this as an out-of-mem error...
354 			//
355 			// findlabel:  "recovermem"
356 
357 			Com_Printf(S_COLOR_RED"Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]);
358 			Z_Details_f();
359 			Com_Error(ERR_FATAL,"(Repeat): Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]);
360 			return NULL;
361 		}
362 	}
363 
364 
365 #ifdef DEBUG_ZONE_ALLOCS
366 	Q_strncpyz(pMemory->sSrcFileBaseName, _D_Z_Filename_WithoutPath(psFile), sizeof(pMemory->sSrcFileBaseName));
367 	pMemory->iSrcFileLineNum	= iLine;
368 	pMemory->sOptionalLabel[0]	= '\0';
369 	pMemory->iSnapshotNumber	= giZoneSnaphotNum;
370 #endif
371 
372 	// Link in
373 	pMemory->iMagic	= ZONE_MAGIC;
374 	pMemory->eTag	= eTag;
375 	pMemory->iSize	= iSize;
376 	pMemory->pNext  = TheZone.Header.pNext;
377 	TheZone.Header.pNext = pMemory;
378 	if (pMemory->pNext)
379 	{
380 		pMemory->pNext->pPrev = pMemory;
381 	}
382 	pMemory->pPrev = &TheZone.Header;
383 	//
384 	// add tail...
385 	//
386 	ZoneTailFromHeader(pMemory)->iMagic = ZONE_MAGIC;
387 
388 	// Update stats...
389 	//
390 	TheZone.Stats.iCurrent += iSize;
391 	TheZone.Stats.iCount++;
392 	TheZone.Stats.iSizesPerTag	[eTag] += iSize;
393 	TheZone.Stats.iCountsPerTag	[eTag]++;
394 
395 	if (TheZone.Stats.iCurrent > TheZone.Stats.iPeak)
396 	{
397 		TheZone.Stats.iPeak	= TheZone.Stats.iCurrent;
398 	}
399 
400 #ifdef DETAILED_ZONE_DEBUG_CODE
401 	mapAllocatedZones[pMemory]++;
402 #endif
403 
404 	Z_Validate();	// check for corruption
405 
406 	void *pvReturnMem = &pMemory[1];
407 	return pvReturnMem;
408 }
409 
410 // Special wrapper around Z_Malloc for better separation between the main engine
411 // code and the bundled minizip library.
412 
413 extern "C" Q_EXPORT void* openjk_minizip_malloc(int size);
414 extern "C" Q_EXPORT int openjk_minizip_free(void* to_free);
415 
openjk_minizip_malloc(int size)416 void* openjk_minizip_malloc(int size)
417 {
418     return Z_Malloc(size, TAG_MINIZIP, qfalse);
419 }
420 
openjk_minizip_free(void * to_free)421 int openjk_minizip_free(void *to_free)
422 {
423     return Z_Free(to_free);
424 }
425 
426 // used during model cacheing to save an extra malloc, lets us morph the disk-load buffer then
427 //	just not fs_freefile() it afterwards.
428 //
Z_MorphMallocTag(void * pvAddress,memtag_t eDesiredTag)429 void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag )
430 {
431 	zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
432 
433 	if (pMemory->iMagic != ZONE_MAGIC)
434 	{
435 		Com_Error(ERR_FATAL, "Z_MorphMallocTag(): Not a valid zone header!");
436 		return;	// won't get here
437 	}
438 
439 	// DEC existing tag stats...
440 	//
441 //	TheZone.Stats.iCurrent	- unchanged
442 //	TheZone.Stats.iCount	- unchanged
443 	TheZone.Stats.iSizesPerTag	[pMemory->eTag] -= pMemory->iSize;
444 	TheZone.Stats.iCountsPerTag	[pMemory->eTag]--;
445 
446 	// morph...
447 	//
448 	pMemory->eTag = eDesiredTag;
449 
450 	// INC new tag stats...
451 	//
452 //	TheZone.Stats.iCurrent	- unchanged
453 //	TheZone.Stats.iCount	- unchanged
454 	TheZone.Stats.iSizesPerTag	[pMemory->eTag] += pMemory->iSize;
455 	TheZone.Stats.iCountsPerTag	[pMemory->eTag]++;
456 }
457 
458 
Zone_FreeBlock(zoneHeader_t * pMemory)459 static int Zone_FreeBlock(zoneHeader_t *pMemory)
460 {
461 	const int iSize = pMemory->iSize;
462 	if (pMemory->eTag != TAG_STATIC)	// belt and braces, should never hit this though
463 	{
464 		// Update stats...
465 		//
466 		TheZone.Stats.iCount--;
467 		TheZone.Stats.iCurrent -= pMemory->iSize;
468 		TheZone.Stats.iSizesPerTag	[pMemory->eTag] -= pMemory->iSize;
469 		TheZone.Stats.iCountsPerTag	[pMemory->eTag]--;
470 
471 		// Sanity checks...
472 		//
473 		assert(pMemory->pPrev->pNext == pMemory);
474 		assert(!pMemory->pNext || (pMemory->pNext->pPrev == pMemory));
475 
476 		// Unlink and free...
477 		//
478 		pMemory->pPrev->pNext = pMemory->pNext;
479 		if(pMemory->pNext)
480 		{
481 			pMemory->pNext->pPrev = pMemory->pPrev;
482 		}
483 
484 		//debugging double frees
485 		pMemory->iMagic = INT_ID('F','R','E','E');
486 		free (pMemory);
487 
488 
489 		#ifdef DETAILED_ZONE_DEBUG_CODE
490 		// this has already been checked for in execution order, but wtf?
491 		int& iAllocCount = mapAllocatedZones[pMemory];
492 		if (iAllocCount == 0)
493 		{
494 			Com_Error(ERR_FATAL, "Zone_FreeBlock(): Double-freeing block!");
495 			return -1;
496 		}
497 		iAllocCount--;
498 		#endif
499 	}
500 	return iSize;
501 }
502 
503 // stats-query function to to see if it's our malloc
504 // returns block size if so
Z_IsFromZone(const void * pvAddress,memtag_t eTag)505 qboolean Z_IsFromZone(const void *pvAddress, memtag_t eTag)
506 {
507 	const zoneHeader_t *pMemory = ((const zoneHeader_t *)pvAddress) - 1;
508 #if 1	//debugging double free
509 	if (pMemory->iMagic == INT_ID('F','R','E','E'))
510 	{
511 		Com_Printf("Z_IsFromZone(%x): Ptr has been freed already!(%9s)\n",pvAddress,pvAddress);
512 		return qfalse;
513 	}
514 #endif
515 	if (pMemory->iMagic != ZONE_MAGIC)
516 	{
517 		return qfalse;
518 	}
519 
520 	//looks like it is from our zone, let's double check the tag
521 
522 	if (pMemory->eTag != eTag)
523 	{
524 		return qfalse;
525 	}
526 
527 	return (qboolean)(pMemory->iSize != 0);
528 }
529 
530 // stats-query function to ask how big a malloc is...
531 //
Z_Size(void * pvAddress)532 int Z_Size(void *pvAddress)
533 {
534 	zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
535 
536 	if (pMemory->eTag == TAG_STATIC)
537 	{
538 		return 0;	// kind of
539 	}
540 
541 	if (pMemory->iMagic != ZONE_MAGIC)
542 	{
543 		Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!");
544 		return 0;	// won't get here
545 	}
546 
547 	return pMemory->iSize;
548 }
549 
550 
551 #ifdef DEBUG_ZONE_ALLOCS
Z_Label(const void * pvAddress,const char * psLabel)552 void Z_Label(const void *pvAddress, const char *psLabel)
553 {
554 	zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
555 
556 	if (pMemory->eTag == TAG_STATIC)
557 	{
558 		return;
559 	}
560 
561 	if (pMemory->iMagic != ZONE_MAGIC)
562 	{
563 		Com_Error(ERR_FATAL, "_D_Z_Label(): Not a valid zone header!");
564 	}
565 
566 	Q_strncpyz(	pMemory->sOptionalLabel, psLabel, sizeof(pMemory->sOptionalLabel));
567 }
568 #endif
569 
570 
571 
572 // Frees a block of memory...
573 //
Z_Free(void * pvAddress)574 int Z_Free(void *pvAddress)
575 {
576 	if (!TheZone.Stats.iCount)
577 	{
578 		//Com_Error(ERR_FATAL, "Z_Free(): Zone has been cleard already!");
579 		Com_Printf("Z_Free(%x): Zone has been cleard already!\n",pvAddress);
580 		return -1;
581 	}
582 
583 	zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1;
584 
585 #if 1	//debugging double free
586 	if (pMemory->iMagic == INT_ID('F','R','E','E'))
587 	{
588 		Com_Error(ERR_FATAL, "Z_Free(%s): Block already-freed, or not allocated through Z_Malloc!",pvAddress);
589 		return -1;
590 	}
591 #endif
592 
593 	if (pMemory->eTag == TAG_STATIC)
594 	{
595 		return 0;
596 	}
597 
598 	#ifdef DETAILED_ZONE_DEBUG_CODE
599 	//
600 	// check this error *before* barfing on bad magics...
601 	//
602 	int& iAllocCount = mapAllocatedZones[pMemory];
603 	if (iAllocCount <= 0)
604 	{
605 		Com_Error(ERR_FATAL, "Z_Free(): Block already-freed, or not allocated through Z_Malloc!");
606 		return -1;
607 	}
608 	#endif
609 
610 	if (pMemory->iMagic != ZONE_MAGIC)
611 	{
612 		Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!");
613 		return -1;
614 	}
615 	if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC)
616 	{
617 		Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone tail!");
618 		return -1;
619 	}
620 
621 	return Zone_FreeBlock(pMemory);
622 }
623 
624 
Z_MemSize(memtag_t eTag)625 int Z_MemSize(memtag_t eTag)
626 {
627 	return TheZone.Stats.iSizesPerTag[eTag];
628 }
629 
630 // Frees all blocks with the specified tag...
631 //
Z_TagFree(memtag_t eTag)632 void Z_TagFree(memtag_t eTag)
633 {
634 //#ifdef _DEBUG
635 //	int iZoneBlocks = TheZone.Stats.iCount;
636 //#endif
637 
638 	zoneHeader_t *pMemory = TheZone.Header.pNext;
639 	while (pMemory)
640 	{
641 		zoneHeader_t *pNext = pMemory->pNext;
642 		if ( (eTag == TAG_ALL) || (pMemory->eTag == eTag))
643 		{
644 			Zone_FreeBlock(pMemory);
645 		}
646 		pMemory = pNext;
647 	}
648 
649 // these stupid pragmas don't work here???!?!?!
650 //
651 //#ifdef _DEBUG
652 //#pragma warning( disable : 4189)
653 //	int iBlocksFreed = iZoneBlocks - TheZone.Stats.iCount;
654 //#pragma warning( default : 4189)
655 //#endif
656 }
657 
658 
659 #ifdef DEBUG_ZONE_ALLOCS
_D_S_Malloc(int iSize,const char * psFile,int iLine)660 void *_D_S_Malloc ( int iSize, const char *psFile, int iLine)
661 {
662 	return _D_Z_Malloc( iSize, TAG_SMALL, qfalse, psFile, iLine );
663 }
664 #else
S_Malloc(int iSize)665 void *S_Malloc( int iSize )
666 {
667 	return Z_Malloc( iSize, TAG_SMALL, qfalse);
668 }
669 #endif
670 
671 
672 #ifdef _DEBUG
Z_MemRecoverTest_f(void)673 static void Z_MemRecoverTest_f(void)
674 {
675 	// needs to be in _DEBUG only, not good for final game!
676 	//
677 	if ( Cmd_Argc() != 2 ) {
678 		Com_Printf( "Usage: zone_memrecovertest max2alloc\n" );
679 		return;
680 	}
681 
682 	int iMaxAlloc = 1024*1024*atoi( Cmd_Argv(1) );
683 	int iTotalMalloc = 0;
684 	while (1)
685 	{
686 		const int iThisMalloc = 5* (1024 * 1024);
687 		Z_Malloc(iThisMalloc, TAG_SPECIAL_MEM_TEST, qfalse);	// and lose, just to consume memory
688 		iTotalMalloc += iThisMalloc;
689 
690 		if (gbMemFreeupOccured || (iTotalMalloc >= iMaxAlloc) )
691 			break;
692 	}
693 
694 	Z_TagFree(TAG_SPECIAL_MEM_TEST);
695 }
696 #endif
697 
698 // Gives a summary of the zone memory usage
699 
Z_Stats_f(void)700 static void Z_Stats_f(void)
701 {
702 	Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n",
703 								  TheZone.Stats.iCurrent,
704 									        (float)TheZone.Stats.iCurrent / 1024.0f / 1024.0f,
705 													  TheZone.Stats.iCount
706 				);
707 
708 	Com_Printf("The zone peaked at %d bytes (%.2fMB)\n",
709 									TheZone.Stats.iPeak,
710 									         (float)TheZone.Stats.iPeak / 1024.0f / 1024.0f
711 				);
712 }
713 
714 // Gives a detailed breakdown of the memory blocks in the zone
715 //
Z_Details_f(void)716 static void Z_Details_f(void)
717 {
718 
719 	Com_Printf("---------------------------------------------------------------------------\n");
720 	Com_Printf("%20s %9s\n","Zone Tag","Bytes");
721 	Com_Printf("%20s %9s\n","--------","-----");
722 	for (int i=0; i<TAG_COUNT; i++)
723 	{
724 		int iThisCount = TheZone.Stats.iCountsPerTag[i];
725 		int iThisSize  = TheZone.Stats.iSizesPerTag	[i];
726 
727 		if (iThisCount)
728 		{
729 			// can you believe that using %2.2f as a format specifier doesn't bloody work?
730 			//	It ignores the left-hand specifier. Sigh, now I've got to do shit like this...
731 			//
732 			float	fSize		= (float)(iThisSize) / 1024.0f / 1024.0f;
733 			int		iSize		= fSize;
734 			int		iRemainder 	= 100.0f * (fSize - floor(fSize));
735 			Com_Printf("%20s %9d (%2d.%02dMB) in %6d blocks (%9d Bytes/block)\n",
736 				psTagStrings[i],
737 				iThisSize,
738 				iSize, iRemainder,
739 				iThisCount, iThisSize / iThisCount);
740 		}
741 	}
742 	Com_Printf("---------------------------------------------------------------------------\n");
743 
744 	Z_Stats_f();
745 }
746 
747 #ifdef DEBUG_ZONE_ALLOCS
748 typedef std::map <sDebugString_t, int> LabelRefCount_t;	// yet another place where Gil's string class works and MS's doesn't
749 typedef std::map <sDebugString_t, LabelRefCount_t>	TagBlockLabels_t;
750 
751 static TagBlockLabels_t AllTagBlockLabels;
752 
Z_Snapshot_f(void)753 static void Z_Snapshot_f(void)
754 {
755 	AllTagBlockLabels.clear();
756 
757 	zoneHeader_t *pMemory = TheZone.Header.pNext;
758 	while (pMemory)
759 	{
760 		AllTagBlockLabels[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++;
761 		pMemory = pMemory->pNext;
762 	}
763 
764 	giZoneSnaphotNum++;
765 	Com_Printf("Ok.    ( Current snapshot num is now %d )\n",giZoneSnaphotNum);
766 }
767 
Z_TagDebug_f(void)768 static void Z_TagDebug_f(void)
769 {
770 	TagBlockLabels_t AllTagBlockLabels_Local;
771 	qboolean bSnapShotTestActive = qfalse;
772 
773 	memtag_t eTag = TAG_ALL;
774 
775 	const char *psTAGName = Cmd_Argv(1);
776 	if (psTAGName[0])
777 	{
778 		// check optional arg...
779 		//
780 		if (!Q_stricmp(psTAGName,"#snap"))
781 		{
782 			bSnapShotTestActive = qtrue;
783 
784 			AllTagBlockLabels_Local = AllTagBlockLabels;	// horrible great STL copy
785 
786 			psTAGName = Cmd_Argv(2);
787 		}
788 
789 		if (psTAGName[0])
790 		{
791 			// skip over "tag_" if user supplied it...
792 			//
793 			if (!Q_stricmpn(psTAGName,"TAG_",4))
794 			{
795 				psTAGName += 4;
796 			}
797 
798 			// see if the user specified a valid tag...
799 			//
800 			for (int i=0; i<TAG_COUNT; i++)
801 			{
802 				if (!Q_stricmp(psTAGName,psTagStrings[i]))
803 				{
804 					eTag = (memtag_t) i;
805 					break;
806 				}
807 			}
808 		}
809 	}
810 	else
811 	{
812 		Com_Printf("Usage: 'zone_tagdebug [#snap] <tag>', e.g. TAG_GHOUL2, TAG_ALL (careful!)\n");
813 		return;
814 	}
815 
816 	Com_Printf("Dumping debug data for tag \"%s\"...%s\n\n",psTagStrings[eTag], bSnapShotTestActive?"( since snapshot only )":"");
817 
818 	Com_Printf("%8s"," ");	// to compensate for code further down:   Com_Printf("(%5d) ",iBlocksListed);
819 	if (eTag == TAG_ALL)
820 	{
821 		Com_Printf("%20s ","Zone Tag");
822 	}
823 	Com_Printf("%9s\n","Bytes");
824 	Com_Printf("%8s"," ");
825 	if (eTag == TAG_ALL)
826 	{
827 		Com_Printf("%20s ","--------");
828 	}
829 	Com_Printf("%9s\n","-----");
830 
831 
832 	if (bSnapShotTestActive)
833 	{
834 		// dec ref counts in last snapshot for all current blocks (which will make new stuff go negative)
835 		//
836 		zoneHeader_t *pMemory = TheZone.Header.pNext;
837 		while (pMemory)
838 		{
839 			if (pMemory->eTag == eTag || eTag == TAG_ALL)
840 			{
841 				AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]--;
842 			}
843 			pMemory = pMemory->pNext;
844 		}
845 	}
846 
847 	// now dump them out...
848 	//
849 	int iBlocksListed = 0;
850 	int iTotalSize = 0;
851 	zoneHeader_t *pMemory = TheZone.Header.pNext;
852 	while (pMemory)
853 	{
854 		if (	(pMemory->eTag == eTag	|| eTag == TAG_ALL)
855 			&&  (!bSnapShotTestActive	|| (pMemory->iSnapshotNumber == giZoneSnaphotNum && AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel] <0) )
856 			)
857 		{
858 			float	fSize		= (float)(pMemory->iSize) / 1024.0f / 1024.0f;
859 			int		iSize		= fSize;
860 			int		iRemainder 	= 100.0f * (fSize - floor(fSize));
861 
862 			Com_Printf("(%5d) ",iBlocksListed);
863 
864 			if (eTag == TAG_ALL)
865 			{
866 				Com_Printf("%20s",psTagStrings[pMemory->eTag]);
867 			}
868 
869 			Com_Printf(" %9d (%2d.%02dMB) File: \"%s\", Line: %d\n",
870 						  pMemory->iSize,
871  							  iSize,iRemainder,
872 												pMemory->sSrcFileBaseName,
873 															pMemory->iSrcFileLineNum
874 					   );
875 			if (pMemory->sOptionalLabel[0])
876 			{
877 				Com_Printf("( Label: \"%s\" )\n",pMemory->sOptionalLabel);
878 			}
879 			iBlocksListed++;
880 			iTotalSize += pMemory->iSize;
881 
882 			if (bSnapShotTestActive)
883 			{
884 				// bump ref count so we only 1 warning per new string, not for every one sharing that label...
885 				//
886 				AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++;
887 			}
888 		}
889 		pMemory = pMemory->pNext;
890 	}
891 
892 	Com_Printf("( %d blocks listed, %d bytes (%.2fMB) total )\n",iBlocksListed, iTotalSize, (float)iTotalSize / 1024.0f / 1024.0f);
893 }
894 #endif
895 
896 // Shuts down the zone memory system and frees up all memory
Com_ShutdownZoneMemory(void)897 void Com_ShutdownZoneMemory(void)
898 {
899 	Cmd_RemoveCommand("zone_stats");
900 	Cmd_RemoveCommand("zone_details");
901 
902 #ifdef _DEBUG
903 	Cmd_RemoveCommand("zone_memrecovertest");
904 #endif
905 
906 #ifdef DEBUG_ZONE_ALLOCS
907 	Cmd_RemoveCommand("zone_tagdebug");
908 	Cmd_RemoveCommand("zone_snapshot");
909 #endif
910 
911 	if(TheZone.Stats.iCount)
912 	{
913 		Com_Printf("Automatically freeing %d blocks making up %d bytes\n", TheZone.Stats.iCount, TheZone.Stats.iCurrent);
914 		Z_TagFree(TAG_ALL);
915 
916 		//assert(!TheZone.Stats.iCount);	// These aren't really problematic per se, it's just warning us that we're freeing extra
917 		//assert(!TheZone.Stats.iCurrent);  // memory that is in the zone manager (but not actively tracked..) so if anything, zone_*
918 											// commands will just simply be wrong in displaying bytes, but in my tests, it's only off
919 											// by like 10 bytes / 1 block, which isn't a real problem --eez
920 		if(TheZone.Stats.iCount < 0) {
921 			Com_Printf(S_COLOR_YELLOW"WARNING: Freeing %d extra blocks (%d bytes) not tracked by the zone manager\n",
922 				abs(TheZone.Stats.iCount), abs(TheZone.Stats.iCurrent));
923 		}
924 	}
925 }
926 
927 // Initialises the zone memory system
928 
Com_InitZoneMemory(void)929 void Com_InitZoneMemory( void )
930 {
931 	Com_Printf("Initialising zone memory .....\n");
932 
933 	memset(&TheZone, 0, sizeof(TheZone));
934 	TheZone.Header.iMagic = ZONE_MAGIC;
935 }
936 
Com_InitZoneMemoryVars(void)937 void Com_InitZoneMemoryVars( void)
938 {
939 	com_validateZone = Cvar_Get("com_validateZone", "0", 0);
940 
941 	Cmd_AddCommand("zone_stats",	Z_Stats_f);
942 	Cmd_AddCommand("zone_details",	Z_Details_f);
943 
944 #ifdef _DEBUG
945 	Cmd_AddCommand("zone_memrecovertest", Z_MemRecoverTest_f);
946 #endif
947 
948 #ifdef DEBUG_ZONE_ALLOCS
949 	Cmd_AddCommand("zone_tagdebug",	Z_TagDebug_f);
950 	Cmd_AddCommand("zone_snapshot",	Z_Snapshot_f);
951 #endif
952 }
953 
954 
955 
956 
957 /*
958 ========================
959 CopyString
960 
961  NOTE:	never write over the memory CopyString returns because
962 		memory from a memstatic_t might be returned
963 ========================
964 */
CopyString(const char * in)965 char *CopyString( const char *in ) {
966 	char	*out;
967 
968 	if (!in[0]) {
969 		return ((char *)&gEmptyString) + sizeof(zoneHeader_t);
970 	}
971 	else if (!in[1]) {
972 		if (in[0] >= '0' && in[0] <= '9') {
973 			return ((char *)&gNumberString[in[0]-'0']) + sizeof(zoneHeader_t);
974 		}
975 	}
976 
977 	out = (char *) S_Malloc (strlen(in)+1);
978 	strcpy (out, in);
979 
980 	Z_Label(out,in);
981 
982 	return out;
983 }
984 
985 
986 /*
987 ===============
988 Com_TouchMemory
989 
990 Touch all known used data to make sure it is paged in
991 ===============
992 */
Com_TouchMemory(void)993 void Com_TouchMemory( void ) {
994 	//int		start, end;
995 	int		i, j;
996 	int		sum;
997 	int		totalTouched;
998 
999 	Z_Validate();
1000 
1001 	//start = Sys_Milliseconds();
1002 
1003 	sum = 0;
1004 	totalTouched=0;
1005 
1006 	zoneHeader_t *pMemory = TheZone.Header.pNext;
1007 	while (pMemory)
1008 	{
1009 		byte *pMem = (byte *) &pMemory[1];
1010 		j = pMemory->iSize >> 2;
1011 		for (i=0; i<j; i+=64){
1012 			sum += ((int*)pMem)[i];
1013 		}
1014 		totalTouched+=pMemory->iSize;
1015 		pMemory = pMemory->pNext;
1016 	}
1017 
1018 	//end = Sys_Milliseconds();
1019 
1020 	//Com_Printf( "Com_TouchMemory: %i bytes, %i msec\n", totalTouched, end - start );
1021 }
1022 
1023 
1024