// Created 3/13/03 by Brian Osman (VV) - Split Zone/Hunk from common //Anything above this #include will be ignored by the compiler #include "../qcommon/exe_headers.h" #include "platform.h" //////////////////////////////////////////////// // #ifdef TAGDEF // itu? #undef TAGDEF #endif #define TAGDEF(blah) #blah const static char *psTagStrings[TAG_COUNT+1]= // +1 because TAG_COUNT will itself become a string here. Oh well. { #include "../qcommon/tags.h" }; // //////////////////////////////////////////////// static void Z_Details_f(void); void CIN_CloseAllVideos(); // 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 typedef struct zoneHeader_s { int iMagic; memtag_t eTag; int iSize; struct zoneHeader_s *pNext; struct zoneHeader_s *pPrev; } 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 void Z_Validate(void) { if(!com_validateZone || !com_validateZone->integer) { return; } 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; } #endif if(pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone header!"); return; } if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Validate(): Corrupt zone tail!"); return; } pMemory = pMemory->pNext; } } // 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) StaticZeroMem_t gZeroMalloc = { {ZONE_MAGIC, TAG_STATIC,0,NULL,NULL},{ZONE_MAGIC}}; StaticMem_t gEmptyString = { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'\0','\0',{ZONE_MAGIC}}; StaticMem_t gNumberString[] = { { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'0','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'1','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'2','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'3','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'4','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'5','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'6','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'7','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'8','\0',{ZONE_MAGIC}}, { {ZONE_MAGIC, TAG_STATIC,2,NULL,NULL},'9','\0',{ZONE_MAGIC}}, }; qboolean gbMemFreeupOccured = qfalse; void *Z_Malloc(int iSize, memtag_t eTag, qboolean bZeroit /* = qfalse */, int iUnusedAlign /* = 4 */) { gbMemFreeupOccured = qfalse; if (iSize == 0) { zoneHeader_t *pMemory = (zoneHeader_t *) &gZeroMalloc; return &pMemory[1]; } // Add in tracking info // 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... // extern qboolean CM_DeleteCachedMap(qboolean bGuaranteedOkToDelete); 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 } #ifndef DEDICATED // ditch any image_t's (and associated GL memory) 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 } #endif // 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(); 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; } } // 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 void Zone_FreeBlock(zoneHeader_t *pMemory) { 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; } 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; } iAllocCount--; #endif } } // 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; } // Frees a block of memory... // void Z_Free(void *pvAddress) { if (pvAddress == NULL) // I've put this in as a safety measure because of some bits of #ifdef BSPC stuff -Ste. { //Com_Error(ERR_FATAL, "Z_Free(): NULL arg"); return; } zoneHeader_t *pMemory = ((zoneHeader_t *)pvAddress) - 1; if (pMemory->eTag == TAG_STATIC) { return; } #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; } #endif if (pMemory->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone header!"); return; } if (ZoneTailFromHeader(pMemory)->iMagic != ZONE_MAGIC) { Com_Error(ERR_FATAL, "Z_Free(): Corrupt zone tail!"); 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 } void *S_Malloc( int iSize ) { return Z_Malloc( iSize, TAG_SMALL ); } #ifdef _DEBUG static void Z_MemRecoverTest_f(void) { // needs to be in _DEBUG only, not good for final game! // fixme: findmeste: Remove this sometime // int iTotalMalloc = 0; while (1) { int iThisMalloc = 5* (1024 * 1024); Z_Malloc(iThisMalloc, TAG_SPECIAL_MEM_TEST, qfalse); // and lose, just to consume memory iTotalMalloc += iThisMalloc; if (gbMemFreeupOccured) 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= '0' && in[0] <= '9') { return ((char *)&gNumberString[in[0]-'0']) + sizeof(zoneHeader_t); } } out = (char *) S_Malloc (strlen(in)+1); strcpy (out, in); return out; } static memtag_t hunk_tag; /* =============== 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; // start = Sys_Milliseconds(); Z_Validate(); sum = 0; zoneHeader_t *pMemory = TheZone.Header.pNext; while (pMemory) { byte *pMem = (byte *) &pMemory[1]; j = pMemory->iSize >> 2; for (i=0; ipNext; } // end = Sys_Milliseconds(); // Com_Printf( "Com_TouchMemory: %i msec\n", end - start ); } qboolean Com_TheHunkMarkHasBeenMade(void) { if (hunk_tag == TAG_HUNK_MARK2) { return qtrue; } return qfalse; } /* ================= Com_InitHunkMemory ================= */ void Com_InitHunkMemory( void ) { hunk_tag = TAG_HUNK_MARK1; Hunk_Clear(); } void Com_ShutdownHunkMemory(void) { //Er, ok. Clear it then I guess. Z_TagFree(TAG_HUNK_MARK1); Z_TagFree(TAG_HUNK_MARK2); } /* ==================== Hunk_MemoryRemaining ==================== */ int Hunk_MemoryRemaining( void ) { return (64*1024*1024) - (Z_MemSize(TAG_HUNK_MARK1)+Z_MemSize(TAG_HUNK_MARK2)); //Yeah. Whatever. We've got no size now. } /* =================== Hunk_SetMark The server calls this after the level and game VM have been loaded =================== */ void Hunk_SetMark( void ) { hunk_tag = TAG_HUNK_MARK2; } /* ================= Hunk_ClearToMark The client calls this before starting a vid_restart or snd_restart ================= */ void Hunk_ClearToMark( void ) { assert(hunk_tag == TAG_HUNK_MARK2); //if this is not true then no mark has been made Z_TagFree(TAG_HUNK_MARK2); } /* ================= Hunk_CheckMark ================= */ qboolean Hunk_CheckMark( void ) { //if( hunk_low.mark || hunk_high.mark ) { if (hunk_tag != TAG_HUNK_MARK1) { return qtrue; } return qfalse; } void CL_ShutdownCGame( void ); void CL_ShutdownUI( void ); void SV_ShutdownGameProgs( void ); /* ================= Hunk_Clear The server calls this before shutting down or loading a new map ================= */ void R_HunkClearCrap(void); #ifdef _FULL_G2_LEAK_CHECKING void G2_DEBUG_ReportLeaks(void); #endif void Hunk_Clear( void ) { #ifndef DEDICATED CL_ShutdownCGame(); CL_ShutdownUI(); #endif SV_ShutdownGameProgs(); #ifndef DEDICATED CIN_CloseAllVideos(); #endif hunk_tag = TAG_HUNK_MARK1; Z_TagFree(TAG_HUNK_MARK1); Z_TagFree(TAG_HUNK_MARK2); R_HunkClearCrap(); // Com_Printf( "Hunk_Clear: reset the hunk ok\n" ); VM_Clear(); //See if any ghoul2 stuff was leaked, at this point it should be all cleaned up. #ifdef _FULL_G2_LEAK_CHECKING assert(g_Ghoul2Allocations == 0 && g_G2ClientAlloc == 0 && g_G2ServerAlloc == 0); if (g_Ghoul2Allocations) { Com_Printf("%i bytes leaked by ghoul2 routines (%i client, %i server)\n", g_Ghoul2Allocations, g_G2ClientAlloc, g_G2ServerAlloc); G2_DEBUG_ReportLeaks(); } #endif } /* ================= Hunk_Alloc Allocate permanent (until the hunk is cleared) memory ================= */ void *Hunk_Alloc( int size, ha_pref preference ) { return Z_Malloc(size, hunk_tag, qtrue); } /* ================= Hunk_AllocateTempMemory This is used by the file loading system. Multiple files can be loaded in temporary memory. When the files-in-use count reaches zero, all temp memory will be deleted ================= */ void *Hunk_AllocateTempMemory( int size ) { // don't bother clearing, because we are going to load a file over it return Z_Malloc(size, TAG_TEMP_HUNKALLOC, qfalse); } /* ================== Hunk_FreeTempMemory ================== */ void Hunk_FreeTempMemory( void *buf ) { Z_Free(buf); } /* ================= Hunk_ClearTempMemory The temp space is no longer needed. If we have left more touched but unused memory on this side, have future permanent allocs use this side. ================= */ void Hunk_ClearTempMemory( void ) { Z_TagFree(TAG_TEMP_HUNKALLOC); }