You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1827 lines
51 KiB
1827 lines
51 KiB
14 years ago
|
/*==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/>.
|
||
|
|
||
|
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.
|
||
|
|
||
|
kResMgrLog(4, ILog(4, " ...Opening page data stream for location 0x%x...", key->GetUoid().GetLocation()));
|
||
|
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->ReadSwap16();
|
||
|
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->WriteSwap16(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 \nloaded. "
|
||
|
"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;
|
||
|
}
|