/*==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 "plKeyImp.h" #include "hsStream.h" #include "hsKeyedObject.h" #include "hsResMgr.h" #include "hsTypes.h" #include "pnMessage/plRefMsg.h" #include "pnMessage/plSelfDestructMsg.h" #include "hsTimer.h" #include "plProfile.h" #include "plgDispatch.h" plProfile_CreateMemCounter("Keys", "Memory", KeyMem); static UInt32 CalcKeySize(plKeyImp* key) { UInt32 nameLen = 0; if (key->GetUoid().GetObjectName()) nameLen = strlen(key->GetUoid().GetObjectName()) + 1; return sizeof(plKeyImp) + nameLen; } //#define LOG_ACTIVE_REFS #ifdef LOG_ACTIVE_REFS #include "plCreatableIndex.h" static const char* kObjName = "GUI_District_OptionsMenuGUI"; static UInt16 kClassType = CLASS_INDEX_SCOPED(plSceneNode); static UInt32 kCloneID = 0; hsBool IsTrackedKey(const plKeyImp* key) { return hsStrEQ(key->GetName(), kObjName) && key->GetUoid().GetClassType() == kClassType && key->GetUoid().GetCloneID() == kCloneID; } #endif plKeyImp::plKeyImp() : fObjectPtr(nil), fStartPos(-1), fDataLen(-1), fNumActiveRefs(0), fPendingRefs(1), fCloneOwner(nil) { #ifdef HS_DEBUGGING fIDName = nil; fClassType = nil; #endif } plKeyImp::plKeyImp(plUoid u, UInt32 pos,UInt32 len): fUoid(u), fObjectPtr(nil), fStartPos(pos), fDataLen(len), fNumActiveRefs(0), fPendingRefs(1), fCloneOwner(nil) { plProfile_NewMem(KeyMem, CalcKeySize(this)); #ifdef HS_DEBUGGING fIDName = fUoid.GetObjectName(); fClassType = plFactory::GetNameOfClass( fUoid.GetClassType() ); #endif } plKeyImp::~plKeyImp() { plProfile_DelMem(KeyMem, CalcKeySize(this)); #if defined(HS_DEBUGGING) && 0 // Colin debugging char buf[512]; sprintf(buf, "0x%x %s %s\n", this, fIDName, fClassType); hsStatusMessage(buf); #endif hsAssert(fObjectPtr == nil, "Deleting non-nil key! Bad idea!"); if (fCloneOwner != nil) { // Must be a clone, remove us from our parent list ((plKeyImp*)fCloneOwner)->RemoveClone(this); } for (int i = 0; i < fClones.GetCount(); i++) { if (fClones[i]) fClones[i]->UnRegister(); } fClones.Reset(); // This is normally empty by now, but if we never got loaded, // there will be unsent ref messages in the NotifyCreated list ClearNotifyCreated(); } void plKeyImp::SetUoid(const plUoid& uoid) { fUoid = uoid; #ifdef HS_DEBUGGING fIDName = fUoid.GetObjectName(); fClassType = plFactory::GetNameOfClass(fUoid.GetClassType()); #endif } const char* plKeyImp::GetName() const { return fUoid.GetObjectName(); } hsKeyedObject* plKeyImp::GetObjectPtr() { return ObjectIsLoaded(); } hsKeyedObject* plKeyImp::ObjectIsLoaded() const { return this ? fObjectPtr : nil; } // Copy the contents of p for cloning process void plKeyImp::CopyForClone(const plKeyImp *p, UInt32 playerID, UInt32 cloneID) { fObjectPtr = nil; // the clone object start as nil fUoid = p->GetUoid(); // we will set the UOID the same to start #ifdef HS_DEBUGGING fIDName = fUoid.GetObjectName(); fClassType = plFactory::GetNameOfClass( fUoid.GetClassType() ); #endif fStartPos = p->GetStartPos(); fDataLen = p->GetDataLen(); fUoid.SetClone(playerID, cloneID); } hsKeyedObject* plKeyImp::VerifyLoaded() { if (!fObjectPtr) hsgResMgr::ResMgr()->ReadObject(this); return fObjectPtr; } //// Read/Write ////////////////////////////////////////////////////////////// // The actual key read/writes for the index file, the only time the whole // key is ever actually stored. void plKeyImp::Read(hsStream* s) { fUoid.Read(s); s->ReadSwap(&fStartPos); s->ReadSwap(&fDataLen); plProfile_NewMem(KeyMem, CalcKeySize(this)); #ifdef HS_DEBUGGING fIDName = fUoid.GetObjectName(); fClassType = plFactory::GetNameOfClass(fUoid.GetClassType()); #endif } void plKeyImp::SkipRead(hsStream* s) { plUoid tempUoid; tempUoid.Read(s); s->ReadSwap32(); s->ReadSwap32(); } void plKeyImp::Write(hsStream* s) { fUoid.Write(s); s->WriteSwap(fStartPos); s->WriteSwap(fDataLen); if (fStartPos == (UInt32)-1) int foo = 0; } //// WriteObject ///////////////////////////////////////////////////////////// // Writes the key's object to the already opened stream void plKeyImp::WriteObject(hsStream* stream) { hsKeyedObject* ko = ObjectIsLoaded(); if (ko == nil) { // Mark the key as not written fStartPos = (UInt32)-1; fDataLen = (UInt32)-1; return; } fStartPos = stream->GetPosition(); hsgResMgr::ResMgr()->WriteCreatable(stream, ko); fDataLen = stream->GetPosition() - fStartPos; } void plKeyImp::UnRegister() // called from plRegistry { plKey safeRefUntilWereDone = plKey::Make(this); hsKeyedObject* ko = ObjectIsLoaded(); if (ko) { INotifyDestroyed(); fObjectPtr = nil; fNumActiveRefs = 0; hsRefCnt_SafeUnRef(ko); } IClearRefs(); ClearNotifyCreated(); }; hsKeyedObject* plKeyImp::RefObject(plRefFlags::Type flags) { if ((flags == plRefFlags::kPassiveRef) && !ObjectIsLoaded()) return nil; #ifdef LOG_ACTIVE_REFS if (IsTrackedKey(this)) hsStatusMessageF("@@@ RefObject adding active ref to %s (%d total)", kObjName, fNumActiveRefs+1); #endif // LOG_ACTIVE_REFS IncActiveRefs(); return VerifyLoaded(); // load object on demand } void plKeyImp::UnRefObject(plRefFlags::Type flags) { // Rather than using hsRefCnt's, make Ref and // UnRef work with ActiveRef system if ( (flags == plRefFlags::kPassiveRef) && !ObjectIsLoaded()) return; #ifdef LOG_ACTIVE_REFS if (IsTrackedKey(this)) hsStatusMessageF("@@@ UnRefObject releasing active ref to %s (%d total)", kObjName, fNumActiveRefs-1); #endif // LOG_ACTIVE_REFS DecActiveRefs(); if( !GetActiveRefs() ) { INotifyDestroyed(); IClearRefs(); ClearNotifyCreated(); plKey key=plKey::Make( this ); // for linux build plSelfDestructMsg* nuke = TRACKED_NEW plSelfDestructMsg( key ); plgDispatch::Dispatch()->MsgSend(nuke); } } hsKeyedObject* plKeyImp::SetObjectPtr(hsKeyedObject* p) { hsKeyedObject* retVal = nil; // If our object is the only one with a ref to us, this function will crash, so we // make sure we have an extra ref, just like in UnRegister(). plKey safeRefUntilWereDone = plKey::Make(this); if (p) { #ifdef HS_DEBUGGING if (fClassType) { char str[2048]; sprintf(str, "Mismatch of class (we are a %s, given a %s)", fClassType, p->ClassName()); hsAssert(fClassType == p->ClassName() || strcmp(fClassType, p->ClassName()) == 0, str); // points to static } else fClassType = p->ClassName(); #endif hsAssert(!fObjectPtr, "Setting an ObjectPtr thats already Set!"); retVal = fObjectPtr = p; } else { if (fObjectPtr) UnRegister(); fObjectPtr = nil; retVal = nil; } return retVal; } void plKeyImp::ClearNotifyCreated() { for (int i = 0; i < fNotifyCreated.GetCount(); i++) hsRefCnt_SafeUnRef(fNotifyCreated[i]); fNotifyCreated.Reset(); fNotified.Reset(); fActiveRefs.Reset(); } void plKeyImp::AddNotifyCreated(plRefMsg* msg, plRefFlags::Type flags) { if (!(flags == plRefFlags::kPassiveRef)) { #ifdef LOG_ACTIVE_REFS if (IsTrackedKey(this)) { hsStatusMessageF("@@@ %s(%s) adding active ref to %s (%d total)", msg->GetReceiver(0)->GetName(), plFactory::GetNameOfClass(msg->GetReceiver(0)->GetUoid().GetClassType()), kObjName, fNumActiveRefs+1); } #endif // LOG_ACTIVE_REFS IncActiveRefs(); SetActiveRef(GetNumNotifyCreated()); } hsRefCnt_SafeRef(msg); fNotifyCreated.Append(msg); } void plKeyImp::RemoveNotifyCreated(int i) { hsRefCnt_SafeUnRef(fNotifyCreated[i]); fNotifyCreated.Remove(i); fNotified.RemoveBit(i); fActiveRefs.RemoveBit(i); } void plKeyImp::AddRef(plKeyImp* key) const { fPendingRefs++; fRefs.Append(key); } void plKeyImp::RemoveRef(plKeyImp* key) const { int idx = fRefs.Find(key); if (fRefs.kMissingIndex != idx) fRefs.Remove(idx); } void plKeyImp::AddClone(plKeyImp* key) { hsAssert(!GetClone(key->GetUoid().GetClonePlayerID(), key->GetUoid().GetCloneID()), "Adding a clone which is already there?"); key->fCloneOwner = plKey::Make(this); fClones.Append(key); } void plKeyImp::RemoveClone(plKeyImp* key) const { if (key->GetUoid().IsClone()) { int idx = fClones.Find(key); if (idx != -1) { fClones.Remove(idx); key->fCloneOwner = nil; } } } plKey plKeyImp::GetClone(UInt32 playerID, UInt32 cloneID) const { for (int i = 0; i < fClones.GetCount(); i++) { plKeyImp* cloneKey = fClones[i]; if (cloneKey && cloneKey->GetUoid().GetCloneID() == cloneID && cloneKey->GetUoid().GetClonePlayerID() == playerID) return plKey::Make(cloneKey); } return plKey(); } UInt32 plKeyImp::GetNumClones() { return fClones.GetCount(); } plKey plKeyImp::GetCloneByIdx(UInt32 idx) { if (idx < fClones.GetCount()) return plKey::Make(fClones[idx]); return nil; } void plKeyImp::SatisfyPending(plRefMsg* msg) const { for (int i = 0; i < msg->GetNumReceivers(); i++) ((plKeyImp*)msg->GetReceiver(i))->SatisfyPending(); } void plKeyImp::SatisfyPending() const { hsAssert(fPendingRefs > 0, "Have more requests satisfied than we made"); if (!--fPendingRefs) { #ifdef PL_SEND_SATISFIED plSatisfiedMsg* msg = TRACKED_NEW plSatisfiedMsg(this); plgDispatch::MsgSend(msg); #endif // PL_SEND_SATISFIED } } void plKeyImp::ISetupNotify(plRefMsg* msg, plRefFlags::Type flags) { msg->SetSender(nil); AddNotifyCreated(msg, flags); hsAssert(msg->GetNumReceivers(), "nil object getting a reference"); for (int i = 0; i < msg->GetNumReceivers(); i++) ((plKeyImp*)msg->GetReceiver(i))->AddRef(plKey::Make(this)); } void plKeyImp::SetupNotify(plRefMsg* msg, plRefFlags::Type flags) { hsKeyedObject* ko = ObjectIsLoaded(); ISetupNotify(msg, flags); // the KeyedObject is already Loaded, so we better go ahead and send the notify message just added. if (ko) { hsRefCnt_SafeRef(ko); msg->SetRef(ko); msg->SetTimeStamp(hsTimer::GetSysSeconds()); // Add ref, since Dispatch will unref this msg but we want to keep using it. hsRefCnt_SafeRef(msg); SetNotified(GetNumNotifyCreated()-1); SatisfyPending(msg); #ifdef LOAD_IN_THREAD // test for resLoader plgDispatch::Dispatch()->MsgQueue(msg); #else plgDispatch::Dispatch()->MsgSend(msg); #endif hsRefCnt_SafeUnRef(ko); } hsRefCnt_SafeUnRef(msg); } // We could just call NotifyCreated() on all our fRefs, and then fix // up fNotified to only get set when the message actually was delivered (i.e. // refMsg->GetReceiver(0)->GetObjectPtr() != nil. But that only really works // if we guarantee the refMsg->GetNumReceivers() == 1. // This looks like it'll take forever to run, but this is only called right // when our object has just been loaded, at which time normally fRefs.GetCount() == 0. void plKeyImp::INotifySelf(hsKeyedObject* ko) { for (int i = 0; i < fRefs.GetCount(); i++) { hsKeyedObject* rcv = fRefs[i]->GetObjectPtr(); if (rcv) { for (int j = 0; j < fRefs[i]->fNotifyCreated.GetCount(); j++) { plRefMsg* refMsg = fRefs[i]->fNotifyCreated[j]; if (refMsg && refMsg->GetRef() && !fRefs[i]->IsNotified(j)) { hsAssert(refMsg->GetRef() == rcv, "Ref message out of sync with its ref"); // GetNumReceivers() should always be 1 for a refMsg. for (int k = 0; k < refMsg->GetNumReceivers(); k++) { if (&(*refMsg->GetReceiver(k)) == (plKeyData*)this) { fRefs[i]->SetNotified(j); fRefs[i]->SatisfyPending(refMsg); hsRefCnt_SafeRef(refMsg); plgDispatch::MsgSend(refMsg); break; } } } } } } } void plKeyImp::NotifyCreated() { hsKeyedObject* ko = GetObjectPtr(); hsRefCnt_SafeRef(ko); hsAssert(ko, "Notifying of created before on nil object"); INotifySelf(ko); for (int i = 0; i < GetNumNotifyCreated(); i++) { if (!IsNotified(i) && GetNotifyCreated(i)->GetReceiver(0)->GetObjectPtr()) { plRefMsg* msg = GetNotifyCreated(i); msg->SetRef(ko); msg->SetTimeStamp(hsTimer::GetSysSeconds()); msg->SetContext(plRefMsg::kOnCreate); hsRefCnt_SafeRef(msg); SetNotified(i); SatisfyPending(msg); plgDispatch::MsgSend(msg); } } hsRefCnt_SafeUnRef(ko); } void plKeyImp::INotifyDestroyed() { hsKeyedObject* ko = GetObjectPtr(); hsAssert(ko, "Notifying of destroy on already destroyed"); int i; for( i = 0; i < GetNumNotifyCreated(); i++ ) { hsAssert(ko, "Notifying of destroy on already destroyed"); plRefMsg* msg = GetNotifyCreated(i); msg->SetRef(ko); msg->SetTimeStamp(hsTimer::GetSysSeconds()); msg->SetContext(plRefMsg::kOnDestroy); hsRefCnt_SafeRef(msg); msg->Send(); } fNotified.Clear(); } void plKeyImp::IClearRefs() { while (GetNumRefs()) IRelease(GetRef(0)); fRefs.Reset(); for (int i = 0; i < GetNumNotifyCreated(); i++) { plRefMsg* msg = GetNotifyCreated(i); for (int j = 0; j < msg->GetNumReceivers(); j++) ((plKeyImp*)msg->GetReceiver(j))->RemoveRef(this); } } void plKeyImp::Release(plKey targetKey) { IRelease((plKeyImp*)targetKey); } void plKeyImp::IRelease(plKeyImp* iTargetKey) { // Remove the target key from my ref list RemoveRef(iTargetKey); // Inspect the target key to find whether it is supposed to send a message // to me on destruction, and to find out if I have an active of passive // ref on this key. Not sure why I don't track my own active/passive ref states hsBool isActive = false; int iTarg = -1; for (int i = 0; (iTarg < 0) && (i < iTargetKey->GetNumNotifyCreated()); i++) { plMessage* rcvMsg = iTargetKey->GetNotifyCreated(i); for (int j = 0; j < rcvMsg->GetNumReceivers(); j++) { if (&(*rcvMsg->GetReceiver(j)) == (plKeyData*)this) { isActive = iTargetKey->IsActiveRef(iTarg = i); break; } } } if (iTarg < 0) { // If it doesn't send me a message on destruction, I am assuming I don't have an // active ref on the key (seems to be always true, but should we depend on it?) // Since it doesn't send me a message, and won't be destroyed by the release, no // need to do anything more here return; } // If releasing this target causes its destruction, we'll notify // everyone that target is dead. Otherwise, we'll remove // releaser from target's notify list and notify releaser // it's been removed. Object doesn't actually get deleted until // it receives the SelfDestruct msg, by which time everyone referencing // it has been notified it is going away. #ifdef LOG_ACTIVE_REFS if (isActive && IsTrackedKey(iTargetKey)) hsStatusMessageF("@@@ %s(%s) releasing active ref on %s (%d total)", GetName(), plFactory::GetNameOfClass(GetUoid().GetClassType()), kObjName, iTargetKey->fNumActiveRefs-1); #endif // LOG_ACTIVE_REFS if (isActive && iTargetKey->GetActiveRefs() && !iTargetKey->DecActiveRefs()) { iTargetKey->INotifyDestroyed(); iTargetKey->IClearRefs(); iTargetKey->ClearNotifyCreated(); plKey key = plKey::Make(iTargetKey); plSelfDestructMsg* nuke = TRACKED_NEW plSelfDestructMsg(key); plgDispatch::Dispatch()->MsgSend(nuke); } else { plRefMsg* refMsg = iTargetKey->GetNotifyCreated(iTarg); hsRefCnt_SafeRef(refMsg); iTargetKey->RemoveNotifyCreated(iTarg); if (refMsg) { refMsg->SetRef(iTargetKey->ObjectIsLoaded()); refMsg->SetTimeStamp(hsTimer::GetSysSeconds()); refMsg->SetContext(plRefMsg::kOnRemove); hsRefCnt_SafeRef(refMsg); plgDispatch::MsgSend(refMsg); } hsRefCnt_SafeUnRef(refMsg); } }