You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

526 lines
18 KiB

/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "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;
}