/*==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 "plAgeLoader.h" #include "hsStream.h" #include "plgDispatch.h" #include "hsResMgr.h" //#include "hsTimer.h" #include "plResPatcher.h" #include "plBackgroundDownloader.h" #include "process.h" // for getpid() #include "pnProduct/pnProduct.h" #include "pnKeyedObject/plKey.h" #include "pnKeyedObject/plFixedKey.h" #include "pnSceneObject/plSceneObject.h" #include "pnMessage/plClientMsg.h" #include "pnNetCommon/plNetApp.h" #include "plScene/plRelevanceMgr.h" #include "plResMgr/plKeyFinder.h" #include "plAgeDescription/plAgeDescription.h" #include "plSDL/plSDL.h" #include "plNetClient/plNetClientMgr.h" #include "plResMgr/plRegistryHelpers.h" #include "plResMgr/plRegistryNode.h" #include "plResMgr/plResManager.h" #include "plFile/plEncryptedStream.h" /// TEMP HACK TO LOAD CONSOLE INIT FILES ON AGE LOAD #include "plMessage/plConsoleMsg.h" #include "plMessage/plLoadAvatarMsg.h" #include "plMessage/plAgeLoadedMsg.h" extern hsBool gDataServerLocal; extern hsBool gUseBackgroundDownloader; // static plAgeLoader* plAgeLoader::fInstance=nil; // // CONSTRUCT // plAgeLoader::plAgeLoader() : fInitialAgeState(nil), fFlags(0) { } // // DESTRUCT // plAgeLoader::~plAgeLoader() { delete fInitialAgeState; fInitialAgeState=nil; if ( PendingAgeFniFiles().size() ) plNetClientApp::StaticErrorMsg( "~plAgeLoader(): %d pending age fni files", PendingAgeFniFiles().size() ); if ( PendingPageOuts().size() ) plNetClientApp::StaticErrorMsg( "~plAgeLoader(): %d pending page outs", PendingPageOuts().size() ); ClearPageExcludeList(); // Clear our debugging exclude list, just to be tidy if (fInstance==this) SetInstance(nil); } void plAgeLoader::Shutdown() { } void plAgeLoader::Init() { RegisterAs( kAgeLoader_KEY ); plgDispatch::Dispatch()->RegisterForExactType(plInitialAgeStateLoadedMsg::Index(), GetKey()); plgDispatch::Dispatch()->RegisterForExactType(plClientMsg::Index(), GetKey()); if (!gDataServerLocal && gUseBackgroundDownloader) plBackgroundDownloader::StartThread(); } // // STATIC // plAgeLoader* plAgeLoader::GetInstance() { return fInstance; } // // STATIC // void plAgeLoader::SetInstance(plAgeLoader* inst) { fInstance=inst; } // // Plasma Msg Handler // hsBool plAgeLoader::MsgReceive(plMessage* msg) { plInitialAgeStateLoadedMsg *stateMsg = plInitialAgeStateLoadedMsg::ConvertNoRef( msg ); if( stateMsg != nil ) { // done receiving the initial state of the age from the server return true; } plClientMsg* clientMsg = plClientMsg::ConvertNoRef(msg); if (clientMsg && clientMsg->GetClientMsgFlag()==plClientMsg::kInitComplete) { ExecPendingAgeFniFiles(); // exec age-specific fni files return true; } return plReceiver::MsgReceive(msg); } // // read in the age desc file and page in/out the rooms belonging to the specified age. // return false on error // //============================================================================ bool plAgeLoader::LoadAge(const char ageName[]) { return ILoadAge(ageName); } //============================================================================ bool plAgeLoader::UpdateAge(const char ageName[]) { bool result = true; if (!gDataServerLocal) { plResPatcher myPatcher(ageName); result = myPatcher.Update(); } return result; } //============================================================================ void plAgeLoader::NotifyAgeLoaded( bool loaded ) { if ( loaded ) fFlags &= ~kLoadingAge; else fFlags &= ~kUnLoadingAge; plAgeLoadedMsg * msg = TRACKED_NEW plAgeLoadedMsg; msg->fLoaded = loaded; msg->Send(); } //// ILoadAge //////////////////////////////////////////////////////////////// // Does the loading-specific stuff for queueing an age to load bool plAgeLoader::ILoadAge(const char ageName[]) { plNetClientApp* nc = plNetClientApp::GetInstance(); ASSERT(!nc->GetFlagsBit(plNetClientApp::kPlayingGame)); StrCopy(fAgeName, ageName, arrsize(fAgeName)); nc->DebugMsg( "Net: Loading age %s", fAgeName); if ((fFlags & kLoadMask) != 0) ErrorFatal(__LINE__, __FILE__, "Fatal Error:\nAlready loading or unloading an age.\n%S will now exit.", ProductShortName()); fFlags |= kLoadingAge; plAgeBeginLoadingMsg* ageBeginLoading = TRACKED_NEW plAgeBeginLoadingMsg(); ageBeginLoading->Send(); /////////////////////////////////////////////////////// /// Step 1: Update all of the dat files for this age /* UpdateAge(fAgeName); */ /// Step 2: Load the keys for this age, so we can find sceneNodes for them // exec age .fni file when data is done loading char consoleIniName[ 256 ]; sprintf( consoleIniName, "dat\\%s.fni", fAgeName); fPendingAgeFniFiles.push_back( consoleIniName ); char csvName[256]; sprintf(csvName, "dat\\%s.csv", fAgeName); fPendingAgeCsvFiles.push_back(csvName); plSynchEnabler p( false ); // turn off dirty tracking while in this function hsStream* stream=GetAgeDescFileStream(fAgeName); if (!stream) { nc->ErrorMsg("Failed loading age. Age desc file %s has nil stream", fAgeName); fFlags &= ~kLoadingAge; return false; } plAgeDescription ad; ad.Read(stream); ad.SetAgeName(fAgeName); stream->Close(); delete stream; ad.SeekFirstPage(); plAgePage *page; plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY ); // Copy, exclude pages we want excluded, and collect our scene nodes fCurAgeDescription.CopyFrom(ad); while( ( page = ad.GetNextPage() ) != nil ) { if( IsPageExcluded( page, fAgeName) ) continue; plKey roomKey = plKeyFinder::Instance().FindSceneNodeKey( fAgeName, page->GetName() ); if( roomKey != nil ) AddPendingPageInRoomKey( roomKey ); } ad.SeekFirstPage(); // Tell the client to load-and-hold all the keys for this age, to make the loading process work better plClientMsg *loadAgeKeysMsg = TRACKED_NEW plClientMsg( plClientMsg::kLoadAgeKeys ); loadAgeKeysMsg->SetAgeName( fAgeName); loadAgeKeysMsg->Send( clientKey ); // // Load the Age's SDL Hook object (and it's python modifier) // plUoid oid=nc->GetAgeSDLObjectUoid(fAgeName); plKey ageSDLObjectKey = hsgResMgr::ResMgr()->FindKey(oid); if (ageSDLObjectKey) hsgResMgr::ResMgr()->AddViaNotify(ageSDLObjectKey, TRACKED_NEW plGenRefMsg(nc->GetKey(), plRefMsg::kOnCreate, -1, plNetClientMgr::kAgeSDLHook), plRefFlags::kActiveRef); int nPages = 0; plClientMsg* pMsg1 = TRACKED_NEW plClientMsg(plClientMsg::kLoadRoom); pMsg1->SetAgeName(fAgeName); // Loop and ref! while( ( page = ad.GetNextPage() ) != nil ) { if( IsPageExcluded( page, fAgeName) ) { nc->DebugMsg( "\tExcluding page %s\n", page->GetName() ); continue; } nPages++; pMsg1->AddRoomLoc(ad.CalcPageLocation(page->GetName())); nc->DebugMsg("\tPaging in room %s\n", page->GetName()); } pMsg1->Send(clientKey); // Send the client a message to let go of the extra keys it was holding on to plClientMsg *dumpAgeKeys = TRACKED_NEW plClientMsg( plClientMsg::kReleaseAgeKeys ); dumpAgeKeys->SetAgeName( fAgeName); dumpAgeKeys->Send( clientKey ); if ( nPages==0 ) { // age is done loading because it has no pages? fFlags &= ~kLoadingAge; } return true; } //// plUnloadAgeCollector //////////////////////////////////////////////////// // Registry page iterator to collect all the loaded pages of a given age // Note: we have to do an IterateAllPages(), since we want to also catch // pages that are partially loaded, which are skipped in the vanilla // IteratePages() call. class plUnloadAgeCollector : public plRegistryPageIterator { public: hsTArray fPages; const char *fAge; plUnloadAgeCollector( const char *a ) : fAge( a ) {} virtual hsBool EatPage( plRegistryPageNode *page ) { if( fAge && stricmp( page->GetPageInfo().GetAge(), fAge ) == 0 ) { fPages.Append( page ); } return true; } }; //// IUnloadAge ////////////////////////////////////////////////////////////// // Does the UNloading-specific stuff for queueing an age to unload. // Far simpler that ILoadAge :) bool plAgeLoader::IUnloadAge() { plNetClientApp* nc = plNetClientApp::GetInstance(); nc->DebugMsg( "Net: Unloading age %s", fAgeName); hsAssert( (fFlags & kLoadMask)==0, "already loading or unloading an age?"); fFlags |= kUnLoadingAge; plAgeBeginLoadingMsg* msg = TRACKED_NEW plAgeBeginLoadingMsg(); msg->fLoading = false; msg->Send(); // Note: instead of going from the .age file, we just want a list of what // is REALLY paged in for this age. So ask the resMgr! plUnloadAgeCollector collector( fAgeName); // WARNING: unsafe cast here, but it's ok, until somebody is mean and makes a non-plResManager resMgr ( (plResManager *)hsgResMgr::ResMgr() )->IterateAllPages( &collector ); // Dat was easy... plKey clientKey = hsgResMgr::ResMgr()->FindKey( kClient_KEY ); // Build up a list of all the rooms we're going to page out plKeyVec newPageOuts; int i; for( i = 0; i < collector.fPages.GetCount(); i++ ) { plRegistryPageNode *page = collector.fPages[ i ]; plKey roomKey = plKeyFinder::Instance().FindSceneNodeKey( page->GetPageInfo().GetLocation() ); if( roomKey != nil && roomKey->ObjectIsLoaded() ) { nc->DebugMsg( "\tPaging out room %s\n", page->GetPageInfo().GetPage() ); newPageOuts.push_back(roomKey); } } // Put them in our pending page outs for( i = 0; i < newPageOuts.size(); i++ ) fPendingPageOuts.push_back(newPageOuts[i]); // ...then send the unload messages. That way we ensure the list is complete // before any messages get processed for( i = 0; i < newPageOuts.size(); i++ ) { plClientMsg *pMsg1 = TRACKED_NEW plClientMsg( plClientMsg::kUnloadRoom ); pMsg1->AddRoomLoc(newPageOuts[i]->GetUoid().GetLocation()); pMsg1->Send( clientKey ); } if ( newPageOuts.size()==0 ) { // age is done unloading because it has no pages? NotifyAgeLoaded( false ); } return true; } void plAgeLoader::ExecPendingAgeFniFiles() { int i; for (i=0;iParseCsvInput(stream); stream->Close(); delete stream; } } fPendingAgeCsvFiles.clear(); } // // return alloced stream or nil // static // hsStream* plAgeLoader::GetAgeDescFileStream(const char* ageName) { if (!ageName) return nil; char ageDescFileName[256]; sprintf(ageDescFileName, "dat\\%s.age", ageName); hsStream* stream = plEncryptedStream::OpenEncryptedFile(ageDescFileName); if (!stream) { char str[256]; sprintf(str, "Can't find age desc file %s", ageDescFileName); hsAssert(false, str); return nil; } return stream; } // // sent from server with joinAck // void plAgeLoader::ISetInitialAgeState(plStateDataRecord* s) { hsAssert(fInitialAgeState != s, "duplicate initial age state"); delete fInitialAgeState; fInitialAgeState=s; }