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