/*==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 "HeadSpin.h" #include "plPluginResManager.h" #include "hsTypes.h" #include "hsTemplates.h" #include "../plResMgr/plRegistryNode.h" #include "../plResMgr/plRegistryHelpers.h" #include "../plResMgr/plVersion.h" #include "../plResMgr/plResMgrSettings.h" #include "../plScene/plSceneNode.h" #include "../pnKeyedObject/plKey.h" #include "../pnKeyedObject/plKeyImp.h" #include "../plAgeDescription/plAgeDescription.h" #include "plgDispatch.h" // For our common object libs #include "plCommonObjLib.h" #include "../MaxComponent/plMiscComponents.h" plKey plPluginResManager::NameToLoc(const char* age, const char* page, Int32 sequenceNumber, hsBool itinerant) { // Hack for now--always prefer paging out sceneNodes first fPageOutHint = plSceneNode::Index(); // Get or create our page plRegistryPageNode* pageNode = INameToPage(age, page, sequenceNumber, itinerant); hsAssert(pageNode != nil, "No page returned from INameToPage(), shouldn't be possible"); // Go find the sceneNode now, since we know the page exists (go through our normal channels, though) char keyName[256]; sprintf(keyName, "%s_%s", age, page); plUoid nodeUoid(pageNode->GetPageInfo().GetLocation(), plSceneNode::Index(), keyName); plKey snKey = FindKey(nodeUoid); if (snKey == nil) { // Not found, create a new one plSceneNode *newSceneNode = TRACKED_NEW plSceneNode; snKey = NewKey(keyName, newSceneNode, pageNode->GetPageInfo().GetLocation()); // Call init after it gets a key newSceneNode->Init(); // Add to our list of exported nodes fExportedNodes.Append(newSceneNode); newSceneNode->GetKey()->RefObject(); } else { hsAssert(snKey->ObjectIsLoaded() != nil, "Somehow we still have the key for a sceneNode that hasn't been loaded."); // Force load, or attempt to at least plSceneNode* node = plSceneNode::ConvertNoRef(snKey->VerifyLoaded()); // Add to our list if necessary if (fExportedNodes.Find(node) == fExportedNodes.kMissingIndex) { fExportedNodes.Append(node); node->GetKey()->RefObject(); } } // Return the key return snKey; } //// INameToPage ///////////////////////////////////////////////////////////// // Given the age/chapter/page combo we all know and love, plus an optional // seqNumber, returns the page for that combo (either by preloading it or // by creating it). plRegistryPageNode* plPluginResManager::INameToPage(const char* age, const char* page, Int32 sequenceNumber, hsBool itinerant) { // Find the location first, to see if it already exists plRegistryPageNode* pageNode = FindPage(age, page); if (pageNode == nil) { // This page does not yet exist, so create a new page if (sequenceNumber != UInt32(-1)) { const plLocation& newLoc = ICreateLocation(age, page, sequenceNumber, itinerant); pageNode = CreatePage(newLoc, age, page); } else { const plLocation& newLoc = ICreateLocation(age, page, itinerant); pageNode = CreatePage(newLoc, age, page); } // Still preload textures on this guy. This should be a no-op for this page since it's new, but won't be // for the shared textures page IPreLoadTextures(pageNode, sequenceNumber); } else if (!pageNode->IsNewPage()) { // Node's already in our registry (i.e. already stored somewhere), so make sure it loads so // we can update from that LoadPageKeys(pageNode); // Now clear out all the unwanted keys. IPreLoadTextures(pageNode, sequenceNumber); } return pageNode; } //// plCommonKeyDistributor ////////////////////////////////////////////////// // Iterator that takes the keys in a common page and distributes them to // whichever commonObjectLibs are interested in them. class plCommonKeyDistributor : public plRegistryKeyIterator { plPluginResManager* fMgr; public: plCommonKeyDistributor(plPluginResManager* mgr) : fMgr(mgr) {} virtual hsBool EatKey(const plKey& key) { UInt32 count = plCommonObjLib::GetNumLibs(); for (UInt32 i = 0; i < count; i++) { plCommonObjLib* lib = plCommonObjLib::GetLib(i); if (lib->IsInteresting(key)) { // Lib wants this guy, so load the object and add it // Note: we want to switch to passive mode here, so any keys read in // will NOT force a load on whichever page those keys belong to. Otherwise, // we'd have to load that entire page and ref all the objects all over // again and I just really don't want to do that... plResMgrSettings::Get().SetPassiveKeyRead(true); hsKeyedObject* object = key->VerifyLoaded(); plResMgrSettings::Get().SetPassiveKeyRead(false); lib->AddObject(object); break; } } return true; } }; //// IPreLoadTextures //////////////////////////////////////////////////////// // Given a page of loaded keys, culls through them and make sure all our // registered commonObjectLibs get them as if we had just converted them. // Note: Broken out in a separate function 5.31.2002 mcn to facilitate // pre-loading textures exported to our special textures page. void plPluginResManager::IPreLoadTextures(plRegistryPageNode* pageNode, Int32 origSeqNumber) { // For common pages, we want to kinda-maybe-load all the objects so they don't get wiped when we // re-export them. However, we don't have a good way of telling whether a page is a common page, // which is where this hack comes in bool common = false; for (int i = 0; i < plAgeDescription::kNumCommonPages; i++) { if (hsStrCaseEQ(plAgeDescription::GetCommonPage(i), pageNode->GetPageInfo().GetPage())) { common = true; break; } } if (common) { // Iterate through all the keys in our page, scattering them to various objectLibs if they're // interested. If nobody likes them, they get unreffed and disappear. plCommonKeyDistributor distributor(this); pageNode->IterateKeys(&distributor); } // Clear out all the unwanted keys in the page we just loaded (by implication; they won't clear if we already // stored the keys via our objectLibs above) { class plEmptyIterator : public plRegistryKeyIterator { public: virtual hsBool EatKey(const plKey& key) { return true; } } empty; pageNode->IterateKeys(&empty); } // We've loaded anything we needed from this page now, so set it as new so // that we won't try loading again pageNode->SetNewPage(); // Get our texture page now, if we're not a texture page if (!common) { // Make sure it's not a global page we're handling either if (!pageNode->GetPageInfo().GetLocation().IsReserved()) { Int32 texSeqNum = -1; if (origSeqNumber != -1) texSeqNum = plPageInfoUtils::GetCommonSeqNumFromNormal(origSeqNumber, plAgeDescription::kTextures); // Note: INameToPage will turn around and call us again, so no need to do the call twice plRegistryPageNode* texturePage = INameToPage(pageNode->GetPageInfo().GetAge(), plAgeDescription::GetCommonPage(plAgeDescription::kTextures), texSeqNum); hsAssert(texturePage != nil, "Unable to get or create the shared textures page? Shouldn't be possible."); // Do the other one Int32 commonSeqNum = -1; if (origSeqNumber != -1) commonSeqNum = plPageInfoUtils::GetCommonSeqNumFromNormal(origSeqNumber, plAgeDescription::kGlobal); // Note: INameToPage will turn around and call us again, so no need to do the call twice plRegistryPageNode* commonPage = INameToPage(pageNode->GetPageInfo().GetAge(), plAgeDescription::GetCommonPage(plAgeDescription::kGlobal), commonSeqNum); hsAssert(commonPage != nil, "Unable to get or create the shared built-in page? Shouldn't be possible."); } } } //// GetCommonPage /////////////////////////////////////////////////////////// // Given a plLocation, finds the texture page that's in the same age and // returns its location. const plLocation& plPluginResManager::GetCommonPage(const plLocation &sisterPage, int whichPage) { if (sisterPage.IsReserved()) return sisterPage; // Reserved pages have no common pages plRegistryPageNode* page = FindPage(sisterPage); if (page == nil) { hsAssert(false, "Trying to find the sister common page to a page that doesn't exist!"); return sisterPage; } // Find the common page in the same age as this one plRegistryPageNode* commonPage = FindPage(page->GetPageInfo().GetAge(), plAgeDescription::GetCommonPage(whichPage)); if (commonPage == nil) { hsAssert(false, "Unable to find sister common page to this page"); return sisterPage; } return commonPage->GetPageInfo().GetLocation(); } //// IShutdown /////////////////////////////////////////////////////////////// void plPluginResManager::IShutdown() { if (!fInited) return; // Loop through all the commonObjLibs and clear their object lists, just // as a safety measure (the creators of the various libs should really // be doing it) for (UInt32 i = 0; i < plCommonObjLib::GetNumLibs(); i++) plCommonObjLib::GetLib(i)->ClearObjectList(); plResManager::IShutdown(); } // Little finder class to, erm, find unused location sequence numbers class plSeqNumberFinder : public plRegistryPageIterator { protected: Int32& fSeqNum; hsBool fWillBeReserved; public: plSeqNumberFinder(Int32& seqNum, hsBool willBeReserved) : fSeqNum(seqNum), fWillBeReserved(willBeReserved) {} virtual hsBool EatPage(plRegistryPageNode* page) { if (fSeqNum <= page->GetPageInfo().GetLocation().GetSequenceNumber() && fWillBeReserved == page->GetPageInfo().GetLocation().IsReserved()) fSeqNum = page->GetPageInfo().GetLocation().GetSequenceNumber() + 1; return true; } }; plLocation plPluginResManager::ICreateLocation(const char* age, const char* page, hsBool itinerant) { Int32 seqNum = VerifySeqNumber(0, age, page); return ICreateLocation(age, page, seqNum, itinerant); } plLocation plPluginResManager::ICreateLocation(const char* age, const char* page, Int32 seqNum, hsBool itinerant) { hsBool willBeReserved = hsStrCaseEQ(age, "global"); Int32 oldNum = seqNum; seqNum = VerifySeqNumber(seqNum, age, page); if (seqNum != oldNum) { hsAssert(false, "Conflicting page sequence number. Somebody called NameToLoc without verifying their seq# first!"); } if (seqNum < 0) { willBeReserved = true; seqNum = -seqNum; } plLocation newLoc; if (willBeReserved) newLoc = plLocation::MakeReserved(seqNum); else newLoc = plLocation::MakeNormal(seqNum); // Flag common pages for (int i = 0; i < plAgeDescription::kNumCommonPages; i++) { if (hsStrEQ(plAgeDescription::GetCommonPage(i), page)) { newLoc.SetFlags(plLocation::kBuiltIn); break; } } // If we have an age description file for the age we're creating a location // for, grab some extra flags from it plAgeDescription* ageDesc = plPageInfoUtils::GetAgeDesc(age); plAgePage* agePage = ageDesc ? ageDesc->FindPage(page) : nil; if (agePage) { if (agePage->GetFlags() & plAgePage::kIsLocalOnly) newLoc.SetFlags(plLocation::kLocalOnly); if (agePage->GetFlags() & plAgePage::kIsVolatile) newLoc.SetFlags(plLocation::kVolatile); } if (itinerant) newLoc.SetFlags(plLocation::kItinerant); delete ageDesc; return newLoc; } class plWritePageIterator : public plRegistryPageIterator { public: plWritePageIterator() {} virtual hsBool EatPage(plRegistryPageNode *page) { if (page->GetPageInfo().GetLocation() != plLocation::kGlobalFixedLoc) page->Write(); return true; } }; void plPluginResManager::WriteAllPages() { plWritePageIterator iter; IteratePages(&iter); } //// EndExport /////////////////////////////////////////////////////////////// // Called after export is done and pages are all written out. Cleans up // by paging out all the sceneNodes we just created. void plPluginResManager::EndExport() { for (int i = 0; i < fExportedNodes.GetCount(); i++) { if (fExportedNodes[i] != nil) fExportedNodes[i]->GetKey()->UnRefObject(); } fExportedNodes.Reset(); for( i = 0; i < fLooseEnds.GetCount(); i++ ) { if( fLooseEnds[i] ) fLooseEnds[i]->UnRefObject(); } fLooseEnds.Reset(); // Flush the message queue, so all the messages for paging out stuff actually get delivered plgDispatch::Dispatch()->MsgQueueProcess(); } void plPluginResManager::AddLooseEnd(plKey key) { if( key ) { key->RefObject(); fLooseEnds.Append(key); } } // Verifies that the given sequence number belongs to the given string combo and ONLY that combo. Returns a new, unique sequenceNumber if not Int32 plPluginResManager::VerifySeqNumber(Int32 sequenceNumber, const char* age, const char* page) { hsBool negated = false, willBeReserved = hsStrCaseEQ(age, "global"); if (sequenceNumber < 0) { sequenceNumber = -sequenceNumber; willBeReserved = negated = true; } fLastVerifyError = kNoVerifyError; fLastVerifyPage = nil; plLocation toCompareTo; if (willBeReserved) plLocation::MakeReserved(sequenceNumber); else plLocation::MakeNormal(sequenceNumber); // Does the page already exist? plRegistryPageNode* pageNode = FindPage(age, page); if (pageNode != nil) { if (pageNode->GetPageInfo().GetLocation() == toCompareTo) // Right page, right sequence #. Assume we're smart enough to already have it right return negated ? -sequenceNumber : sequenceNumber; // Right page, wrong seq #...tag our last error field so we can know this later on fLastVerifyError = kErrRightPageWrongSeq; } // Page doesn't yet exist, check to make sure the seq # isn't used yet if (sequenceNumber > 0) { pageNode = FindPage(toCompareTo); if (pageNode == nil) // Safe to use return negated ? -sequenceNumber : sequenceNumber; else { // If there is no error yet, set the error to "already taken" if (fLastVerifyError == kNoVerifyError) fLastVerifyError = kErrSeqAlreadyTaken; fLastVerifyPage = &pageNode->GetPageInfo(); } } // Gotta find a good sequence number to use, so keep searching until we find one // (but start at a good high number so we won't hopefully ever run into anybody else) const int kTemporarySequenceStartPrefix = 100; // can't be larger then 0xFE, so well start out at 100 for kicks sequenceNumber = plPageInfoUtils::CombineSeqNum(kTemporarySequenceStartPrefix, 0); Int32 upperLimit = 0xFEFFFF; // largest legal sequence number is a prefix of FE and a suffix of FFFF for(; sequenceNumber < upperLimit; sequenceNumber++) { if (willBeReserved) toCompareTo = plLocation::MakeReserved(sequenceNumber); else toCompareTo = plLocation::MakeNormal(sequenceNumber); pageNode = FindPage(toCompareTo); if (pageNode == nil) return negated ? -sequenceNumber : sequenceNumber; } hsAssert(false, "Unable to find a valid sequence number to use"); fLastVerifyError = kErrCantFindValid; return 0; } //// NukeKeyAndObject //////////////////////////////////////////////////////// // Given a key, will ref and unref the associated object so it goes away, // then nukes the key and sets it to nil. The key's UOID should be safe to // reuse at that point, unless someone else still has a ref, in which case // this function returns false (returns true if successful). bool plPluginResManager::NukeKeyAndObject(plKey& objectKey) { class plPublicRefKey : public plKeyImp { public: UInt16 GetRefCount() const { return fRefCount; } }; plKeyImp* keyData = (plKeyImp*)objectKey; // Check the ref count on the object. Nobody should have a ref to it // except the key hsKeyedObject* object = objectKey->ObjectIsLoaded(); if (object != nil) { if (keyData->GetActiveRefs()) // Somebody still has a ref to this object, so we can't nuke it return false; } // Nobody has a ref to the object, so we're clear to nuke keyData->SetObjectPtr(nil); // Check the key. The refcount should be 1 at this point, for the copy // we're holding in this function. Nobody else should be holding the key // now that the object is gone. if (((plPublicRefKey*)keyData)->GetRefCount() > 1) return false; // Nuke out the key as well objectKey = nil; // All done! return true; }