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.
1843 lines
55 KiB
1843 lines
55 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/>. |
|
|
|
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 "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<plKey> 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<plKey> fKeys; |
|
std::string fAge; |
|
|
|
plResAgeHolder() {} |
|
plResAgeHolder( const char* age ) : fAge( age ) {} |
|
~plResAgeHolder() { fKeys.Reset(); } |
|
}; |
|
|
|
//// plResHolderIterator ///////////////////////////////////////////////////// |
|
|
|
class plResHolderIterator : public plRegistryPageIterator |
|
{ |
|
protected: |
|
hsTArray<plKey>& fKeys; |
|
const char* fAgeName; |
|
plResManager* fResMgr; |
|
|
|
public: |
|
plResHolderIterator(const char* age, hsTArray<plKey>& 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<plKey> &fRefArray; |
|
UInt16 fClassToFind; |
|
plKey &fFoundKey; |
|
|
|
public: |
|
|
|
plOurRefferAndFinder( hsTArray<plKey> &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<plKey> 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<plLocation> 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<plRegistryPageNode*> 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<plRegistryPageNode*>& 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<plRegistryPageNode*>& 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<plRegistryPageNode*> &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<plKey> 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; |
|
}
|
|
|