/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#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 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 fKeys;
std::string fAge;
plResAgeHolder() {}
plResAgeHolder( const char* age ) : fAge( age ) {}
~plResAgeHolder() { fKeys.Reset(); }
};
//// plResHolderIterator /////////////////////////////////////////////////////
class plResHolderIterator : public plRegistryPageIterator
{
protected:
hsTArray& fKeys;
const char* fAgeName;
plResManager* fResMgr;
public:
plResHolderIterator(const char* age, hsTArray& 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 &fRefArray;
UInt16 fClassToFind;
plKey &fFoundKey;
public:
plOurRefferAndFinder( hsTArray &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 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 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 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& 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& 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 &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 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;
}