/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////////////// // // plResManagerHelper - The wonderful helper class that can receive messages // for the resManager. // //// History ///////////////////////////////////////////////////////////////// // // 6.7.2002 mcn - Created // ////////////////////////////////////////////////////////////////////////////// #include "hsTypes.h" #include "plResManagerHelper.h" #include "plResManager.h" #include "plRegistryNode.h" #include "plRegistryHelpers.h" //#include "plRegistry.h" #include "plResMgrSettings.h" #include "../pnKeyedObject/plFixedKey.h" #include "../plMessage/plResMgrHelperMsg.h" #include "../plStatusLog/plStatusLog.h" #include "hsTimer.h" #ifdef MCN_RESMGR_DEBUGGING static const int kLogSize = 40; static const float kUpdateDelay = 0.5f; #include "../plInputCore/plInputInterface.h" #include "../plInputCore/plInputDevice.h" #include "../plInputCore/plInputInterfaceMgr.h" #include "../pnInputCore/plInputMap.h" #include "../plMessage/plInputEventMsg.h" #include "../plMessage/plInputIfaceMgrMsg.h" #include "../pnKeyedObject/plKeyImp.h" #endif /// Logging #define for easier use #define kResMgrLog( level ) if( plResMgrSettings::Get().GetLoggingLevel() >= level ) plStatusLog::AddLineS( "resources.log", //// Constructor/Destructor ////////////////////////////////////////////////// plResManagerHelper *plResManagerHelper::fInstance = nil; plResManagerHelper::plResManagerHelper( plResManager *resMgr ) { fResManager = resMgr; fInstance = this; fInShutdown = false; #ifdef MCN_RESMGR_DEBUGGING fDebugScreen = nil; fCurrAge = -1; fCurrAgeExpanded = false; fRefreshing = false; fDebugDisplayType = 0; #endif } plResManagerHelper::~plResManagerHelper() { fInstance = nil; } //// Shutdown //////////////////////////////////////////////////////////////// void plResManagerHelper::Shutdown( void ) { EnableDebugScreen( false ); UnRegisterAs( kResManagerHelper_KEY ); } //// Init //////////////////////////////////////////////////////////////////// void plResManagerHelper::Init( void ) { RegisterAs( kResManagerHelper_KEY ); } //// MsgReceive ////////////////////////////////////////////////////////////// hsBool plResManagerHelper::MsgReceive( plMessage *msg ) { plResMgrHelperMsg *refferMsg = plResMgrHelperMsg::ConvertNoRef( msg ); if( refferMsg != nil ) { if( refferMsg->GetCommand() == plResMgrHelperMsg::kKeyRefList ) { // Message to let go of these keys. So unref the key list, destroy it and we're done! kResMgrLog( 2 ) 0xff80ff80, "Dropping page keys after timed delay" ); hsStatusMessage( "*** Dropping page keys after timed delay ***" ); delete refferMsg->fKeyList; refferMsg->fKeyList = nil; } else if( refferMsg->GetCommand() == plResMgrHelperMsg::kUpdateDebugScreen ) { IUpdateDebugScreen(); } else if( refferMsg->GetCommand() == plResMgrHelperMsg::kEnableDebugScreen ) EnableDebugScreen( true ); else if( refferMsg->GetCommand() == plResMgrHelperMsg::kDisableDebugScreen ) EnableDebugScreen( false ); return true; } return hsKeyedObject::MsgReceive( msg ); } //// Read/Write ////////////////////////////////////////////////////////////// void plResManagerHelper::Read( hsStream *s, hsResMgr *mgr ) { hsAssert( false, "You should never read me in!" ); } void plResManagerHelper::Write( hsStream *s, hsResMgr *mgr ) { hsAssert( false, "You should never write me out!" ); } //// LoadAndHoldPageKeys ///////////////////////////////////////////////////// // Loads and refs the keys for the given page, then sends the ref list as // a list to ourself, time delayed 1 second, so that we can unref them one // second later. void plResManagerHelper::LoadAndHoldPageKeys( plRegistryPageNode *page ) { hsAssert( GetKey() != nil, "Can't load and hold keys when we don't have a key for the helper" ); // Create our msg plResMgrHelperMsg *refferMsg = TRACKED_NEW plResMgrHelperMsg( plResMgrHelperMsg::kKeyRefList ); refferMsg->fKeyList = TRACKED_NEW plResPageKeyRefList; fResManager->LoadPageKeys(page); page->IterateKeys( refferMsg->fKeyList ); // Load and ref the keys #ifdef HS_DEBUGGING char msg[ 256 ]; sprintf( msg, "*** Temporarily loading keys for room %s>%s based on FindKey() query, will drop in 1 sec ***", page->GetPageInfo().GetAge(), page->GetPageInfo().GetPage() ); hsStatusMessage( msg ); #endif kResMgrLog( 2 ) 0xff80ff80, "Temporarily loading keys for room %s>%s, will drop in 1 sec", page->GetPageInfo().GetAge(), page->GetPageInfo().GetPage() ); // Deliver the message to ourselves! refferMsg->SetTimeStamp( hsTimer::GetSysSeconds() + 1.f ); refferMsg->Send( GetKey() ); } #ifdef MCN_RESMGR_DEBUGGING ////////////////////////////////////////////////////////////////////////////// //// plResMgrDebugInterface Definition /////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// class plResMgrDebugInterface : public plInputInterface { protected: plResManagerHelper * const fParent; virtual ControlEventCode *IGetOwnedCodeList( void ) const { static ControlEventCode codes[] = { END_CONTROLS }; return codes; } public: plResMgrDebugInterface( plResManagerHelper * const mgr ) : fParent( mgr ) { SetEnabled( true ); } virtual UInt32 GetPriorityLevel( void ) const { return kGUISystemPriority + 10; } virtual hsBool InterpretInputEvent( plInputEventMsg *pMsg ) { plKeyEventMsg *pKeyMsg = plKeyEventMsg::ConvertNoRef( pMsg ); if( pKeyMsg != nil && pKeyMsg->GetKeyDown() ) { if( pKeyMsg->GetKeyCode() == KEY_UP && fParent->fCurrAge >= 0 ) { fParent->fCurrAge--; fParent->IUpdateDebugScreen( true ); return true; } else if( pKeyMsg->GetKeyCode() == KEY_DOWN ) { fParent->fCurrAge++; fParent->IUpdateDebugScreen( true ); return true; } else if( pKeyMsg->GetKeyCode() == KEY_ENTER ) { fParent->fCurrAgeExpanded = !fParent->fCurrAgeExpanded; fParent->IUpdateDebugScreen( true ); return true; } else if( pKeyMsg->GetKeyCode() == KEY_ESCAPE ) { plResMgrHelperMsg *msg = TRACKED_NEW plResMgrHelperMsg( plResMgrHelperMsg::kDisableDebugScreen ); msg->Send( fParent->GetKey() ); return true; } else if( pKeyMsg->GetKeyCode() == KEY_RIGHT ) { if( !fParent->fCurrAgeExpanded ) fParent->fCurrAgeExpanded = true; else { fParent->fDebugDisplayType++; if( fParent->fDebugDisplayType == plResManagerHelper::kMaxDisplayType ) fParent->fDebugDisplayType = 0; } fParent->IUpdateDebugScreen( true ); return true; } else if( pKeyMsg->GetKeyCode() == KEY_LEFT ) { fParent->fCurrAgeExpanded = false; fParent->IUpdateDebugScreen( true ); return true; } } return false; } virtual UInt32 GetCurrentCursorID( void ) const { return 0; } virtual hsBool HasInterestingCursorID( void ) const { return false; } }; #endif //// EnableDebugScreen /////////////////////////////////////////////////////// void plResManagerHelper::EnableDebugScreen( hsBool enable ) { #ifdef MCN_RESMGR_DEBUGGING if( enable ) { if( fDebugScreen == nil ) { fDebugScreen = plStatusLogMgr::GetInstance().CreateStatusLog( kLogSize, "ResManager Status", plStatusLog::kFilledBackground | plStatusLog::kDontWriteFile ); fRefreshing = true; plResMgrHelperMsg *msg = TRACKED_NEW plResMgrHelperMsg( plResMgrHelperMsg::kUpdateDebugScreen ); // msg->SetTimeStamp( hsTimer::GetSysSeconds() + kUpdateDelay ); msg->Send( GetKey() ); fDebugInput = TRACKED_NEW plResMgrDebugInterface( this ); plInputIfaceMgrMsg *imsg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kAddInterface ); imsg->SetIFace( fDebugInput ); imsg->Send(); } } else { fRefreshing = false; if( fDebugScreen != nil ) { delete fDebugScreen; fDebugScreen = nil; plInputIfaceMgrMsg *imsg = TRACKED_NEW plInputIfaceMgrMsg( plInputIfaceMgrMsg::kRemoveInterface ); imsg->SetIFace( fDebugInput ); imsg->Send(); hsRefCnt_SafeUnRef( fDebugInput ); fDebugInput = nil; } } #endif } //// IUpdateDebugScreen ///////////////////////////////////////////////////// #ifdef MCN_RESMGR_DEBUGGING class plDebugPrintIterator : public plRegistryPageIterator, plRegistryKeyIterator { public: plStatusLog *fLog; UInt8 fStep, fLines; UInt32 &fLoadedCount, &fHoldingCount, fPageCount, fAgeIndex; char fCurrAge[ 128 ]; UInt32 fLoadedKeys, fTotalKeys, fTotalSize, fLoadedSize; plResManagerHelper *fParent; plDebugPrintIterator( plResManagerHelper *parent, plStatusLog *log, UInt32 &loadedCount, UInt32 &holdingCount ) : fParent( parent ), fLog( log ), fStep( 0 ), fLines( 0 ), fLoadedCount( loadedCount ), fHoldingCount( holdingCount ) { fLoadedCount = fHoldingCount = 0; fCurrAge[ 0 ] = 0; fPageCount = 0; fAgeIndex = 0; } virtual hsBool EatPage( plRegistryPageNode *page ) { if( fStep == 0 ) { fLog->AddLineF( 0xff80ff80, "Loaded Pages" ); fStep = 1; fLines++; } else if( fStep == 1 && page != nil && !page->IsLoaded() ) { fStep = 2; fLog->AddLineF( 0xff80ff80, "Holding Pages" ); fLines++; } if( page != nil && page->IsLoaded() ) fLoadedCount++; else if( page != nil ) fHoldingCount++; // Changed ages? if( page == nil || strcmp( fCurrAge, page->GetPageInfo().GetAge() ) != 0 ) { // Print some info for the last age we were on if( fCurrAge[ 0 ] != 0 ) { if( fParent->fCurrAge != fAgeIndex || !fParent->fCurrAgeExpanded ) { if( fLines < kLogSize - 4 ) { UInt32 color = plStatusLog::kWhite; if( fParent->fCurrAge == fAgeIndex ) color = plStatusLog::kYellow; fLog->AddLineF( color, " %s (%d pages)", fCurrAge, fPageCount ); fLines++; } else if( fLines == kLogSize - 4 ) { fLog->AddLineF( plStatusLog::kWhite, " ..." ); fLines++; } } fAgeIndex++; } fPageCount = 0; if( page != nil ) strncpy( fCurrAge, page->GetPageInfo().GetAge(), sizeof( fCurrAge ) - 1 ); else fCurrAge[ 0 ] = 0; if( fParent->fCurrAge == fAgeIndex && fParent->fCurrAgeExpanded ) { // Print header now, since we won't be printing a footer if( fLines < kLogSize - 4 ) { fLog->AddLineF( plStatusLog::kYellow, " %s>", fCurrAge ); fLines++; } else if( fLines == kLogSize - 4 ) { fLog->AddLineF( plStatusLog::kWhite, " ..." ); fLines++; } } } fPageCount++; if( fParent->fCurrAge == fAgeIndex && fParent->fCurrAgeExpanded && page != nil ) { // Count keys for this page fTotalKeys = fLoadedKeys = fTotalSize = fLoadedSize = 0; page->IterateKeys( this ); // Print page for this expanded age view if( fLines < kLogSize - 4 ) { if( fParent->fDebugDisplayType == plResManagerHelper::kSizes ) fLog->AddLineF( plStatusLog::kWhite, " %s (%d keys @ %4.1fk, %d loaded @ %4.1fk)", page->GetPageInfo().GetPage(), fTotalKeys, fTotalSize / 1024.f, fLoadedKeys, fLoadedSize / 1024.f ); else if( fParent->fDebugDisplayType == plResManagerHelper::kPercents ) fLog->AddLineF( plStatusLog::kWhite, " %s (%d%% loaded of %d keys @ %4.1fk)", page->GetPageInfo().GetPage(), fLoadedSize * 100 / ( fTotalSize > 0 ? fTotalSize : -1 ), fTotalKeys, fTotalSize / 1024.f ); else //if( fParent->fDebugDisplayType == plResManagerHelper::kBars ) { const int startPos = 20, length = 32; char line[ 128 ]; memset( line, ' ', sizeof( line ) - 1 ); line[ 127 ] = 0; if( strlen( page->GetPageInfo().GetPage() ) < startPos - 2 ) memcpy( line + 2, page->GetPageInfo().GetPage(), strlen( page->GetPageInfo().GetPage() ) ); else memcpy( line + 2, page->GetPageInfo().GetPage(), startPos - 2 ); line[ startPos ] = '|'; if( fTotalSize == 0 ) { line[ startPos + 1 ] = '|'; line[ startPos + 2 ] = 0; } else { char temp[ 12 ]; sprintf( temp, "%d%%", fLoadedSize * 100 / fTotalSize ); line[ startPos + length + 1 ] = '|'; int i, sum = 0; for( i = startPos + 1; i < startPos + length + 1 && sum < fLoadedSize; i++ ) { line[ i ] = '='; sum += fTotalSize / length; } line[ startPos + length + 2 ] = 0; memcpy( line + startPos + 1, temp, strlen( temp ) ); } fLog->AddLine( line, plStatusLog::kWhite ); } fLines++; } else if( fLines == kLogSize - 4 ) { fLog->AddLineF( plStatusLog::kWhite, " ..." ); fLines++; } } return true; } virtual hsBool EatKey( const plKey& key ) { if( key->ObjectIsLoaded() ) { fLoadedKeys++; fLoadedSize += ((plKeyImp *)key)->GetDataLen(); } fTotalKeys++; fTotalSize += ((plKeyImp *)key)->GetDataLen(); return true; } }; #endif void plResManagerHelper::IUpdateDebugScreen( hsBool force ) { #ifdef MCN_RESMGR_DEBUGGING if( !fRefreshing ) return; plRegistry *reg = fResManager->IGetRegistry(); UInt32 loadedCnt, holdingCnt; fDebugScreen->Clear(); plDebugPrintIterator iter( this, fDebugScreen, loadedCnt, holdingCnt ); reg->IterateAllPages( &iter ); iter.EatPage( nil ); // Force a final update fDebugScreen->AddLineF( plStatusLog::kGreen, "%d pages loaded, %d holding", loadedCnt, holdingCnt ); if( fCurrAge >= iter.fAgeIndex ) fCurrAge = -1; // Repump our update if( !force ) { plResMgrHelperMsg *msg = TRACKED_NEW plResMgrHelperMsg( plResMgrHelperMsg::kUpdateDebugScreen ); msg->SetTimeStamp( hsTimer::GetSysSeconds() + kUpdateDelay ); msg->Send( GetKey() ); } #endif } #if 0 // FIXME hsBool VerifyKeyUnloaded(const char* logFile, const plKey& key); // Verifies that a key which shouldn't be loaded isn't, and if it is tries to figure out why. void VerifyAgeUnloaded(const char* logFile, const char* age); // Helper for VerifyKeyUnloaded hsBool IVerifyKeyUnloadedRecur(const char* logFile, const plKey& baseKey, const plKey& upKey, const char* baseAge); bool ILookForCyclesRecur(const char* logFile, const plKey& key, hsTArray& tree, int& cycleStart); bool plResManager::ILookForCyclesRecur(const char* logFile, const plKey& key, hsTArray& tree, int& cycleStart) { int idx = tree.Find(key); tree.Append(key); if (tree.kMissingIndex != idx) { cycleStart = idx; // Found a cycle. return true; } // Now recurse up the active reference tree. for (int i = 0; i < key->GetNumNotifyCreated(); i++) { if (key->GetActiveBits().IsBitSet(i)) { for (int j = 0; j < key->GetNotifyCreated(i)->GetNumReceivers(); j++) { plKey reffer = key->GetNotifyCreated(i)->GetReceiver(j); if (ILookForCyclesRecur(logFile, reffer, tree, cycleStart)) return true; } } } tree.Pop(); return false; } bool plResManager::IVerifyKeyUnloadedRecur(const char* logFile, const plKey& baseKey, const plKey& upKey, const char* baseAge) { const plPageInfo& pageInfo = FindPage(upKey->GetUoid().GetLocation())->GetPageInfo(); const char* upAge = pageInfo.GetAge(); const char* upPage = pageInfo.GetPage(); if( !upKey->GetActiveRefs() ) { // We've hit a key active reffing us that should be inactive. // If it's object is loaded, then it somehow missed getting unloaded. // Else it must have missed letting go of us when it got unloaded. if( upKey->ObjectIsLoaded() ) { plStatusLog::AddLineS(logFile, "\tHeld by %s [%s] page %s which is loaded but nothing is reffing", upKey->GetName(), plFactory::GetNameOfClass(upKey->GetUoid().GetClassType()), upPage); return true; } else { plStatusLog::AddLineS(logFile, "\tHeld by %s [%s] page %s which isn't even loaded", upKey->GetName(), plFactory::GetNameOfClass(upKey->GetUoid().GetClassType()), upPage); return true; } } // if the age of this key is different from the age on the baseKey, // we've got a cross age active ref, which is illegal. if( stricmp(upAge, baseAge) ) { plStatusLog::AddLineS(logFile, "\tHeld by %s [%s] which is in a different age %s-%s", upKey->GetName(), plFactory::GetNameOfClass(upKey->GetUoid().GetClassType()), upAge, upPage); return true; } int numActive = 0; int i; for( i = 0; i < upKey->GetNumNotifyCreated(); i++ ) { if( upKey->GetActiveBits().IsBitSet(i) ) { numActive++; } } if( numActive < upKey->GetActiveRefs() ) { // Someone has AddRef'd us plStatusLog::AddLineS(logFile, "\tHeld by %s [%s] page %s which is loaded due to %d AddRef(s)", upKey->GetName(), plFactory::GetNameOfClass(upKey->GetUoid().GetClassType()), upPage, upKey->GetActiveRefs()-numActive); return true; } // Now recurse up the active reference tree. for( i = 0; i < upKey->GetNumNotifyCreated(); i++ ) { if( upKey->GetActiveBits().IsBitSet(i) ) { int j; for( j = 0; j < upKey->GetNotifyCreated(i)->GetNumReceivers(); j++ ) { plKey reffer = upKey->GetNotifyCreated(i)->GetReceiver(j); if( IVerifyKeyUnloadedRecur(logFile, baseKey, reffer, baseAge) ) { return true; } } } } return false; } hsBool plResManager::VerifyKeyUnloaded(const char* logFile, const plKey& key) { if( key->ObjectIsLoaded() ) { const plPageInfo& pageInfo = FindPage(key->GetUoid().GetLocation())->GetPageInfo(); const char* age = pageInfo.GetAge(); const char* page = pageInfo.GetPage(); plStatusLog::AddLineS(logFile, "=================================="); plStatusLog::AddLineS(logFile, "Object %s [%s] page %s is loaded", key->GetName(), plFactory::GetNameOfClass(key->GetUoid().GetClassType()), page); hsTArray tree; int cycleStart; hsBool hasCycle = ILookForCyclesRecur(logFile, key, tree, cycleStart); if( hasCycle ) { plStatusLog::AddLineS(logFile, "\t%s [%s] held by dependency cycle", key->GetName(), plFactory::GetNameOfClass(key->GetUoid().GetClassType())); int i; for( i = cycleStart; i < tree.GetCount(); i++ ) { plStatusLog::AddLineS(logFile, "\t%s [%s]", tree[i]->GetName(), plFactory::GetNameOfClass(tree[i]->GetUoid().GetClassType())); } plStatusLog::AddLineS(logFile, "\tEnd Cycle"); return true; } else { return IVerifyKeyUnloadedRecur(logFile, key, key, age); } } return false; } class plValidateKeyIterator : public plRegistryKeyIterator { protected: plRegistry* fRegistry; const char* fLogFile; public: plValidateKeyIterator(const char* logFile, plRegistry* reg) { fRegistry = reg; fLogFile = logFile; } virtual hsBool EatKey(const plKey& key) { fRegistry->VerifyKeyUnloaded(fLogFile, key); return true; } }; class plValidatePageIterator : public plRegistryPageIterator { protected: const char* fAge; plRegistryKeyIterator* fIter; public: plValidatePageIterator(const char* age, plRegistryKeyIterator* iter) : fAge(age), fIter(iter) {} virtual hsBool EatPage( plRegistryPageNode *keyNode ) { if( !stricmp(fAge, keyNode->GetPageInfo().GetAge()) ) return keyNode->IterateKeys( fIter ); return true; } }; void plResManager::VerifyAgeUnloaded(const char* logFile, const char* age) { hsBool autoLog = false; char buff[256]; if( !logFile || !*logFile ) { sprintf(buff, "%s.log", age); logFile = buff; autoLog = true; } if( !autoLog ) { plStatusLog::AddLineS(logFile, "///////////////////////////////////"); plStatusLog::AddLineS(logFile, "Begin Verification of age %s", age); } plValidateKeyIterator keyIter(logFile, this); plValidatePageIterator pageIter(age, &keyIter); IterateAllPages(&pageIter); if( !autoLog ) { plStatusLog::AddLineS(logFile, "End Verification of age %s", age); plStatusLog::AddLineS(logFile, "///////////////////////////////////"); } } #endif