Memory Leaks @ Vanilla Torque
by Bullitt Sesariza · in Torque Game Engine · 10/22/2008 (8:18 pm) · 42 replies
Dear Torque Masters,
I am currently investigating unusual high memory consumption in my game after it has been running for long time (a few days). Unable to find any clues, I dug into vanilla Torque Game Engine 1.5.2, starter.fps mod, scripting it to enter the game (via "Start Mission" button), and disconnect from mission (via "Esc" / exit mission). Surprisingly, the memory footprint rises everytime this process is completed - leading to > 2Gb of memory usage in just a few hours, ending in crash.
I've followed the resources from http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=9641, flagging the memory allocation during the game startup and dumping the memory leaks during game shutdown and get thousands of leak alerts, some of which I can confidently filtered out but a lot that I have no idea of.
Does anyone ever encounter this problem, or could it possibly just my "misconfiguration" of the engine? Could anybody point me to the direction where I can get rid of the leak alerts, it is difficult and very time consuming to check each line of the codes that possibly introduce the leak as there are hardly any 'source-code-documentation' within the engine.
Any help is highly appreciated, thank you for your kind attention.
I am currently investigating unusual high memory consumption in my game after it has been running for long time (a few days). Unable to find any clues, I dug into vanilla Torque Game Engine 1.5.2, starter.fps mod, scripting it to enter the game (via "Start Mission" button), and disconnect from mission (via "Esc" / exit mission). Surprisingly, the memory footprint rises everytime this process is completed - leading to > 2Gb of memory usage in just a few hours, ending in crash.
I've followed the resources from http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=9641, flagging the memory allocation during the game startup and dumping the memory leaks during game shutdown and get thousands of leak alerts, some of which I can confidently filtered out but a lot that I have no idea of.
Does anyone ever encounter this problem, or could it possibly just my "misconfiguration" of the engine? Could anybody point me to the direction where I can get rid of the leak alerts, it is difficult and very time consuming to check each line of the codes that possibly introduce the leak as there are hardly any 'source-code-documentation' within the engine.
Any help is highly appreciated, thank you for your kind attention.
#2
thanks for your reply, nice to hear from you!
In my case (Torque 1.5.2), it happens in all configurations (Debug, DebugFastRelight, and Release), which makes it easier to debug, however, I got a very long list of memory leaks, some of which I've managed to fix but a lot more I'm still stucked. The largest leak I notice is in AudioEmitter.
I've send a bug report to GG though, but, there seems to be no solution yet. Let's keep this thread up to date.
10/27/2008 (9:26 pm)
Hi Joey,thanks for your reply, nice to hear from you!
In my case (Torque 1.5.2), it happens in all configurations (Debug, DebugFastRelight, and Release), which makes it easier to debug, however, I got a very long list of memory leaks, some of which I've managed to fix but a lot more I'm still stucked. The largest leak I notice is in AudioEmitter.
I've send a bug report to GG though, but, there seems to be no solution yet. Let's keep this thread up to date.
#3
10/28/2008 (1:01 pm)
@Bullitt - Do you have an excessive amount of console log output? Not sure about in dedicated server mode, but on a normal client it would keep the entire log in memory... we had to make changes to only keep a few hundred lines in memory at any time.
#4
11/02/2008 (7:16 pm)
Thanks Tom, I just found the time to check. Yeah, I am running a normal client and the entire log is stored in consoleLog static variable in console.cc. I will post the leaks that I managed to fix soon.
#5
SEVERITY: Fatal
Please note that the location is relative to Vanilla Torque 1.5.2
11/02/2008 (7:34 pm)
LEAK_FIX GuiMessageVectorCtrl class on gui/game/guiMessageVectorCtrl.cc line 263SEVERITY: Fatal
Please note that the location is relative to Vanilla Torque 1.5.2
void GuiMessageVectorCtrl::createSpecialMarkers(SpecialMarkers& rSpecial, const char* string)
{
// The first thing we need to do is create a version of the string with no uppercase
// chars for matching...
char* pLCCopy = new char[dStrlen(string) + 1]; // KIM 081030 Here is the LEAK...
for (U32 i = 0; string[i] != '[[60c1db107584f]]'; i++)
pLCCopy[i] = dTolower(string[i]);
pLCCopy[dStrlen(string)] = '[[60c1db107584f]]';
Vector<TempLineBreak> tempSpecials(__FILE__, __LINE__);
Vector<S32> tempTypes(__FILE__, __LINE__);
const char* pCurr = pLCCopy;
while (pCurr[0] != '[[60c1db107584f]]') {
const char* pMinMatch = &pLCCopy[dStrlen(string)];
U32 minMatchType = 0xFFFFFFFF;
AssertFatal(pMinMatch[0] == '[[60c1db107584f]]', "Error, bad positioning of sentry...");
for (U32 i = 0; i < 16; i++) {
if (mAllowedMatches[i][0] == '[[60c1db107584f]]')
continue;
// Not the most efficient...
char matchBuffer[512];
dStrncpy(matchBuffer, mAllowedMatches[i], 500);
matchBuffer[499] = '[[60c1db107584f]]';
dStrcat(matchBuffer, "://");
const char* pMatch = dStrstr(pCurr, (const char*)matchBuffer);
if (pMatch != NULL && pMatch < pMinMatch) {
pMinMatch = pMatch;
minMatchType = i;
}
}
if (pMinMatch[0] != '[[60c1db107584f]]') {
AssertFatal(minMatchType != 0xFFFFFFFF, "Hm, that's bad");
// Found a match...
U32 start = pMinMatch - pLCCopy;
U32 j;
for (j = 0; pLCCopy[start + j] != '[[60c1db107584f]]'; j++) {
if (pLCCopy[start + j] == '\n' ||
pLCCopy[start + j] == ' ' ||
pLCCopy[start + j] == '\t')
break;
}
AssertFatal(j > 0, "Error, j must be > 0 at this point!");
U32 end = start + j - 1;
tempSpecials.increment();
tempSpecials.last().start = start;
tempSpecials.last().end = end;
tempTypes.push_back(minMatchType);
pCurr = &pLCCopy[end + 1];
} else {
// No match. This will cause the while loop to terminate...
pCurr = pMinMatch;
}
}
if ((rSpecial.numSpecials = tempSpecials.size()) != 0) {
rSpecial.specials = new SpecialMarkers::Special[tempSpecials.size()];
for (U32 i = 0; i < tempSpecials.size(); i++) {
rSpecial.specials[i].start = tempSpecials[i].start;
rSpecial.specials[i].end = tempSpecials[i].end;
rSpecial.specials[i].specialType = tempTypes[i];
}
} else {
rSpecial.specials = NULL;
}
delete pLCCopy; // + KIM 081030 LEAK_FIX
}
#6
11/02/2008 (7:35 pm)
I have a fix for the console that allows you to put a limit on how much it keeps in memory. I'll talk to Matt Fairfax about getting it into the TGEA 1.8 beta.
#7
SEVERITY: bad
11/02/2008 (7:41 pm)
LEAK AbstractClassRep::Field on console/consoleObject.h line 230SEVERITY: bad
struct Field {
// + KIM 081031 LEAK_FIX
~Field()
{
if ( validator )
{
delete validator;
}
}
// - KIM 081031 LEAK_FIX
const char* pFieldname; ///< Name of the field.
const char* pGroupname;
// ... ...
#8
SEVERITY: bad
Fix:
Step 1: terrain/terrData.cc line 80 : comment out TerrainBlock destructor
Step 2: put this code in terrain/terrCollision.cc line 345 (before TerrainBlock::buildConvex)
11/02/2008 (7:49 pm)
LEAK Convex sTerrainConvexList; on terrain/terrCollision.cc line 26SEVERITY: bad
Fix:
Step 1: terrain/terrData.cc line 80 : comment out TerrainBlock destructor
/********* KIM 081020 Move to terrain/terrCollision.cc. purpose: LEAK_FIX
TerrainBlock::~TerrainBlock()
{
delete lightMap;
if(whiteMap)
delete whiteMap;
}
**********/Step 2: put this code in terrain/terrCollision.cc line 345 (before TerrainBlock::buildConvex)
// ++ KIM 081020 LEAK_FIX Moved from terrain/terrData.cc
TerrainBlock::~TerrainBlock()
{
//Con::errorf("~TerrainBlock : %d", this->getId());
sTerrainConvexList.collectGarbage(); // + KIM 081020 LEAK_FIX
delete lightMap;
if(whiteMap)
delete whiteMap;
}
// -- KIM 081020 MOVE
#9
My CRT leak detector detects leak on 3 spots:
1. core/dataChunker.cc line 15
2. core/dataChunker.cc line 30
3. core/dataChunker.cc line 42
These particular ones on dataChunker are very confusing as I have traced and re-traced (especially the one on line 42 as it leaks the most) and seems like they are safe. However, the various memory leak detectors that I tried point to the same three spots..,forming Bermuda Triangle of Death which I can't Triangulate any solution :)
Any help will be highly appreciated :) (: pleeaaaaaaaaaseeee, help meeeeee! )
11/02/2008 (8:09 pm)
I am currently investigating the leak in core/dataChunker.ccMy CRT leak detector detects leak on 3 spots:
1. core/dataChunker.cc line 15
DataChunker::DataChunker(S32 size)
{
chunkSize = size;
curBlock = new DataBlock(size); // core/dataChunker.cc line 15, leak 12 bytes
curBlock->next = NULL;
curBlock->curIndex = 0;
}2. core/dataChunker.cc line 30
void *DataChunker::alloc(S32 size)
{
AssertFatal(size <= chunkSize, "Data chunk too large.");
if(!curBlock || size + curBlock->curIndex > chunkSize)
{
DataBlock *temp = new DataBlock(chunkSize); // core/dataChunker.cc line 30, leak 12 bytes
temp->next = curBlock;
temp->curIndex = 0;
curBlock = temp;
}
void *ret = curBlock->data + curBlock->curIndex;
curBlock->curIndex += (size + 3) & ~3; // dword align
return ret;
}3. core/dataChunker.cc line 42
DataChunker::DataBlock::DataBlock(S32 size)
{
data = new U8[size]; // core/dataChunker.cc line 42, leak 16376 bytes and happens quite often
}These particular ones on dataChunker are very confusing as I have traced and re-traced (especially the one on line 42 as it leaks the most) and seems like they are safe. However, the various memory leak detectors that I tried point to the same three spots..,
Any help will be highly appreciated :) (
#10
11/02/2008 (8:13 pm)
@Bullitt - Most leak detectors can tell you the allocation number which can then be used to break on that same allocation on the next execution. If the leak occurs every time and you can reproduce the same execution then you can usually capture the call stack that way.
#11
Thanks for your input!
11/02/2008 (8:50 pm)
@Tom - I am using Torque memory profiler (Memory::setBreakAlloc), Visual Studio built in CRT leak detector (_CrtSetBreakAlloc), and they rarely break on the allocation number. (Only in some other cases, they break, but they never break on DataChunker case. And I'm quite sure the execution paths are similar because I am using the Journal JSave and JPlay).Thanks for your input!
#12
SEVERITY: once every application startup and shutdown -edit-: helper fix
FIX:
1. platform/gameInterface.h line 31
2. platform/gameInterface.cc line 27
11/04/2008 (1:55 am)
LEAK Mutex::createMutex() on platformWin32/winMutex.cc line 11SEVERITY: once every application startup and shutdown -edit-: helper fix
FIX:
1. platform/gameInterface.h line 31
~GameInterface(); // KIM 081103 LEAK_FIX
2. platform/gameInterface.cc line 27
// + KIM 081103 LEAK_FIX
GameInterface::~GameInterface()
{
Mutex::destroyMutex(gGameEventQueueMutex);
}
// - KIM 081103 LEAK_FIX
#13
console/CMDscan.cc line 1718 (40 bytes)
console/CMDscan.cc line 1727 (16386 bytes)
severity: once every application startup and shutdown -edit- : helper fix
FIX:
1. add to console/consoleParser.h line 37
2. add to console/consoleParser.h line 52
3. add to console/consoleParser.h line 64 & line 65
4. change console/consoleParser.h line 69 - 71 to
5. add to console/consoleParser.cc line 21 Console::freeConsoleParserList()
6. edit console/consoleParser.cc line 29 Console::addConsoleParser(... ..., fnShutdown shtdwn, ...)
7. add to console/console.cc line 263 Con::shutdown()
8. add to console/CMDscan.cc line 18
9. move this chunk of code from console/CMDscan.cc line 2045 (9 lines) to line 160 (before declaration of struct yy_buffer_state)
10. add to console/CMDscan.cc line 163
11. finally (for CMDscan.cc, we still have BASscan.cc yet) add this to end of file console/CMDscan.cc (line 2427)
// + KIM 081104 LEAK_FIX
11/04/2008 (7:57 pm)
LEAKs: console/CMDscan.cc line 1718 (40 bytes)
console/CMDscan.cc line 1727 (16386 bytes)
severity: once every application startup and shutdown -edit- : helper fix
FIX:
1. add to console/consoleParser.h line 37
// + KIM 081104 LEAK_FIX ////////////////////////////////////////////////////////////////////////// /// \brief Function for cleaning up memory leak ////////////////////////////////////////////////////////////////////////// typedef void (*fnShutdown)(); // - KIM 081104 LEAK_FIX
2. add to console/consoleParser.h line 52
...
struct ConsoleParser
{
...
...
fnSetScanBuffer setScanBuffer; //!< SetScanBuffer lexer function (console/consoleParser.h line 51)
fnShutdown shutdown; //!< Shutdown function, to cleanup memory leak // + KIM 081104 LEAK_FIX
};
...
...3. add to console/consoleParser.h line 64 & line 65
#define CON_DECLARE_PARSER(prefix) \ const char * prefix##GetCurrentFile(); \ S32 prefix##GetCurrentLine(); \ void prefix##SetScanBuffer(const char *sb, const char *fn); \ S32 prefix##parse(); \ void prefix##restart(FILE *input_file); \ // + KIM 081104 line continuation symbol '\' void prefix##shutdown() // + KIM 081104 LEAK_FIX void prefix##shutdown
4. change console/consoleParser.h line 69 - 71 to
#define CON_ADD_PARSER(prefix, ext, def) \ Compiler::addConsoleParser(ext, prefix##GetCurrentFile, prefix##GetCurrentLine, prefix##parse, \ prefix##restart, prefix##SetScanBuffer, prefix##shutdown, def) // +KIM 081104 LEAK_FIX prefix##shutdown
5. add to console/consoleParser.cc line 21 Console::freeConsoleParserList()
void freeConsoleParserList(void)
{
ConsoleParser *pParser;
while(pParser = gParserList)
{
// + KIM 081104 LEAK_FIX - console/consoleParser.cc line 21
if ( pParser->shutdown )
pParser->shutdown();
// - KIM 081104 LEAK_FIX
gParserList = pParser->next;
delete pParser;
}
gDefaultParser = NULL;
}6. edit console/consoleParser.cc line 29 Console::addConsoleParser(... ..., fnShutdown shtdwn, ...)
// KIM 081104 add fnShutdown to the argument list
bool addConsoleParser(char *ext, fnGetCurrentFile gcf, fnGetCurrentLine gcl, fnParse p, fnRestart r, fnSetScanBuffer ssb, fnShutdown shtdwn, bool def /* = false */)
{
AssertFatal(ext && gcf && gcl && p && r, "AddConsoleParser called with one or more NULL arguments");
ConsoleParser *pParser;
if(pParser = new ConsoleParser)
{
...
...
}7. add to console/console.cc line 263 Con::shutdown()
void shutdown()
{
AssertFatal(active == true, "Con::shutdown should only be called once.");
active = false;
consoleLogFile.close();
Namespace::shutdown();
// + KIM 081104 LEAK_FIX
Compiler::freeConsoleParserList();
// - KIM 081104 LEAK_FIX
}8. add to console/CMDscan.cc line 18
// + KIM 081104 LEAK_FIX #define yyshutdown CMDshutdown // - KIM 081104 LEAK_FIX
9. move this chunk of code from console/CMDscan.cc line 2045 (9 lines) to line 160 (before declaration of struct yy_buffer_state)
// + KIM 081104 LEAK_FIX MOVED originally from line 2045
#ifdef YY_USE_PROTOS
static void yy_flex_free( void *ptr )
#else
static void yy_flex_free( ptr )
void *ptr;
#endif
{
free( ptr );
}
// - KIM 081104 LEAK_FIX MOVED originally from line 204510. add to console/CMDscan.cc line 163
struct yy_buffer_state
{
// + KIM 081104 LEAK_FIX - line 163
~yy_buffer_state()
{
if ( yy_ch_buf )
{
yy_flex_free( (void*) yy_ch_buf );
}
}
// - KIM 081104 LEAK_FIX
FILE *yy_input_file;
... ... ...11. finally (for CMDscan.cc, we still have BASscan.cc yet) add this to end of file console/CMDscan.cc (line 2427)
// + KIM 081104 LEAK_FIX
void yyshutdown()
{
if ( yy_current_buffer )
{
yy_current_buffer->~yy_buffer_state();
yy_flex_free( (void*) yy_current_buffer );
}
}
// - KIM 081104 LEAK_FIX
#14
12. add to console/BASscan.cc line 18
13. console/BASscan.cc line 2050 - comment out line 2050 to 2058
14. add to console/BASscan.cc line 161 (just before struct yy_buffer_state declaration)
15. add to console/BASscan.cc line 163 (struct yy_buffer_state)
16. finally... add this method to the end of console/BASscan.cc (line 2191, vanilla Torque)
11/04/2008 (8:24 pm)
... continued from previous post, similar edits to BASscan.cc as to CMDscan.cc12. add to console/BASscan.cc line 18
// + KIM 081104 LEAK_FIX #define yyshutdown BASshutdown // - KIM 081104 LEAK_FIX
13. console/BASscan.cc line 2050 - comment out line 2050 to 2058
// ++ KIM 081104 LEAK_FIX MOVED
/****************************************
#ifdef YY_USE_PROTOS
static void yy_flex_free( void *ptr )
#else
static void yy_flex_free( ptr )
void *ptr;
#endif
{
free( ptr );
}
*****************************************/
// -- KIM 081104 LEAK_FIX MOVED14. add to console/BASscan.cc line 161 (just before struct yy_buffer_state declaration)
// + KIM 081104 LEAK_FIX MOVED
#ifdef YY_USE_PROTOS
static void yy_flex_free( void *ptr )
#else
static void yy_flex_free( ptr )
void *ptr;
#endif
{
free( ptr );
}
// - KIM 081104 LEAK_FIX MOVED15. add to console/BASscan.cc line 163 (struct yy_buffer_state)
struct yy_buffer_state
{
// + KIM 081104 LEAK_FIX
~yy_buffer_state()
{
if ( yy_ch_buf )
{
yy_flex_free( (void*) yy_ch_buf );
}
}
// - KIM 081104 LEAK_FIX
FILE *yy_input_file;
... ... ...16. finally... add this method to the end of console/BASscan.cc (line 2191, vanilla Torque)
// + KIM 081104 LEAK_FIX
void yyshutdown()
{
if ( yy_current_buffer )
{
yy_current_buffer->~yy_buffer_state();
yy_flex_free( (void*) yy_current_buffer );
}
}
// - KIM 081104 LEAK_FIX
#15
StringStack::mBuffer - console/stringStack.h line 47 (10240 bytes)
StringStack::mArgBuffer - console/stringStack.h line 55 (4096 bytes)
SEVERITY: once every application startup and shutdown
FIX:
1. add to console/stringStack.h line 19
2. add to console/stringStack.cc end of file (line 26)
11/04/2008 (10:59 pm)
LEAK:StringStack::mBuffer - console/stringStack.h line 47 (10240 bytes)
StringStack::mArgBuffer - console/stringStack.h line 55 (4096 bytes)
SEVERITY: once every application startup and shutdown
FIX:
1. add to console/stringStack.h line 19
struct StringStack
{
// + KIM 081105 LEAK_FIX
~StringStack();
// - KIM 081105 LEAK_FIX
enum {
... ... ...2. add to console/stringStack.cc end of file (line 26)
// KIM 081105 LEAK_FIX
StringStack::~StringStack()
{
if ( mBuffer )
dFree( mBuffer );
if ( mArgBuffer )
dFree( mArgBuffer );
}
// KIM 081105 LEAK_FIX
#16
mBinArray = new SceneObjectRef[csmNumBins * csmNumBins];
Severity: 2x every application startup
FIX:
add this to sim/sceneObject.cc line 833
11/05/2008 (7:27 pm)
LEAK: sim/sceneObject.cc line 806 leak 2x @ 5120 bytes mBinArray = new SceneObjectRef[csmNumBins * csmNumBins];
Severity: 2x every application startup
FIX:
add this to sim/sceneObject.cc line 833
Container::~Container()
{
// + KIM 081105 LEAK_FIX
if ( mBinArray )
{
delete mBinArray;
mBinArray = NULL;
}
// - KIM 081105 LEAK_FIX
for (U32 i = 0; i < mRefPoolBlocks.size(); i++)
... ... ...
#17
we were having issues with leaking SimObjects.
it was easy to build a routine which dumps the counts of each type of SimObject,
and that was helpful, but it didn't tell us *where* the leaks were coming from.
to help with that, we added some instrumentation which essentially marks every simObject during its creation with the current script stack trace. that forms the basis for some nice reports which can instantly tell you "oh, we're leaking a SimGroup every time function foo() is called!"
it's a fairly sprawling change and our implementation also uses STL, so it's not the greatest for a resource, but the main idea has proven sound.
in some cases we also weren't leaking SimObjects, but we were creating and destroying an awful lot of them. (think hundreds per second). in a very large and complex codebase it would have been difficult to track down the source of that churn, but having this instrumentation made it almost trivial.
if Torsion ever decided to require an engine recompile (it doesn't, does it?),
similar to Plastic Games's recent Tweaker tool, something like that would make a great addition.
11/05/2008 (11:31 pm)
Relatedly,we were having issues with leaking SimObjects.
it was easy to build a routine which dumps the counts of each type of SimObject,
and that was helpful, but it didn't tell us *where* the leaks were coming from.
to help with that, we added some instrumentation which essentially marks every simObject during its creation with the current script stack trace. that forms the basis for some nice reports which can instantly tell you "oh, we're leaking a SimGroup every time function foo() is called!"
it's a fairly sprawling change and our implementation also uses STL, so it's not the greatest for a resource, but the main idea has proven sound.
in some cases we also weren't leaking SimObjects, but we were creating and destroying an awful lot of them. (think hundreds per second). in a very large and complex codebase it would have been difficult to track down the source of that churn, but having this instrumentation made it almost trivial.
if Torsion ever decided to require an engine recompile (it doesn't, does it?),
similar to Plastic Games's recent Tweaker tool, something like that would make a great addition.
#18
if ( mBinArray )
{
delete mBinArray;
}
// - KIM 081105 LEAK_FIX
Patch this leads to a DebugBreak() when executing "delete mBinArray";
11/06/2008 (1:35 am)
// + KIM 081105 LEAK_FIXif ( mBinArray )
{
delete mBinArray;
}
// - KIM 081105 LEAK_FIX
Patch this leads to a DebugBreak() when executing "delete mBinArray";
#19
Thanks for noting, I've just checked it out. You need to define TORQUE_DISABLE_MEMORY_MANAGER on Torque Demo project scope. I didn't check why it did not work on torque memory manager, anyway, I remember reading somewhere in this forum that for Windows 5.0 and above (Win XP), windows memory manager has been more efficient at allocating and de-allocating memory, so Torque's memory manager might not be necessary and might introduce more overhead on memory usage.
@Orion Elenzil:
I think I am also having problem with SimObjects, I'm not into that yet, but would you please share how you dump your SimObjects and SimGroups? Thank you very much indeed.
11/06/2008 (7:28 pm)
@Soar_o1:Thanks for noting, I've just checked it out. You need to define TORQUE_DISABLE_MEMORY_MANAGER on Torque Demo project scope. I didn't check why it did not work on torque memory manager, anyway, I remember reading somewhere in this forum that for Windows 5.0 and above (Win XP), windows memory manager has been more efficient at allocating and de-allocating memory, so Torque's memory manager might not be necessary and might introduce more overhead on memory usage.
@Orion Elenzil:
I think I am also having problem with SimObjects, I'm not into that yet, but would you please share how you dump your SimObjects and SimGroups? Thank you very much indeed.
#20
here is some script-only code to dump the counts.
you can put it at the bottom of main.cs or wherever you please.
sample output running from the TGE 1.4 demo
11/06/2008 (9:50 pm)
Bullitt,here is some script-only code to dump the counts.
you can put it at the bottom of main.cs or wherever you please.
/////////////////////////////////////////////////////////////////
// sim object counts
// orion elenzil
// 20081107
//
// this utility will count all the instanciations of each class in your simulation.
// usage:
// dumpClassInstanceCounts(simGroup %group, bool %verbose)
// where
// %group is a simGroup of interest, such as a particular GuiCOntrol or RootGroup
// %verbose is a boolean telling the routine to dump a tree along the way.
//
// note you may get many script warnings about undefined method "getCount()".
// if this happens and bothers you, see the note in the code about how to get around it.
//
// oxe
function dumpClassInstanceCounts(%group, %verbose)
{
if (%group $= "")
{
%group = RootGroup;
// other groups of interest:
// MissionGroup
// ServerGroup
// Canvas
// MyGuiControl
}
%list = getClassInstanceCounts(%group, %verbose);
echo("here they are:" NL %list);
}
function getClassInstanceCounts(%group, %verbose)
{
$gClassCountSeenList = " "; // this will accumulate a list of all the different classes we've seen
if (%verbose $= "")
%verbose = false;
_accumulateClassInstanceCounts_Recursive(%group, %verbose, "");
// okay, $gClassCountSeenList is now a space-delimited list of all the classes we encountered,
// and $gClassCounts[<some classname>] is the count.
// let's compose it all into fields and records and stuff.
// there's a leading blank word as well.
%ret = "";
%num = getWordCount($gClassCountSeenList) - 1;
for (%n = 1; %n <= %num; %n++)
{
%className = getWord($gClassCountSeenList, %n);
%classCount = $gClassCounts[%className];
%ret = %ret @ %className SPC %classCount @ "\n";
}
return %ret;
}
// don't call this directly. call getClassInstanceCounts() instead.
function _accumulateClassInstanceCounts_Recursive(%object, %verbose, %indent)
{
%className = %object.getClassName();
if (%verbose)
{
echo(%indent @ %className SPC %object.getId() SPC %object.getName());
}
%classNameMunged = " " @ %className @ " "; // to avoid class names which may be proper substrings of other classnames.
if (strstr($gClassCountSeenList, %classNameMunged) < 0)
{
// first time seen, let's record it as seen and clear out the totals
$gClassCounts[%className] = 0;
$gClassCountSeenList = $gClassCountSeenList @ %className @ " ";
// echo("saw" SPC %className);
}
// increment the count
$gClassCounts[%className] = $gClassCounts[%className] + 1;
// the following throws craploads of warnings,
// but i don't know how else to do it w/ stock TGE script.
// you can get around the warns by implementing the "isSimSet" resource:
// http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=13244
%num = %object.getCount();
for (%n = 0; %n < %num; %n++)
{
%child = %object.getObject(%n);
_accumulateClassInstanceCounts_Recursive(%child, %verbose, %indent @ "| ");
}
}sample output running from the TGE 1.4 demo
==>dumpClassInstanceCounts(canvas, 0); here they are: GuiCanvas 1 GuiChunkedBitmapCtrl 1 GuiTreeViewCtrl 1 GuiControl 4 GuiBitmapCtrl 9 GuiBitmapButtonCtrl 14 GuiMLTextCtrl 8 GuiFadeinBitmapCtrl 1 GuiWindowCtrl 1 GuiScrollCtrl 2 GuiConsole 1 GuiConsoleEditCtrl 1 GuiPaneControl 1 ==>dumpClassInstanceCounts(canvas, 1); GuiCanvas 1057 Canvas | GuiChunkedBitmapCtrl 1273 MainMenuGui | | GuiTreeViewCtrl 1274 thetree | GuiControl 1361 MainMenuDlg | | GuiControl 1362 | | | GuiBitmapCtrl 1363 | | | GuiBitmapCtrl 1364 racing | | | GuiBitmapCtrl 1365 | | | GuiBitmapButtonCtrl 1366 | | | | GuiMLTextCtrl 1367 | | | GuiBitmapButtonCtrl 1368 | | | | GuiMLTextCtrl 1369 | | | GuiBitmapButtonCtrl 1370 | | | | GuiMLTextCtrl 1371 | | | GuiBitmapButtonCtrl 1372 | | | | GuiMLTextCtrl 1373 | | | GuiBitmapCtrl 1374 | GuiControl 1253 OverlayDlg | | GuiBitmapCtrl 1254 | | GuiBitmapCtrl 1255 | | GuiBitmapCtrl 1256 | | GuiBitmapCtrl 1257 | | GuiBitmapCtrl 1258 | | GuiBitmapButtonCtrl 1259 OverlayQuitPage | | GuiBitmapButtonCtrl 1260 OverlayPrevPage | | GuiBitmapButtonCtrl 1261 OverlayNextPage | | GuiMLTextCtrl 1262 OverlayTitle | | GuiMLTextCtrl 1263 OverlayF10F11 | | GuiBitmapButtonCtrl 1264 op_home | | GuiBitmapButtonCtrl 1265 op_website | | GuiBitmapButtonCtrl 1266 op_purchase | | GuiBitmapButtonCtrl 1267 op_options | | GuiBitmapButtonCtrl 1268 op_credits | | GuiBitmapButtonCtrl 1269 op_exit | | GuiMLTextCtrl 1270 OverlayDesc | | GuiBitmapButtonCtrl 1271 OverlayBuyNow | | GuiFadeinBitmapCtrl 1272 TorqueLogoScreen | GuiControl 1109 ConsoleDlg | | GuiWindowCtrl 1110 ConsoleDlgWindow | | | GuiScrollCtrl 1111 | | | | GuiConsole 1112 | | | GuiConsoleEditCtrl 1113 ConsoleEntry | | | GuiPaneControl 1114 ConsoleErrorPane | | | | GuiScrollCtrl 1115 ConsoleErrorScroller | | | | | GuiMLTextCtrl 1116 ConsoleErrorDisplay here they are: GuiCanvas 1 GuiChunkedBitmapCtrl 1 GuiTreeViewCtrl 1 GuiControl 4 GuiBitmapCtrl 9 GuiBitmapButtonCtrl 14 GuiMLTextCtrl 8 GuiFadeinBitmapCtrl 1 GuiWindowCtrl 1 GuiScrollCtrl 2 GuiConsole 1 GuiConsoleEditCtrl 1 GuiPaneControl 1
Torque Owner Joey Skinner