diff --git a/Editor/Sector Summary.cpp b/Editor/Sector Summary.cpp index 8897394a..d90f8b35 100644 --- a/Editor/Sector Summary.cpp +++ b/Editor/Sector Summary.cpp @@ -95,7 +95,7 @@ INT32 giCurrentViewLevel = ALL_LEVELS_MASK; BOOLEAN gbSectorLevels[16][16]; BOOLEAN gfGlobalSummaryLoaded = FALSE; -SUMMARYFILE *gpSectorSummary[16][16][8]; +SUMMARYFILE *gpSectorSummary[16][16][8] = {}; SUMMARYFILE *gpCurrentSectorSummary; MOUSE_REGION MapRegion; @@ -2419,6 +2419,8 @@ void LoadGlobalSummary() if( gfMustForceUpdateAllMaps ) { OutputDebugString( (LPCSTR)String( "A MAJOR MAP UPDATE EVENT HAS BEEN DETECTED FOR %d MAPS!!!!.\n", gusNumberOfMapsToBeForceUpdated ) ); + //ADB update it! + ApologizeOverrideAndForceUpdateEverything(); } @@ -2503,6 +2505,11 @@ void WriteSectorSummaryUpdate( STR8 puiFilename, UINT8 ubLevel, SUMMARYFILE *pSu else x = (puiFilename[ 1 ] - '0') * 10 + puiFilename[ 2 ] - '0' - 1; + if( gpSectorSummary[x][y][ubLevel] ) + { + MemFree( gpSectorSummary[x][y][ubLevel] ); + gpSectorSummary[x][y][ubLevel] = NULL; + } gpSectorSummary[x][y][ubLevel] = pSummaryFileInfo; // Snap: Restore the data directory once we are finished. @@ -2568,6 +2575,9 @@ void LoadSummary( STR8 pSector, UINT8 ubLevel, FLOAT dMajorMapVersion ) if( !fp ) { gusNumEntriesWithOutdatedOrNoSummaryInfo++; + //ADB don't forget that these might need to be updated!!! + gusNumberOfMapsToBeForceUpdated++; + gfMustForceUpdateAllMaps = TRUE; return; } fread( &temp, 1, sizeof( SUMMARYFILE ), fp ); @@ -2731,16 +2741,23 @@ void SummaryUpdateCallback( GUI_BUTTON *btn, INT32 reason ) SetProgressBarTitle( 0, L"Generating map summary", BLOCKFONT2, FONT_RED, FONT_NEARBLACK ); SetProgressBarMsgAttributes( 0, SMALLCOMPFONT, FONT_BLACK, FONT_BLACK ); +#if 0 + // 0verhaul: This should NOT be freed. An array index can be freed when it is recalculated, + // as this function is about to do. And then it SHOULD be freed first, which it wasn't doing. + // Either way, using this pointer is not a safe way to free the current sector's summary data. if( gpCurrentSectorSummary ) { MemFree( gpCurrentSectorSummary ); gpCurrentSectorSummary = NULL; } +#endif sprintf( str, "%c%d", gsSelSectorY + 'A' - 1, gsSelSectorX ); EvaluateWorld( str, (UINT8)giCurrLevel ); - gpSectorSummary[ gsSelSectorX ][ gsSelSectorY ][ giCurrLevel ] = gpCurrentSectorSummary; + // ADB: The current sector summary should be set to the array value, not the other way around. + // Also the sector X and Y values are 1-based and the array is 0-based. So subtract 1 from X and Y + gpCurrentSectorSummary = gpSectorSummary[ gsSelSectorX - 1][ gsSelSectorY - 1][ giCurrLevel ]; gfRenderSummary = TRUE; @@ -2765,7 +2782,7 @@ void ExtractTempFilename() void ApologizeOverrideAndForceUpdateEverything() { INT32 x, y; - CHAR16 str[ 50 ]; + CHAR16 str[ 128 ]; CHAR8 name[50]; SUMMARYFILE *pSF; //Create one huge assed button @@ -2883,7 +2900,8 @@ void ApologizeOverrideAndForceUpdateEverything() } } - EvaluateWorld( "p3_m.dat", 0 ); + // ADB: The call to EvaluateWorld does not use the .dat extension + EvaluateWorld( "p3_m", 0 ); RemoveProgressBar( 2 ); gfUpdatingNow = FALSE; @@ -2933,7 +2951,6 @@ void SetupItemDetailsMode( BOOLEAN fAllowRecursion ) if( gpCurrentSectorSummary->ubSummaryVersion == GLOBAL_SUMMARY_VERSION ) gusNumEntriesWithOutdatedOrNoSummaryInfo++; SummaryUpdateCallback( ButtonList[ iSummaryButton[ SUMMARY_UPDATE ] ], MSYS_CALLBACK_REASON_LBUTTON_UP ); - gpCurrentSectorSummary = gpSectorSummary[ gsSelSectorX - 1 ][ gsSelSectorY - 1 ][ giCurrLevel ]; } //Open the original map for the sector sprintf( szFilename, "MAPS\\%S", gszFilename ); @@ -2942,21 +2959,28 @@ void SetupItemDetailsMode( BOOLEAN fAllowRecursion ) { //The file couldn't be found! return; } - //Now fileseek directly to the file position where the number of world items are stored - if( !FileSeek( hfile, gpCurrentSectorSummary->uiNumItemsPosition, FILE_SEEK_FROM_START ) ) - { //Position couldn't be found! - FileClose( hfile ); - return; - } - //Now load the number of world items from the map. - FileRead( hfile, &uiNumItems, 4, &uiNumBytesRead ); - if( uiNumBytesRead != 4 ) - { //Invalid situation. - FileClose( hfile ); - return; + // ADB: The uiNumItemsPosition may be 0 here due to the recursion further down. + // If so, skip the read + uiNumItems = 0; + if (gpCurrentSectorSummary->uiNumItemsPosition) + { + //Now fileseek directly to the file position where the number of world items are stored + if( !FileSeek( hfile, gpCurrentSectorSummary->uiNumItemsPosition, FILE_SEEK_FROM_START ) ) + { //Position couldn't be found! + FileClose( hfile ); + return; + } + //Now load the number of world items from the map. + FileRead( hfile, &uiNumItems, 4, &uiNumBytesRead ); + if( uiNumBytesRead != 4 ) + { //Invalid situation. + FileClose( hfile ); + return; + } } + //Now compare this number with the number the summary thinks we should have. If they are different, - //the the summary doesn't match the map. What we will do is force regenerate the map so that they do + //then the summary doesn't match the map. What we will do is force regenerate the map so that they do //match if( uiNumItems != gpCurrentSectorSummary->usNumItems && fAllowRecursion ) { @@ -3108,4 +3132,22 @@ UINT8 GetCurrentSummaryVersion() return 0; } +void ClearSummaryInfo() +{ + for (int x = 0; x < 16; x++) + { + for (int y=0; y < 16; y++) + { + for (int z=0; z < 8; z++) + { + if (gpSectorSummary[x][y][z]) + { + MemFree( gpSectorSummary[x][y][z]); + gpSectorSummary[x][y][z] = NULL; + } + } + } + } +} + #endif \ No newline at end of file diff --git a/Editor/Summary Info.h b/Editor/Summary Info.h index 2009c1ec..cc2885ab 100644 --- a/Editor/Summary Info.h +++ b/Editor/Summary Info.h @@ -104,6 +104,7 @@ extern void WriteSectorSummaryUpdate( STR8 puiFilename, UINT8 ubLevel, SUMMARYFI extern BOOLEAN gfMustForceUpdateAllMaps; extern BOOLEAN gfMajorUpdate; void ApologizeOverrideAndForceUpdateEverything(); +void ClearSummaryInfo(); #endif #endif diff --git a/Editor/editscreen.cpp b/Editor/editscreen.cpp index 61298070..795304de 100644 --- a/Editor/editscreen.cpp +++ b/Editor/editscreen.cpp @@ -73,6 +73,7 @@ #include "Music Control.h" #include "Soldier Profile.h" #include "GameSettings.h" + #include "Summary Info.h" #endif @@ -305,6 +306,9 @@ BOOLEAN EditModeInit( void ) fFirstTimeInEditModeInit = FALSE; } + // Clear the summary info array + ClearSummaryInfo(); + //Initialize editor specific stuff for each Taskbar. EntryInitEditorTerrainInfo(); EntryInitEditorItemsInfo(); @@ -557,6 +561,9 @@ BOOLEAN EditModeShutdown( void ) HideExitGrids(); + // Clear the summary info array + ClearSummaryInfo(); + UnPauseGame(); return TRUE; @@ -581,7 +588,7 @@ void SetBackgroundTexture( ) usIndex = (UINT16)(rand( ) % 10 ); // Adjust for type - usIndex += gTileTypeStartIndex[ gCurrentBackground ]; + usIndex = usIndex + gTileTypeStartIndex[ gCurrentBackground ]; // Set land index if( TypeRangeExistsInLandLayer( cnt, FIRSTFLOOR, LASTFLOOR, &Dummy ) ) @@ -1557,7 +1564,7 @@ void HandleKeyboardShortcuts( ) RemoveAllRoofsOfTypeRange( i, FIRSTTEXTURE, LASTITEM ); RemoveAllOnRoofsOfTypeRange( i, FIRSTTEXTURE, LASTITEM ); RemoveAllShadowsOfTypeRange( i, FIRSTROOF, LASTSLANTROOF ); - usRoofIndex = 9 + ( rand() % 3 ); + usRoofIndex = (UINT16) (9 + ( rand() % 3 )); GetTileIndexFromTypeSubIndex( usRoofType, usRoofIndex, &usTileIndex ); AddRoofToHead( i, usTileIndex ); } @@ -3141,7 +3148,7 @@ void EnsureStatusOfEditorButtons() void HandleMouseClicksInGameScreen() { - EXITGRID dummy={0,0,0,0}; + //EXITGRID dummy={0,0,0,0}; INT16 sX, sY; BOOLEAN fPrevState; if( !GetMouseXY( &sGridX, &sGridY ) ) diff --git a/Path Debug.vsprops b/Path Debug.vsprops new file mode 100644 index 00000000..bf0b67d0 --- /dev/null +++ b/Path Debug.vsprops @@ -0,0 +1,11 @@ + + + + diff --git a/Roof Debug.vsprops b/Roof Debug.vsprops new file mode 100644 index 00000000..6975c0a4 --- /dev/null +++ b/Roof Debug.vsprops @@ -0,0 +1,7 @@ + + + diff --git a/builddefines.h b/builddefines.h index e5ee732a..3c7de8cf 100644 --- a/builddefines.h +++ b/builddefines.h @@ -49,4 +49,6 @@ #define _CRT_SECURE_NO_WARNINGS #define _CRT_NON_CONFORMING_SWPRINTFS +#include "Profiler.h" + #endif \ No newline at end of file diff --git a/profiler.cpp b/profiler.cpp new file mode 100644 index 00000000..0918caba --- /dev/null +++ b/profiler.cpp @@ -0,0 +1,442 @@ +#ifdef PROFILER_ENABLED + +#include "builddefines.h" +#include + +#ifdef PERIODIC_PROFILING +bool gRecordingProfile = false; +#endif + +PerfManager* PerfManager::_instance(NULL); + +PerfDatum::PerfDatum( const char* const fileName, + const char* const functionName, + const int lineNumber, + __int64 cycles, + __int64 calls) + : + _fileName(fileName), + _functionName(functionName), + _lineNumber(lineNumber), + _cycles(cycles), + _calls(calls) +{ +} + +PerfManager::PerfManager(void) + : + _callTime(0), + _totalTime(0) +{ + //Set up a root function so that the stack is never empty: + //(this will be the only entry with _lineNumber equal to 0) + _perfLog.insert(PerfDatum(__FILE__, "Root",0)); + _perfStack.push_back(&*(_perfLog.begin())); + _lastTime = getCPUCount(); + calibrate(); +} + +void PerfManager::calibrate() +{ +} + +PerfManager::~PerfManager(void) +{ +} + +PerfManager* PerfManager::instance() +{ + if(!_instance) //Double-check locking (first check) + { _instance = new PerfManager(); + } + return _instance; +} + +void PerfManager::enterFunction(const char* const fileName, + const char* const functionName, + const int lineNumber) +{ + //Mark the time when we end processing the previous function + __int64 endTime = getCPUCount(); + //assuming we haven't corrupted the stack and reduced it to zero elements + if(_perfStack.size()) + { PERF_STACK_T::reverse_iterator lastMarker = _perfStack.rbegin(); + //Update the total time spent processing the previous function + __int64 runTime = (endTime - _lastTime) - _callTime; + (**lastMarker)._cycles += runTime; + _totalTime += runTime; + } + + //If we fail to insert the performance marker into our log, it means it is + //a duplicate. We can ignore this "error" as using the existing copy is + //the correct behavior. + std::pair result = + _perfLog.insert(PerfDatum(fileName, functionName, lineNumber)); + //Update the call count on the new function: + PerfDatum* marker = &*(result.first); + marker->_calls++; + //Add performance marker to the stack as well as the log: + _perfStack.push_back(marker); + //Now that we are clear of any complex operations, we can set the call + //time of the new marker more accurately: + _lastTime = getCPUCount(); +} + +void PerfManager::exitFunction() +{ + //Mark the time when we exited the function + __int64 endTime = getCPUCount(); + //Update the function in our log with its new runtime + PERF_STACK_T::reverse_iterator lastMarker = _perfStack.rbegin(); + __int64 runTime = endTime - _lastTime; + (**lastMarker)._cycles += runTime; + _totalTime += runTime; + _perfStack.pop_back(); + //Lastly, update the function we've fallen back to with the new current + //run time. + _lastTime = getCPUCount(); +} + +__int64 PerfManager::getCPUCount () const +{ + _asm rdtsc +} + +bool PerfSort::operator ()(const PerfDatum& lhs, const PerfDatum&rhs) const +{ + if(rhs._lineNumber == lhs._lineNumber) + { return 0 < strcmp(rhs._fileName, lhs._fileName); + } + else + { return rhs._lineNumber > lhs._lineNumber; + } +} + +bool PerfSort2::operator ()(const PerfDatum& lhs, const PerfDatum&rhs) const +{ + return lhs._cycles > rhs._cycles; +} + +int PerfManager::getPercision(const double value) const +{ + int retVal = 1; + if(10.0 <= value) + { retVal = 3; + } + else if (1.0 <= value) + { retVal = 2; + } + return retVal; +} + +void PerfManager::log(std::ostream &os) +{ + //Create a copy of the log that sorts by cycles used + std::set logCopy; + for(PERF_LOG_T::iterator i = _perfLog.begin(); i != _perfLog.end(); i++) + { + logCopy.insert(*i); + } + + int cyclesWidth = 1; + //I want the formatting to be nice! + for(std::set::iterator i = logCopy.begin(); + i != logCopy.end(); + i++) + { + if(i->_lineNumber) { + std::stringstream cyclesString; + cyclesString << i->_cycles; + std::string tempString; + cyclesString >> tempString; + cyclesWidth = tempString.size(); + break; + } + } + + //Stream the sorted log to the supplied ostream + for(std::set::iterator i = logCopy.begin(); + i != logCopy.end(); + ++i) + { + if(i->_lineNumber) + { //Truncate path from file name if the compiler included it. + const char* j = i->_fileName + strlen(i->_fileName); + while('\\' != *j && '/' != *j && j >= i->_fileName){j--;} + if('\\' == *j || '/' == *j){j++;} + std::stringstream cyclesString; + std::string tempString; + cyclesString << i->_cycles; + cyclesString >> tempString; + os.width(cyclesWidth); + os << tempString.c_str() << ", "; + __int64 cyclesPerCall = i->_cycles / i->_calls; + cyclesString.clear(); + tempString.clear(); + cyclesString << cyclesPerCall; + cyclesString >> tempString; + os.width(cyclesWidth); + os << tempString.c_str() << "] cycles ("; + double percent = double(i->_cycles) / double(_totalTime) * 100; + os.precision(getPercision(percent)); + os.width(4); + os << percent << "%) used in ["; + os.width(8); + os << i->_calls << "] calls by " << j << "::" << i->_functionName\ + << " at line " << i->_lineNumber << std::endl; + } + } +} + +PerfMarker::PerfMarker( const char* const fileName, + const char* const functionName, + const int lineNumber) + : +#ifdef PERIODIC_PROFILING + _onStack(gRecordingProfile) +#else + _onStack(true) +#endif +{ +#ifdef PERIODIC_PROFILING + if (gRecordingProfile == true) { +#endif + PerfManager::instance()->enterFunction(fileName,functionName,lineNumber); +#ifdef PERIODIC_PROFILING + } +#endif +} + +PerfMarker::~PerfMarker() +{ + endMark(); +} + +void PerfMarker::endMark() +{ + if(_onStack) + { PerfManager::instance()->exitFunction(); + _onStack = false; + } +} +#include "profiler.h" +#include + +#ifdef PERIODIC_PROFILING +bool gRecordingProfile = false; +#endif + +PerfManager* PerfManager::_instance(NULL); + +PerfDatum::PerfDatum( const char* const fileName, + const char* const functionName, + const int lineNumber, + __int64 cycles, + __int64 calls) + : + _fileName(fileName), + _functionName(functionName), + _lineNumber(lineNumber), + _cycles(cycles), + _calls(calls) +{ +} + +PerfManager::PerfManager(void) + : + _callTime(0), + _totalTime(0) +{ + //Set up a root function so that the stack is never empty: + //(this will be the only entry with _lineNumber equal to 0) + _perfLog.insert(PerfDatum(__FILE__, "Root",0)); + _perfStack.push_back(&*(_perfLog.begin())); + _lastTime = getCPUCount(); + calibrate(); +} + +void PerfManager::calibrate() +{ +} + +PerfManager::~PerfManager(void) +{ +} + +PerfManager* PerfManager::instance() +{ + if(!_instance) //Double-check locking (first check) + { _instance = new PerfManager(); + } + return _instance; +} + +void PerfManager::enterFunction(const char* const fileName, + const char* const functionName, + const int lineNumber) +{ + //Mark the time when we end processing the previous function + __int64 endTime = getCPUCount(); + //assuming we haven't corrupted the stack and reduced it to zero elements + if(_perfStack.size()) + { PERF_STACK_T::reverse_iterator lastMarker = _perfStack.rbegin(); + //Update the total time spent processing the previous function + __int64 runTime = (endTime - _lastTime) - _callTime; + (**lastMarker)._cycles += runTime; + _totalTime += runTime; + } + + //If we fail to insert the performance marker into our log, it means it is + //a duplicate. We can ignore this "error" as using the existing copy is + //the correct behavior. + std::pair result = + _perfLog.insert(PerfDatum(fileName, functionName, lineNumber)); + //Update the call count on the new function: + PerfDatum* marker = &*(result.first); + marker->_calls++; + //Add performance marker to the stack as well as the log: + _perfStack.push_back(marker); + //Now that we are clear of any complex operations, we can set the call + //time of the new marker more accurately: + _lastTime = getCPUCount(); +} + +void PerfManager::exitFunction() +{ + //Mark the time when we exited the function + __int64 endTime = getCPUCount(); + //Update the function in our log with its new runtime + PERF_STACK_T::reverse_iterator lastMarker = _perfStack.rbegin(); + __int64 runTime = endTime - _lastTime; + (**lastMarker)._cycles += runTime; + _totalTime += runTime; + _perfStack.pop_back(); + //Lastly, update the function we've fallen back to with the new current + //run time. + _lastTime = getCPUCount(); +} + +__int64 PerfManager::getCPUCount () const +{ + _asm rdtsc +} + +bool PerfSort::operator ()(const PerfDatum& lhs, const PerfDatum&rhs) const +{ + if(rhs._lineNumber == lhs._lineNumber) + { return 0 < strcmp(rhs._fileName, lhs._fileName); + } + else + { return rhs._lineNumber > lhs._lineNumber; + } +} + +bool PerfSort2::operator ()(const PerfDatum& lhs, const PerfDatum&rhs) const +{ + return lhs._cycles > rhs._cycles; +} + +int PerfManager::getPercision(const double value) const +{ + int retVal = 1; + if(10.0 <= value) + { retVal = 3; + } + else if (1.0 <= value) + { retVal = 2; + } + return retVal; +} + +void PerfManager::log(std::ostream &os) +{ + //Create a copy of the log that sorts by cycles used + std::set logCopy; + for(PERF_LOG_T::iterator i = _perfLog.begin(); i != _perfLog.end(); i++) + { + logCopy.insert(*i); + } + + int cyclesWidth = 1; + //I want the formatting to be nice! + for(std::set::iterator i = logCopy.begin(); + i != logCopy.end(); + i++) + { + if(i->_lineNumber) { + std::stringstream cyclesString; + cyclesString << i->_cycles; + std::string tempString; + cyclesString >> tempString; + cyclesWidth = tempString.size(); + break; + } + } + + //Stream the sorted log to the supplied ostream + for(std::set::iterator i = logCopy.begin(); + i != logCopy.end(); + ++i) + { + if(i->_lineNumber) + { //Truncate path from file name if the compiler included it. + const char* j = i->_fileName + strlen(i->_fileName); + while('\\' != *j && '/' != *j && j >= i->_fileName){j--;} + if('\\' == *j || '/' == *j){j++;} + std::stringstream cyclesString; + std::string tempString; + cyclesString << i->_cycles; + cyclesString >> tempString; + os.width(cyclesWidth); + os << tempString.c_str() << ", "; + __int64 cyclesPerCall = i->_cycles / i->_calls; + cyclesString.clear(); + tempString.clear(); + cyclesString << cyclesPerCall; + cyclesString >> tempString; + os.width(cyclesWidth); + os << tempString.c_str() << "] cycles ("; + double percent = double(i->_cycles) / double(_totalTime) * 100; + os.precision(getPercision(percent)); + os.width(4); + os << percent << "%) used in ["; + os.width(8); + os << i->_calls << "] calls by " << j << "::" << i->_functionName\ + << " at line " << i->_lineNumber << std::endl; + } + } +} + +PerfMarker::PerfMarker( const char* const fileName, + const char* const functionName, + const int lineNumber) + : +#ifdef PERIODIC_PROFILING + _onStack(gRecordingProfile) +#else + _onStack(true) +#endif +{ +#ifdef PERIODIC_PROFILING + if (gRecordingProfile == true) { +#endif + PerfManager::instance()->enterFunction(fileName,functionName,lineNumber); +#ifdef PERIODIC_PROFILING + } +#endif +} + +PerfMarker::~PerfMarker() +{ + endMark(); +} + +void PerfMarker::endMark() +{ + if(_onStack) + { PerfManager::instance()->exitFunction(); + _onStack = false; + } +} + +#endif diff --git a/profiler.h b/profiler.h new file mode 100644 index 00000000..e8e45a1b --- /dev/null +++ b/profiler.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include + +//#ifndef PROFILER_ENABLED +//#define PROFILER_ENABLED +//#endif + +#ifdef PROFILER_ENABLED +#define STRINGIZE(x) #x +#define NAMED_ PERFORMANCE_MARKER(x) PerfMarker x(__FILE__, STRINGIZE(x), __LINE__); +#define END_NAMED_ PERFORMANCE_MARKER(x) x.endMark(); +#define PERFORMANCE_MARKER PerfMarker MARK(__FILE__, __FUNCTION__, __LINE__); +#define PERIODIC_PROFILING +#else +#define PERFORMANCE_MARKER +#endif +#pragma warning (disable : 4512)//disables assignment operator could not be generated + +struct PerfDatum +{ + PerfDatum( const char* const fileName, + const char* const functionName, + const int lineNumber, + __int64 cycles = 0, + __int64 calls = 0); + const char* const _fileName; + const char* const _functionName; + const int _lineNumber; + __int64 _cycles; //Number of CPU cycles used + __int64 _calls; //Number of calls to this function +}; +#pragma warning (default : 4512)//disables assignment operator could not be generated + +class PerfSort +{ +public: + bool operator()(const PerfDatum& lhs, const PerfDatum& rhs) const; +}; + +class PerfSort2 +{ +public: + bool operator()(const PerfDatum& lhs, const PerfDatum& rhs) const; +}; + + +typedef std::set PERF_LOG_T; +typedef std::vector PERF_STACK_T; + +class PerfManager +{ +public: + ~PerfManager(void); + static PerfManager* instance(); + void enterFunction( const char* const fileName, + const char* const functionName, + const int lineNumber); + void exitFunction(); + void log(std::ostream &os); +private: + PerfManager(void); + __int64 getCPUCount(void) const; + void calibrate(void); + int getPercision(const double value) const; //used for formatting percentages + static PerfManager* _instance; + PERF_LOG_T _perfLog; + PERF_STACK_T _perfStack; + __int64 _callTime; //Time it takes to call the getCPUCount function + __int64 _totalTime; + __int64 _lastTime; //The last time we got hte CPU count. +}; + +class PerfMarker +{ +public: + PerfMarker( const char* const fileName, + const char* const functionName, + const int lineNumber); + ~PerfMarker(); + void endMark(); +private: + bool _onStack; +}; +