/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. 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