/*==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 "plSDL.h"
#include "plNetMessage/plNetMessage.h"
#include "pnNetCommon/plNetApp.h"
const plString plSDL::kAgeSDLObjectName = "AgeSDLHook";
// static
const uint8_t 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->ReadLE16();
else
*val = s->ReadLE32();
}
//
// 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->WriteLE16(val);
}
else
s->WriteLE32(val);
}
/////////////////////////////////////////////////////////////////////////////////
// State Data
/////////////////////////////////////////////////////////////////////////////////
plStateDataRecord::plStateDataRecord(const plString& 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 plString& name, int version)
{
IInitDescriptor(name, version);
}
void plStateDataRecord::IDeleteVarsList(VarsList& vars)
{
std::for_each( vars.begin(), vars.end(),
[](plStateVariable* var) { delete var; }
);
vars.clear();
}
void plStateDataRecord::IInitDescriptor(const plString& name, int version)
{
plStateDescriptor* sd = plSDLMgr::GetInstance()->FindDescriptor(name, version);
//hsAssert( sd, plString::Format("Failed to find sdl descriptor: %s,%d. Missing legacy descriptor?", name.c_str(), 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(new plSDStateVariable(vd->GetAsSDVarDescriptor()));
}
else
{
hsAssert(vd->GetAsSimpleVarDescriptor(), "var class problem");
fVarsList.push_back(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_t readOptions)
{
fFlags = s->ReadLE16();
uint8_t 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().c_str("?") : "?");
return false;
}
}
}
catch(...)
{
hsAssert( false,
plFormat("Something bad happened while reading simple var data, desc:{}",
fDescriptor ? 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().c_str("?") : "?");
return false;
}
}
}
catch(...)
{
hsAssert( false,
plFormat("Something bad happened while reading nested var data, desc:{}",
fDescriptor ? 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, plFormat("Failed to find latest sdl descriptor for: {}", 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_t 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->WriteLE16((uint16_t)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, plString* name, int* version, plUoid* objUoid)
{
uint16_t savFlags;
s->ReadLE(&savFlags);
if (!(savFlags & plSDL::kAddedVarLengthIO)) // using to establish a new version in the header, can delete in 8/03
{
*name = "";
return false; // bad version
}
*name = s->ReadSafeString();
*version = s->ReadLE16();
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_t savFlags=plSDL::kAddedVarLengthIO; // using to establish a new version in the header, can delete in 8/03
if (objUoid)
savFlags |= plSDL::kHasUoid;
s->WriteLE(savFlags);
s->WriteSafeString(GetDescriptor()->GetName());
s->WriteLE16((int16_t)GetDescriptor()->GetVersion());
if (objUoid)
objUoid->Write(s);
}
//
// create and prepare a net msg with this data
//
plNetMsgSDLState* plStateDataRecord::PrepNetMsg(float timeConvert, uint32_t writeOptions) const
{
// save to stream
hsRAMStream stream;
WriteStreamHeader(&stream);
Write(&stream, timeConvert, writeOptions);
// fill in net msg
plNetMsgSDLState* msg;
if (writeOptions & plSDL::kBroadcast)
msg = new plNetMsgSDLStateBCast;
else
msg = new plNetMsgSDLState;
msg->StreamInfo()->CopyStream(&stream);
return msg;
}
//
// Destroys 'this' and makes a total copy of other
//
void plStateDataRecord::CopyFrom(const plStateDataRecord& other, uint32_t 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_t writeOptions/*=0*/)
{
if ( GetDescriptor()->GetVersion()!=other.GetDescriptor()->GetVersion() )
{
plStateDescriptor* sd=plSDLMgr::GetInstance()->FindDescriptor( other.GetDescriptor()->GetName(), other.GetDescriptor()->GetVersion() );
hsAssert(sd, plFormat("Failed to find sdl descriptor {},{}. Missing legacy descriptor?",
other.GetDescriptor()->GetName(), other.GetDescriptor()->GetVersion()).c_str());
ConvertTo( sd );
}
hsAssert(other.GetDescriptor()==fDescriptor,
plFormat("descriptor mismatch in UpdateFromDirty, SDL={},{} version {} {}",
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, plFormat("descriptor mismatch in FlagDifferentState, mine {} {}, other {} {}",
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, plFormat("descriptor mismatch in FlagNewerState, mine {} {}, other {} {}",
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 plString& name) const
{
for (int i = 0; i < vars.size(); i++)
{
if (!vars[i]->GetVarDescriptor()->GetName().CompareI(name))
return vars[i];
}
if (plSDLMgr::GetInstance()->GetNetApp())
plSDLMgr::GetInstance()->GetNetApp()->ErrorMsg("Failed to find SDL var %s", name.c_str());
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(!fDescriptor->GetName().CompareI(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().c_str(), 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;
plString pad = plString::Fill(level * 3, ' ');
int numVars = dirtyOnly ? GetNumDirtyVars() : GetNumUsedVars();
int numSDVars = dirtyOnly ? GetNumDirtySDVars() : GetNumUsedSDVars();
dbg->LogMsg(fAssocObject.IsValid() ? fAssocObject.GetObjectName().c_str() : " ");
if (msg)
dbg->LogMsg(plFormat("{}{}", pad, msg).c_str());
dbg->LogMsg(plFormat("{}SDR(0x{x}), desc={}, showDirty={}, numVars={}, vol={}",
pad, (uintptr_t)this, fDescriptor->GetName(), dirtyOnly, numVars+numSDVars, fFlags&kVolatile).c_str());
// dump simple vars
for (size_t i=0; iIsDirty()) || (!dirtyOnly && fVarsList[i]->IsUsed()) )
{
fVarsList[i]->DumpToObjectDebugger(dirtyOnly, level+1);
}
}
// dump nested vars
for (size_t 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
{
plString pad = plString::Fill(level * 3, ' ');
int numVars = dirtyOnly ? GetNumDirtyVars() : GetNumUsedVars();
int numSDVars = dirtyOnly ? GetNumDirtySDVars() : GetNumUsedSDVars();
plString logStr = fAssocObject.IsValid() ? fAssocObject.GetObjectName() : " ";
stream->Write(logStr.GetSize(), logStr.c_str());
if (msg)
{
logStr = plFormat("{}{}", pad, msg);
stream->Write(logStr.GetSize(), logStr.c_str());
}
logStr = plFormat("{}SDR(0x{x}), desc={}, showDirty={}, numVars={}, vol={}",
pad, (uintptr_t)this, fDescriptor->GetName(), dirtyOnly, numVars+numSDVars, fFlags&kVolatile);
stream->Write(logStr.GetSize(), logStr.c_str());
// dump simple vars
for (size_t i=0; iIsDirty()) || (!dirtyOnly && fVarsList[i]->IsUsed()) )
{
fVarsList[i]->DumpToStream(stream, dirtyOnly, level+1);
}
}
// dump nested vars
for (size_t i=0; iIsDirty()) || (!dirtyOnly && fSDVarsList[i]->IsUsed()) )
{
fSDVarsList[i]->DumpToStream(stream, dirtyOnly, level+1);
}
}
logStr = "\n";
stream->Write(logStr.GetSize(), 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();
}
}