// Created 2/3/03 by Brian Osman - split Zone code from common.cpp #include "../game/q_shared.h" #include "qcommon.h" #include "../qcommon/sstring.h" #include "platform.h" #ifdef DEBUG_ZONE_ALLOCS int giZoneSnaphotNum=0; #define DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE 256 typedef sstring sDebugString_t; #endif static void Z_Details_f(void); // define a string table of all mem tags... // #ifdef TAGDEF // itu? #undef TAGDEF #endif #define TAGDEF(blah) #blah static const char *psTagStrings[TAG_COUNT+1]= // +1 because TAG_COUNT will itself become a string here. Oh well. { #include "../qcommon/tags.h" }; // This handles zone memory allocation. // It is a wrapper around malloc with a tag id and a magic number at the start #define ZONE_MAGIC 0x21436587 // if you change ANYTHING in this structure, be sure to update the tables below using DEF_STATIC... // typedef struct zoneHeader_s { int iMagic; memtag_t eTag; int iSize; struct zoneHeader_s *pNext; struct zoneHeader_s *pPrev; #ifdef DEBUG_ZONE_ALLOCS char sSrcFileBaseName[MAX_QPATH]; int iSrcFileLineNum; char sOptionalLabel[DEBUG_ZONE_ALLOC_OPTIONAL_LABEL_SIZE]; int iSnapshotNumber; #endif } zoneHeader_t; typedef struct { int iMagic; } zoneTail_t; static inline zoneTail_t *ZoneTailFromHeader(zoneHeader_t *pHeader) { return (zoneTail_t*) ( (char*)pHeader + sizeof(*pHeader) + pHeader->iSize ); } #ifdef DETAILED_ZONE_DEBUG_CODE map mapAllocatedZones; #endif typedef struct zoneStats_s { int iCount; int iCurrent; int iPeak; // I'm keeping these updated on the fly, since it's quicker for cache-pool // purposes rather than recalculating each time... // int iSizesPerTag [TAG_COUNT]; int iCountsPerTag[TAG_COUNT]; } zoneStats_t; typedef struct zone_s { zoneStats_t Stats; zoneHeader_t Header; } zone_t; cvar_t *com_validateZone; zone_t TheZone = {0}; // Scans through the linked list of mallocs and makes sure no data has been overwritten int Z_Validate(void) { int ret=0; if(!com_validateZone || !com_validateZone->integer) { return ret; } zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { #ifdef DETAILED_ZONE_DEBUG_CODE // this won't happen here, but wtf? int& iAllocCount = mapAllocatedZones[pMemory]; if (iAllocCount <= 0) { Com_Error(ERR_FATAL, "Z_Validate(): Bad block allocation count!"); return ret; } #endif if(pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone header!"); return ret; } // this block of code is intended to make sure all of the data is paged in if (pMemory->eTag != TAG_IMAGE_T && pMemory->eTag != TAG_MODEL_MD3 && pMemory->eTag != TAG_MODEL_GLM && pMemory->eTag != TAG_MODEL_GLA ) //don't bother with disk caches as they've already been hit or will be thrown out next { unsigned char *memstart = (unsigned char *)pMemory; int totalSize = pMemory->iSize; while (totalSize > 4096) { memstart += 4096; ret += (int)(*memstart); // this fools the optimizer totalSize -= 4096; } } if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone tail!"); return ret; } pMemory = pMemory->pNext; } return ret; } // static mem blocks to reduce a lot of small zone overhead // #pragma pack(push) #pragma pack(1) typedef struct { zoneHeader_t Header; // byte mem[0]; zoneTail_t Tail; } StaticZeroMem_t; typedef struct { zoneHeader_t Header; byte mem[2]; zoneTail_t Tail; } StaticMem_t; #pragma pack(pop) const static StaticZeroMem_t gZeroMalloc = { {ZONE_MAGIC, TAG_STATIC,0,NULL,NULL},{ZONE_MAGIC}}; #ifdef DEBUG_ZONE_ALLOCS #define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL, "",0,"",0},_char,'\0',{ZONE_MAGIC} #else #define DEF_STATIC(_char) {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL },_char,'\0',{ZONE_MAGIC} #endif const static StaticMem_t gEmptyString = { DEF_STATIC('\0') }; const static StaticMem_t gNumberString[] = { { DEF_STATIC('0') }, { DEF_STATIC('1') }, { DEF_STATIC('2') }, { DEF_STATIC('3') }, { DEF_STATIC('4') }, { DEF_STATIC('5') }, { DEF_STATIC('6') }, { DEF_STATIC('7') }, { DEF_STATIC('8') }, { DEF_STATIC('9') }, }; qboolean gbMemFreeupOccured = qfalse; #ifdef DEBUG_ZONE_ALLOCS void *_D_Z_Malloc ( int iSize, memtag_t eTag, qboolean bZeroit, const char *psFile, int iLine) #else void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit, int unusedAlign) #endif { gbMemFreeupOccured = qfalse; if (iSize == 0) { zoneHeader_t *pMemory = (zoneHeader_t *) &gZeroMalloc; return &pMemory[1]; } // Add in tracking info and round to a longword... (ignore longword aligning now we're not using contiguous blocks) // // int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t) + 3) & 0xfffffffc; int iRealSize = (iSize + sizeof(zoneHeader_t) + sizeof(zoneTail_t)); // Allocate a chunk... // zoneHeader_t *pMemory = NULL; while (pMemory == NULL) { #ifdef _WIN32 if (gbMemFreeupOccured) { Sleep(1000); // sleep for a second, so Windows has a chance to shuffle mem to de-swiss-cheese it } #endif if (bZeroit) { pMemory = (zoneHeader_t *) calloc ( iRealSize, 1 ); } else { pMemory = (zoneHeader_t *) malloc ( iRealSize ); } if (!pMemory) { // new bit, if we fail to malloc memory, try dumping some of the cached stuff that's non-vital and try again... // // ditch the BSP cache... // if (CM_DeleteCachedMap(qfalse)) { gbMemFreeupOccured = qtrue; continue; // we've just ditched a whole load of memory, so try again with the malloc } // ditch any sounds not used on this level... // extern qboolean SND_RegisterAudio_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); if (SND_RegisterAudio_LevelLoadEnd(qtrue)) { gbMemFreeupOccured = qtrue; continue; // we've dropped at least one sound, so try again with the malloc } // ditch any image_t's (and associated GL texture mem) not used on this level... // extern qboolean RE_RegisterImages_LevelLoadEnd(void); if (RE_RegisterImages_LevelLoadEnd()) { gbMemFreeupOccured = qtrue; continue; // we've dropped at least one image, so try again with the malloc } // ditch the model-binaries cache... (must be getting desperate here!) // extern qboolean RE_RegisterModels_LevelLoadEnd(qboolean bDeleteEverythingNotUsedThisLevel); if (RE_RegisterModels_LevelLoadEnd(qtrue)) { gbMemFreeupOccured = qtrue; continue; } // as a last panic measure, dump all the audio memory, but not if we're in the audio loader // (which is annoying, but I'm not sure how to ensure we're not dumping any memory needed by the sound // currently being loaded if that was the case)... // // note that this keeps querying until it's freed up as many bytes as the requested size, but freeing // several small blocks might not mean that one larger one is satisfiable after freeup, however that'll // just make it go round again and try for freeing up another bunch of blocks until the total is satisfied // again (though this will have freed twice the requested amount in that case), so it'll either work // eventually or not free up enough and drop through to the final ERR_DROP. No worries... // extern qboolean gbInsideLoadSound; extern int SND_FreeOldestSound(void); // I had to add a void-arg version of this because of link issues, sigh if (!gbInsideLoadSound) { int iBytesFreed = SND_FreeOldestSound(); if (iBytesFreed) { int iTheseBytesFreed = 0; while ( (iTheseBytesFreed = SND_FreeOldestSound()) != 0) { iBytesFreed += iTheseBytesFreed; if (iBytesFreed >= iRealSize) break; // early opt-out since we've managed to recover enough (mem-contiguity issues aside) } gbMemFreeupOccured = qtrue; continue; } } // sigh, dunno what else to try, I guess we'll have to give up and report this as an out-of-mem error... // // findlabel: "recovermem" Com_Printf(S_COLOR_RED"Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]); Z_Details_f(); Com_Error(ERR_FATAL,"(Repeat): Z_Malloc(): Failed to alloc %d bytes (TAG_%s) !!!!!\n", iSize, psTagStrings[eTag]); return NULL; } } #ifdef DEBUG_ZONE_ALLOCS extern char *Filename_WithoutPath(const char *psFilename); Q_strncpyz(pMemory->sSrcFileBaseName, Filename_WithoutPath(psFile), sizeof(pMemory->sSrcFileBaseName)); pMemory->iSrcFileLineNum = iLine; pMemory->sOptionalLabel[0] = '\0'; pMemory->iSnapshotNumber = giZoneSnaphotNum; #endif // Link in pMemory->iMagic = ZONE_MAGIC; pMemory->eTag = eTag; pMemory->iSize = iSize; pMemory->pNext = TheZone.Header.pNext; TheZone.Header.pNext = pMemory; if (pMemory->pNext) { pMemory->pNext->pPrev = pMemory; } pMemory->pPrev = &TheZone.Header; // // add tail... // ZoneTailFromHeader(pMemory)->iMagic = ZONE_MAGIC; // Update stats... // TheZone.Stats.iCurrent += iSize; TheZone.Stats.iCount++; TheZone.Stats.iSizesPerTag [eTag] += iSize; TheZone.Stats.iCountsPerTag [eTag]++; if (TheZone.Stats.iCurrent > TheZone.Stats.iPeak) { TheZone.Stats.iPeak = TheZone.Stats.iCurrent; } #ifdef DETAILED_ZONE_DEBUG_CODE mapAllocatedZones[pMemory]++; #endif Z_Validate(); // check for corruption void *pvReturnMem = &pMemory[1]; return pvReturnMem; } // used during model cacheing to save an extra malloc, lets us morph the disk-load buffer then // just not fs_freefile() it afterwards. // void Z_MorphMallocTag( void *pvAddress, memtag_t eDesiredTag ) { zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; if (pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_MorphMallocTag(): Not a valid zone header!"); return; // won't get here } // DEC existing tag stats... // // TheZone.Stats.iCurrent - unchanged // TheZone.Stats.iCount - unchanged TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize; TheZone.Stats.iCountsPerTag [pMemory->eTag]--; // morph... // pMemory->eTag = eDesiredTag; // INC new tag stats... // // TheZone.Stats.iCurrent - unchanged // TheZone.Stats.iCount - unchanged TheZone.Stats.iSizesPerTag [pMemory->eTag] += pMemory->iSize; TheZone.Stats.iCountsPerTag [pMemory->eTag]++; } static int Zone_FreeBlock(zoneHeader_t *pMemory) { const int iSize = pMemory->iSize; if (pMemory->eTag != TAG_STATIC) // belt and braces, should never hit this though { // Update stats... // TheZone.Stats.iCount--; TheZone.Stats.iCurrent -= pMemory->iSize; TheZone.Stats.iSizesPerTag [pMemory->eTag] -= pMemory->iSize; TheZone.Stats.iCountsPerTag [pMemory->eTag]--; // Sanity checks... // assert(pMemory->pPrev->pNext == pMemory); assert(!pMemory->pNext || (pMemory->pNext->pPrev == pMemory)); // Unlink and free... // pMemory->pPrev->pNext = pMemory->pNext; if(pMemory->pNext) { pMemory->pNext->pPrev = pMemory->pPrev; } //debugging double frees pMemory->iMagic = 'FREE'; free (pMemory); #ifdef DETAILED_ZONE_DEBUG_CODE // this has already been checked for in execution order, but wtf? int& iAllocCount = mapAllocatedZones[pMemory]; if (iAllocCount == 0) { Com_Error(ERR_FATAL, "Zone_FreeBlock(): Double-freeing block!"); return -1; } iAllocCount--; #endif } return iSize; } // stats-query function to to see if it's our malloc // returns block size if so qboolean Z_IsFromZone(void *pvAddress, memtag_t eTag) { zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; #if 1 //debugging double free if (pMemory->iMagic == 'FREE') { Com_Printf("Z_IsFromZone(%x): Ptr has been freed already!(%9s)\n",pvAddress,pvAddress); return qfalse; } #endif if (pMemory->iMagic != ZONE_MAGIC) { return qfalse; } //looks like it is from our zone, let's double check the tag if (pMemory->eTag != eTag) { return qfalse; } return pMemory->iSize; } // stats-query function to ask how big a malloc is... // int Z_Size(void *pvAddress) { zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; if (pMemory->eTag == TAG_STATIC) { return 0; // kind of } if (pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Size(): Not a valid zone header!"); return 0; // won't get here } return pMemory->iSize; } #ifdef DEBUG_ZONE_ALLOCS void _D_Z_Label(const void *pvAddress, const char *psLabel) { zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; if (pMemory->eTag == TAG_STATIC) { return; } if (pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "_D_Z_Label(): Not a valid zone header!"); } Q_strncpyz( pMemory->sOptionalLabel, psLabel, sizeof(pMemory->sOptionalLabel)); pMemory->sOptionalLabel[ sizeof(pMemory->sOptionalLabel)-1 ] = '\0'; } #endif // Frees a block of memory... // int Z_Free(void *pvAddress) { if (!TheZone.Stats.iCount) { //Com_Error(ERR_FATAL, "Z_Free(): Zone has been cleard already!"); Com_Printf("Z_Free(%x): Zone has been cleard already!\n",pvAddress); return -1; } zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; #if 1 //debugging double free if (pMemory->iMagic == 'FREE') { Com_Error(ERR_FATAL, "Z_Free(%s): Block already-freed, or not allocated through Z_Malloc!",pvAddress); return -1; } #endif if (pMemory->eTag == TAG_STATIC) { return 0; } #ifdef DETAILED_ZONE_DEBUG_CODE // // check this error *before* barfing on bad magics... // int& iAllocCount = mapAllocatedZones[pMemory]; if (iAllocCount <= 0) { Com_Error(ERR_FATAL, "Z_Free(): Block already-freed, or not allocated through Z_Malloc!"); return -1; } #endif if (pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); return -1; } if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone tail!"); return -1; } return Zone_FreeBlock(pMemory); } int Z_MemSize(memtag_t eTag) { return TheZone.Stats.iSizesPerTag[eTag]; } // Frees all blocks with the specified tag... // void Z_TagFree(memtag_t eTag) { //#ifdef _DEBUG // int iZoneBlocks = TheZone.Stats.iCount; //#endif zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { zoneHeader_t *pNext = pMemory->pNext; if ( (eTag == TAG_ALL) || (pMemory->eTag == eTag)) { Zone_FreeBlock(pMemory); } pMemory = pNext; } // these stupid pragmas don't work here???!?!?! // //#ifdef _DEBUG //#pragma warning( disable : 4189) // int iBlocksFreed = iZoneBlocks - TheZone.Stats.iCount; //#pragma warning( default : 4189) //#endif } #ifdef DEBUG_ZONE_ALLOCS void *_D_S_Malloc ( int iSize, const char *psFile, int iLine) { return _D_Z_Malloc( iSize, TAG_SMALL, qfalse, psFile, iLine ); } #else void *S_Malloc( int iSize ) { return Z_Malloc( iSize, TAG_SMALL, qfalse); } #endif #ifdef _DEBUG static void Z_MemRecoverTest_f(void) { // needs to be in _DEBUG only, not good for final game! // if ( Cmd_Argc() != 2 ) { Com_Printf( "Usage: zone_memrecovertest max2alloc\n" ); return; } int iMaxAlloc = 1024*1024*atoi( Cmd_Argv(1) ); int iTotalMalloc = 0; while (1) { const int iThisMalloc = 5* (1024 * 1024); Z_Malloc(iThisMalloc, TAG_SPECIAL_MEM_TEST, qfalse); // and lose, just to consume memory iTotalMalloc += iThisMalloc; if (gbMemFreeupOccured || (iTotalMalloc >= iMaxAlloc) ) break; } Z_TagFree(TAG_SPECIAL_MEM_TEST); } #endif // Gives a summary of the zone memory usage static void Z_Stats_f(void) { Com_Printf("\nThe zone is using %d bytes (%.2fMB) in %d memory blocks\n", TheZone.Stats.iCurrent, (float)TheZone.Stats.iCurrent / 1024.0f / 1024.0f, TheZone.Stats.iCount ); Com_Printf("The zone peaked at %d bytes (%.2fMB)\n", TheZone.Stats.iPeak, (float)TheZone.Stats.iPeak / 1024.0f / 1024.0f ); } // Gives a detailed breakdown of the memory blocks in the zone // static void Z_Details_f(void) { Com_Printf("---------------------------------------------------------------------------\n"); Com_Printf("%20s %9s\n","Zone Tag","Bytes"); Com_Printf("%20s %9s\n","--------","-----"); for (int i=0; i LabelRefCount_t; // yet another place where Gil's tring class works and MS's doesn't typedef map TagBlockLabels_t; TagBlockLabels_t AllTagBlockLabels; #pragma warning (disable:4503) // decorated name length xceeded, name was truncated static void Z_Snapshot_f(void) { AllTagBlockLabels.clear(); zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { AllTagBlockLabels[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++; pMemory = pMemory->pNext; } giZoneSnaphotNum++; Com_Printf("Ok. ( Current snapshot num is now %d )\n",giZoneSnaphotNum); } static void Z_TagDebug_f(void) { TagBlockLabels_t AllTagBlockLabels_Local; qboolean bSnapShotTestActive = qfalse; memtag_t eTag = TAG_ALL; const char *psTAGName = Cmd_Argv(1); if (psTAGName[0]) { // check optional arg... // if (!Q_stricmp(psTAGName,"#snap")) { bSnapShotTestActive = qtrue; AllTagBlockLabels_Local = AllTagBlockLabels; // horrible great STL copy psTAGName = Cmd_Argv(2); } if (psTAGName[0]) { // skip over "tag_" if user supplied it... // if (!Q_stricmpn(psTAGName,"TAG_",4)) { psTAGName += 4; } // see if the user specified a valid tag... // for (int i=0; i', e.g. TAG_GHOUL2, TAG_ALL (careful!)\n"); return; } Com_Printf("Dumping debug data for tag \"%s\"...%s\n\n",psTagStrings[eTag], bSnapShotTestActive?"( since snapshot only )":""); Com_Printf("%8s"," "); // to compensate for code further down: Com_Printf("(%5d) ",iBlocksListed); if (eTag == TAG_ALL) { Com_Printf("%20s ","Zone Tag"); } Com_Printf("%9s\n","Bytes"); Com_Printf("%8s"," "); if (eTag == TAG_ALL) { Com_Printf("%20s ","--------"); } Com_Printf("%9s\n","-----"); if (bSnapShotTestActive) { // dec ref counts in last snapshot for all current blocks (which will make new stuff go negative) // zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { if (pMemory->eTag == eTag || eTag == TAG_ALL) { AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]--; } pMemory = pMemory->pNext; } } // now dump them out... // int iBlocksListed = 0; int iTotalSize = 0; zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { if ( (pMemory->eTag == eTag || eTag == TAG_ALL) && (!bSnapShotTestActive || (pMemory->iSnapshotNumber == giZoneSnaphotNum && AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel] <0) ) ) { float fSize = (float)(pMemory->iSize) / 1024.0f / 1024.0f; int iSize = fSize; int iRemainder = 100.0f * (fSize - floor(fSize)); Com_Printf("(%5d) ",iBlocksListed); if (eTag == TAG_ALL) { Com_Printf("%20s",psTagStrings[pMemory->eTag]); } Com_Printf(" %9d (%2d.%02dMB) File: \"%s\", Line: %d\n", pMemory->iSize, iSize,iRemainder, pMemory->sSrcFileBaseName, pMemory->iSrcFileLineNum ); if (pMemory->sOptionalLabel[0]) { Com_Printf("( Label: \"%s\" )\n",pMemory->sOptionalLabel); } iBlocksListed++; iTotalSize += pMemory->iSize; if (bSnapShotTestActive) { // bump ref count so we only 1 warning per new string, not for every one sharing that label... // AllTagBlockLabels_Local[psTagStrings[pMemory->eTag]][pMemory->sOptionalLabel]++; } } pMemory = pMemory->pNext; } Com_Printf("( %d blocks listed, %d bytes (%.2fMB) total )\n",iBlocksListed, iTotalSize, (float)iTotalSize / 1024.0f / 1024.0f); } #endif // Shuts down the zone memory system and frees up all memory void Com_ShutdownZoneMemory(void) { Cmd_RemoveCommand("zone_stats"); Cmd_RemoveCommand("zone_details"); #ifdef _DEBUG Cmd_RemoveCommand("zone_memrecovertest"); #endif #ifdef DEBUG_ZONE_ALLOCS Cmd_RemoveCommand("zone_tagdebug"); Cmd_RemoveCommand("zone_snapshot"); #endif if(TheZone.Stats.iCount) { //Com_Printf("Automatically freeing %d blocks making up %d bytes\n", TheZone.Stats.iCount, TheZone.Stats.iCurrent); Z_TagFree(TAG_ALL); assert(!TheZone.Stats.iCount); assert(!TheZone.Stats.iCurrent); } } // Initialises the zone memory system void Com_InitZoneMemory( void ) { Com_Printf("Initialising zone memory .....\n"); memset(&TheZone, 0, sizeof(TheZone)); TheZone.Header.iMagic = ZONE_MAGIC; com_validateZone = Cvar_Get("com_validateZone", "0", 0); Cmd_AddCommand("zone_stats", Z_Stats_f); Cmd_AddCommand("zone_details", Z_Details_f); #ifdef _DEBUG Cmd_AddCommand("zone_memrecovertest", Z_MemRecoverTest_f); #endif #ifdef DEBUG_ZONE_ALLOCS Cmd_AddCommand("zone_tagdebug", Z_TagDebug_f); Cmd_AddCommand("zone_snapshot", Z_Snapshot_f); #endif } /* ======================== CopyString NOTE: never write over the memory CopyString returns because memory from a memstatic_t might be returned ======================== */ char *CopyString( const char *in ) { char *out; if (!in[0]) { return ((char *)&gEmptyString) + sizeof(zoneHeader_t); } else if (!in[1]) { if (in[0] >= '0' && in[0] <= '9') { return ((char *)&gNumberString[in[0]-'0']) + sizeof(zoneHeader_t); } } out = (char *) S_Malloc (strlen(in)+1); strcpy (out, in); Z_Label(out,in); return out; } /* =============== Com_TouchMemory Touch all known used data to make sure it is paged in =============== */ void Com_TouchMemory( void ) { int start, end; int i, j; int sum; int totalTouched; Z_Validate(); start = Sys_Milliseconds(); sum = 0; totalTouched=0; zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { byte *pMem = (byte *) &pMemory[1]; j = pMemory->iSize >> 2; for (i=0; iiSize; pMemory = pMemory->pNext; } end = Sys_Milliseconds(); //Com_Printf( "Com_TouchMemory: %i bytes, %i msec\n", totalTouched, end - start ); }