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