wwmemlog.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634
  1. /*
  2. ** Command & Conquer Generals(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /***********************************************************************************************
  19. *** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
  20. ***********************************************************************************************
  21. * *
  22. * Project Name : WWDebug *
  23. * *
  24. * $Archive:: /VSS_Sync/wwdebug/wwmemlog.cpp $*
  25. * *
  26. * Original Author:: Greg Hjelstrom *
  27. * *
  28. * $Author:: Vss_sync $*
  29. * *
  30. * $Modtime:: 8/29/01 10:23p $*
  31. * *
  32. * $Revision:: 21 $*
  33. * *
  34. *---------------------------------------------------------------------------------------------*
  35. * Functions: *
  36. * WWMemoryLogClass::Allocate_Memory -- allocates memory *
  37. * WWMemoryLogClass::Release_Memory -- frees memory *
  38. * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
  39. #include "wwmemlog.h"
  40. #include "wwdebug.h"
  41. #include "vector.h"
  42. #include <windows.h>
  43. #if (STEVES_NEW_CATCHER || PARAM_EDITING_ON)
  44. #define DISABLE_MEMLOG 1
  45. #else
  46. #define DISABLE_MEMLOG 1
  47. #endif //STEVES_NEW_CATCHER
  48. static unsigned AllocateCount;
  49. static unsigned FreeCount;
  50. /*
  51. ** Name for each memory category. I'm padding the array with some "undefined" strings in case
  52. ** someone forgets to set the name when adding a new category.
  53. */
  54. static char * _MemoryCategoryNames[] =
  55. {
  56. "UNKNOWN",
  57. "Geometry",
  58. "Animation",
  59. "Texture",
  60. "Pathfind",
  61. "Vis",
  62. "Sound",
  63. "CullingData",
  64. "Strings",
  65. "GameData",
  66. "PhysicsData",
  67. "W3dData",
  68. "<undefined>",
  69. "<undefined>",
  70. "<undefined>",
  71. "<undefined>",
  72. "<undefined>",
  73. "<undefined>",
  74. };
  75. /**
  76. ** MemoryCounterClass
  77. ** This object will store statistics for each memory category. It can provide things like
  78. ** the current amount of allocated memory and the peak amount of allocated memory.
  79. */
  80. class MemoryCounterClass
  81. {
  82. public:
  83. MemoryCounterClass(void) : CurrentAllocation(0), PeakAllocation(0) { }
  84. void Memory_Allocated(int size) { CurrentAllocation+=size; PeakAllocation = max(PeakAllocation,CurrentAllocation); }
  85. void Memory_Released(int size) { CurrentAllocation-=size; }
  86. int Get_Current_Allocated_Memory(void) { return CurrentAllocation; }
  87. int Get_Peak_Allocated_Memory(void) { return PeakAllocation; }
  88. protected:
  89. int CurrentAllocation;
  90. int PeakAllocation;
  91. };
  92. /**
  93. ** ActiveCategoryStackClass
  94. ** This object is used to keep track of the "active memory category". Whenever memory is allocated
  95. ** it will be charged to the current active memory category. To be thread-safe, there will be
  96. ** one ActiveCategoryStack per thread that is encountered in the program.
  97. */
  98. const int MAX_CATEGORY_STACK_DEPTH = 1024;
  99. class ActiveCategoryStackClass : public VectorClass<int>
  100. {
  101. public:
  102. ActiveCategoryStackClass(void) :
  103. VectorClass<int>(MAX_CATEGORY_STACK_DEPTH),
  104. ThreadID(-1),
  105. Count(0)
  106. { }
  107. ~ActiveCategoryStackClass(void) { WWASSERT(Count == 1); }
  108. ActiveCategoryStackClass & operator = (const ActiveCategoryStackClass & that);
  109. bool operator == (const ActiveCategoryStackClass &) { return false; }
  110. bool operator != (const ActiveCategoryStackClass &) { return true; }
  111. void Init(int thread_id) { ThreadID = thread_id; Count = 0; Push(MEM_UNKNOWN); }
  112. void Set_Thread_ID(int id) { ThreadID = id; }
  113. int Get_Thread_ID(void) { return ThreadID; }
  114. void Push(int active_category) { (*this)[Count] = active_category; Count++; }
  115. void Pop(void) { Count--; }
  116. int Current(void) { return (*this)[Count-1]; }
  117. protected:
  118. int ThreadID;
  119. int Count;
  120. };
  121. /**
  122. ** ActiveCategoryClass
  123. ** This is a dynamic vector of ActiveCategoryStackClasses which adds a new stack each time
  124. ** a new thread is encountered. It also is able to return to you the active category for
  125. ** the currently active thread automatically.
  126. */
  127. const int MAX_CATEGORY_STACKS = 256; // maximum number of threads we expect to encounter...
  128. class ActiveCategoryClass : public VectorClass<ActiveCategoryStackClass>
  129. {
  130. public:
  131. ActiveCategoryClass(void) : VectorClass<ActiveCategoryStackClass>(MAX_CATEGORY_STACKS), Count(0) { }
  132. void Push(int active_category) { Get_Active_Stack().Push(active_category); }
  133. void Pop(void) { Get_Active_Stack().Pop(); }
  134. int Current(void) { return Get_Active_Stack().Current(); }
  135. protected:
  136. ActiveCategoryStackClass & Get_Active_Stack(void);
  137. int Count;
  138. };
  139. /**
  140. ** MemLogClass
  141. ** This class ties all of the logging datastructures together into a single object
  142. ** which can be created on demand when the first 'new' call is encountered.
  143. */
  144. class MemLogClass
  145. {
  146. public:
  147. int Get_Current_Allocated_Memory(int category);
  148. int Get_Peak_Allocated_Memory(int category);
  149. /*
  150. ** Interface for recording allocations and de-allocations
  151. */
  152. int Register_Memory_Allocated(int size);
  153. void Register_Memory_Released(int category,int size);
  154. void Push_Active_Category(int category);
  155. void Pop_Active_Category(void);
  156. private:
  157. MemoryCounterClass _MemoryCounters[MEM_COUNT];
  158. ActiveCategoryClass _ActiveCategoryTracker;
  159. };
  160. /**
  161. ** Static Variables
  162. ** _TheMemLog - object which encapsulates all logging. will be allocated on first use
  163. ** _MemLogMutex - handle to the mutex used to arbtirate access to the logging data structures
  164. ** _MemLogLockCounter - count of the active mutex locks.
  165. */
  166. static MemLogClass * _TheMemLog = NULL;
  167. static void * _MemLogMutex = NULL;
  168. static int _MemLogLockCounter = 0;
  169. static bool _MemLogAllocated = false;
  170. /*
  171. ** Use this code to get access to the mutex...
  172. */
  173. void * Get_Mem_Log_Mutex(void)
  174. {
  175. if (_MemLogMutex == NULL) {
  176. _MemLogMutex=CreateMutex(NULL,false,NULL);
  177. WWASSERT(_MemLogMutex);
  178. }
  179. return _MemLogMutex;
  180. }
  181. void Lock_Mem_Log_Mutex(void)
  182. {
  183. void * mutex = Get_Mem_Log_Mutex();
  184. #ifdef DEBUG_CRASHING
  185. int res =
  186. #endif
  187. WaitForSingleObject(mutex,INFINITE);
  188. WWASSERT(res==WAIT_OBJECT_0);
  189. _MemLogLockCounter++;
  190. }
  191. void Unlock_Mem_Log_Mutex(void)
  192. {
  193. void * mutex = Get_Mem_Log_Mutex();
  194. _MemLogLockCounter--;
  195. #ifdef DEBUG_CRASHING
  196. int res=
  197. #endif
  198. ReleaseMutex(mutex);
  199. WWASSERT(res);
  200. }
  201. class MemLogMutexLockClass
  202. {
  203. public:
  204. MemLogMutexLockClass(void) { Lock_Mem_Log_Mutex(); }
  205. ~MemLogMutexLockClass(void) { Unlock_Mem_Log_Mutex(); }
  206. };
  207. /***************************************************************************************************
  208. **
  209. ** ActiveCategoryStackClass Implementation
  210. **
  211. ***************************************************************************************************/
  212. ActiveCategoryStackClass &
  213. ActiveCategoryStackClass::operator = (const ActiveCategoryStackClass & that)
  214. {
  215. if (this != &that) {
  216. VectorClass<int>::operator == (that);
  217. ThreadID = that.ThreadID;
  218. Count = that.Count;
  219. }
  220. return *this;
  221. }
  222. /***************************************************************************************************
  223. **
  224. ** ActiveCategoryClass Implementation
  225. **
  226. ***************************************************************************************************/
  227. ActiveCategoryStackClass & ActiveCategoryClass::Get_Active_Stack(void)
  228. {
  229. int current_thread = ::GetCurrentThreadId();
  230. /*
  231. ** If we already have an allocated category stack for the current thread,
  232. ** just return its active category.
  233. */
  234. for (int i=0; i<Count; i++) {
  235. ActiveCategoryStackClass & cat_stack = (*this)[i];
  236. if (cat_stack.Get_Thread_ID() == current_thread) {
  237. return cat_stack;
  238. }
  239. }
  240. /*
  241. ** If we fall through to here, we need to allocate a new category stack
  242. ** for this thread.
  243. */
  244. (*this)[Count].Init(current_thread);
  245. Count++;
  246. return (*this)[Count-1];
  247. }
  248. /***************************************************************************************************
  249. **
  250. ** MemLogClass Implementation
  251. **
  252. ***************************************************************************************************/
  253. int MemLogClass::Get_Current_Allocated_Memory(int category)
  254. {
  255. MemLogMutexLockClass lock;
  256. return _MemoryCounters[category].Get_Current_Allocated_Memory();
  257. }
  258. int MemLogClass::Get_Peak_Allocated_Memory(int category)
  259. {
  260. MemLogMutexLockClass lock;
  261. return _MemoryCounters[category].Get_Peak_Allocated_Memory();
  262. }
  263. int MemLogClass::Register_Memory_Allocated(int size)
  264. {
  265. MemLogMutexLockClass lock;
  266. int active_category = _ActiveCategoryTracker.Current();
  267. WWASSERT((active_category >= 0) && (active_category < MEM_COUNT));
  268. _MemoryCounters[active_category].Memory_Allocated(size);
  269. return active_category;
  270. }
  271. void MemLogClass::Register_Memory_Released(int category,int size)
  272. {
  273. MemLogMutexLockClass lock;
  274. _MemoryCounters[category].Memory_Released(size);
  275. }
  276. void MemLogClass::Push_Active_Category(int category)
  277. {
  278. MemLogMutexLockClass lock;
  279. WWASSERT((category >= 0) && (category < MEM_COUNT));
  280. _ActiveCategoryTracker.Push(category);
  281. }
  282. void MemLogClass::Pop_Active_Category(void)
  283. {
  284. MemLogMutexLockClass lock;
  285. _ActiveCategoryTracker.Pop();
  286. }
  287. /***************************************************************************************************
  288. **
  289. ** WWMemoryLogClass Implementation
  290. **
  291. ***************************************************************************************************/
  292. int WWMemoryLogClass::Get_Category_Count(void)
  293. {
  294. return MEM_COUNT;
  295. }
  296. const char * WWMemoryLogClass::Get_Category_Name(int category)
  297. {
  298. return _MemoryCategoryNames[category];
  299. }
  300. int WWMemoryLogClass::Get_Current_Allocated_Memory(int category)
  301. {
  302. return Get_Log()->Get_Current_Allocated_Memory(category);
  303. }
  304. int WWMemoryLogClass::Get_Peak_Allocated_Memory(int category)
  305. {
  306. return Get_Log()->Get_Peak_Allocated_Memory(category);
  307. }
  308. void WWMemoryLogClass::Push_Active_Category(int category)
  309. {
  310. #if (DISABLE_MEMLOG == 0)
  311. Get_Log()->Push_Active_Category(category);
  312. #endif //(DISABLE_MEMLOG == 0)
  313. }
  314. void WWMemoryLogClass::Pop_Active_Category(void)
  315. {
  316. #if (DISABLE_MEMLOG == 0)
  317. Get_Log()->Pop_Active_Category();
  318. #endif //(DISABLE_MEMLOG == 0)
  319. }
  320. int WWMemoryLogClass::Register_Memory_Allocated(int size)
  321. {
  322. return Get_Log()->Register_Memory_Allocated(size);
  323. }
  324. void WWMemoryLogClass::Register_Memory_Released(int category,int size)
  325. {
  326. Get_Log()->Register_Memory_Released(category,size);
  327. }
  328. static void __cdecl _MemLogCleanup(void)
  329. {
  330. delete _TheMemLog;
  331. }
  332. MemLogClass * WWMemoryLogClass::Get_Log(void)
  333. {
  334. MemLogMutexLockClass lock;
  335. if (_TheMemLog == NULL) {
  336. //assert(!_MemLogAllocated);
  337. _TheMemLog = W3DNEW MemLogClass;
  338. #ifdef STEVES_NEW_CATCHER
  339. /*
  340. ** This was me trying to be clever and fix the memory leak in the memlog. Unfortunately, the Get_Log member can be called
  341. ** during the process of exiting the process (IYSWIM) and you get it trying to re-allocate the MemLogClass I just freed.
  342. ** Solution is just to disable memlog when I'm trying to find memory leaks. ST - 6/18/2001 9:51PM
  343. */
  344. if (!_MemLogAllocated) {
  345. atexit(&Release_Log);
  346. }
  347. _MemLogAllocated = true;
  348. #endif //STEVES_NEW_CATCHER
  349. }
  350. return _TheMemLog;
  351. }
  352. /***********************************************************************************************
  353. * WWMemoryLogClass::Release_Log -- Free the memory used by WWMemoryLogClass so it doesn't leak*
  354. * *
  355. * *
  356. * *
  357. * INPUT: Nothing *
  358. * *
  359. * OUTPUT: Nothing *
  360. * *
  361. * WARNINGS: Called as part of _onexit processing *
  362. * *
  363. * It's messy, but I assume there's a reason it's not statically allocated... *
  364. * OK, now I get it *
  365. * *
  366. * HISTORY: *
  367. * 6/13/2001 8:55PM ST : Created *
  368. *=============================================================================================*/
  369. void __cdecl WWMemoryLogClass::Release_Log(void)
  370. {
  371. MemLogMutexLockClass lock;
  372. if (_TheMemLog) {
  373. delete _TheMemLog;
  374. _TheMemLog = NULL;
  375. }
  376. }
  377. /***************************************************************************************************
  378. **
  379. ** Allocating and Freeing memory
  380. **
  381. ** PLEASE NOTE: The user is expected to implement global new and delete functions in his own
  382. ** code which call WWMemoryLogClass::Allocate_Memory and WWMemoryLogClass::Release_Memory.
  383. ** This was the only solution I could come up given that some APPS have their own new and delete
  384. ** functions or enable the CRT ones. It was also not an option to move this entire system into
  385. ** the APP because I wanted all of our LIBs to participate in the memory usage logging...
  386. **
  387. ***************************************************************************************************/
  388. const int WWMEMLOG_KEY0 = (unsigned('G')<<24) | (unsigned('g')<<16) | (unsigned('0')<<8) | unsigned('l');
  389. const int WWMEMLOG_KEY1 = (unsigned('~')<<24) | (unsigned('_')<<16) | (unsigned('d')<<8) | unsigned('3');
  390. /**
  391. ** MemoryLogStruct
  392. ** This structure is added to the beginning of each memory allocation to facilitate
  393. ** tracking which category the memory belongs to when it is freed. The size of
  394. ** this struct is also 16 bytes so that we wont be seriously affecting the alignment
  395. ** of allocated memory...
  396. */
  397. struct MemoryLogStruct
  398. {
  399. MemoryLogStruct(int category,int size) :
  400. Key0(WWMEMLOG_KEY0),
  401. Key1(WWMEMLOG_KEY1),
  402. Category(category),
  403. Size(size)
  404. {}
  405. bool Is_Valid_Memory_Log(void) { return ((Key0 == WWMEMLOG_KEY0) && (Key1 == WWMEMLOG_KEY1)); }
  406. int Key0; // if this is not equal to WWMEMLOG_KEY0 then we don't have a valid log
  407. int Key1; // should be equal to WWMEMLOG_KEY1
  408. int Category; // category this memory belongs to
  409. int Size; // size of the allocation
  410. };
  411. /***********************************************************************************************
  412. * WWMemoryLogClass::Allocate_Memory -- allocates memory *
  413. * *
  414. * This function adds a header to the memory allocated so that when the memory is freed *
  415. * the proper memory category size can be decremented. The application using this logging *
  416. * system should call this function from inside its overloaded 'new' operator. *
  417. * *
  418. * INPUT: *
  419. * *
  420. * OUTPUT: *
  421. * *
  422. * WARNINGS: *
  423. * *
  424. * HISTORY: *
  425. * 5/29/2001 gth : Created. *
  426. *=============================================================================================*/
  427. void * WWMemoryLogClass::Allocate_Memory(size_t size)
  428. {
  429. #if DISABLE_MEMLOG
  430. AllocateCount++;
  431. return ::malloc(size);
  432. #else
  433. __declspec( thread ) static bool reentrancy_test = false;
  434. MemLogMutexLockClass lock;
  435. if (reentrancy_test) {
  436. return ::malloc(size);
  437. } else {
  438. reentrancy_test = true;
  439. /*
  440. ** Allocate space for the requested buffer + our logging structure
  441. */
  442. void * ptr = ::malloc (size + sizeof(MemoryLogStruct));
  443. if (ptr != NULL) {
  444. /*
  445. ** Record this allocation
  446. */
  447. int active_category = WWMemoryLogClass::Register_Memory_Allocated(size);
  448. /*
  449. ** Write our logging structure into the beginning of the buffer. I'm using
  450. ** placement new syntax to initialize the log structure right in the memory buffer
  451. */
  452. new(ptr) MemoryLogStruct(active_category,size);
  453. /*
  454. ** Return the allocated memory to the user, skipping past our log structure.
  455. */
  456. reentrancy_test = false;
  457. return (void*)(((char *)ptr) + sizeof(MemoryLogStruct));
  458. } else {
  459. reentrancy_test = false;
  460. return ptr;
  461. }
  462. }
  463. #endif //DISABLE_MEMLOG
  464. }
  465. /***********************************************************************************************
  466. * WWMemoryLogClass::Release_Memory -- frees memory *
  467. * *
  468. * This function checks for a wwmemlog header and decrements the relevant memory category. *
  469. * It should be called in the application's custom delete operator. *
  470. * *
  471. * INPUT: *
  472. * *
  473. * OUTPUT: *
  474. * *
  475. * WARNINGS: *
  476. * *
  477. * HISTORY: *
  478. * 5/29/2001 gth : Created. *
  479. *=============================================================================================*/
  480. void WWMemoryLogClass::Release_Memory(void *ptr)
  481. {
  482. #if DISABLE_MEMLOG
  483. free(ptr);
  484. FreeCount++;
  485. #else
  486. MemLogMutexLockClass lock;
  487. if (ptr) {
  488. /*
  489. ** Check if this memory is preceeded by a valid MemoryLogStruct
  490. */
  491. MemoryLogStruct * memlog = (MemoryLogStruct*)((char*)ptr - sizeof(MemoryLogStruct));
  492. if (memlog->Is_Valid_Memory_Log()) {
  493. /*
  494. ** Valid MemoryLogStruct found, track the de-allocation and pass on
  495. ** to the built-in free function.
  496. */
  497. WWMemoryLogClass::Register_Memory_Released(memlog->Category,memlog->Size);
  498. free((void*)memlog);
  499. } else {
  500. /*
  501. ** No valid MemoryLogStruct found, just call free on the memory.
  502. */
  503. free(ptr);
  504. }
  505. }
  506. #endif //DISABLE_MEMLOG
  507. }
  508. // Reset allocate and free counters
  509. void WWMemoryLogClass::Reset_Counters()
  510. {
  511. AllocateCount=0;
  512. FreeCount=0;
  513. }
  514. // Return allocate count since last reset
  515. int WWMemoryLogClass::Get_Allocate_Count()
  516. {
  517. return AllocateCount;
  518. }
  519. // Return allocate count since last reset
  520. int WWMemoryLogClass::Get_Free_Count()
  521. {
  522. return FreeCount;
  523. }