/*==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 . Additional permissions under GNU GPL version 3 section 7 If you modify this Program, or any covered work, by linking or combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK (or a modified version of those libraries), containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of OpenSSL and IJG JPEG Library used as well as that of the covered work. You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ #include "plResManager.h" #include "plRegistryNode.h" #include "plResManagerHelper.h" #include "plResMgrSettings.h" #include "plLocalization.h" #include "hsSTLStream.h" #include "hsTimer.h" #include "pnTimer/plTimerCallbackManager.h" #include "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 "pnFactory/plCreator.h" #include "pnNetCommon/plSynchedObject.h" #include "pnNetCommon/plNetApp.h" bool gDataServerLocal = false; /// Logging #define for easier use #define kResMgrLog(level, log) if (plResMgrSettings::Get().GetLoggingLevel() >= level) log static void ILog(uint8_t 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_t 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), 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"); } bool 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 std::vector prpFiles = plFileSystem::ListDir(fDataPath, "*.prp"); for (auto iter = prpFiles.begin(); iter != prpFiles.end(); ++iter) { plRegistryPageNode* node = new plRegistryPageNode(*iter); plPageInfo pi = node->GetPageInfo(); fAllPages[pi.GetLocation()] = node; } } // Special case: we always create pages for the predefined pages CreatePage(plLocation::kGlobalFixedLoc, "Global", "FixedKeys"); hsAssert(!fDispatch, "Dispatch already set"); fDispatch = new plDispatch; plgTimerCallbackMgr::Init(); // Init our helper fMyHelper = new plResManagerHelper(this); fMyHelper->Init(); hsAssert(fMyHelper->GetKey() != nil, "ResManager helper didn't init properly!" ); kResMgrLog(1, ILog(1, " ...Init was successful!")); return true; } bool 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(); // Formerly, we destroyed the Dispatcher here to clean up keys for leak reporting. // However, if there are *real* leaked keys, then they will want to unload after this step. // To unload, plKeyImp needs to dispatcher. SO, we're going to pitch everything currently in the // Dispatcher and kill it later fDispatch->BeginShutdown(); // 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(); PageMap::const_iterator it; for (it = fAllPages.begin(); it != fAllPages.end(); it++) delete it->second; fAllPages.clear(); fLoadedPages.clear(); IUnlockPages(); fLastFoundPage = nil; // Now, kill off the Dispatcher hsRefCnt_SafeUnRef(fDispatch); fDispatch = nullptr; kResMgrLog(1, ILog(1, " ...Shutdown successful!")); fInited = false; } void plResManager::AddSinglePage(const plFileName& pagePath) { plRegistryPageNode* node = new plRegistryPageNode(pagePath); AddPage(node); } plRegistryPageNode* plResManager::FindSinglePage(const plFileName& path) const { PageMap::const_iterator it; for (it = fAllPages.begin(); it != fAllPages.end(); it++) { if (it->second->GetPagePath().AsString().CompareI(path.AsString()) == 0) return it->second; } return nil; } void plResManager::RemoveSinglePage(const plFileName& path) { plRegistryPageNode* node = FindSinglePage(path); if (node) { plLocation loc = node->GetPageInfo().GetLocation(); fAllPages.erase(loc); delete node; } } plDispatchBase *plResManager::Dispatch() { return fDispatch; } void plResManager::LogReadTimes(bool 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_t count = origKey->GetNumClones(); for (uint32_t 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 bool plResManager::ReadObject(plKeyImp* key) { // Read in the object. If while we are doing this something else requests a // load (through AddViaNotify or ReadKeyNotifyMe) we consider it a child load // and put it in a queue. This keeps us from jumping over to another object's // data while we're still reading in its parent (which is bad because that // trashes our file buffering) // // Also, we find the pageNode and open its stream here. We close the // stream when child reads are done. If a child load is using the same stream, // it will just inc/dec the open/close count during its read, and not actually // close the stream, so we don't lose our place, lose our file handle, and thrash. kResMgrLog(4, ILog(4, " ...Opening page data stream for location %s...", key->GetUoid().GetLocation().StringIze().c_str())); 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; } bool plResManager::IReadObject(plKeyImp* pKey, hsStream *stream) { static uint64_t totalTime = 0; uint64_t startTotalTime = totalTime; uint64_t 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_t(-1), "Missing StartPos"); hsAssert(pKey->GetDataLen() != uint32_t(-1), "Missing Data Length"); if (pKey->GetStartPos() == uint32_t(-1) || pKey->GetDataLen() == uint32_t(-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().c_str())); 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().c_str())); if (fLogReadTimes) { uint64_t ourTime = hsTimer::GetFullTickCount() - startTime; uint64_t childTime = totalTime - startTotalTime; ourTime -= childTime; plStatusLog::AddLineS("readtimings.log", plStatusLog::kWhite, "%s, %s, %u, %.1f", pKey->GetUoid().GetObjectName().c_str(), 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_t fHint; public: plPageOutIterator(plResManager* resMgr, uint16_t hint) : fResMgr(resMgr), fHint(hint) { fResMgr->IterateAllPages(this); } virtual bool EatPage(plRegistryPageNode* page) { fResMgr->UnloadPageObjects(page, fHint); return true; } }; // Just the scene nodes (and objects referenced by the node... and so on) void plResManager::IPageOutSceneNodes(bool forceAll) { plSynchEnabler ps(false); // disable dirty tracking while paging out if (forceAll) { hsStatusMessage( "--- plResManager Object Leak Report (BEGIN) ---" ); plPageOutIterator iter(this, static_cast(-1)); hsStatusMessage( "--- plResManager Object Leak Report (END) ---" ); } else { plPageOutIterator iter(this, 0); } } //// FindKey ///////////////////////////////////////////////////////////////// inline plKeyImp* IFindKeyLocalized(const plUoid& uoid, plRegistryPageNode* page) { const plString& objectName = uoid.GetObjectName(); // If we're running localized, try to find a localized version first if ((!objectName.IsNull()) && plLocalization::IsLocalized()) { plFileName localName = plLocalization::GetLocalized(objectName.c_str()); if (localName.IsValid()) { plKeyImp* localKey = page->FindKey(uoid.GetClassType(), localName.AsString()); 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 = new plKeyImp(uoid, uint32_t(-1), uint32_t(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 plString& age, const plString& page) const { static plLocation invalidLoc; plRegistryPageNode* pageNode = FindPage(age, page); if (pageNode) return pageNode->GetPageInfo().GetLocation(); return invalidLoc; } void plResManager::GetLocationStrings(const plLocation& loc, plString* ageBuffer, plString* pageBuffer) const { plRegistryPageNode* page = FindPage(loc); const plPageInfo& info = page->GetPageInfo(); // Those buffers better be big enough... if (ageBuffer) *ageBuffer = info.GetAge(); if (pageBuffer) *pageBuffer = info.GetPage(); } bool 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); } bool 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; } bool 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. bool 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().c_str()); 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 plString& name, hsKeyedObject* object, const plLocation& loc, const plLoadMask& m ) { hsAssert(!name.IsEmpty(), "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 = new plKeyImp; newKey->SetUoid(newUoid); AddKey(newKey); plKey keyPtr = plKey::Make(newKey); object->SetKey(keyPtr); return keyPtr; } plKey plResManager::ReRegister(const plString& 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 = 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) { bool 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_t playerID, uint32_t 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 = 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. // bool plResManager::Unload(const plKey& objKey) { if (objKey) { ((plKeyImp*)objKey)->UnRegister(); fDispatch->UnRegisterAll(objKey); return true; } return false; } plCreatable* plResManager::IReadCreatable(hsStream* s) const { uint16_t hClass = s->ReadLE16(); plCreatable* pCre = plFactory::Create(hClass); if (!pCre) hsAssert( hClass == 0x8000, "Invalid creatable index" ); return pCre; } plCreatable* plResManager::ReadCreatable(hsStream* s) { plCreatable *pCre = IReadCreatable(s); if (pCre) pCre->Read(s, this); return pCre; } plCreatable* plResManager::ReadCreatableVersion(hsStream* s) { plCreatable *pCre = IReadCreatable(s); if (pCre) pCre->ReadVersion(s, this); return pCre; } inline void IWriteCreatable(hsStream* s, plCreatable* pCre) { int16_t hClass = pCre ? pCre->ClassIndex() : 0x8000; hsAssert(pCre == nil || plFactory::IsValidClassIndex(hClass), "Invalid class index on write"); s->WriteLE16(hClass); } void plResManager::WriteCreatable(hsStream* s, plCreatable* pCre) { IWriteCreatable(s, pCre); if (pCre) pCre->Write(s, this); } void plResManager::WriteCreatableVersion(hsStream* s, plCreatable* pCre) { IWriteCreatable(s, pCre); if (pCre) pCre->WriteVersion(s, this); } void plResManager::SetProgressBarProc(plProgressProc proc) { fProgressProc = proc; } ////////////////////////////////////////////////////////////////////////////// //// Paging Functions //////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// //// plResAgeHolder ////////////////////////////////////////////////////////// // Helper object that stores all the keys for an age, to optimize the load // process. class plResAgeHolder : public hsRefCnt { public: hsTArray fKeys; plString fAge; plResAgeHolder() {} plResAgeHolder( const plString& age ) : fAge( age ) {} ~plResAgeHolder() { fKeys.Reset(); } }; //// plResHolderIterator ///////////////////////////////////////////////////// class plResHolderIterator : public plRegistryPageIterator { protected: hsTArray& fKeys; plString fAgeName; plResManager* fResMgr; public: plResHolderIterator(const plString& age, hsTArray& keys, plResManager* resMgr) : fAgeName(age), fKeys(keys), fResMgr(resMgr) {} virtual bool EatPage(plRegistryPageNode* page) { if (page->GetPageInfo().GetAge().CompareI(fAgeName) == 0) { fResMgr->LoadPageKeys(page); plKeyCollector collector(fKeys); page->IterateKeys(&collector); } return true; } }; //// LoadAndHoldAgeKeys ////////////////////////////////////////////////////// void plResManager::LoadAgeKeys(const plString& age) { hsAssert(!age.IsEmpty(), "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.c_str())); hsStatusMessageF("*** Reffing age keys for age %s ***\n", age.c_str()); plResAgeHolder* holder = it->second; holder->Ref(); } else { kResMgrLog(1, ILog(1, "Loading age keys for age %s", age.c_str())); hsStatusMessageF("*** Loading age keys for age %s ***\n", age.c_str()); plResAgeHolder* holder = 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 plString& 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.c_str())); fHeldAgeKeys.erase(it); } else { kResMgrLog(1, ILog(1, "Unreffing age keys for age %s", age.c_str())); } 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_t fClassToFind; plKey &fFoundKey; public: plOurRefferAndFinder( hsTArray &refArray, uint16_t classToFind, plKey &foundKey ) : fRefArray( refArray ), fClassToFind( classToFind ), fFoundKey( foundKey ) { } virtual bool 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_t objClassToRef, plRefMsg* refMsg) { uint64_t 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().c_str(), pageNode->GetPageInfo().GetPage().c_str())); // Step 0.5: Verify the page, just to make sure we really should be loading it PageCond cond = pageNode->GetPageCondition(); if (cond != kPageOk) { plString 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())); plString msg = plString::Format("Data Problem: Age:%s Page:%s Error:%s", pageNode->GetPageInfo().GetAge().c_str(), pageNode->GetPageInfo().GetPage().c_str(), condStr.c_str()); hsMessageBox(msg.c_str(), "Error", hsMessageBoxNormal, hsMessageBoxIconError); hsRefCnt_SafeUnRef(refMsg); return; } // Step 0.9: Open the stream on this page, so it remains open for the entire loading process pageNode->OpenStream(); // 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()" ); pageNode->CloseStream(); 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); // Step 5.9: Close the page stream pageNode->CloseStream(); // 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().c_str(), pageNode->GetPageInfo().GetPage().c_str(), hsTimer::FullTicksToMs(readRoomTime)); } } class plPageInAgeIter : public plRegistryPageIterator { private: plKey fDestKey; plString fAgeName; std::vector fLocations; public: plPageInAgeIter(plKey destKey, const plString &ageName) : fDestKey(destKey), fAgeName(ageName) {} ~plPageInAgeIter() { plClientMsg* pMsg1 = new plClientMsg(plClientMsg::kLoadRoomHold); for (int i = 0; i < fLocations.size(); i++) { pMsg1->AddRoomLoc(fLocations[i]); } pMsg1->Send(fDestKey); } virtual bool EatPage(plRegistryPageNode* page) { if (page->GetPageInfo().GetAge().CompareI(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 plString &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 = 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 bool plResManager::VerifyPages() { hsTArray invalidPages, newerPages; PageMap::iterator it = fAllPages.begin(); // Step 1: verify major/minor version changes if (plResMgrSettings::Get().GetFilterNewerPageVersions() || plResMgrSettings::Get().GetFilterOlderPageVersions()) { while (it != fAllPages.end()) { plLocation loc = it->first; plRegistryPageNode* page = it->second; ++it; if (page->GetPageCondition() == kPageTooNew && plResMgrSettings::Get().GetFilterNewerPageVersions()) { newerPages.Append(page); fAllPages.erase(loc); } else if ( (page->GetPageCondition() == kPageCorrupt || page->GetPageCondition() == kPageOutOfDate) && plResMgrSettings::Get().GetFilterOlderPageVersions()) { invalidPages.Append(page); fAllPages.erase(loc); } } } // 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 // This isn't possible with a std::map /*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->second; ++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; } plString pageFile = pages[i]->GetPagePath().GetFileName(); if (strlen(buf) + pageFile.GetSize() > bufSize - 5) { strcat(buf, "...\n"); break; } strcat(buf, pageFile.c_str()); strcat(buf, "\n"); } } bool plResManager::IDeleteBadPages(hsTArray& invalidPages, bool 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. bool 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 bool 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 plString& age, const plString& page) { plRegistryPageNode* pageNode = new plRegistryPageNode(location, age, page, fDataPath); fAllPages[location] = pageNode; return pageNode; } //// AddPage ///////////////////////////////////////////////////////////////// void plResManager::AddPage(plRegistryPageNode* page) { plLocation loc = page->GetPageInfo().GetLocation(); fAllPages[loc] = 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_t 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("\tLeaks in page %s>%s[%08x]:\n", lastPage->GetPageInfo().GetAge().c_str(), lastPage->GetPageInfo().GetPage().c_str(), lastPage->GetPageInfo().GetLocation().GetSequenceNumber()); alreadyDone = true; } int refsLeft = ((plKeyImpRef*)key)->GetRefCnt() - 1; if (refsLeft == 0) return; plStringStream ss; ss << "\t\t" << plFactory::GetNameOfClass(key->GetUoid().GetClassType()) << ": "; ss << key->GetUoid().StringIze() << " "; if (key->ObjectIsLoaded()) ss << "- " << key->GetDataLen() << " bytes - " << refsLeft << " refs left"; else ss << "(key only, " << refsLeft << " refs left)"; hsStatusMessage(ss.GetString().c_str()); } //// 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_t classIndexHint) { if (!pageNode->IsLoaded()) return; class plUnloadObjectsIterator : public plRegistryKeyIterator { public: virtual bool EatKey(const plKey& key) { sIReportLeak((plKeyImp*)key, nil); return true; } }; sIReportLeak(nil, pageNode); plUnloadObjectsIterator iterator; if (classIndexHint != static_cast(-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; PageMap::const_iterator it = fAllPages.find(location); if (it != fAllPages.end()) { fLastFoundPage = it->second; return it->second; } return nil; } //// FindPage //////////////////////////////////////////////////////////////// plRegistryPageNode* plResManager::FindPage(const plString& age, const plString& page) const { PageMap::const_iterator it; for (it = fAllPages.begin(); it != fAllPages.end(); ++it) { const plPageInfo& info = (it->second)->GetPageInfo(); if (info.GetAge().CompareI(age) == 0 && info.GetPage().CompareI(page) == 0) return it->second; } 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 bool EatPage(plRegistryPageNode* keyNode) { return keyNode->IterateKeys(fIter); } }; //// IterateKeys ///////////////////////////////////////////////////////////// bool plResManager::IterateKeys(plRegistryKeyIterator* iterator) { plKeyIterEater myEater(iterator); return IteratePages(&myEater, nil); } bool 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 bool plResManager::IteratePages(plRegistryPageIterator* iterator, const plString& 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.IsNull() || page->GetPageInfo().GetAge().CompareI(ageToRestrictTo) == 0) { if (!iterator->EatPage(page)) { IUnlockPages(); return false; } } } IUnlockPages(); return true; } //// IterateAllPages ///////////////////////////////////////////////////////// // Iterate through ALL pages bool plResManager::IterateAllPages(plRegistryPageIterator* iterator) { ILockPages(); PageMap::const_iterator it; for (it = fAllPages.begin(); it != fAllPages.end(); ++it) { if (it->first == plLocation::kGlobalFixedLoc) continue; plRegistryPageNode* page = it->second; 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(); PageMap::const_iterator it; for (it = fAllPages.begin(); it != fAllPages.end(); ++it) { plRegistryPageNode* page = it->second; 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; }