/*==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 . 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 #include "hsTimer.h" #include "hsTemplates.h" #include "hsStream.h" #include "hsStlUtils.h" #include "plSDL.h" #include "../plNetMessage/plNetMessage.h" #include "../pnNetCommon/plNetApp.h" const char* plSDL::kAgeSDLObjectName = {"AgeSDLHook"}; // static const UInt8 plStateDataRecord::kIOVersion=6; // // helper // void plSDL::VariableLengthRead(hsStream* s, int size, int* val) { // hsAssert(size, "unexpected size"); if (size < (1<<8)) *val = s->ReadByte(); else if (size < (1<<16)) *val = s->ReadSwap16(); else *val = s->ReadSwap32(); } // // helper // void plSDL::VariableLengthWrite(hsStream* s, int size, int val) { // hsAssert(size, "unexpected size"); if (size < (1<<8)) { hsAssert(val < (1<<8), "SDL data loss"); s->WriteByte(val); } else if (size < (1<<16)) { hsAssert(val < (1<<16), "SDL data loss"); s->WriteSwap16(val); } else s->WriteSwap32(val); } ///////////////////////////////////////////////////////////////////////////////// // State Data ///////////////////////////////////////////////////////////////////////////////// plStateDataRecord::plStateDataRecord(const char* name, int version) : fFlags(0) , fDescriptor( nil ) { SetDescriptor(name, version); } plStateDataRecord::plStateDataRecord(plStateDescriptor* sd) : fFlags(0) , fDescriptor( nil ) { IInitDescriptor(sd); } plStateDataRecord::~plStateDataRecord() { IDeleteVarsList(fVarsList); IDeleteVarsList(fSDVarsList); } void plStateDataRecord::SetDescriptor(const char* name, int version) { IInitDescriptor(name, version); } void plStateDataRecord::IDeleteVarsList(VarsList& vars) { std::for_each( vars.begin(), vars.end(), xtl::delete_ptr() ); vars.clear(); } void plStateDataRecord::IInitDescriptor(const char* name, int version) { plStateDescriptor* sd = plSDLMgr::GetInstance()->FindDescriptor(name, version); //hsAssert( sd, xtl::format("Failed to find sdl descriptor: %s,%d. Missing legacy descriptor?", name, version ).c_str() ); if (sd) IInitDescriptor(sd); } // // Point to descriptor. // setup state variables which correspond to the state descriptor. // void plStateDataRecord::IInitDescriptor(const plStateDescriptor* sd) { // pt to state desc fDescriptor=sd; // delete old vars IDeleteVarsList(fVarsList); IDeleteVarsList(fSDVarsList); // create vars defined by state desc if (sd) { for(int i = 0; i < sd->GetNumVars(); ++i) { if (plVarDescriptor* vd = sd->GetVar(i)) { if (vd->GetAsSDVarDescriptor()) { // it's a var which references another state descriptor. fSDVarsList.push_back(TRACKED_NEW plSDStateVariable(vd->GetAsSDVarDescriptor())); } else { hsAssert(vd->GetAsSimpleVarDescriptor(), "var class problem"); fVarsList.push_back(TRACKED_NEW plSimpleStateVariable(vd->GetAsSimpleVarDescriptor())); } } } } } /////////////////// // DIRTY VARS /////////////////// int plStateDataRecord::IGetNumDirtyVars(const VarsList& vars) const { int i, cnt=0; for(i=0;iIsDirty()) cnt++; return cnt; } int plStateDataRecord::IGetDirtyVars(const VarsList& varsIn, VarsList* varsOut) const { int i; for(i=0;iIsDirty()) varsOut->push_back(varsIn[i]); return varsOut->size(); } bool plStateDataRecord::IHasDirtyVars(const VarsList& vars) const { int i; for(i=0;iIsDirty()) return true; return false; } //////////////// // USED VARS //////////////// int plStateDataRecord::IGetNumUsedVars(const VarsList& vars) const { int i, cnt=0; for(i=0;iIsUsed()) cnt++; return cnt; } int plStateDataRecord::IGetUsedVars(const VarsList& varsIn, VarsList* varsOut) const { int i; for(i=0;iIsUsed()) varsOut->push_back(varsIn[i]); return varsOut->size(); } bool plStateDataRecord::IHasUsedVars(const VarsList& vars) const { int i; for(i=0;iIsUsed()) return true; return false; } /////////////////////////////////////// // IO /////////////////////////////////////// // // read state vars and indices, return true on success // bool plStateDataRecord::Read(hsStream* s, float timeConvert, UInt32 readOptions) { fFlags = s->ReadSwap16(); UInt8 ioVersion = s->ReadByte(); if (ioVersion != kIOVersion) return false; // // read simple var data // hsAssert(fDescriptor, "State Data Record has nil SDL descriptor"); if (!fDescriptor) return false; int num; plSDL::VariableLengthRead(s, fDescriptor->GetNumVars(), &num ); // if we are readeing the entire list, we don't need to read each index bool all = (num==fVarsList.size()); int i; try { for(i=0;iGetNumVars(), &idx ); else idx=i; if (idx>=fVarsList.size() || !fVarsList[idx]->ReadData(s, timeConvert, readOptions)) { if (plSDLMgr::GetInstance()->GetNetApp()) plSDLMgr::GetInstance()->GetNetApp()->ErrorMsg("Failed reading SDL, desc %s", fDescriptor && fDescriptor->GetName() ? fDescriptor->GetName() : "?"); return false; } } } catch(...) { hsAssert( false, xtl::format("Something bad happened while reading simple var data, desc:%s", fDescriptor && fDescriptor->GetName() ? fDescriptor->GetName() : "?").c_str()); return false; } // // read nested var data // plSDL::VariableLengthRead(s, fDescriptor->GetNumVars(), &num ); // if we are readeing the entire list, we don't need to write each index all = (num==fSDVarsList.size()); try { for(i=0;iGetNumVars(), &idx ); else idx=i; if (idx>=fSDVarsList.size() || !fSDVarsList[idx]->ReadData(s, timeConvert, readOptions)) // calls plStateDataRecord::Read recursively { if (plSDLMgr::GetInstance()->GetNetApp()) plSDLMgr::GetInstance()->GetNetApp()->ErrorMsg("Failed reading nested SDL, desc %s", fDescriptor && fDescriptor->GetName() ? fDescriptor->GetName() : "?"); return false; } } } catch(...) { hsAssert( false, xtl::format("Something bad happened while reading nested var data, desc:%s", fDescriptor && fDescriptor->GetName() ? fDescriptor->GetName() : "?").c_str()); return false; } // convert to latest descriptor // Only really need to do this the first time this descriptor is read... plStateDescriptor* latestDesc=plSDLMgr::GetInstance()->FindDescriptor(fDescriptor->GetName(), plSDL::kLatestVersion); hsAssert( latestDesc, xtl::format("Failed to find latest sdl descriptor for: %s", fDescriptor->GetName() ).c_str() ); bool forceConvert = (readOptions&plSDL::kForceConvert)!=0; if ( latestDesc && ( forceConvert || ( fDescriptor->GetVersion()!=latestDesc->GetVersion() ) ) ) { DumpToObjectDebugger( "PreConvert" ); ConvertTo( latestDesc, forceConvert ); DumpToObjectDebugger( "PostConvert" ); } return true; // ok } // // write out the state vars, along with their index // void plStateDataRecord::Write(hsStream* s, float timeConvert, UInt32 writeOptions) const { #ifdef HS_DEBUGGING if ( !plSDLMgr::GetInstance()->AllowTimeStamping() && (writeOptions & plSDL::kWriteTimeStamps) ) { hsAssert( false, "SDL behavior flags disallow var timestamping on write.\nRemoving kWriteTimeStamps flag from writeOptions..." ); writeOptions &= ~plSDL::kWriteTimeStamps; } #endif s->WriteSwap16((UInt16)fFlags); s->WriteByte(kIOVersion); // // write simple vars // bool dirtyOnly = (writeOptions & plSDL::kDirtyOnly) != 0; int num = dirtyOnly ? GetNumDirtyVars() : GetNumUsedVars(); plSDL::VariableLengthWrite(s, fDescriptor->GetNumVars(), num ); // write affected vars count // if we are writing he entire list, we don't need to write each index bool all = (num==fVarsList.size()); int i; for(i=0;iIsDirty()) || (!dirtyOnly && fVarsList[i]->IsUsed()) ) { if (!all) plSDL::VariableLengthWrite(s, fDescriptor->GetNumVars(), i ); // index fVarsList[i]->WriteData(s, timeConvert, writeOptions); // data } } // // write nested vars // num = dirtyOnly ? GetNumDirtySDVars() : GetNumUsedSDVars(); plSDL::VariableLengthWrite(s, fDescriptor->GetNumVars(), num ); // write affected vars count // if we are writing he entire list, we don't need to write each index all = (num==fSDVarsList.size()); for(i=0;iIsDirty()) || (!dirtyOnly && fSDVarsList[i]->IsUsed()) ) { if (!all) plSDL::VariableLengthWrite(s, fDescriptor->GetNumVars(), i ); // index fSDVarsList[i]->WriteData(s, timeConvert, writeOptions); // data, calls plStateDataRecord::Write recursively } } } // // STATIC - read prefix header. returns true on success // bool plStateDataRecord::ReadStreamHeader(hsStream* s, char** name, int* version, plUoid* objUoid) { UInt16 savFlags; s->ReadSwap(&savFlags); if (!(savFlags & plSDL::kAddedVarLengthIO)) // using to establish a new version in the header, can delete in 8/03 { *name = nil; return false; // bad version } *name = s->ReadSafeString(); *version = s->ReadSwap16(); if (objUoid) { hsAssert(savFlags & plSDL::kHasUoid, "SDL state data rec expecting to read a uoid, but there isn't one"); } if (savFlags & plSDL::kHasUoid) { if (objUoid) objUoid->Read(s); else { plUoid tmp; tmp.Read(s); } } return true; // ok } // // non-static - write prefix header. helper fxn // void plStateDataRecord::WriteStreamHeader(hsStream* s, plUoid* objUoid) const { UInt16 savFlags=plSDL::kAddedVarLengthIO; // using to establish a new version in the header, can delete in 8/03 if (objUoid) savFlags |= plSDL::kHasUoid; s->WriteSwap(savFlags); s->WriteSafeString(GetDescriptor()->GetName()); s->WriteSwap16((Int16)GetDescriptor()->GetVersion()); if (objUoid) objUoid->Write(s); } // // create and prepare a net msg with this data // plNetMsgSDLState* plStateDataRecord::PrepNetMsg(float timeConvert, UInt32 writeOptions) const { // save to stream hsRAMStream stream; WriteStreamHeader(&stream); Write(&stream, timeConvert, writeOptions); // fill in net msg plNetMsgSDLState* msg; if (writeOptions & plSDL::kBroadcast) msg = TRACKED_NEW plNetMsgSDLStateBCast; else msg = TRACKED_NEW plNetMsgSDLState; msg->StreamInfo()->CopyStream(&stream); return msg; } // // Destroys 'this' and makes a total copy of other // void plStateDataRecord::CopyFrom(const plStateDataRecord& other, UInt32 writeOptions/*=0*/) { fFlags = other.GetFlags(); IInitDescriptor(other.GetDescriptor()); int i; for(i=0;iIsUsed()) GetVar(i)->CopyData(other.GetVar(i),writeOptions); } for(i=0;iIsUsed()) GetSDVar(i)->CopyFrom(other.GetSDVar(i),writeOptions); } } // // Find the data items which are dirty in 'other' and // copy them to my corresponding item. // Requires that records have the same descriptor. // void plStateDataRecord::UpdateFrom(const plStateDataRecord& other, UInt32 writeOptions/*=0*/) { if ( GetDescriptor()->GetVersion()!=other.GetDescriptor()->GetVersion() ) { plStateDescriptor* sd=plSDLMgr::GetInstance()->FindDescriptor( other.GetDescriptor()->GetName(), other.GetDescriptor()->GetVersion() ); hsAssert( sd, xtl::format( "Failed to find sdl descriptor %s,%d. Missing legacy descriptor?", other.GetDescriptor()->GetName(), other.GetDescriptor()->GetVersion() ).c_str() ); ConvertTo( sd ); } hsAssert(other.GetDescriptor()==fDescriptor, xtl::format("descriptor mismatch in UpdateFromDirty, SDL=%s,%s version %d %d", GetDescriptor()->GetName(), other.GetDescriptor()->GetName(), GetDescriptor()->GetVersion(), other.GetDescriptor()->GetVersion()).c_str()); bool dirtyOnly = (writeOptions & plSDL::kDirtyOnly); int i; for(i=0;iIsDirty()) || (!dirtyOnly && other.GetVar(i)->IsUsed()) ) { GetVar(i)->NotifyStateChange(other.GetVar(i), GetDescriptor()->GetName()); // see if there is enough difference to send state chg notification GetVar(i)->CopyData(other.GetVar(i), writeOptions ); // simple vars get copied completely, non-partial } } for(i=0;iIsDirty()) || (!dirtyOnly && other.GetSDVar(i)->IsUsed()) ) GetSDVar(i)->UpdateFrom(other.GetSDVar(i), writeOptions); } } // // dirty my items which are different from the corresponding one in 'other'. // Requires that records have the same descriptor. // void plStateDataRecord::FlagDifferentState(const plStateDataRecord& other) { if (other.GetDescriptor()==fDescriptor) { int i; for(i=0;iIsUsed() && ! (*other.GetVar(i) == *GetVar(i)) ); GetVar(i)->SetDirty(diff); } for(i=0;iIsUsed() && ! (*other.GetSDVar(i) == *GetSDVar(i)) ); GetSDVar(i)->SetDirty(diff); } } else { hsAssert(false, xtl::format("descriptor mismatch in FlagDifferentState, mine %s %d, other %s %d", fDescriptor->GetName(), fDescriptor->GetVersion(), other.GetDescriptor()->GetName(), other.GetDescriptor()->GetVersion()).c_str()); } } // // dirty my items which are flagged as alwaysNew. // void plStateDataRecord::FlagAlwaysNewState() { int i; for(i=0;iIsUsed() && GetVar(i)->GetVarDescriptor()->IsAlwaysNew()); // flagged to be always new GetVar(i)->SetDirty(newer); } for(i=0;iIsUsed()) { GetSDVar(i)->FlagAlwaysNewState(); } } } // // dirty my items which are newer than the corresponding one in 'other'. // Requires that records have the same descriptor. // void plStateDataRecord::FlagNewerState(const plStateDataRecord& other, bool respectAlwaysNew) { if (other.GetDescriptor()==fDescriptor) { int i; for(i=0;iIsUsed() && (GetVar(i)->GetTimeStamp() > other.GetVar(i)->GetTimeStamp() || // later timestamp (respectAlwaysNew && GetVar(i)->GetVarDescriptor()->IsAlwaysNew())) ); // flagged to be always new GetVar(i)->SetDirty(newer); } for(i=0;iIsUsed()) { GetSDVar(i)->FlagNewerState(*other.GetSDVar(i), respectAlwaysNew); } } } else { hsAssert(false, xtl::format("descriptor mismatch in FlagNewerState, mine %s %d, other %s %d", fDescriptor->GetName(), fDescriptor->GetVersion(), other.GetDescriptor()->GetName(), other.GetDescriptor()->GetVersion()).c_str()); } } // // assumes matching state descriptors // bool plStateDataRecord::operator==(const plStateDataRecord &other) const { // hsAssert(other.GetDescriptor()==fDescriptor, "descriptor mismatch in equality check"); if (other.GetDescriptor()!=fDescriptor) return false; int i; for(i=0;iSetFromDefaults(false); if (!fromVar) return true; // no corresponding var in the old state, done // Copy the value to the dst, converting if necessary fromVar->ConvertTo(toVar->GetSimpleVarDescriptor(),force); toVar->CopyData(fromVar, plSDL::kWriteTimeStamps | plSDL::kKeepDirty); return true; // ok } plStateVariable* plStateDataRecord::IFindVar(const VarsList& vars, const char* name) const { for (int i = 0; i < vars.size(); i++) { if (!stricmp(vars[i]->GetVarDescriptor()->GetName(), name)) return vars[i]; } if (plSDLMgr::GetInstance()->GetNetApp()) plSDLMgr::GetInstance()->GetNetApp()->ErrorMsg("Failed to find SDL var %s", name); return nil; } // // try to convert our data (old version) to other's (new) version. // return false on err. // bool plStateDataRecord::ConvertTo( plStateDescriptor* other, bool force ) { if (!other && !force) return false; // err hsAssert(!stricmp(fDescriptor->GetName(), other->GetName()), "descriptor mismatch"); if ( !force && (other == fDescriptor || other->GetVersion()==fDescriptor->GetVersion())) return true; // ok, nothing to do hsAssert(other->GetVersion()>=fDescriptor->GetVersion(), "converting to an older state descriptor version?"); hsLogEntry( plNetApp::StaticDebugMsg( "SDR(%p) converting sdl record %s from version %d to %d (force:%d)", this, fDescriptor->GetName(), fDescriptor->GetVersion(), other->GetVersion(), force ) ); // make other StateData to represent other descriptor, // this will be the destination for the convert operation plStateDataRecord otherStateData(other); // for each var in the other dtor, // use the corresponding value in mine (type converted if necessary), // or use other's default value. Put the final value in the otherData buffer int i; for(i=0;iGetVarDescriptor()->GetName(); // find corresponding var in my data plSimpleStateVariable* myVar=FindVar(otherVarName); IConvertVar(myVar /* fromVar */, otherVar /* toVar */, force); } // for each nested stateDesc in the other guy, // run the convert operation on it recursively. for(i=0;iGetVarDescriptor()->GetName(); // find corresponding var in my data plSDStateVariable* mySDVar=FindSDVar(otherSDVarName); if (mySDVar) { mySDVar->ConvertTo( otherSDVar, force ); otherSDVar->CopyFrom( mySDVar ); } } // adopt new descriptor and data CopyFrom(otherStateData, plSDL::kWriteTimeStamps | plSDL::kKeepDirty); return true; } // // // void plStateDataRecord::DumpToObjectDebugger(const char* msg, bool dirtyOnly, int level) const { plNetObjectDebuggerBase* dbg = plNetObjectDebuggerBase::GetInstance(); if (!dbg) return; std::string pad; int i; for(i=0;iLogMsg(xtl::format("%s", fAssocObject.IsValid() ? fAssocObject.GetObjectName() : " ").c_str()); if (msg) dbg->LogMsg(xtl::format("%s%s", pad.c_str(),msg).c_str()); dbg->LogMsg(xtl::format("%sSDR(%p), desc=%s, showDirty=%d, numVars=%d, vol=%d", pad.c_str(), this, fDescriptor->GetName(), dirtyOnly, numVars+numSDVars, fFlags&kVolatile).c_str()); // dump simple vars for(i=0;iIsDirty()) || (!dirtyOnly && fVarsList[i]->IsUsed()) ) { fVarsList[i]->DumpToObjectDebugger(dirtyOnly, level+1); } } // dump nested vars for(i=0;iIsDirty()) || (!dirtyOnly && fSDVarsList[i]->IsUsed()) ) { fSDVarsList[i]->DumpToObjectDebugger(dirtyOnly, level+1); } } } void plStateDataRecord::DumpToStream(hsStream* stream, const char* msg, bool dirtyOnly, int level) const { std::string pad; int i; for(i=0;iWrite(logStr.length(), logStr.c_str()); if (msg) { logStr = xtl::format("%s%s", pad.c_str(),msg); stream->Write(logStr.length(), logStr.c_str()); } logStr = xtl::format("%sSDR(%p), desc=%s, showDirty=%d, numVars=%d, vol=%d", pad.c_str(), this, fDescriptor->GetName(), dirtyOnly, numVars+numSDVars, fFlags&kVolatile); stream->Write(logStr.length(), logStr.c_str()); // dump simple vars for(i=0;iIsDirty()) || (!dirtyOnly && fVarsList[i]->IsUsed()) ) { fVarsList[i]->DumpToStream(stream, dirtyOnly, level+1); } } // dump nested vars for(i=0;iIsDirty()) || (!dirtyOnly && fSDVarsList[i]->IsUsed()) ) { fSDVarsList[i]->DumpToStream(stream, dirtyOnly, level+1); } } logStr = '\n'; stream->Write(logStr.length(), logStr.c_str()); } void plStateDataRecord::SetFromDefaults(bool timeStampNow) { int i; // set simple vars for(i=0;iSetFromDefaults(timeStampNow); } // set nested vars for(i=0;iSetFromDefaults(timeStampNow); } } void plStateDataRecord::TimeStampDirtyVars() { int i; // set simple vars for(i=0;iIsDirty() ) fVarsList[i]->TimeStamp(); } // set nested vars for(i=0;iIsDirty() ) fSDVarsList[i]->TimeStamp(); } }