tge/engine/sim/netGhost.cc
2017-04-17 06:17:10 -06:00

1104 lines
34 KiB
C++
Executable File

//-----------------------------------------------------------------------------
// Torque Game Engine
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "core/dnet.h"
#include "console/simBase.h"
#include "sim/netConnection.h"
#include "core/bitStream.h"
#include "sim/netObject.h"
#include "core/resManager.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#define DebugChecksum 0xF00DBAAD
extern U32 gGhostUpdates;
class GhostAlwaysObjectEvent : public NetEvent
{
SimObjectId objectId;
U32 ghostIndex;
NetObject *object;
bool validObject;
public:
GhostAlwaysObjectEvent(NetObject *obj = NULL, U32 index = 0)
{
if(obj)
{
objectId = obj->getId();
ghostIndex = index;
}
object = NULL;
}
~GhostAlwaysObjectEvent()
{ delete object; }
void pack(NetConnection *ps, BitStream *bstream)
{
bstream->writeInt(ghostIndex, NetConnection::GhostIdBitSize);
NetObject *obj = (NetObject *) Sim::findObject(objectId);
if(bstream->writeFlag(obj != NULL))
{
S32 classId = obj->getClassId(ps->getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeObject, ps->getNetClassGroup());
obj->packUpdate(ps, 0xFFFFFFFF, bstream);
}
}
void write(NetConnection *ps, BitStream *bstream)
{
bstream->writeInt(ghostIndex, NetConnection::GhostIdBitSize);
if(bstream->writeFlag(validObject))
{
S32 classId = object->getClassId(ps->getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeObject, ps->getNetClassGroup());
object->packUpdate(ps, 0xFFFFFFFF, bstream);
}
}
void unpack(NetConnection *ps, BitStream *bstream)
{
ghostIndex = bstream->readInt(NetConnection::GhostIdBitSize);
if(bstream->readFlag())
{
S32 classId = bstream->readClassId(NetClassTypeObject, ps->getNetClassGroup());
if(classId == -1)
{
ps->setLastError("Invalid packet.");
return;
}
object = (NetObject *) ConsoleObject::create(ps->getNetClassGroup(), NetClassTypeObject, classId);
if(!object)
{
ps->setLastError("Invalid packet.");
return;
}
object->mNetFlags = NetObject::IsGhost;
object->mNetIndex = ghostIndex;
object->unpackUpdate(ps, bstream);
validObject = true;
}
else
{
object = new NetObject;
validObject = false;
}
}
void process(NetConnection *ps)
{
Con::executef(1, "onGhostAlwaysObjectReceived");
ps->setGhostAlwaysObject(object, ghostIndex);
object = NULL;
}
DECLARE_CONOBJECT(GhostAlwaysObjectEvent);
};
IMPLEMENT_CO_NETEVENT_V1(GhostAlwaysObjectEvent);
ConsoleMethod( NetConnection, getGhostsActive, S32, 2, 2, "()"
"Returns number of ghosts active.")
{
return object->getGhostsActive();
}
void NetConnection::setGhostTo(bool ghostTo)
{
if(mLocalGhosts) // if ghosting to this is already enabled, silently return
return;
if(ghostTo)
{
mLocalGhosts = new NetObject *[MaxGhostCount];
for(S32 i = 0; i < MaxGhostCount; i++)
mLocalGhosts[i] = NULL;
}
}
void NetConnection::setGhostFrom(bool ghostFrom)
{
if(mGhostArray)
return;
if(ghostFrom)
{
mGhostFreeIndex = mGhostZeroUpdateIndex = 0;
mGhostArray = new GhostInfo *[MaxGhostCount];
mGhostRefs = new GhostInfo[MaxGhostCount];
S32 i;
for(i = 0; i < MaxGhostCount; i++)
{
mGhostRefs[i].obj = NULL;
mGhostRefs[i].index = i;
mGhostRefs[i].updateMask = 0;
}
mGhostLookupTable = new GhostInfo *[GhostLookupTableSize];
for(i = 0; i < GhostLookupTableSize; i++)
mGhostLookupTable[i] = 0;
}
}
void NetConnection::ghostOnRemove()
{
if(mGhostArray)
clearGhostInfo();
}
void NetConnection::ghostPacketDropped(PacketNotify *notify)
{
GhostRef *packRef = notify->ghostList;
// loop through all the packRefs in the packet
while(packRef)
{
GhostRef *temp = packRef->nextRef;
U32 orFlags = 0;
AssertFatal(packRef->nextUpdateChain == NULL, "Out of order notify!!");
// clear out the ref for this object, plus or together all
// flags from updates after this
GhostRef **walk = &(packRef->ghost->updateChain);
while(*walk != packRef)
{
orFlags |= (*walk)->mask;
walk = &((*walk)->nextUpdateChain);
}
*walk = 0;
// for any flags we haven't updated since this (dropped) packet
// or them into the mask so they'll get updated soon
orFlags = packRef->mask & ~orFlags;
if(orFlags)
{
if(!packRef->ghost->updateMask)
{
packRef->ghost->updateMask = orFlags;
ghostPushNonZero(packRef->ghost);
}
else
packRef->ghost->updateMask |= orFlags;
}
// if this packet was ghosting an object, set it
// to re ghost at it's earliest convenience
if(packRef->ghostInfoFlags & GhostInfo::Ghosting)
{
packRef->ghost->flags |= GhostInfo::NotYetGhosted;
packRef->ghost->flags &= ~GhostInfo::Ghosting;
}
// otherwise, if it was being deleted,
// set it to re-delete
else if(packRef->ghostInfoFlags & GhostInfo::KillingGhost)
{
packRef->ghost->flags |= GhostInfo::KillGhost;
packRef->ghost->flags &= ~GhostInfo::KillingGhost;
}
delete packRef;
packRef = temp;
}
}
void NetConnection::ghostPacketReceived(PacketNotify *notify)
{
GhostRef *packRef = notify->ghostList;
// loop through all the notifies in this packet
while(packRef)
{
GhostRef *temp = packRef->nextRef;
AssertFatal(packRef->nextUpdateChain == NULL, "Out of order notify!!");
// clear this notify from the end of the object's notify
// chain
GhostRef **walk = &(packRef->ghost->updateChain);
while(*walk != packRef)
walk = &((*walk)->nextUpdateChain);
*walk = 0;
// if this object was ghosting , it is now ghosted
if(packRef->ghostInfoFlags & GhostInfo::Ghosting)
packRef->ghost->flags &= ~GhostInfo::Ghosting;
// otherwise, if it was dieing, free the ghost
else if(packRef->ghostInfoFlags & GhostInfo::KillingGhost)
freeGhostInfo(packRef->ghost);
delete packRef;
packRef = temp;
}
}
struct UpdateQueueEntry
{
F32 priority;
GhostInfo *obj;
UpdateQueueEntry(F32 in_priority, GhostInfo *in_obj)
{ priority = in_priority; obj = in_obj; }
};
static S32 QSORT_CALLBACK UQECompare(const void *a,const void *b)
{
GhostInfo *ga = *((GhostInfo **) a);
GhostInfo *gb = *((GhostInfo **) b);
F32 ret = ga->priority - gb->priority;
return (ret < 0) ? -1 : ((ret > 0) ? 1 : 0);
}
void NetConnection::ghostWritePacket(BitStream *bstream, PacketNotify *notify)
{
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(DebugChecksum, 32);
#endif
notify->ghostList = NULL;
if(!isGhostingFrom())
return;
if(!bstream->writeFlag(mGhosting))
return;
// fill a packet (or two) with ghosting data
// first step is to check all our polled ghosts:
// 1. Scope query - find if any new objects have come into
// scope and if any have gone out.
// 2. call scoped objects' priority functions if the flag set is nonzero
// A removed ghost is assumed to have a high priority
// 3. call updates based on sorted priority until the packet is
// full. set flags to zero for all updated objects
CameraScopeQuery camInfo;
camInfo.camera = NULL;
camInfo.pos.set(0,0,0);
camInfo.orientation.set(0,1,0);
camInfo.visibleDistance = 1;
camInfo.fov = (F32)(3.1415f / 4.0f);
camInfo.sinFov = 0.7071f;
camInfo.cosFov = 0.7071f;
GhostInfo *walk;
// only need to worry about the ghosts that have update masks set...
S32 maxIndex = 0;
S32 i;
for(i = 0; i < mGhostZeroUpdateIndex; i++)
{
// increment the updateSkip for everyone... it's all good
walk = mGhostArray[i];
walk->updateSkipCount++;
if(!(walk->flags & (GhostInfo::ScopeAlways | GhostInfo::ScopeLocalAlways)))
walk->flags &= ~GhostInfo::InScope;
}
if(mScopeObject)
mScopeObject->onCameraScopeQuery(this, &camInfo);
for(i = mGhostZeroUpdateIndex - 1; i >= 0; i--)
{
if(!(mGhostArray[i]->flags & GhostInfo::InScope))
detachObject(mGhostArray[i]);
}
for(i = mGhostZeroUpdateIndex - 1; i >= 0; i--)
{
walk = mGhostArray[i];
if(walk->index > maxIndex)
maxIndex = walk->index;
// clear out any kill objects that haven't been ghosted yet
if((walk->flags & GhostInfo::KillGhost) && (walk->flags & GhostInfo::NotYetGhosted))
{
freeGhostInfo(walk);
continue;
}
// don't do any ghost processing on objects that are being killed
// or in the process of ghosting
else if(!(walk->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting)))
{
if(walk->flags & GhostInfo::KillGhost)
walk->priority = 10000;
else
walk->priority = walk->obj->getUpdatePriority(&camInfo, walk->updateMask, walk->updateSkipCount);
}
else
walk->priority = 0;
}
GhostRef *updateList = NULL;
dQsort(mGhostArray, mGhostZeroUpdateIndex, sizeof(GhostInfo *), UQECompare);
// reset the array indices...
for(i = mGhostZeroUpdateIndex - 1; i >= 0; i--)
mGhostArray[i]->arrayIndex = i;
S32 sendSize = 1;
while(maxIndex >>= 1)
sendSize++;
if(sendSize < 3)
sendSize = 3;
bstream->writeInt(sendSize - 3, GhostIndexBitSize);
U32 count = 0;
//
for(i = mGhostZeroUpdateIndex - 1; i >= 0 && !bstream->isFull(); i--)
{
GhostInfo *walk = mGhostArray[i];
if(walk->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting))
continue;
bstream->writeFlag(true);
bstream->writeInt(walk->index, sendSize);
U32 updateMask = walk->updateMask;
GhostRef *upd = new GhostRef;
upd->nextRef = updateList;
updateList = upd;
upd->nextUpdateChain = walk->updateChain;
walk->updateChain = upd;
upd->ghost = walk;
upd->ghostInfoFlags = 0;
if(walk->flags & GhostInfo::KillGhost)
{
walk->flags &= ~GhostInfo::KillGhost;
walk->flags |= GhostInfo::KillingGhost;
walk->updateMask = 0;
upd->mask = updateMask;
ghostPushToZero(walk);
upd->ghostInfoFlags = GhostInfo::KillingGhost;
bstream->writeFlag(true); // killing ghost
}
else
{
bstream->writeFlag(false);
U32 startPos = bstream->getCurPos();
if(walk->flags & GhostInfo::NotYetGhosted)
{
S32 classId = walk->obj->getClassId(getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeObject, getNetClassGroup());
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(classId ^ DebugChecksum, 32);
#endif
walk->flags &= ~GhostInfo::NotYetGhosted;
walk->flags |= GhostInfo::Ghosting;
upd->ghostInfoFlags = GhostInfo::Ghosting;
}
#ifdef TORQUE_DEBUG_NET
else {
S32 classId = walk->obj->getClassId(getNetClassGroup());
bstream->writeClassId(classId, NetClassTypeObject, getNetClassGroup());
bstream->writeInt(classId ^ DebugChecksum, 32);
}
#endif
// update the object
U32 retMask = walk->obj->packUpdate(this, updateMask, bstream);
DEBUG_LOG(("PKLOG %d GHOST %d: %s", getId(), bstream->getCurPos() - 16 - startPos, walk->obj->getClassName()));
AssertFatal((retMask & (~updateMask)) == 0, "Cannot set new bits in packUpdate return");
walk->updateMask = retMask;
if(!retMask)
ghostPushToZero(walk);
upd->mask = updateMask & ~retMask;
//PacketStream::getStats()->addBits(PacketStats::Send, bstream->getCurPos() - startPos, walk->obj->getPersistTag());
#ifdef TORQUE_DEBUG_NET
bstream->writeInt(walk->index ^ DebugChecksum, 32);
#endif
}
walk->updateSkipCount = 0;
count++;
}
//Con::printf("Ghosts updated: %d (%d remain)", count, mGhostZeroUpdateIndex);
// no more objects...
bstream->writeFlag(false);
notify->ghostList = updateList;
}
void NetConnection::ghostReadPacket(BitStream *bstream)
{
#ifdef TORQUE_DEBUG_NET
U32 sum = bstream->readInt(32);
AssertISV(sum == DebugChecksum, "Invalid checksum.");
#endif
if(!isGhostingTo())
return;
if(!bstream->readFlag())
return;
S32 idSize;
idSize = bstream->readInt( GhostIndexBitSize);
idSize += 3;
// while there's an object waiting...
while(bstream->readFlag())
{
gGhostUpdates++;
U32 index;
//S32 startPos = bstream->getCurPos();
index = (U32) bstream->readInt(idSize);
if(bstream->readFlag()) // is this ghost being deleted?
{
mGhostsActive--;
AssertFatal(mLocalGhosts[index] != NULL, "Error, NULL ghost encountered.");
mLocalGhosts[index]->deleteObject();
mLocalGhosts[index] = NULL;
}
else
{
if(!mLocalGhosts[index]) // it's a new ghost... cool
{
mGhostsActive++;
S32 classId = bstream->readClassId(NetClassTypeObject, getNetClassGroup());
if(classId == -1)
{
setLastError("Invalid packet.");
return;
}
NetObject *obj = (NetObject *) ConsoleObject::create(getNetClassGroup(), NetClassTypeObject, classId);
if(!obj)
{
setLastError("Invalid packet.");
return;
}
obj->mNetFlags = NetObject::IsGhost;
// object gets initial update before adding to the manager
obj->mNetIndex = index;
mLocalGhosts[index] = obj;
#ifdef TORQUE_DEBUG_NET
U32 checksum = bstream->readInt(32);
S32 origId = checksum ^ DebugChecksum;
AssertISV(mLocalGhosts[index] != NULL, "Invalid dest ghost.");
AssertISV(origId == mLocalGhosts[index]->getClassId(getNetClassGroup()),
avar("class id mismatch for dest class %s.",
mLocalGhosts[index]->getClassName()) );
#endif
mLocalGhosts[index]->unpackUpdate(this, bstream);
if(!obj->registerObject())
{
if(!mErrorBuffer[0])
setLastError("Invalid packet.");
return;
}
if(mRemoteConnection)
obj->mServerObject = mRemoteConnection->resolveObjectFromGhostIndex(index);
addObject(obj);
}
else
{
#ifdef TORQUE_DEBUG_NET
S32 classId = bstream->readClassId(NetClassTypeObject, getNetClassGroup());
U32 checksum = bstream->readInt(32);
S32 origId = checksum ^ DebugChecksum;
AssertISV(mLocalGhosts[index] != NULL, "Invalid dest ghost.");
AssertISV(origId == mLocalGhosts[index]->getClassId(getNetClassGroup()),
avar("class id mismatch for dest class %s.",
mLocalGhosts[index]->getClassName()) );
#endif
mLocalGhosts[index]->unpackUpdate(this, bstream);
}
//PacketStream::getStats()->addBits(PacketStats::Receive, bstream->getCurPos() - startPos, ghostRefs[index].localGhost->getPersistTag());
#ifdef TORQUE_DEBUG_NET
U32 checksum = bstream->readInt(32);
S32 origIndex = checksum ^ DebugChecksum;
AssertISV(origIndex == index,
avar("unpackUpdate did not match packUpdate for object of class %s.",
mLocalGhosts[index]->getClassName()) );
#endif
if(mErrorBuffer[0])
return;
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void NetConnection::setScopeObject(NetObject *obj)
{
if(((NetObject *) mScopeObject) == obj)
return;
mScopeObject = obj;
}
void NetConnection::detachObject(GhostInfo *info)
{
// mark it for ghost killin'
info->flags |= GhostInfo::KillGhost;
// if the mask is in the zero range, we've got to move it up...
if(!info->updateMask)
{
info->updateMask = 0xFFFFFFFF;
ghostPushNonZero(info);
}
if(info->obj)
{
if(info->prevObjectRef)
info->prevObjectRef->nextObjectRef = info->nextObjectRef;
else
info->obj->mFirstObjectRef = info->nextObjectRef;
if(info->nextObjectRef)
info->nextObjectRef->prevObjectRef = info->prevObjectRef;
// remove it from the lookup table
U32 id = info->obj->getId();
for(GhostInfo **walk = &mGhostLookupTable[id & (GhostLookupTableSize - 1)]; *walk; walk = &((*walk)->nextLookupInfo))
{
GhostInfo *temp = *walk;
if(temp == info)
{
*walk = temp->nextLookupInfo;
break;
}
}
info->prevObjectRef = info->nextObjectRef = NULL;
info->obj = NULL;
}
}
void NetConnection::freeGhostInfo(GhostInfo *ghost)
{
AssertFatal(ghost->arrayIndex < mGhostFreeIndex, "Ghost already freed.");
if(ghost->arrayIndex < mGhostZeroUpdateIndex)
{
AssertFatal(ghost->updateMask != 0, "Invalid ghost mask.");
ghost->updateMask = 0;
ghostPushToZero(ghost);
}
ghostPushZeroToFree(ghost);
AssertFatal(ghost->updateChain == NULL, "Ack!");
}
//-----------------------------------------------------------------------------
void NetConnection::objectLocalScopeAlways(NetObject *obj)
{
if(!isGhostingFrom())
return;
objectInScope(obj);
for(GhostInfo *walk = mGhostLookupTable[obj->getId() & (GhostLookupTableSize - 1)]; walk; walk = walk->nextLookupInfo)
{
if(walk->obj != obj)
continue;
walk->flags |= GhostInfo::ScopeLocalAlways;
return;
}
}
void NetConnection::objectLocalClearAlways(NetObject *obj)
{
if(!isGhostingFrom())
return;
for(GhostInfo *walk = mGhostLookupTable[obj->getId() & (GhostLookupTableSize - 1)]; walk; walk = walk->nextLookupInfo)
{
if(walk->obj != obj)
continue;
walk->flags &= ~GhostInfo::ScopeLocalAlways;
return;
}
}
bool NetConnection::validateGhostArray()
{
AssertFatal(mGhostZeroUpdateIndex >= 0 && mGhostZeroUpdateIndex <= mGhostFreeIndex, "Invalid update index range.");
AssertFatal(mGhostFreeIndex <= MaxGhostCount, "Invalid free index range.");
U32 i;
for(i = 0; i < mGhostZeroUpdateIndex; i ++)
{
AssertFatal(mGhostArray[i]->arrayIndex == i, "Invalid array index.");
AssertFatal(mGhostArray[i]->updateMask != 0, "Invalid ghost mask.");
}
for(; i < mGhostFreeIndex; i ++)
{
AssertFatal(mGhostArray[i]->arrayIndex == i, "Invalid array index.");
AssertFatal(mGhostArray[i]->updateMask == 0, "Invalid ghost mask.");
}
for(; i < MaxGhostCount; i++)
{
AssertFatal(mGhostArray[i]->arrayIndex == i, "Invalid array index.");
}
return true;
}
void NetConnection::objectInScope(NetObject *obj)
{
if (!mScoping || !isGhostingFrom())
return;
if (obj->isScopeLocal() && !isLocalConnection())
return;
S32 index = obj->getId() & (GhostLookupTableSize - 1);
// check if it's already in scope
// the object may have been cleared out without the lookupTable being cleared
// so validate that the object pointers are the same.
for(GhostInfo *walk = mGhostLookupTable[index ]; walk; walk = walk->nextLookupInfo)
{
if(walk->obj != obj)
continue;
walk->flags |= GhostInfo::InScope;
return;
}
if (mGhostFreeIndex == MaxGhostCount)
{
AssertWarn(0,"NetConnection::objectInScope: too many ghosts");
return;
}
GhostInfo *giptr = mGhostArray[mGhostFreeIndex];
ghostPushFreeToZero(giptr);
giptr->updateMask = 0xFFFFFFFF;
ghostPushNonZero(giptr);
giptr->flags = GhostInfo::NotYetGhosted | GhostInfo::InScope;
if(obj->mNetFlags.test(NetObject::ScopeAlways))
giptr->flags |= GhostInfo::ScopeAlways;
giptr->obj = obj;
giptr->updateChain = NULL;
giptr->updateSkipCount = 0;
giptr->connection = this;
giptr->nextObjectRef = obj->mFirstObjectRef;
if(obj->mFirstObjectRef)
obj->mFirstObjectRef->prevObjectRef = giptr;
giptr->prevObjectRef = NULL;
obj->mFirstObjectRef = giptr;
giptr->nextLookupInfo = mGhostLookupTable[index];
mGhostLookupTable[index] = giptr;
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
}
//-----------------------------------------------------------------------------
void NetConnection::handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount)
{
if(( message == SendNextDownloadRequest
|| message == FileDownloadSizeMessage
|| message == GhostAlwaysStarting
|| message == GhostAlwaysDone
|| message == EndGhosting) && !isGhostingTo())
{
setLastError("Invalid packet.");
return;
}
S32 i;
GhostSave sv;
switch(message)
{
case GhostAlwaysDone:
mGhostingSequence = sequence;
// ok, all the ghost always objects are now on the client... but!
// it's possible that there were some file load errors...
// if so, we need to indicate to the server to restart ghosting, after
// we download all the files...
sv.ghost = NULL;
mGhostAlwaysSaveList.push_back(sv);
if(mGhostAlwaysSaveList.size() == 1)
loadNextGhostAlwaysObject(true);
break;
case ReadyForNormalGhosts:
if(sequence != mGhostingSequence)
return;
Con::executef(this, 1, "onGhostAlwaysObjectsReceived");
Con::printf("Ghost Always objects received.");
mGhosting = true;
for(i = 0; i < mGhostFreeIndex; i++)
{
if(mGhostArray[i]->flags & GhostInfo::ScopedEvent)
mGhostArray[i]->flags &= ~(GhostInfo::Ghosting | GhostInfo::ScopedEvent);
}
break;
case EndGhosting:
// just delete all the local ghosts,
// and delete all the ghosts in the current save list
for(i = 0; i < MaxGhostCount; i++)
{
if(mLocalGhosts[i])
{
mLocalGhosts[i]->deleteObject();
mLocalGhosts[i] = NULL;
}
}
while(mGhostAlwaysSaveList.size())
{
delete mGhostAlwaysSaveList[0].ghost;
mGhostAlwaysSaveList.pop_front();
}
break;
case GhostAlwaysStarting:
Con::executef(2, "onGhostAlwaysStarted", Con::getIntArg(ghostCount));
break;
case SendNextDownloadRequest:
sendNextFileDownloadRequest();
break;
case FileDownloadSizeMessage:
mCurrentFileBufferSize = sequence;
mCurrentFileBuffer = dRealloc(mCurrentFileBuffer, mCurrentFileBufferSize);
mCurrentFileBufferOffset = 0;
break;
}
}
void NetConnection::activateGhosting()
{
if(!isGhostingFrom())
return;
mGhostingSequence++;
// iterate through the ghost always objects and InScope them...
// also post em all to the other side.
SimSet* ghostAlwaysSet = Sim::getGhostAlwaysSet();
SimSet::iterator i;
AssertFatal((mGhostFreeIndex == 0) && (mGhostZeroUpdateIndex == 0), "Error: ghosts in the ghost list before activate.");
U32 sz = ghostAlwaysSet->size();
S32 j;
for(j = 0; j < sz; j++)
{
U32 idx = MaxGhostCount - sz + j;
mGhostArray[j] = mGhostRefs + idx;
mGhostArray[j]->arrayIndex = j;
}
for(j = sz; j < MaxGhostCount; j++)
{
U32 idx = j - sz;
mGhostArray[j] = mGhostRefs + idx;
mGhostArray[j]->arrayIndex = j;
}
mScoping = true; // so that objectInScope will work
for(i = ghostAlwaysSet->begin(); i != ghostAlwaysSet->end(); i++)
{
AssertFatal(dynamic_cast<NetObject *>(*i) != NULL, avar("Non NetObject in GhostAlwaysSet: %s", (*i)->getClassName()));
NetObject *obj = (NetObject *)(*i);
if(obj->mNetFlags.test(NetObject::Ghostable))
objectInScope(obj);
}
sendConnectionMessage(GhostAlwaysStarting, mGhostingSequence, ghostAlwaysSet->size());
for(j = mGhostZeroUpdateIndex - 1; j >= 0; j--)
{
AssertFatal((mGhostArray[j]->flags & GhostInfo::ScopeAlways) != 0, "Non-scope always in the scope always list.")
// we may end up resending state here, but at least initial state
// will not be resent.
mGhostArray[j]->updateMask = 0;
ghostPushToZero(mGhostArray[j]);
mGhostArray[j]->flags &= ~GhostInfo::NotYetGhosted;
mGhostArray[j]->flags |= GhostInfo::ScopedEvent;
postNetEvent(new GhostAlwaysObjectEvent(mGhostArray[j]->obj, mGhostArray[j]->index));
}
sendConnectionMessage(GhostAlwaysDone, mGhostingSequence);
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
}
void NetConnection::clearGhostInfo()
{
// gotta clear out the ghosts...
for(PacketNotify *walk = mNotifyQueueHead; walk; walk = walk->nextPacket)
{
ghostPacketReceived(walk);
walk->ghostList = NULL;
}
for(S32 i = 0; i < MaxGhostCount; i++)
{
if(mGhostRefs[i].arrayIndex < mGhostFreeIndex)
{
detachObject(&mGhostRefs[i]);
freeGhostInfo(&mGhostRefs[i]);
}
}
AssertFatal((mGhostFreeIndex == 0) && (mGhostZeroUpdateIndex == 0), "Invalid indices.");
}
void NetConnection::resetGhosting()
{
if(!isGhostingFrom())
return;
// stop all ghosting activity
// send a message to the other side notifying of this
mGhosting = false;
mScoping = false;
sendConnectionMessage(EndGhosting, mGhostingSequence);
mGhostingSequence++;
clearGhostInfo();
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
}
void NetConnection::setGhostAlwaysObject(NetObject *object, U32 index)
{
if(!isGhostingTo())
{
object->deleteObject();
setLastError("Invalid packet.");
return;
}
object->mNetFlags = NetObject::IsGhost;
object->mNetIndex = index;
// while there's an object waiting...
if (isLocalConnection()) {
object->mServerObject = mRemoteConnection->resolveObjectFromGhostIndex(index);
}
GhostSave sv;
sv.ghost = object;
sv.index = index;
mGhostAlwaysSaveList.push_back(sv);
// check if we are already downloading files for a previous object:
if(mGhostAlwaysSaveList.size() == 1)
loadNextGhostAlwaysObject(true); // the initial call always has "new" files
}
void NetConnection::fileDownloadSegmentComplete()
{
// this is called when a the file list has finished processing...
// at this point we can try again to add the object
// subclasses can override this to do, for example, datablock redos.
if(mGhostAlwaysSaveList.size())
loadNextGhostAlwaysObject(mNumDownloadedFiles != 0);
}
void NetConnection::loadNextGhostAlwaysObject(bool hadNewFiles)
{
if(!mGhostAlwaysSaveList.size())
return;
while(mGhostAlwaysSaveList.size())
{
// only check for new files if this is the first load, or if new
// files were downloaded from the server.
if(hadNewFiles)
ResourceManager->setMissingFileLogging(true);
ResourceManager->clearMissingFileList();
NetObject *object = mGhostAlwaysSaveList[0].ghost;
U32 index = mGhostAlwaysSaveList[0].index;
if(!object)
{
// a null object is used to signify that the last ghost in the list is down
mGhostAlwaysSaveList.pop_front();
AssertFatal(mGhostAlwaysSaveList.size() == 0, "Error! Ghost save list should be empty!");
sendConnectionMessage(ReadyForNormalGhosts, mGhostingSequence);
ResourceManager->setMissingFileLogging(false);
return;
}
mFilesWereDownloaded = hadNewFiles;
if(!object->registerObject())
{
mFilesWereDownloaded = false;
// make sure there's an error message if necessary
if(!mErrorBuffer[0])
setLastError("Invalid packet.");
// if there were no new files, make sure the error message
// is the one from the last time we tried to add this object
if(!hadNewFiles)
{
dStrcpy(mErrorBuffer, mLastFileErrorBuffer);
ResourceManager->setMissingFileLogging(false);
return;
}
// object failed to load, let's see if it had any missing files
if(!ResourceManager->getMissingFileList(mMissingFileList))
{
// no missing files, must be an error
// connection will automagically delete the ghost always list
// when this error is reported.
ResourceManager->setMissingFileLogging(false);
return;
}
// ok, copy the error buffer out to a scratch pad for now
dStrcpy(mLastFileErrorBuffer, mErrorBuffer);
mErrorBuffer[0] = 0;
// request the missing files...
mNumDownloadedFiles = 0;
sendNextFileDownloadRequest();
break;
}
mFilesWereDownloaded = false;
ResourceManager->setMissingFileLogging(false);
addObject(object);
mGhostAlwaysSaveList.pop_front();
AssertFatal(mLocalGhosts[index] == NULL, "Ghost already in table!");
mLocalGhosts[index] = object;
hadNewFiles = true;
}
}
//-----------------------------------------------------------------------------
NetObject *NetConnection::resolveGhost(S32 id)
{
return mLocalGhosts[id];
}
NetObject *NetConnection::resolveObjectFromGhostIndex(S32 id)
{
return mGhostRefs[id].obj;
}
S32 NetConnection::getGhostIndex(NetObject *obj)
{
if(!isGhostingFrom())
return obj->mNetIndex;
S32 index = obj->getId() & (GhostLookupTableSize - 1);
for(GhostInfo *gptr = mGhostLookupTable[index]; gptr; gptr = gptr->nextLookupInfo)
{
if(gptr->obj == obj && (gptr->flags & (GhostInfo::KillingGhost | GhostInfo::Ghosting | GhostInfo::NotYetGhosted | GhostInfo::KillGhost)) == 0)
return gptr->index;
}
return -1;
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void NetConnection::ghostWriteStartBlock(ResizeBitStream *stream)
{
// Ok, writing the start block for the ghosts:
// here's how it goes.
//
// First we record out all the indices and class ids for all the objects
// This is so when the objects are read in, all the objects are instantiated
// before they are unpacked. The unpack code may reference other
// existing ghosts, so we want to make sure that all the ghosts are in the
// table with the correct pointers before any of the unpacks are called.
stream->write(mGhostingSequence);
// first write out the indices and ids:
for(U32 i = 0; i < MaxGhostCount; i++)
{
if(mLocalGhosts[i])
{
stream->writeFlag(true);
stream->writeInt(i, GhostIdBitSize);
stream->writeClassId(mLocalGhosts[i]->getClassId(getNetClassGroup()), NetClassTypeObject, getNetClassGroup());
stream->validate();
}
}
// mark off the end of the ghost list:
// it would be more space efficient to write out a count of active ghosts followed
// by index run lengths, but hey, what's a few bits here and there?
stream->writeFlag(false);
// then, for each ghost written into the start block, write the full pack update
// into the start block. For demos to work properly, packUpdate must
// be callable from client objects.
for(U32 i = 0; i < MaxGhostCount; i++)
{
if(mLocalGhosts[i])
{
mLocalGhosts[i]->packUpdate(this, 0xFFFFFFFF, stream);
stream->validate();
}
}
}
void NetConnection::ghostReadStartBlock(BitStream *stream)
{
stream->read(&mGhostingSequence);
// read em back in.
// first, read in the index/class id, construct the object, and place it in mLocalGhosts[i]
while(stream->readFlag())
{
U32 index = stream->readInt(GhostIdBitSize);
S32 tag = stream->readClassId(NetClassTypeObject, getNetClassGroup());
NetObject *obj = (NetObject *) ConsoleObject::create(getNetClassGroup(), NetClassTypeObject, tag);
if(!obj)
{
setLastError("Invalid packet.");
return;
}
obj->mNetFlags = NetObject::IsGhost;
obj->mNetIndex = index;
mLocalGhosts[index] = obj;
}
// now, all the ghosts are in the mLocalGhosts, so we loop
// through all non-null mLocalGhosts, unpacking the objects
// as we go:
for(U32 i = 0; i < MaxGhostCount; i++)
{
if(mLocalGhosts[i])
{
mLocalGhosts[i]->unpackUpdate(this, stream);
if(!mLocalGhosts[i]->registerObject())
{
if(mErrorBuffer[0])
setLastError("Invalid packet.");
return;
}
addObject(mLocalGhosts[i]);
}
}
// MARKF - TODO - looks like we could have memory leaks here
// if there are errors.
}