/*==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