/*==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 < http : //www.gnu.org/licenses/>.
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 3 ds 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 , 3 ds 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 = = */
# include "plResManager.h"
# include "plRegistryNode.h"
# include "plResManagerHelper.h"
# include "plResMgrSettings.h"
# include "plLocalization.h"
# include "hsSTLStream.h"
# include "hsTimer.h"
# include "pnTimer/plTimerCallbackManager.h"
# include "hsStlUtils.h"
# include "plScene/plSceneNode.h"
# include "pnKeyedObject/hsKeyedObject.h"
# include "pnKeyedObject/plFixedKey.h"
# include "pnKeyedObject/plKeyImp.h"
# include "pnDispatch/plDispatch.h"
# include "plStatusLog/plStatusLog.h"
# include "pnMessage/plRefMsg.h"
# include "pnMessage/plObjRefMsg.h"
# include "plMessage/plAgeLoadedMsg.h"
# include "pnMessage/plClientMsg.h"
# include "plFile/hsFiles.h"
# include "plFile/plFileUtils.h"
# include "pnFactory/plCreator.h"
# include "pnNetCommon/plSynchedObject.h"
# include "pnNetCommon/plNetApp.h"
hsBool gDataServerLocal = false ;
/// Logging #define for easier use
# define kResMgrLog(level, log) if (plResMgrSettings::Get().GetLoggingLevel() >= level) log
static void ILog ( UInt8 level , const char * format , . . . )
{
static plStatusLog * log = plStatusLogMgr : : GetInstance ( ) . CreateStatusLog
(
plStatusLogMgr : : kDefaultNumLines ,
" resources.log " ,
plStatusLog : : kFilledBackground | plStatusLog : : kDeleteForMe
) ;
va_list arguments ;
va_start ( arguments , format ) ;
UInt32 color = 0 ;
switch ( level )
{
case 1 : color = 0xffffffff ; break ;
case 2 : color = 0xff8080ff ; break ;
case 3 : color = 0xffffff80 ; break ;
case 4 : color = 0xff8080ff ; break ;
}
log - > AddLineV ( color , format , arguments ) ;
}
plResManager : : plResManager ( ) :
fInited ( false ) ,
fPageOutHint ( 0 ) ,
fDispatch ( nil ) ,
fReadingObject ( false ) ,
fCurCloneID ( 0 ) ,
fCurClonePlayerID ( 0 ) ,
fCloningCounter ( 0 ) ,
fProgressProc ( nil ) ,
fMyHelper ( nil ) ,
fLogReadTimes ( false ) ,
fPageListLock ( 0 ) ,
fPagesNeedCleanup ( false ) ,
fLastFoundPage ( nil )
{
# ifdef HS_DEBUGGING
plFactory : : Validate ( hsKeyedObject : : Index ( ) ) ;
# endif
}
plResManager : : ~ plResManager ( )
{
// verify shutDown
hsAssert ( ! fInited , " ResMgr not shutdown " ) ;
}
hsBool plResManager : : IInit ( )
{
if ( fInited )
return true ;
fInited = true ;
kResMgrLog ( 1 , ILog ( 1 , " Initializing resManager... " ) ) ;
if ( plResMgrSettings : : Get ( ) . GetLoadPagesOnInit ( ) )
{
// We want to go through all the data files in our data path and add new
// plRegistryPageNodes to the regTree for each
hsFolderIterator pathIterator ( fDataPath . c_str ( ) ) ;
while ( pathIterator . NextFileSuffix ( " .prp " ) )
{
char fileName [ kFolderIterator_MaxPath ] ;
pathIterator . GetPathAndName ( fileName ) ;
plRegistryPageNode * node = TRACKED_NEW plRegistryPageNode ( fileName ) ;
fAllPages . insert ( node ) ;
}
}
// Special case: we always create pages for the predefined pages
CreatePage ( plLocation : : kGlobalFixedLoc , " Global " , " FixedKeys " ) ;
hsAssert ( ! fDispatch , " Dispatch already set " ) ;
fDispatch = TRACKED_NEW plDispatch ;
plgTimerCallbackMgr : : Init ( ) ;
// Init our helper
fMyHelper = TRACKED_NEW plResManagerHelper ( this ) ;
fMyHelper - > Init ( ) ;
hsAssert ( fMyHelper - > GetKey ( ) ! = nil , " ResManager helper didn't init properly! " ) ;
kResMgrLog ( 1 , ILog ( 1 , " ...Init was successful! " ) ) ;
return true ;
}
hsBool plResManager : : IReset ( ) // Used to Re-Export (number of times)
{
BeginShutdown ( ) ;
IShutdown ( ) ;
return IInit ( ) ;
}
void plResManager : : BeginShutdown ( )
{
if ( fMyHelper )
fMyHelper - > SetInShutdown ( true ) ;
}
void plResManager : : IShutdown ( )
{
if ( ! fInited )
return ;
kResMgrLog ( 1 , ILog ( 1 , " Shutting down resManager... " ) ) ;
// Make sure we're not holding on to any ages for load optimization
IDropAllAgeKeys ( ) ;
// At this point, we may have an undelivered future time stamped message
// in the Dispatch, which is reffing a bunch of keys we "temporarily" loaded.
// The obvious problems with that solution to avoid loading and unloading
// and reloading keys aside, those keys will continue to exist until the
// dispatch is destroyed, so they will show up as key leaks in the report.
// But they won't show up as memory leaks, because they'll be destroyed
// when the Dispatch is destructed.
// Update - now we call BeginShutdown, well, at the beginning of Shutdown.
// We pass that on to the Helper, so it knows to immediately dump the keys
// for any pages it loads, cuz there's no tomorrow.
IPageOutSceneNodes ( false ) ;
// Shut down our helper
fMyHelper - > Shutdown ( ) ; // This will call UnregisterAs(), which will delete itself
fMyHelper = nil ;
// TimerCallbackMgr is a fixed-keyed object, so needs to shut down before the registry
plgTimerCallbackMgr : : Shutdown ( ) ;
// Destroy the dispatch. Note that we do this before the registry so that any lingering messages
// can free up keys properly
hsRefCnt_SafeUnRef ( fDispatch ) ;
fDispatch = nil ;
// Just before we shut down the registry, page out any keys that still exist.
// (They shouldn't... they're baaaaaad.)
IPageOutSceneNodes ( true ) ;
// Shut down the registry (finally!)
ILockPages ( ) ;
PageSet : : const_iterator it ;
for ( it = fAllPages . begin ( ) ; it ! = fAllPages . end ( ) ; it + + )
delete * it ;
fAllPages . clear ( ) ;
fLoadedPages . clear ( ) ;
IUnlockPages ( ) ;
fLastFoundPage = nil ;
kResMgrLog ( 1 , ILog ( 1 , " ...Shutdown successful! " ) ) ;
fInited = false ;
}
void plResManager : : AddSinglePage ( const char * pagePath )
{
plRegistryPageNode * node = TRACKED_NEW plRegistryPageNode ( pagePath ) ;
AddPage ( node ) ;
}
plRegistryPageNode * plResManager : : FindSinglePage ( const char * path ) const
{
PageSet : : const_iterator it ;
for ( it = fAllPages . begin ( ) ; it ! = fAllPages . end ( ) ; it + + )
{
if ( hsStrCaseEQ ( ( * it ) - > GetPagePath ( ) , path ) )
return * it ;
}
return nil ;
}
void plResManager : : RemoveSinglePage ( const char * path )
{
plRegistryPageNode * node = FindSinglePage ( path ) ;
if ( node )
{
fAllPages . erase ( node ) ;
delete node ;
}
}
plDispatchBase * plResManager : : Dispatch ( )
{
return fDispatch ;
}
void plResManager : : LogReadTimes ( hsBool logReadTimes )
{
fLogReadTimes = logReadTimes ;
if ( fLogReadTimes )
{
plStatusLog : : AddLineS ( " readtimings.log " , plStatusLog : : kWhite , " Created readtimings log " ) ;
}
}
hsKeyedObject * plResManager : : IGetSharedObject ( plKeyImp * pKey )
{
plKeyImp * origKey = ( plKeyImp * ) pKey - > GetCloneOwner ( ) ;
// Find the first non-nil key and ask it to clone itself
UInt32 count = origKey - > GetNumClones ( ) ;
for ( UInt32 i = 0 ; i < count ; i + + )
{
plKey cloneKey = origKey - > GetCloneByIdx ( i ) ;
if ( cloneKey )
{
hsKeyedObject * obj = cloneKey - > ObjectIsLoaded ( ) ;
if ( obj )
{
hsKeyedObject * sharedObj = obj - > GetSharedObject ( ) ;
if ( sharedObj )
return sharedObj ;
}
}
}
return nil ;
}
//// ReadObject /////////////////////////////////////////////////////////////
// Given a key, goes off and reads in the actual object from its source
hsBool plResManager : : ReadObject ( plKeyImp * key )
{
// Read in the object. If while we are doing this something else requests a
// load (through AddViaNotify or ReadKeyNotifyMe) we consider it a child load
// and put it in a queue. This keeps us from jumping over to another object's
// data while we're still reading in its parent (which is bad because that
// trashes our file buffering)
//
// Also, we find the pageNode and open its stream here. We close the
// stream when child reads are done. If a child load is using the same stream,
// it will just inc/dec the open/close count during its read, and not actually
// close the stream, so we don't lose our place, lose our file handle, and thrash.
char locstr [ 64 ] ;
kResMgrLog ( 4 , ILog ( 4 , " ...Opening page data stream for location %s... " , key - > GetUoid ( ) . GetLocation ( ) . StringIze ( locstr ) ) ) ;
plRegistryPageNode * pageNode = FindPage ( key - > GetUoid ( ) . GetLocation ( ) ) ;
if ( ! pageNode )
{
kResMgrLog ( 3 , ILog ( 3 , " ...Data stream failed to open on read! " ) ) ;
return false ;
}
fReadingObject = true ;
bool ret = IReadObject ( key , pageNode - > OpenStream ( ) ) ;
fReadingObject = false ;
if ( ! fQueuedReads . empty ( ) )
{
// Now that the parent object is completely read in, we can do the child
// loads. We copy off all the children that were queued during our load so
// that we won't get our children's child loads mixed in (a parent only loads
// its immediate children)
std : : vector < plKey > children = fQueuedReads ;
fQueuedReads . clear ( ) ;
for ( int i = 0 ; i < children . size ( ) ; i + + )
{
plKey childKey = children [ i ] ;
childKey - > VerifyLoaded ( ) ;
}
}
// we're done loading, and all our children are too, so send the notify
key - > NotifyCreated ( ) ;
pageNode - > CloseStream ( ) ;
return ret ;
}
hsBool plResManager : : IReadObject ( plKeyImp * pKey , hsStream * stream )
{
static UInt64 totalTime = 0 ;
UInt64 startTotalTime = totalTime ;
UInt64 startTime = 0 ;
if ( fLogReadTimes )
startTime = hsTimer : : GetFullTickCount ( ) ;
hsKeyedObject * ko = nil ;
hsAssert ( pKey , " Null Key " ) ;
if ( pKey - > GetUoid ( ) . GetLoadMask ( ) . DontLoad ( ) )
return nil ;
hsAssert ( pKey - > GetStartPos ( ) ! = UInt32 ( - 1 ) , " Missing StartPos " ) ;
hsAssert ( pKey - > GetDataLen ( ) ! = UInt32 ( - 1 ) , " Missing Data Length " ) ;
if ( pKey - > GetStartPos ( ) = = UInt32 ( - 1 ) | | pKey - > GetDataLen ( ) = = UInt32 ( - 1 ) )
return false ; // Try to recover from this by just not reading an object
kResMgrLog ( 3 , ILog ( 3 , " Reading object %s::%s " , plFactory : : GetNameOfClass ( pKey - > GetUoid ( ) . GetClassType ( ) ) , pKey - > GetUoid ( ) . GetObjectName ( ) ) ) ;
const plUoid & uoid = pKey - > GetUoid ( ) ;
bool isClone = uoid . IsClone ( ) ;
kResMgrLog ( 4 , ILog ( 4 , " ...is%s a clone " , isClone ? " " : " not " ) ) ;
// If we're loading the root object of a clone (the object for the key that
// was actually cloned), set up the global cloning flags so any child objects
// read in will get them. Also turn off synching until the object is fully
// loaded, so we don't send out any partially loaded state.
bool setClone = false ;
if ( isClone & & fCurCloneID ! = uoid . GetCloneID ( ) )
{
kResMgrLog ( 4 , ILog ( 4 , " ...fCurCloneID = %d, uoid's cloneID = %d " , fCurCloneID , uoid . GetCloneID ( ) ) ) ;
if ( fCurCloneID ! = 0 )
{
hsAssert ( false , " Recursive clone " ) ;
kResMgrLog ( 3 , ILog ( 3 , " ...RECURSIVE CLONE DETECTED. ABORTING READ... " ) ) ;
return false ;
}
fCurClonePlayerID = uoid . GetClonePlayerID ( ) ;
fCurCloneID = uoid . GetCloneID ( ) ;
setClone = true ;
kResMgrLog ( 4 , ILog ( 4 , " ...now fCurCloneID = %d, fCurClonePlayerID = %d " , fCurCloneID , fCurClonePlayerID ) ) ;
}
// If this is a clone key, try and get the original object to give us a clone
if ( isClone )
{
kResMgrLog ( 4 , ILog ( 4 , " ...Trying to get shared object... " ) ) ;
ko = IGetSharedObject ( pKey ) ;
kResMgrLog ( 4 , ILog ( 4 , " ...IGetSharedObject() %s " , ( ko ! = nil ) ? " succeeded " : " failed " ) ) ;
}
// If we couldn't share the object, read in a fresh copy
if ( ! ko )
{
stream - > SetPosition ( pKey - > GetStartPos ( ) ) ;
kResMgrLog ( 4 , ILog ( 4 , " ...Reading from position %d bytes... " , pKey - > GetStartPos ( ) ) ) ;
plCreatable * cre = ReadCreatable ( stream ) ;
hsAssert ( cre , " Could not Create Object " ) ;
if ( cre )
{
ko = hsKeyedObject : : ConvertNoRef ( cre ) ;
if ( ko ! = nil )
kResMgrLog ( 4 , ILog ( 4 , " ...Creatable read and valid " ) ) ;
else
kResMgrLog ( 3 , ILog ( 3 , " ...Creatable read from stream not keyed object! " ) ) ;
if ( fProgressProc ! = nil )
fProgressProc ( plKey : : Make ( pKey ) ) ;
}
else
{
kResMgrLog ( 3 , ILog ( 3 , " ...ERROR: Unable to read creatable from stream! " ) ) ;
}
}
if ( isClone & & setClone )
{
fCurClonePlayerID = 0 ;
fCurCloneID = 0 ;
}
kResMgrLog ( 4 , ILog ( 4 , " ...Read complete for object %s::%s " , plFactory : : GetNameOfClass ( pKey - > GetUoid ( ) . GetClassType ( ) ) , pKey - > GetUoid ( ) . GetObjectName ( ) ) ) ;
if ( fLogReadTimes )
{
UInt64 ourTime = hsTimer : : GetFullTickCount ( ) - startTime ;
UInt64 childTime = totalTime - startTotalTime ;
ourTime - = childTime ;
plStatusLog : : AddLineS ( " readtimings.log " , plStatusLog : : kWhite , " %s, %s, %u, %.1f " ,
pKey - > GetUoid ( ) . GetObjectName ( ) ,
plFactory : : GetNameOfClass ( pKey - > GetUoid ( ) . GetClassType ( ) ) ,
pKey - > GetDataLen ( ) ,
hsTimer : : FullTicksToMs ( ourTime ) ) ;
totalTime + = ( hsTimer : : GetFullTickCount ( ) - startTime ) - childTime ;
}
return ( ko ! = nil ) ;
}
//// plPageOutIterator ///////////////////////////////////////////////////////
// See below function
class plPageOutIterator : public plRegistryPageIterator
{
protected :
plResManager * fResMgr ;
UInt16 fHint ;
public :
plPageOutIterator ( plResManager * resMgr , UInt16 hint ) : fResMgr ( resMgr ) , fHint ( hint )
{
fResMgr - > IterateAllPages ( this ) ;
}
virtual hsBool EatPage ( plRegistryPageNode * page )
{
fResMgr - > UnloadPageObjects ( page , fHint ) ;
return true ;
}
} ;
# if HS_BUILD_FOR_UNIX
static void sLeakReportRedirectFn ( const char message [ ] )
{
hsUNIXStream stream ;
stream . Open ( " resMgrMemLeaks.txt " , " at " ) ;
stream . WriteString ( message ) ;
stream . Close ( ) ;
}
static bool sFirstTime = true ;
# endif
// Just the scene nodes (and objects referenced by the node... and so on)
void plResManager : : IPageOutSceneNodes ( hsBool forceAll )
{
plSynchEnabler ps ( false ) ; // disable dirty tracking while paging out
# if HS_BUILD_FOR_UNIX
if ( sFirstTime )
{
hsUNIXStream stream ;
stream . Open ( " resMgrMemLeaks.txt " , " wt " ) ;
stream . Close ( ) ;
sFirstTime = false ;
}
hsDebugMessageProc oldProc = hsSetStatusMessageProc ( sLeakReportRedirectFn ) ;
# endif
if ( forceAll )
{
hsStatusMessage ( " --- plResManager Object Leak Report (BEGIN) --- " ) ;
plPageOutIterator iter ( this , UInt16 ( - 1 ) ) ;
hsStatusMessage ( " --- plResManager Object Leak Report (END) --- " ) ;
}
else
{
plPageOutIterator iter ( this , fPageOutHint ) ;
}
# if HS_BUILD_FOR_UNIX
hsSetStatusMessageProc ( oldProc ) ;
# endif
}
//// FindKey /////////////////////////////////////////////////////////////////
inline plKeyImp * IFindKeyLocalized ( const plUoid & uoid , plRegistryPageNode * page )
{
const char * objectName = uoid . GetObjectName ( ) ;
// If we're running localized, try to find a localized version first
if ( plLocalization : : IsLocalized ( ) )
{
char localName [ 256 ] ;
if ( plLocalization : : GetLocalized ( objectName , localName ) )
{
plKeyImp * localKey = page - > FindKey ( uoid . GetClassType ( ) , localName ) ;
if ( localKey ! = nil )
return localKey ;
}
}
// Try to find the non-localized version
return page - > FindKey ( uoid ) ;
}
plKey plResManager : : FindOriginalKey ( const plUoid & uoid )
{
plKey key ;
plKeyImp * foundKey = nil ;
plRegistryPageNode * page = FindPage ( uoid . GetLocation ( ) ) ;
if ( page = = nil )
return key ;
// Try our find first, without loading
foundKey = IFindKeyLocalized ( uoid , page ) ;
if ( foundKey ! = nil )
key = plKey : : Make ( foundKey ) ;
if ( ! key & & plResMgrSettings : : Get ( ) . GetPassiveKeyRead ( ) )
{
// Passive key read mode is where we read keys in and attempt to match
// them to keys in the registry, but we will NOT force a load on the page
// to find the keys. If the key isn't already in the registry to match,
// we create what we call a "passive key", i.e. it's a key with no real
// info apart from the uoid. Used when you want to read in a object that
// contains keys but don't want to actually use those keys (only write
// them back out).
// Note: startPos of -1 means we didn't read it from disk, but 0 length
// is our special key that we're a passively created key
foundKey = TRACKED_NEW plKeyImp ( uoid , UInt32 ( - 1 ) , UInt32 ( 0 ) ) ;
key = plKey : : Make ( foundKey ) ;
}
// OK, find didn't work. Can we load and try again?
if ( ! key & & ! page - > IsFullyLoaded ( ) )
{
// Tell the resManager's helper to load and hold our page keys temporarily
plResManagerHelper : : GetInstance ( ) - > LoadAndHoldPageKeys ( page ) ;
// Try again
foundKey = IFindKeyLocalized ( uoid , page ) ;
if ( foundKey ! = nil )
key = plKey : : Make ( foundKey ) ;
}
return key ;
}
plKey plResManager : : FindKey ( const plUoid & uoid )
{
plKey key = FindOriginalKey ( uoid ) ;
// If we're looking for a clone, get the clone instead of the original
if ( key & & uoid . IsClone ( ) )
key = ( ( plKeyImp * ) key ) - > GetClone ( uoid . GetClonePlayerID ( ) , uoid . GetCloneID ( ) ) ;
return key ;
}
const plLocation & plResManager : : FindLocation ( const char * age , const char * page ) const
{
static plLocation invalidLoc ;
plRegistryPageNode * pageNode = FindPage ( age , page ) ;
if ( pageNode )
return pageNode - > GetPageInfo ( ) . GetLocation ( ) ;
return invalidLoc ;
}
const void plResManager : : GetLocationStrings ( const plLocation & loc , char * ageBuffer , char * pageBuffer ) const
{
plRegistryPageNode * page = FindPage ( loc ) ;
const plPageInfo & info = page - > GetPageInfo ( ) ;
// Those buffers better be big enough...
if ( ageBuffer )
hsStrcpy ( ageBuffer , info . GetAge ( ) ) ;
if ( pageBuffer )
hsStrcpy ( pageBuffer , info . GetPage ( ) ) ;
}
hsBool plResManager : : AddViaNotify ( plRefMsg * msg , plRefFlags : : Type flags )
{
hsAssert ( msg & & msg - > GetRef ( ) & & msg - > GetRef ( ) - > GetKey ( ) , " Improperly filled out ref message " ) ;
plKey key = msg - > GetRef ( ) - > GetKey ( ) ; // for linux build
return AddViaNotify ( key , msg , flags ) ;
}
hsBool plResManager : : AddViaNotify ( const plKey & key , plRefMsg * msg , plRefFlags : : Type flags )
{
hsAssert ( key , " Can't add without a Key " ) ;
if ( ! key )
{
hsRefCnt_SafeUnRef ( msg ) ;
return false ;
}
( ( plKeyImp * ) key ) - > SetupNotify ( msg , flags ) ;
if ( flags ! = plRefFlags : : kPassiveRef )
{
hsKeyedObject * ko = key - > ObjectIsLoaded ( ) ;
if ( ! ko )
Load ( key ) ;
}
return true ;
}
hsBool plResManager : : SendRef ( hsKeyedObject * ko , plRefMsg * refMsg , plRefFlags : : Type flags )
{
if ( ! ko )
return false ;
plKey key = ko - > GetKey ( ) ;
return SendRef ( key , refMsg , flags ) ;
}
//////////////////////////
// This one does the dirty. Calls, the protected ISetupNotify on the key being reffed.
// That will setup the notifications on the key, but not send any. If the object is
// currently not loaded, we're done (and this behaves exactly like AddViaNotify().
// If it is in memory, and the one making the reference is in memory (it presumably
// is in the absence of strange doings), we bypass the Dispatch system and call
// the object making the reference's MsgReceive directly, so the object will have
// received the reference via its normal message processing without the message
// having to wait in the queue, and more importantly, before the SendRef call returns.
// This doesn't mean you are guaranteed to have your ref at the return of SendRef,
// because if it's not in memory, we don't wait around while we load it, we just
// return false.
hsBool plResManager : : SendRef ( const plKey & key , plRefMsg * refMsg , plRefFlags : : Type flags )
{
if ( ! key )
{
hsRefCnt_SafeUnRef ( refMsg ) ;
return false ;
}
plKeyImp * iKey = ( plKeyImp * ) key ;
iKey - > ISetupNotify ( refMsg , flags ) ;
hsRefCnt_SafeUnRef ( refMsg ) ;
if ( flags ! = plRefFlags : : kPassiveRef )
iKey - > VerifyLoaded ( ) ;
hsKeyedObject * ko = key - > ObjectIsLoaded ( ) ;
if ( ! ko )
return false ;
refMsg - > SetRef ( ko ) ;
refMsg - > SetTimeStamp ( hsTimer : : GetSysSeconds ( ) ) ;
for ( int i = 0 ; i < refMsg - > GetNumReceivers ( ) ; i + + )
{
hsKeyedObject * rcv = refMsg - > GetReceiver ( i ) - > ObjectIsLoaded ( ) ;
if ( rcv )
rcv - > MsgReceive ( refMsg ) ;
}
return true ;
}
void plResManager : : Load ( const plKey & key ) // places on list to be loaded
{
if ( fReadingObject )
fQueuedReads . push_back ( key ) ;
else
key - > VerifyLoaded ( ) ; // force Load
}
plKey plResManager : : ReadKeyNotifyMe ( hsStream * stream , plRefMsg * msg , plRefFlags : : Type flags )
{
plKey key = ReadKey ( stream ) ;
if ( ! key )
{
hsRefCnt_SafeUnRef ( msg ) ;
return nil ;
}
if ( key - > GetUoid ( ) . GetLoadMask ( ) . DontLoad ( ) )
{
hsStatusMessageF ( " %s being skipped because of load mask " , key - > GetName ( ) ) ;
hsRefCnt_SafeUnRef ( msg ) ;
return nil ;
}
( ( plKeyImp * ) key ) - > SetupNotify ( msg , flags ) ;
hsKeyedObject * ko = key - > ObjectIsLoaded ( ) ;
if ( ! ko )
{
Load ( key ) ;
}
return key ;
}
//// NewKey //////////////////////////////////////////////////////////////////
// Creates a new key and assigns it to the given keyed object, also placing
// it into the registry.
plKey plResManager : : NewKey ( const char * name , hsKeyedObject * object , const plLocation & loc , const plLoadMask & m )
{
hsAssert ( name & & name [ 0 ] ! = ' \0 ' , " No name for new key " ) ;
plUoid newUoid ( loc , object - > ClassIndex ( ) , name , m ) ;
return NewKey ( newUoid , object ) ;
}
plKey plResManager : : NewKey ( plUoid & newUoid , hsKeyedObject * object )
{
hsAssert ( fInited , " Attempting to create a new key before we're inited! " ) ;
plKeyImp * newKey = TRACKED_NEW plKeyImp ;
newKey - > SetUoid ( newUoid ) ;
AddKey ( newKey ) ;
plKey keyPtr = plKey : : Make ( newKey ) ;
object - > SetKey ( keyPtr ) ;
return keyPtr ;
}
plKey plResManager : : ReRegister ( const char * nm , const plUoid & oid )
{
hsAssert ( fInited , " Attempting to reregister a key before we're inited! " ) ;
bool canClone = false ;
if ( fCurCloneID ! = 0 )
{
// Not allowed to clone these things
int oidType = oid . GetClassType ( ) ;
if ( oidType ! = CLASS_INDEX_SCOPED ( plSceneNode ) & &
oidType ! = CLASS_INDEX_SCOPED ( plLOSDispatch ) & &
oidType ! = CLASS_INDEX_SCOPED ( plTimerCallbackManager ) & &
oidType ! = CLASS_INDEX_SCOPED ( pfConsole ) & &
oidType ! = CLASS_INDEX_SCOPED ( plAudioSystem ) & &
oidType ! = CLASS_INDEX_SCOPED ( plInputManager ) & &
oidType ! = CLASS_INDEX_SCOPED ( plClient ) & &
oidType ! = CLASS_INDEX_SCOPED ( plNetClientMgr ) & &
oidType ! = CLASS_INDEX_SCOPED ( plAvatarAnimMgr ) & &
oidType ! = CLASS_INDEX_SCOPED ( plSoundBuffer ) & &
oidType ! = CLASS_INDEX_SCOPED ( plResManagerHelper ) & &
oidType ! = CLASS_INDEX_SCOPED ( plSharedMesh ) )
canClone = true ;
// Can't clone fixed keys
if ( oid . GetLocation ( ) = = plLocation : : kGlobalFixedLoc )
canClone = false ;
}
plKey pOrigKey = FindOriginalKey ( oid ) ;
if ( ! canClone )
{
if ( pOrigKey )
{
return pOrigKey ;
}
// the clone doesn't exist
else if ( oid . IsClone ( ) )
{
return nil ;
}
}
else //we are cloning
{
if ( pOrigKey )
{
plKey cloneKey = ( ( plKeyImp * ) pOrigKey ) - > GetClone ( fCurClonePlayerID , fCurCloneID ) ;
if ( cloneKey )
return cloneKey ;
}
}
plKeyImp * pKey = TRACKED_NEW plKeyImp ;
if ( canClone & & pOrigKey )
{
pKey - > CopyForClone ( ( plKeyImp * ) pOrigKey , fCurClonePlayerID , fCurCloneID ) ;
( ( plKeyImp * ) pOrigKey ) - > AddClone ( pKey ) ;
}
else
{
// Make sure key doesn't already exist
if ( pOrigKey )
{
hsAssert ( false , " Attempting to add duplicate key " ) ;
delete pKey ;
return nil ;
}
pKey - > SetUoid ( oid ) ; // Tell the Key its ID
AddKey ( pKey ) ;
}
hsAssert ( pKey , " ReRegister: returning nil key? " ) ;
return plKey : : Make ( pKey ) ;
}
//// ReadKey /////////////////////////////////////////////////////////////////
// Reads a "key" from the given stream. What we secretly do is read in the
// plUoid for a key and look up to find the key. Nobody else will know :)
plKey plResManager : : ReadKey ( hsStream * s )
{
hsBool nonNil = s - > ReadBool ( ) ;
if ( ! nonNil )
return nil ;
plUoid uoid ;
uoid . Read ( s ) ;
plKey key ;
if ( fCurCloneID ! = 0 )
{
// We're reading child of a clone object, it needs to be cloned too
key = ReRegister ( uoid . GetObjectName ( ) , uoid ) ;
}
else if ( uoid . GetCloneID ( ) ! = 0 )
{
// We're reading a clone key. first see if we already have that key around....
key = FindKey ( uoid ) ;
if ( key = = nil )
{
fCurClonePlayerID = uoid . GetClonePlayerID ( ) ;
fCurCloneID = uoid . GetCloneID ( ) ;
key = ReRegister ( uoid . GetObjectName ( ) , uoid ) ;
fCurClonePlayerID = 0 ;
fCurCloneID = 0 ;
}
}
else
{
// We're reading a regular, non-clone object
key = FindKey ( uoid ) ;
}
return key ;
}
//// WriteKey ////////////////////////////////////////////////////////////////
void plResManager : : WriteKey ( hsStream * s , hsKeyedObject * obj )
{
if ( obj )
WriteKey ( s , obj - > GetKey ( ) ) ;
else
WriteKey ( s , plKey ( nil ) ) ;
}
void plResManager : : WriteKey ( hsStream * s , const plKey & key )
{
s - > WriteBool ( key ! = nil ) ;
if ( key )
key - > GetUoid ( ) . Write ( s ) ;
}
//
// Create cloned key but don't load yet
//
plKey plResManager : : CloneKey ( const plKey & objKey )
{
if ( ! objKey )
{
hsStatusMessage ( " CloneKey: nil key, returning nil " ) ;
return nil ;
}
fCloningCounter + + ;
return ICloneKey ( objKey - > GetUoid ( ) , plNetClientApp : : GetInstance ( ) - > GetPlayerID ( ) , fCloningCounter ) ;
}
plKey plResManager : : ICloneKey ( const plUoid & objUoid , UInt32 playerID , UInt32 cloneID )
{
hsAssert ( fCurCloneID = = 0 , " Recursive clone " ) ;
fCurCloneID = cloneID ;
fCurClonePlayerID = playerID ;
plKey cloneKey = ReRegister ( " " , objUoid ) ;
fCurClonePlayerID = 0 ;
fCurCloneID = 0 ;
// Then notify NetClientMgr when object loads
plObjRefMsg * refMsg = TRACKED_NEW plObjRefMsg ( plNetClientApp : : GetInstance ( ) - > GetKey ( ) , plRefMsg : : kOnCreate , 0 , 0 ) ;
AddViaNotify ( cloneKey , refMsg , plRefFlags : : kPassiveRef ) ;
return cloneKey ;
}
//
// Unregisters (deletes) an object.
// Currently, this means the object is going away permanently.
// When support for paging is added, key->UnRegister() should not clear its notify lists.
// Return true if successful.
//
hsBool plResManager : : Unload ( const plKey & objKey )
{
if ( objKey )
{
( ( plKeyImp * ) objKey ) - > UnRegister ( ) ;
fDispatch - > UnRegisterAll ( objKey ) ;
return true ;
}
return false ;
}
plCreatable * plResManager : : IReadCreatable ( hsStream * s ) const
{
UInt16 hClass = s - > ReadLE16 ( ) ;
plCreatable * pCre = plFactory : : Create ( hClass ) ;
if ( ! pCre )
hsAssert ( hClass = = 0x8000 , " Invalid creatable index " ) ;
return pCre ;
}
plCreatable * plResManager : : ReadCreatable ( hsStream * s )
{
plCreatable * pCre = IReadCreatable ( s ) ;
if ( pCre )
pCre - > Read ( s , this ) ;
return pCre ;
}
plCreatable * plResManager : : ReadCreatableVersion ( hsStream * s )
{
plCreatable * pCre = IReadCreatable ( s ) ;
if ( pCre )
pCre - > ReadVersion ( s , this ) ;
return pCre ;
}
inline void IWriteCreatable ( hsStream * s , plCreatable * pCre )
{
Int16 hClass = pCre ? pCre - > ClassIndex ( ) : 0x8000 ;
hsAssert ( pCre = = nil | | plFactory : : IsValidClassIndex ( hClass ) , " Invalid class index on write " ) ;
s - > WriteLE16 ( hClass ) ;
}
void plResManager : : WriteCreatable ( hsStream * s , plCreatable * pCre )
{
IWriteCreatable ( s , pCre ) ;
if ( pCre )
pCre - > Write ( s , this ) ;
}
void plResManager : : WriteCreatableVersion ( hsStream * s , plCreatable * pCre )
{
IWriteCreatable ( s , pCre ) ;
if ( pCre )
pCre - > WriteVersion ( s , this ) ;
}
void plResManager : : SetProgressBarProc ( plProgressProc proc )
{
fProgressProc = proc ;
}
//////////////////////////////////////////////////////////////////////////////
//// Paging Functions ////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// plResAgeHolder //////////////////////////////////////////////////////////
// Helper object that stores all the keys for an age, to optimize the load
// process.
class plResAgeHolder : public hsRefCnt
{
public :
hsTArray < plKey > fKeys ;
std : : string fAge ;
plResAgeHolder ( ) { }
plResAgeHolder ( const char * age ) : fAge ( age ) { }
~ plResAgeHolder ( ) { fKeys . Reset ( ) ; }
} ;
//// plResHolderIterator /////////////////////////////////////////////////////
class plResHolderIterator : public plRegistryPageIterator
{
protected :
hsTArray < plKey > & fKeys ;
const char * fAgeName ;
plResManager * fResMgr ;
public :
plResHolderIterator ( const char * age , hsTArray < plKey > & keys , plResManager * resMgr )
: fAgeName ( age ) , fKeys ( keys ) , fResMgr ( resMgr ) { }
virtual hsBool EatPage ( plRegistryPageNode * page )
{
if ( stricmp ( page - > GetPageInfo ( ) . GetAge ( ) , fAgeName ) = = 0 )
{
fResMgr - > LoadPageKeys ( page ) ;
plKeyCollector collector ( fKeys ) ;
page - > IterateKeys ( & collector ) ;
}
return true ;
}
} ;
//// LoadAndHoldAgeKeys //////////////////////////////////////////////////////
void plResManager : : LoadAgeKeys ( const char * age )
{
hsAssert ( age & & age [ 0 ] ! = ' \0 ' , " age is nil " ) ;
HeldAgeKeyMap : : const_iterator it = fHeldAgeKeys . find ( age ) ;
if ( it ! = fHeldAgeKeys . end ( ) )
{
kResMgrLog ( 1 , ILog ( 1 , " Reffing age keys for age %s " , age ) ) ;
hsStatusMessageF ( " *** Reffing age keys for age %s *** \n " , age ) ;
plResAgeHolder * holder = it - > second ;
holder - > Ref ( ) ;
}
else
{
kResMgrLog ( 1 , ILog ( 1 , " Loading age keys for age %s " , age ) ) ;
hsStatusMessageF ( " *** Loading age keys for age %s *** \n " , age ) ;
plResAgeHolder * holder = TRACKED_NEW plResAgeHolder ( age ) ;
fHeldAgeKeys [ age ] = holder ;
// Go find pages that match this age, load the keys, and ref them all
plResHolderIterator iter ( age , holder - > fKeys , this ) ;
IterateAllPages ( & iter ) ;
}
}
//// DropAgeKeys /////////////////////////////////////////////////////////////
void plResManager : : DropAgeKeys ( const char * age )
{
HeldAgeKeyMap : : iterator it = fHeldAgeKeys . find ( age ) ;
if ( it ! = fHeldAgeKeys . end ( ) )
{
plResAgeHolder * holder = it - > second ;
if ( holder - > RefCnt ( ) = = 1 )
{
// Found it!
kResMgrLog ( 1 , ILog ( 1 , " Dropping held age keys for age %s " , age ) ) ;
fHeldAgeKeys . erase ( it ) ;
}
else
{
kResMgrLog ( 1 , ILog ( 1 , " Unreffing age keys for age %s " , age ) ) ;
}
holder - > UnRef ( ) ;
}
}
//// IDropAllAgeKeys /////////////////////////////////////////////////////////
void plResManager : : IDropAllAgeKeys ( )
{
kResMgrLog ( 1 , ILog ( 1 , " Dropping any remaining age keys " ) ) ;
for ( HeldAgeKeyMap : : iterator it = fHeldAgeKeys . begin ( ) ; it ! = fHeldAgeKeys . end ( ) ; + + it )
{
plResAgeHolder * holder = it - > second ;
kResMgrLog ( 1 , ILog ( 1 , " Dropping age keys for age %s " , holder - > fAge . c_str ( ) ) ) ;
while ( holder - > RefCnt ( ) > 1 )
holder - > UnRef ( ) ;
holder - > UnRef ( ) ; // deletes holder
}
}
//// PageInRoom //////////////////////////////////////////////////////////////
// Normal finds will have to potentially reload all the keys for a page, but
// paging in this way will avoid having to reload keys every single find
// during a load--we load all the keys once, ref them so they're in, then
// do the entire load and unref when we're done.
//
// The objClassToRef parameter is a bit tricky. Basically, you assume that
// there's one object in the page that you're loading based off of (say, a
// sceneNode). That's the object that'll get reffed by the refMsg passed in.
// The only reason we abstract it is so we can keep plResManager from being
// dependent on any particular class type.
//
// This function is not guaranteed to be synchronous, so you better wait for
// the refMsg to be sent before you assume it's done.
class plOurRefferAndFinder : public plRegistryKeyIterator
{
hsTArray < plKey > & fRefArray ;
UInt16 fClassToFind ;
plKey & fFoundKey ;
public :
plOurRefferAndFinder ( hsTArray < plKey > & refArray , UInt16 classToFind , plKey & foundKey )
: fRefArray ( refArray ) , fClassToFind ( classToFind ) , fFoundKey ( foundKey ) { }
virtual hsBool EatKey ( const plKey & key )
{
// This is cute. Thanks to our new plKey smart pointers, all we have to
// do is append the key to our ref array. This automatically guarantees us
// an extra ref on the key, which is what we're trying to do. Go figure.
fRefArray . Append ( key ) ;
// Also do our find
if ( key - > GetUoid ( ) . GetClassType ( ) = = fClassToFind )
fFoundKey = key ;
return true ;
}
} ;
void plResManager : : PageInRoom ( const plLocation & page , UInt16 objClassToRef , plRefMsg * refMsg )
{
UInt64 readRoomTime = 0 ;
if ( fLogReadTimes )
readRoomTime = hsTimer : : GetFullTickCount ( ) ;
plSynchEnabler ps ( false ) ; // disable dirty tracking while paging in
kResMgrLog ( 1 , ILog ( 1 , " Paging in room 0x%x... " , page . GetSequenceNumber ( ) ) ) ;
// Step 0: Find the pageNode
plRegistryPageNode * pageNode = FindPage ( page ) ;
if ( pageNode = = nil )
{
kResMgrLog ( 1 , ILog ( 1 , " ...Page not found! " ) ) ;
hsAssert ( false , " Invalid location given to PageInRoom() " ) ;
return ;
}
kResMgrLog ( 2 , ILog ( 2 , " ...Found, page is ID'd as %s>%s " , pageNode - > GetPageInfo ( ) . GetAge ( ) , pageNode - > GetPageInfo ( ) . GetPage ( ) ) ) ;
// Step 0.5: Verify the page, just to make sure we really should be loading it
PageCond cond = pageNode - > GetPageCondition ( ) ;
if ( cond ! = kPageOk )
{
std : : string condStr = " Checksum invalid " ;
if ( cond = = kPageTooNew )
condStr = " Page Version too new " ;
else
if ( cond = = kPageOutOfDate )
condStr = " Page Version out of date " ;
kResMgrLog ( 1 , ILog ( 1 , " ...IGNORING pageIn request; verification failed! (%s) " , condStr . c_str ( ) ) ) ;
std : : string msg = xtl : : format ( " Data Problem: Age:%s Page:%s Error:%s " ,
pageNode - > GetPageInfo ( ) . GetAge ( ) , pageNode - > GetPageInfo ( ) . GetPage ( ) , condStr . c_str ( ) ) ;
hsMessageBox ( msg . c_str ( ) , " Error " , hsMessageBoxNormal , hsMessageBoxIconError ) ;
hsRefCnt_SafeUnRef ( refMsg ) ;
return ;
}
// Step 1: We force a load on all the keys in the given page
kResMgrLog ( 2 , ILog ( 2 , " ...Loading page keys... " ) ) ;
LoadPageKeys ( pageNode ) ;
// Step 2: Now ref all the keys in that page, every single one. This lets us unref
// (and thus potentially delete) them later. Note that we also use this for our find.
kResMgrLog ( 2 , ILog ( 2 , " ...Reffing keys... " ) ) ;
plKey objKey ;
hsTArray < plKey > keyRefList ;
plOurRefferAndFinder reffer ( keyRefList , objClassToRef , objKey ) ;
pageNode - > IterateKeys ( & reffer ) ;
// Step 3: Do our load
if ( objKey = = nil )
{
kResMgrLog ( 1 , ILog ( 1 , " ...SceneNode not found to base page-in op on. Aborting... " ) ) ;
// This is coming up a lot lately; too intrusive to be an assert.
// hsAssert( false, "No object found on which to base our PageInRoom()" );
return ;
}
// Forces a load
kResMgrLog ( 2 , ILog ( 2 , " ...Forcing load via sceneNode... " ) ) ;
objKey - > VerifyLoaded ( ) ;
// Step 4: Unref the keys. This'll make the unused ones go away again. And guess what,
// since we just have an array of keys, all we have to do to do this is clear the array.
// Note that since objKey is a plKey, our object that we loaded will have an extra ref...
// Scary, huh?
kResMgrLog ( 2 , ILog ( 2 , " ...Dumping extra key refs... " ) ) ;
keyRefList . Reset ( ) ;
// Step 5: Ref the object
kResMgrLog ( 2 , ILog ( 2 , " ...Dispatching refMessage... " ) ) ;
AddViaNotify ( objKey , refMsg , plRefFlags : : kActiveRef ) ;
// All done!
kResMgrLog ( 1 , ILog ( 1 , " ...Page in complete! " ) ) ;
if ( fLogReadTimes )
{
readRoomTime = hsTimer : : GetFullTickCount ( ) - readRoomTime ;
plStatusLog : : AddLineS ( " readtimings.log " , plStatusLog : : kWhite , " ----- Reading page %s>%s took %.1f ms " ,
pageNode - > GetPageInfo ( ) . GetAge ( ) , pageNode - > GetPageInfo ( ) . GetPage ( ) ,
hsTimer : : FullTicksToMs ( readRoomTime ) ) ;
}
}
class plPageInAgeIter : public plRegistryPageIterator
{
private :
plKey fDestKey ;
const char * fAgeName ;
std : : vector < plLocation > fLocations ;
public :
plPageInAgeIter ( plKey destKey , const char * ageName ) : fDestKey ( destKey ) , fAgeName ( ageName ) { }
~ plPageInAgeIter ( )
{
plClientMsg * pMsg1 = TRACKED_NEW plClientMsg ( plClientMsg : : kLoadRoomHold ) ;
for ( int i = 0 ; i < fLocations . size ( ) ; i + + )
{
pMsg1 - > AddRoomLoc ( fLocations [ i ] ) ;
}
pMsg1 - > Send ( fDestKey ) ;
}
virtual hsBool EatPage ( plRegistryPageNode * page )
{
if ( stricmp ( page - > GetPageInfo ( ) . GetAge ( ) , fAgeName ) = = 0 )
{
plUoid uoid ( page - > GetPageInfo ( ) . GetLocation ( ) , 0 , " " ) ;
fLocations . push_back ( uoid . GetLocation ( ) ) ;
}
return true ;
}
} ;
// PageInAge is intended for bulk global ages, like GlobalAnimations or GlobalClothing
// that store a lot of data we always want available. (Used to be known as PageInHold)
void plResManager : : PageInAge ( const char * age )
{
plSynchEnabler ps ( false ) ; // disable dirty tracking while paging in
plUoid lu ( kClient_KEY ) ;
plKey clientKey = hsgResMgr : : ResMgr ( ) - > FindKey ( lu ) ;
// Tell the client to load all the keys for this age, to make the loading process work better
plClientMsg * loadAgeKeysMsg = TRACKED_NEW plClientMsg ( plClientMsg : : kLoadAgeKeys ) ;
loadAgeKeysMsg - > SetAgeName ( age ) ;
loadAgeKeysMsg - > Send ( clientKey ) ;
// Then iterate through each room in the age. The iterator will send the load message
// off on destruction.
plPageInAgeIter iter ( clientKey , age ) ;
IterateAllPages ( & iter ) ;
}
//// VerifyPages /////////////////////////////////////////////////////////////
// Runs through all the pages and ensures they are all up-to-date in version
// numbers and that no out-of-date objects exist in them
hsBool plResManager : : VerifyPages ( )
{
hsTArray < plRegistryPageNode * > invalidPages , newerPages ;
// Step 1: verify major/minor version changes
if ( plResMgrSettings : : Get ( ) . GetFilterNewerPageVersions ( ) | |
plResMgrSettings : : Get ( ) . GetFilterOlderPageVersions ( ) )
{
PageSet : : iterator it = fAllPages . begin ( ) ;
while ( it ! = fAllPages . end ( ) )
{
plRegistryPageNode * page = * it ;
it + + ;
if ( page - > GetPageCondition ( ) = = kPageTooNew & & plResMgrSettings : : Get ( ) . GetFilterNewerPageVersions ( ) )
{
newerPages . Append ( page ) ;
fAllPages . erase ( page ) ;
}
else if (
( page - > GetPageCondition ( ) = = kPageCorrupt | |
page - > GetPageCondition ( ) = = kPageOutOfDate )
& & plResMgrSettings : : Get ( ) . GetFilterOlderPageVersions ( ) )
{
invalidPages . Append ( page ) ;
fAllPages . erase ( page ) ;
}
}
}
// Handle all our invalid pages now
if ( invalidPages . GetCount ( ) > 0 )
{
if ( ! IDeleteBadPages ( invalidPages , false ) )
return false ;
}
// Warn about newer pages
if ( newerPages . GetCount ( ) > 0 )
{
if ( ! IWarnNewerPages ( newerPages ) )
return false ;
}
// Step 2 of verification: make sure no sequence numbers conflict
PageSet : : iterator it = fAllPages . begin ( ) ;
for ( ; it ! = fAllPages . end ( ) ; it + + )
{
plRegistryPageNode * page = * it ;
PageSet : : iterator itUp = it ;
itUp + + ;
for ( ; itUp ! = fAllPages . end ( ) ; itUp + + )
{
plRegistryPageNode * upPage = * itUp ;
if ( page - > GetPageInfo ( ) . GetLocation ( ) = = upPage - > GetPageInfo ( ) . GetLocation ( ) )
{
invalidPages . Append ( upPage ) ;
fAllPages . erase ( itUp ) ;
break ;
}
}
}
// Redo our loaded pages list, since Verify() might force the page's keys to load or unload
fLoadedPages . clear ( ) ;
it = fAllPages . begin ( ) ;
while ( it ! = fAllPages . end ( ) )
{
plRegistryPageNode * page = * it ;
it + + ;
if ( page - > IsLoaded ( ) )
fLoadedPages . insert ( page ) ;
}
// Handle all our conflicting pages now
if ( invalidPages . GetCount ( ) > 0 )
return IDeleteBadPages ( invalidPages , true ) ;
return true ;
}
//// IDeleteBadPages /////////////////////////////////////////////////////////
// Given an array of pages that are invalid (major version out-of-date or
// whatnot), asks the user what we should do about them.
static void ICatPageNames ( hsTArray < plRegistryPageNode * > & pages , char * buf , int bufSize )
{
for ( int i = 0 ; i < pages . GetCount ( ) ; i + + )
{
if ( i > = 25 )
{
strcat ( buf , " ... \n " ) ;
break ;
}
const char * pagePath = pages [ i ] - > GetPagePath ( ) ;
const char * pageFile = plFileUtils : : GetFileName ( pagePath ) ;
if ( strlen ( buf ) + strlen ( pageFile ) > bufSize - 5 )
{
strcat ( buf , " ... \n " ) ;
break ;
}
strcat ( buf , pageFile ) ;
strcat ( buf , " \n " ) ;
}
}
hsBool plResManager : : IDeleteBadPages ( hsTArray < plRegistryPageNode * > & invalidPages , hsBool conflictingSeqNums )
{
# ifndef PLASMA_EXTERNAL_RELEASE
if ( ! hsMessageBox_SuppressPrompts )
{
char msg [ 4096 ] ;
// Prompt what to do
if ( conflictingSeqNums )
strcpy ( msg , " The following pages have conflicting sequence numbers. This usually happens when "
" you copy data files between machines that had random sequence numbers assigned at "
" export. To avoid crashing, these pages will be deleted: \n \n " ) ;
else
strcpy ( msg , " The following pages are out of date and will be deleted: \n \n " ) ;
ICatPageNames ( invalidPages , msg , sizeof ( msg ) ) ;
hsMessageBox ( msg , " Warning " , hsMessageBoxNormal ) ;
}
# endif // PLASMA_EXTERNAL_RELEASE
// Delete 'em
for ( int i = 0 ; i < invalidPages . GetCount ( ) ; i + + )
{
invalidPages [ i ] - > DeleteSource ( ) ;
delete invalidPages [ i ] ;
}
invalidPages . Reset ( ) ;
fLastFoundPage = nil ;
return true ;
}
//// IWarnNewerPages /////////////////////////////////////////////////////////
// Given an array of pages that are newer (minor or major version are newer
// than the "current" one), warns the user about them but does nothing to
// them.
hsBool plResManager : : IWarnNewerPages ( hsTArray < plRegistryPageNode * > & newerPages )
{
# ifndef PLASMA_EXTERNAL_RELEASE
if ( ! hsMessageBox_SuppressPrompts )
{
char msg [ 4096 ] ;
// Prompt what to do
strcpy ( msg , " The following pages have newer version numbers than this client and cannot be \n loaded. "
" They will be ignored but their files will NOT be deleted: \n \n " ) ;
ICatPageNames ( newerPages , msg , sizeof ( msg ) ) ;
hsMessageBox ( msg , " Warning " , hsMessageBoxNormal ) ;
}
# endif // PLASMA_EXTERNAL_RELEASE
// Not deleting the files, just delete them from memory
for ( int i = 0 ; i < newerPages . GetCount ( ) ; i + + )
delete newerPages [ i ] ;
newerPages . Reset ( ) ;
fLastFoundPage = nil ;
return true ;
}
//// plOurReffer /////////////////////////////////////////////////////////////
// Our little reffer key iterator
class plOurReffer : public plRegistryKeyIterator
{
protected :
hsTArray < plKey > fRefArray ;
public :
plOurReffer ( ) { }
virtual ~ plOurReffer ( ) { UnRef ( ) ; }
void UnRef ( ) { fRefArray . Reset ( ) ; }
virtual hsBool EatKey ( const plKey & key )
{
// This is cute. Thanks to our new plKey smart pointers, all we have to
// do is append the key to our ref array. This automatically guarantees us
// an extra ref on the key, which is what we're trying to do. Go figure.
fRefArray . Append ( key ) ;
return true ;
}
} ;
void plResManager : : DumpUnusedKeys ( plRegistryPageNode * page ) const
{
plOurReffer reffer ;
page - > IterateKeys ( & reffer ) ;
}
plRegistryPageNode * plResManager : : CreatePage ( const plLocation & location , const char * age , const char * page )
{
plRegistryPageNode * pageNode = TRACKED_NEW plRegistryPageNode ( location , age , page , fDataPath . c_str ( ) ) ;
fAllPages . insert ( pageNode ) ;
return pageNode ;
}
//// AddPage /////////////////////////////////////////////////////////////////
void plResManager : : AddPage ( plRegistryPageNode * page )
{
fAllPages . insert ( page ) ;
if ( page - > IsLoaded ( ) )
fLoadedPages . insert ( page ) ;
}
//// LoadPageKeys ///////////////////////////////////////////////////////////
void plResManager : : LoadPageKeys ( plRegistryPageNode * pageNode )
{
if ( pageNode - > IsFullyLoaded ( ) )
return ;
// Load it and add it to the loaded list
pageNode - > LoadKeys ( ) ;
if ( fPageListLock = = 0 )
fLoadedPages . insert ( pageNode ) ;
else
fPagesNeedCleanup = true ;
}
//// sIReportLeak ////////////////////////////////////////////////////////////
// Handy tiny function here
static void sIReportLeak ( plKeyImp * key , plRegistryPageNode * page )
{
class plKeyImpRef : public plKeyImp
{
public :
UInt16 GetRefCnt ( ) const { return fRefCount ; }
} ;
static bool alreadyDone = false ;
static plRegistryPageNode * lastPage ;
if ( page ! = nil )
lastPage = page ;
if ( key = = nil )
{
alreadyDone = false ;
return ;
}
if ( ! alreadyDone )
{
// Print out page header
hsStatusMessageF ( " Leaks in page %s>%s[%08x]: \n " , lastPage - > GetPageInfo ( ) . GetAge ( ) , lastPage - > GetPageInfo ( ) . GetPage ( ) , lastPage - > GetPageInfo ( ) . GetLocation ( ) . GetSequenceNumber ( ) ) ;
alreadyDone = true ;
}
int refsLeft = ( ( plKeyImpRef * ) key ) - > GetRefCnt ( ) - 1 ;
if ( refsLeft = = 0 )
return ;
char tempStr [ 256 ] , tempStr2 [ 128 ] ;
if ( key - > ObjectIsLoaded ( ) = = nil )
sprintf ( tempStr2 , " (key only, %d refs left) " , refsLeft ) ;
else
sprintf ( tempStr2 , " - %d bytes - %d refs left " , key - > GetDataLen ( ) , refsLeft ) ;
hsStatusMessageF ( " %s: %s %s \n " , plFactory : : GetNameOfClass ( key - > GetUoid ( ) . GetClassType ( ) ) ,
key - > GetUoid ( ) . StringIze ( tempStr ) , tempStr2 ) ;
}
//// UnloadPageObjects ///////////////////////////////////////////////////////
// Unloads all the objects in a given page. Once this is complete, all
// object pointers for every key in the page *should* be nil. Note that we're
// given a hint class index to start with (like plSceneNode) that should do
// most of the work for us via unreffing.
//
// Update 5.20: since there are so many problems with doing this, don't
// delete the objects, just print out a memleak report. -mcn
void plResManager : : UnloadPageObjects ( plRegistryPageNode * pageNode , UInt16 classIndexHint )
{
if ( ! pageNode - > IsLoaded ( ) )
return ;
class plUnloadObjectsIterator : public plRegistryKeyIterator
{
public :
virtual hsBool EatKey ( const plKey & key )
{
sIReportLeak ( ( plKeyImp * ) key , nil ) ;
return true ;
}
} ;
sIReportLeak ( nil , pageNode ) ;
plUnloadObjectsIterator iterator ;
if ( classIndexHint ! = UInt16 ( - 1 ) )
pageNode - > IterateKeys ( & iterator , classIndexHint ) ;
else
pageNode - > IterateKeys ( & iterator ) ;
}
//// FindPage ////////////////////////////////////////////////////////////////
plRegistryPageNode * plResManager : : FindPage ( const plLocation & location ) const
{
// Quick optimization
if ( fLastFoundPage ! = nil & & fLastFoundPage - > GetPageInfo ( ) . GetLocation ( ) = = location )
return fLastFoundPage ;
PageSet : : const_iterator it ;
for ( it = fAllPages . begin ( ) ; it ! = fAllPages . end ( ) ; it + + )
{
const plLocation & pageloc = ( * it ) - > GetPageInfo ( ) . GetLocation ( ) ;
if ( pageloc = = location )
{
fLastFoundPage = * it ;
return fLastFoundPage ;
}
}
return nil ;
}
//// FindPage ////////////////////////////////////////////////////////////////
plRegistryPageNode * plResManager : : FindPage ( const char * age , const char * page ) const
{
PageSet : : const_iterator it ;
for ( it = fAllPages . begin ( ) ; it ! = fAllPages . end ( ) ; it + + )
{
const plPageInfo & info = ( * it ) - > GetPageInfo ( ) ;
if ( hsStrCaseEQ ( info . GetAge ( ) , age ) & &
hsStrCaseEQ ( info . GetPage ( ) , page ) )
return * it ;
}
return nil ;
}
//////////////////////////////////////////////////////////////////////////////
//// Key Operations //////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// AddKey //////////////////////////////////////////////////////////////////
// Adds a key to the registry. Assumes uoid already set.
void plResManager : : AddKey ( plKeyImp * key )
{
plRegistryPageNode * page = FindPage ( key - > GetUoid ( ) . GetLocation ( ) ) ;
if ( page = = nil )
return ;
page - > AddKey ( key ) ;
fLoadedPages . insert ( page ) ;
}
void plResManager : : IKeyReffed ( plKeyImp * key )
{
plRegistryPageNode * page = FindPage ( key - > GetUoid ( ) . GetLocation ( ) ) ;
if ( page = = nil )
{
hsAssert ( 0 , " Couldn't find page that key belongs to " ) ;
return ;
}
page - > SetKeyUsed ( key ) ;
}
void plResManager : : IKeyUnreffed ( plKeyImp * key )
{
plRegistryPageNode * page = FindPage ( key - > GetUoid ( ) . GetLocation ( ) ) ;
if ( page = = nil )
{
hsAssert ( 0 , " Couldn't find page that key belongs to " ) ;
return ;
}
bool removed = page - > SetKeyUnused ( key ) ;
hsAssert ( removed , " Key wasn't removed from page " ) ;
if ( removed )
{
if ( ! page - > IsLoaded ( ) )
{
if ( fPageListLock = = 0 )
fLoadedPages . erase ( page ) ;
else
fPagesNeedCleanup = true ;
}
}
}
//////////////////////////////////////////////////////////////////////////////
//// Iterator Functions //////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
//// IterateKeys Helper Class ////////////////////////////////////////////////
class plKeyIterEater : public plRegistryPageIterator
{
protected :
plRegistryKeyIterator * fIter ;
public :
plKeyIterEater ( plRegistryKeyIterator * iter ) : fIter ( iter ) { }
virtual hsBool EatPage ( plRegistryPageNode * keyNode )
{
return keyNode - > IterateKeys ( fIter ) ;
}
} ;
//// IterateKeys /////////////////////////////////////////////////////////////
hsBool plResManager : : IterateKeys ( plRegistryKeyIterator * iterator )
{
plKeyIterEater myEater ( iterator ) ;
return IteratePages ( & myEater , nil ) ;
}
hsBool plResManager : : IterateKeys ( plRegistryKeyIterator * iterator , const plLocation & pageToRestrictTo )
{
plRegistryPageNode * page = FindPage ( pageToRestrictTo ) ;
if ( page = = nil )
{
hsAssert ( false , " Page not found to iterate through " ) ;
return false ;
}
plKeyIterEater myEater ( iterator ) ;
return myEater . EatPage ( page ) ;
}
//// IteratePages ////////////////////////////////////////////////////////////
// Iterate through all LOADED pages
hsBool plResManager : : IteratePages ( plRegistryPageIterator * iterator , const char * ageToRestrictTo )
{
ILockPages ( ) ;
PageSet : : const_iterator it ;
for ( it = fLoadedPages . begin ( ) ; it ! = fLoadedPages . end ( ) ; it + + )
{
plRegistryPageNode * page = * it ;
if ( page - > GetPageInfo ( ) . GetLocation ( ) = = plLocation : : kGlobalFixedLoc )
continue ;
if ( ! ageToRestrictTo | | hsStrCaseEQ ( page - > GetPageInfo ( ) . GetAge ( ) , ageToRestrictTo ) )
{
if ( ! iterator - > EatPage ( page ) )
{
IUnlockPages ( ) ;
return false ;
}
}
}
IUnlockPages ( ) ;
return true ;
}
//// IterateAllPages /////////////////////////////////////////////////////////
// Iterate through ALL pages
hsBool plResManager : : IterateAllPages ( plRegistryPageIterator * iterator )
{
ILockPages ( ) ;
PageSet : : const_iterator it ;
for ( it = fAllPages . begin ( ) ; it ! = fAllPages . end ( ) ; it + + )
{
plRegistryPageNode * page = * it ;
if ( page - > GetPageInfo ( ) . GetLocation ( ) = = plLocation : : kGlobalFixedLoc )
continue ;
if ( ! iterator - > EatPage ( page ) )
{
IUnlockPages ( ) ;
return false ;
}
}
IUnlockPages ( ) ;
return true ;
}
//// ILockPages //////////////////////////////////////////////////////////////
// See, when we iterate through pages, our iterate function might decide to
// move pages, either explicitly through loads or implicitly through key
// deletions. So, before we iterate, we lock 'em all so they won't move,
// then unlock and move them to their proper places at the end.
void plResManager : : ILockPages ( )
{
if ( fPageListLock = = 0 )
fPagesNeedCleanup = false ;
fPageListLock + + ;
}
//// IUnlockPages ////////////////////////////////////////////////////////////
void plResManager : : IUnlockPages ( )
{
fPageListLock - - ;
if ( fPageListLock = = 0 & & fPagesNeedCleanup )
{
fPagesNeedCleanup = false ;
fLoadedPages . clear ( ) ;
PageSet : : const_iterator it ;
for ( it = fAllPages . begin ( ) ; it ! = fAllPages . end ( ) ; it + + )
{
plRegistryPageNode * page = * it ;
if ( page - > IsLoaded ( ) )
fLoadedPages . insert ( page ) ;
}
}
}
// Defined here 'cause release build hates it defined in settings.h for some reason
# include "plResMgrSettings.h"
plResMgrSettings & plResMgrSettings : : Get ( )
{
static plResMgrSettings fSettings ;
return fSettings ;
}