Engine directory for ticket #1

This commit is contained in:
DavidWyand-GG 2012-09-19 11:15:01 -04:00
parent 352279af7a
commit 7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions

View file

@ -0,0 +1,607 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "T3D/gameBase/hifi/hifiGameProcess.h"
#include "platform/profiler.h"
#include "core/frameAllocator.h"
#include "core/stream/bitStream.h"
#include "math/mathUtils.h"
#include "T3D/gameBase/hifi/hifiMoveList.h"
#include "T3D/gameBase/gameConnection.h"
#include "T3D/gameFunctions.h"
MODULE_BEGIN( ProcessList )
MODULE_INIT
{
HifiServerProcessList::init();
HifiClientProcessList::init();
}
MODULE_SHUTDOWN
{
HifiServerProcessList::shutdown();
HifiClientProcessList::shutdown();
}
MODULE_END;
void HifiServerProcessList::init()
{
smServerProcessList = new HifiServerProcessList();
}
void HifiServerProcessList::shutdown()
{
delete smServerProcessList;
}
void HifiClientProcessList::init()
{
smClientProcessList = new HifiClientProcessList();
}
void HifiClientProcessList::shutdown()
{
delete smClientProcessList;
}
//----------------------------------------------------------------------------
F32 gMaxHiFiVelSq = 100 * 100;
namespace
{
inline GameBase * GetGameBase(ProcessObject * obj)
{
return static_cast<GameBase*>(obj);
}
// local work class
struct GameBaseListNode
{
GameBaseListNode()
{
mPrev=this;
mNext=this;
mObject=NULL;
}
GameBaseListNode * mPrev;
GameBaseListNode * mNext;
GameBase * mObject;
void linkBefore(GameBaseListNode * obj)
{
// Link this before obj
mNext = obj;
mPrev = obj->mPrev;
obj->mPrev = this;
mPrev->mNext = this;
}
};
// Structure used for synchronizing move lists on client/server
struct MoveSync
{
enum { ActionCount = 4 };
S32 moveDiff;
S32 moveDiffSteadyCount;
S32 moveDiffSameSignCount;
bool doAction() { return moveDiffSteadyCount>=ActionCount || moveDiffSameSignCount>=4*ActionCount; }
void reset() { moveDiff=0; moveDiffSteadyCount=0; moveDiffSameSignCount=0; }
void update(S32 diff);
} moveSync;
void MoveSync::update(S32 diff)
{
if (diff && diff==moveDiff)
{
moveDiffSteadyCount++;
moveDiffSameSignCount++;
}
else if (diff*moveDiff>0)
{
moveDiffSteadyCount = 0;
moveDiffSameSignCount++;
}
else
reset();
moveDiff = diff;
}
} // namespace
//--------------------------------------------------------------------------
// HifiClientProcessList
//--------------------------------------------------------------------------
HifiClientProcessList::HifiClientProcessList()
{
mSkipAdvanceObjectsMs = 0;
mForceHifiReset = false;
mCatchup = 0;
}
bool HifiClientProcessList::advanceTime( SimTime timeDelta )
{
PROFILE_SCOPE( AdvanceClientTime );
if ( mSkipAdvanceObjectsMs && timeDelta > mSkipAdvanceObjectsMs )
{
timeDelta -= mSkipAdvanceObjectsMs;
advanceTime( mSkipAdvanceObjectsMs );
AssertFatal( !mSkipAdvanceObjectsMs, "mSkipAdvanceObjectsMs must always be positive." );
}
if ( doBacklogged( timeDelta ) )
return false;
// remember interpolation value because we might need to set it back
F32 oldLastDelta = mLastDelta;
bool ret = Parent::advanceTime( timeDelta );
if ( !mSkipAdvanceObjectsMs )
{
AssertFatal( mLastDelta >= 0.0f && mLastDelta <= 1.0f, "mLastDelta must always be zero to one." );
for ( ProcessObject *pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next )
{
if ( pobj->isTicking() )
pobj->interpolateTick( mLastDelta );
}
// Inform objects of total elapsed delta so they can advance
// client side animations.
F32 dt = F32( timeDelta ) / 1000;
for ( ProcessObject *pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
pobj->advanceTime( dt );
}
}
else
{
mSkipAdvanceObjectsMs -= timeDelta;
mLastDelta = oldLastDelta;
}
return ret;
}
void HifiClientProcessList::onAdvanceObjects()
{
GameConnection* connection = GameConnection::getConnectionToServer();
if(connection)
{
// process any demo blocks that are NOT moves, and exactly one move
// we advance time in the demo stream by a move inserted on
// each tick. So before doing the tick processing we advance
// the demo stream until a move is ready
if(connection->isPlayingBack())
{
U32 blockType;
do
{
blockType = connection->getNextBlockType();
bool res = connection->processNextBlock();
// if there are no more blocks, exit out of this function,
// as no more client time needs to process right now - we'll
// get it all on the next advanceClientTime()
if(!res)
return;
}
while(blockType != GameConnection::BlockTypeMove);
}
if (!mSkipAdvanceObjectsMs)
{
connection->mMoveList->collectMove();
advanceObjects();
}
connection->mMoveList->onAdvanceObjects();
}
}
void HifiClientProcessList::onTickObject(ProcessObject * pobj)
{
// Each object is advanced a single tick
// If it's controlled by a client, tick using a move.
Move *movePtr;
U32 numMoves;
GameConnection *con = pobj->getControllingClient();
SimObjectPtr<GameBase> obj = getGameBase( pobj );
if ( obj && con && con->getControlObject() == obj && con->mMoveList->getMoves( &movePtr, &numMoves) )
{
#ifdef TORQUE_DEBUG_NET_MOVES
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
#endif
obj->processTick( movePtr );
if ( bool(obj) && obj->getControllingClient() )
{
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
// set checksum if not set or check against stored value if set
movePtr->checksum = newsum;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf( "move checksum: %i, (start %i), (move %f %f %f)",
movePtr->checksum,sum,movePtr->yaw,movePtr->y,movePtr->z );
#endif
}
con->mMoveList->clearMoves( 1 );
}
else if ( pobj->isTicking() )
pobj->processTick( 0 );
if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) )
{
GameConnection * serverConnection = GameConnection::getConnectionToServer();
TickCacheEntry * tce = obj->getTickCache().addCacheEntry();
BitStream bs( tce->packetData, TickCacheEntry::MaxPacketSize );
obj->writePacketData( serverConnection, &bs );
Point3F vel = obj->getVelocity();
F32 velSq = mDot( vel, vel );
gMaxHiFiVelSq = getMax( gMaxHiFiVelSq, velSq );
}
}
void HifiClientProcessList::advanceObjects()
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("Advance client time...");
#endif
// client re-computes this each time objects are advanced
gMaxHiFiVelSq = 0;
Parent::advanceObjects();
// We need to consume a move on the connections whether
// there is a control object to consume the move or not,
// otherwise client and server can get out of sync move-wise
// during startup. If there is a control object, we cleared
// a move above. Handle case where no control object here.
// Note that we might consume an extra move here and there when
// we had a control object in above loop but lost it during tick.
// That is no big deal so we don't bother trying to carefully
// track it.
GameConnection * client = GameConnection::getConnectionToServer();
if (client && client->getControlObject() == NULL)
client->mMoveList->clearMoves(1);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
}
void HifiClientProcessList::ageTickCache(S32 numToAge, S32 len)
{
for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
GameBase *obj = getGameBase(pobj);
if ( obj && obj->getTypeMask() & GameBaseHiFiObjectType )
obj->getTickCache().ageCache(numToAge,len);
}
}
void HifiClientProcessList::updateMoveSync(S32 moveDiff)
{
moveSync.update(moveDiff);
if (moveSync.doAction() && moveDiff<0)
{
skipAdvanceObjects(TickMs * -moveDiff);
moveSync.reset();
}
}
void HifiClientProcessList::clientCatchup(GameConnection * connection)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("client catching up... (%i)%s", mCatchup, mForceHifiReset ? " reset" : "");
#endif
if (connection->getControlObject() && connection->getControlObject()->isGhostUpdated())
// if control object is reset, make sure moves are reset too
connection->mMoveList->resetCatchup();
const F32 maxVel = mSqrt(gMaxHiFiVelSq) * 1.25f;
F32 dt = F32(mCatchup+1) * TickSec;
Point3F bigDelta(maxVel*dt,maxVel*dt,maxVel*dt);
// walk through all process objects looking for ones which were updated
// -- during first pass merely collect neighbors which need to be reset and updated in unison
ProcessObject * pobj;
if (mCatchup && !mForceHifiReset)
{
for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
GameBase *obj = getGameBase( pobj );
static SimpleQueryList nearby;
nearby.mList.clear();
// check for nearby objects which need to be reset and then caught up
// note the funky loop logic -- first time through obj is us, then
// we start iterating through nearby list (to look for objects nearby
// the nearby objects), which is why index starts at -1
// [objects nearby the nearby objects also get added to the nearby list]
for (S32 i=-1; obj; obj = ++i<nearby.mList.size() ? (GameBase*)nearby.mList[i] : NULL)
{
if (obj->isGhostUpdated() && (obj->getTypeMask() & GameBaseHiFiObjectType) && !obj->isNetNearbyAdded())
{
Point3F start = obj->getWorldSphere().center;
Point3F end = start + 1.1f * dt * obj->getVelocity();
F32 rad = 1.5f * obj->getWorldSphere().radius;
// find nearby items not updated but are hi fi, mark them as updated (and restore old loc)
// check to see if added items have neighbors that need updating
Box3F box;
Point3F rads(rad,rad,rad);
box.minExtents = box.maxExtents = start;
box.minExtents -= bigDelta + rads;
box.maxExtents += bigDelta + rads;
// CodeReview - this is left in for MBU, but also so we can deal with the issue later.
// add marble blast hack so hifi networking can see hidden objects
// (since hidden is under control of hifi networking)
// gForceNotHidden = true;
S32 j = nearby.mList.size();
gClientContainer.findObjects(box, GameBaseHiFiObjectType, SimpleQueryList::insertionCallback, &nearby);
// CodeReview - this is left in for MBU, but also so we can deal with the issue later.
// disable above hack
// gForceNotHidden = false;
// drop anyone not heading toward us or already checked
for (; j<nearby.mList.size(); j++)
{
GameBase * obj2 = (GameBase*)nearby.mList[j];
// if both passive, these guys don't interact with each other
bool passive = obj->isHifiPassive() && obj2->isHifiPassive();
if (!obj2->isGhostUpdated() && !passive)
{
// compare swept spheres of obj and obj2
// if collide, reset obj2, setGhostUpdated(true), and continue
Point3F end2 = obj2->getWorldSphere().center;
Point3F start2 = end2 - 1.1f * dt * obj2->getVelocity();
F32 rad2 = 1.5f * obj->getWorldSphere().radius;
if (MathUtils::capsuleCapsuleOverlap(start,end,rad,start2,end2,rad2))
{
// better add obj2
obj2->getTickCache().beginCacheList();
TickCacheEntry * tce = obj2->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj2->readPacketData(connection,&bs);
obj2->setGhostUpdated(true);
// continue so we later add the neighbors too
continue;
}
}
// didn't pass above test...so don't add it or nearby objects
nearby.mList[j] = nearby.mList.last();
nearby.mList.decrement();
j--;
}
obj->setNetNearbyAdded(true);
}
}
}
}
// save water mark -- for game base list
FrameAllocatorMarker mark;
// build ordered list of client objects which need to be caught up
GameBaseListNode list;
for (pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
{
GameBase *obj = getGameBase( pobj );
//GameBase *obj = dynamic_cast<GameBase*>( pobj );
//GameBase *obj = (GameBase*)pobj;
// Not a GameBase object so nothing to do.
if ( !obj )
continue;
if (obj->isGhostUpdated() && (obj->getTypeMask() & GameBaseHiFiObjectType))
{
// construct process object and add it to the list
// hold pointer to our object in mAfterObject
GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode));
po->mObject = obj;
po->linkBefore(&list);
// begin iterating through tick list (skip first tick since that is the state we've been reset to)
obj->getTickCache().beginCacheList();
obj->getTickCache().incCacheList();
}
else if (mForceHifiReset && (obj->getTypeMask() & GameBaseHiFiObjectType))
{
// add all hifi objects
obj->getTickCache().beginCacheList();
TickCacheEntry * tce = obj->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->readPacketData(connection,&bs);
obj->setGhostUpdated(true);
// construct process object and add it to the list
// hold pointer to our object in mAfterObject
GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode));
po->mObject = obj;
po->linkBefore(&list);
}
else if (obj == connection->getControlObject() && obj->isGhostUpdated())
{
// construct process object and add it to the list
// hold pointer to our object in mAfterObject
// .. but this is not a hi fi object, so don't mess with tick cache
GameBaseListNode * po = (GameBaseListNode*)FrameAllocator::alloc(sizeof(GameBaseListNode));
po->mObject = obj;
po->linkBefore(&list);
}
else if (obj->isGhostUpdated())
{
// not hifi but we were updated, so perform net smooth now
obj->computeNetSmooth(mLastDelta);
}
// clear out work flags
obj->setNetNearbyAdded(false);
obj->setGhostUpdated(false);
}
// run through all the moves in the move list so we can play them with our control object
Move* movePtr;
U32 numMoves;
connection->mMoveList->resetClientMoves();
connection->mMoveList->getMoves(&movePtr, &numMoves);
AssertFatal(mCatchup<=numMoves,"doh");
// tick catchup time
for (U32 m=0; m<mCatchup; m++)
{
for (GameBaseListNode * walk = list.mNext; walk != &list; walk = walk->mNext)
{
// note that we get object from after object not getGameBase function
// this is because we are an on the fly linked list which uses mAfterObject
// rather than the linked list embedded in GameBase (clean this up?)
GameBase * obj = walk->mObject;
// it's possible for a non-hifi object to get in here, but
// only if it is a control object...make sure we don't do any
// of the tick cache stuff if we are not hifi.
bool hifi = obj->getTypeMask() & GameBaseHiFiObjectType;
TickCacheEntry * tce = hifi ? obj->getTickCache().incCacheList() : NULL;
// tick object
if (obj==connection->getControlObject())
{
obj->processTick(movePtr);
movePtr->checksum = obj->getPacketDataChecksum(connection);
movePtr++;
}
else
{
AssertFatal(tce && hifi,"Should not get in here unless a hi fi object!!!");
obj->processTick(tce->move);
}
if (hifi)
{
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->writePacketData(connection,&bs);
}
}
if (connection->getControlObject() == NULL)
movePtr++;
}
connection->mMoveList->clearMoves(mCatchup);
// Handle network error smoothing here...but only for control object
GameBase * control = connection->getControlObject();
if (control && !control->isNewGhost())
{
control->computeNetSmooth(mLastDelta);
control->setNewGhost(false);
}
if (moveSync.doAction() && moveSync.moveDiff>0)
{
S32 moveDiff = moveSync.moveDiff;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("client timewarping to catchup %i moves",moveDiff);
#endif
while (moveDiff--)
advanceObjects();
moveSync.reset();
}
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("---------");
#endif
// all caught up
mCatchup = 0;
}
//--------------------------------------------------------------------------
// HifiServerProcessList
//--------------------------------------------------------------------------
void HifiServerProcessList::onTickObject(ProcessObject * pobj)
{
// Each object is advanced a single tick
// If it's controlled by a client, tick using a move.
Move *movePtr;
U32 numMoves;
GameConnection *con = pobj->getControllingClient();
SimObjectPtr<GameBase> obj = getGameBase( pobj );
if ( obj && con && con->getControlObject() == obj && con->mMoveList->getMoves( &movePtr, &numMoves ) )
{
#ifdef TORQUE_DEBUG_NET_MOVES
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
#endif
obj->processTick(movePtr);
if ( bool(obj) && obj->getControllingClient() )
{
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
// check move checksum
if ( movePtr->checksum != newsum )
{
#ifdef TORQUE_DEBUG_NET_MOVES
if ( !obj->mIsAiControlled )
Con::printf( "move %i checksum disagree: %i != %i, (start %i), (move %f %f %f)",
movePtr->id, movePtr->checksum, newsum, sum, movePtr->yaw, movePtr->y, movePtr->z );
#endif
movePtr->checksum = Move::ChecksumMismatch;
}
else
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf( "move %i checksum agree: %i == %i, (start %i), (move %f %f %f)",
movePtr->id, movePtr->checksum, newsum, sum, movePtr->yaw, movePtr->y, movePtr->z );
#endif
}
// Adding this seems to fix constant corrections, but is it
// really a sound fix?
con->mMoveList->clearMoves( 1 );
}
}
else if ( pobj->isTicking() )
pobj->processTick( 0 );
}

View file

@ -0,0 +1,91 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _GAMEPROCESS_HIFI_H_
#define _GAMEPROCESS_HIFI_H_
#ifndef _GAMEPROCESS_H_
#include "T3D/gameBase/gameProcess.h"
#endif
/// List to keep track of GameBases to process.
class HifiClientProcessList : public ClientProcessList
{
typedef ClientProcessList Parent;
friend class HifiMoveList;
public:
HifiClientProcessList();
// ProcessList
bool advanceTime(SimTime timeDelta);
// ClientProcessList
void clientCatchup(GameConnection*);
static void init();
static void shutdown();
protected:
// tick cache functions -- client only
void ageTickCache(S32 numToAge, S32 len);
void forceHifiReset(bool reset) { mForceHifiReset=reset; }
U32 getTotalTicks() { return mTotalTicks; }
void updateMoveSync(S32 moveDiff);
void skipAdvanceObjects(U32 ms) { mSkipAdvanceObjectsMs += ms; }
// ProcessList
void onTickObject(ProcessObject *);
void advanceObjects();
void onAdvanceObjects();
void setCatchup(U32 catchup) { mCatchup = catchup; }
protected:
U32 mSkipAdvanceObjectsMs;
bool mForceHifiReset;
U32 mCatchup;
};
class HifiServerProcessList : public ServerProcessList
{
typedef ServerProcessList Parent;
public:
HifiServerProcessList() {}
static void init();
static void shutdown();
protected:
// ProcessList
void onTickObject(ProcessObject *);
};
#endif // _GAMEPROCESS_HIFI_H_

View file

@ -0,0 +1,443 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "T3D/gameBase/hifi/hifiMoveList.h"
#include "T3D/gameBase/hifi/hifiGameProcess.h"
#include "T3D/gameBase/gameConnection.h"
#include "core/stream/bitStream.h"
#define MAX_MOVE_PACKET_SENDS 4
const U32 DefaultTargetMoveListSize = 3;
const U32 DefaultMaxMoveSizeList = 5;
const F32 DefaultSmoothMoveAvg = 0.15f;
const F32 DefaultMoveListSizeSlack = 1.0f;
HifiMoveList::HifiMoveList()
{
mLastSentMove = 0;
mAvgMoveQueueSize = DefaultTargetMoveListSize ;
mTargetMoveListSize = DefaultTargetMoveListSize;
mMaxMoveListSize = DefaultMaxMoveSizeList;
mSmoothMoveAvg = DefaultSmoothMoveAvg;
mMoveListSizeSlack = DefaultMoveListSizeSlack;
mTotalServerTicks = ServerTicksUninitialized;
mSuppressMove = false;
}
void HifiMoveList::updateClientServerTickDiff(S32 & tickDiff)
{
if (mLastMoveAck==0)
tickDiff=0;
// Make adjustments to move list to account for tick mis-matches between client and server.
if (tickDiff>0)
{
// Server ticked more than client. Adjust for this by reseting all hifi objects
// to a later position in the tick cache (see ageTickCache below) and at the same
// time pulling back some moves we thought we had made (so that time on client
// doesn't change).
S32 dropTicks = tickDiff;
while (dropTicks)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("dropping move%s",mLastClientMove>mFirstMoveIndex ? "" : " but none there");
#endif
if (mLastClientMove>mFirstMoveIndex)
mLastClientMove--;
else
tickDiff--;
dropTicks--;
}
AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move.");
}
else
{
// Client ticked more than server. Adjust for this by taking extra moves
// (either adding back moves that were dropped above, or taking new ones).
for (S32 i=0; i<-tickDiff; i++)
{
if (mMoveVec.size() > mLastClientMove - mFirstMoveIndex)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("add back move");
#endif
mLastClientMove++;
}
else
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("add back move -- create one");
#endif
collectMove();
mLastClientMove++;
}
}
}
// drop moves that are not made yet (because we rolled them back) and not yet sent
U32 len = getMax(mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex);
mMoveVec.setSize(len);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("move list size: %i, last move: %i, last sent: %i",mMoveVec.size(),mLastClientMove-mFirstMoveIndex,mLastSentMove-mFirstMoveIndex);
#endif
}
S32 HifiMoveList::getServerTicks(U32 serverTickNum)
{
S32 serverTicks=0;
if (serverTicksInitialized())
{
// handle tick wrapping...
const S32 MaxTickCount = (1<<TotalTicksBits);
const S32 HalfMaxTickCount = MaxTickCount>>1;
U32 prevTickNum = mTotalServerTicks & TotalTicksMask;
serverTicks = serverTickNum-prevTickNum;
if (serverTicks>HalfMaxTickCount)
serverTicks -= MaxTickCount;
else if (-serverTicks>HalfMaxTickCount)
serverTicks += MaxTickCount;
AssertFatal(serverTicks>=0,"Server can't tick backwards!!!");
if (serverTicks<0)
serverTicks=0;
}
mTotalServerTicks = serverTickNum;
return serverTicks;
}
void HifiMoveList::markControlDirty()
{
mLastClientMove = mLastMoveAck;
// save state for future update
GameBase *obj = mConnection->getControlObject();
AssertFatal(obj,"ClientProcessList::markControlDirty: no control object");
obj->setGhostUpdated(true);
obj->getTickCache().beginCacheList();
TickCacheEntry * tce = obj->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->writePacketData( mConnection, &bs );
}
void HifiMoveList::resetMoveList()
{
mMoveVec.clear();
mLastMoveAck = 0;
mLastClientMove = 0;
mFirstMoveIndex = 0;
mLastSentMove = 0;
}
U32 HifiMoveList::getMoves(Move** movePtr,U32* numMoves)
{
if (mConnection->isConnectionToServer())
// give back moves starting at the last client move...
return Parent::getMoves(movePtr,numMoves);
if (mSuppressMove || mMoveVec.size()==0)
{
*numMoves=0;
*movePtr=NULL;
}
else
{
*numMoves=1;
*movePtr=mMoveVec.begin();
}
return *numMoves;
}
void HifiMoveList::advanceMove()
{
S32 numMoves = mMoveVec.size();
mAvgMoveQueueSize *= (1.0f-mSmoothMoveAvg);
mAvgMoveQueueSize += mSmoothMoveAvg * F32(numMoves);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("moves remaining: %i, running avg: %f",numMoves,mAvgMoveQueueSize);
#endif
if (mAvgMoveQueueSize<mTargetMoveListSize-mMoveListSizeSlack && numMoves<mTargetMoveListSize && numMoves)
{
numMoves=0;
mAvgMoveQueueSize = (F32)getMax(S32(mAvgMoveQueueSize + mMoveListSizeSlack + 0.5f),numMoves);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("too few moves on server, padding with null move");
#endif
}
if (numMoves)
numMoves=1;
if ( mMoveVec.size()>mMaxMoveListSize || (mAvgMoveQueueSize>mTargetMoveListSize+mMoveListSizeSlack && mMoveVec.size()>mTargetMoveListSize) )
{
U32 drop = mMoveVec.size()-mTargetMoveListSize;
clearMoves(drop);
mAvgMoveQueueSize = (F32)mTargetMoveListSize;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("too many moves on server, dropping moves (%i)",drop);
#endif
}
mSuppressMove = numMoves == 0;
// now clear move
if (areMovesPending())
clearMoves(1);
}
void HifiMoveList::clientWriteMovePacket(BitStream *bstream)
{
if (!serverTicksInitialized())
resetMoveList();
AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index.");
// enforce limit on number of moves sent
if (mLastSentMove<mFirstMoveIndex)
mLastSentMove=mFirstMoveIndex;
U32 count = mLastSentMove-mFirstMoveIndex;
Move * move = mMoveVec.address();
U32 start = mLastMoveAck;
U32 offset;
for(offset = 0; offset < count; offset++)
if(move[offset].sendCount < MAX_MOVE_PACKET_SENDS)
break;
if(offset == count && count != 0)
offset--;
start += offset;
count -= offset;
if (count > MaxMoveCount)
count = MaxMoveCount;
bstream->writeInt(start,32);
bstream->writeInt(count,MoveCountBits);
Move * prevMove = NULL;
for (int i = 0; i < count; i++)
{
move[offset + i].sendCount++;
move[offset + i].pack(bstream,prevMove);
bstream->writeInt(move[offset + i].checksum,Move::ChecksumBits);
prevMove = &move[offset+i];
}
}
void HifiMoveList::serverReadMovePacket(BitStream *bstream)
{
// Server side packet read.
U32 start = bstream->readInt(32);
U32 count = bstream->readInt(MoveCountBits);
Move * prevMove = NULL;
Move prevMoveHolder;
// Skip forward (must be starting up), or over the moves
// we already have.
int skip = mLastMoveAck - start;
if (skip < 0)
{
mLastMoveAck = start;
}
else
{
if (skip > count)
skip = count;
for (int i = 0; i < skip; i++)
{
prevMoveHolder.unpack(bstream,prevMove);
prevMoveHolder.checksum = bstream->readInt(Move::ChecksumBits);
prevMove = &prevMoveHolder;
S32 idx = mMoveVec.size()-skip+i;
if (idx>=0)
{
#ifdef TORQUE_DEBUG_NET_MOVES
if (mMoveVec[idx].checksum != prevMoveHolder.checksum)
Con::printf("updated checksum on move %i from %i to %i",mMoveVec[idx].id,mMoveVec[idx].checksum,prevMoveHolder.checksum);
#endif
mMoveVec[idx].checksum = prevMoveHolder.checksum;
}
}
start += skip;
count = count - skip;
}
// Put the rest on the move list.
int index = mMoveVec.size();
mMoveVec.increment(count);
while (index < mMoveVec.size())
{
mMoveVec[index].unpack(bstream,prevMove);
mMoveVec[index].checksum = bstream->readInt(Move::ChecksumBits);
prevMove = &mMoveVec[index];
mMoveVec[index].id = start++;
index ++;
}
mLastMoveAck += count;
if (mMoveVec.size()>mMaxMoveListSize)
{
U32 drop = mMoveVec.size()-mTargetMoveListSize;
clearMoves(drop);
mAvgMoveQueueSize = (F32)mTargetMoveListSize;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("too many moves on server, dropping moves (%i)",drop);
#endif
}
}
void HifiMoveList::serverWriteMovePacket(BitStream * bstream)
{
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("ack %i minus %i",mLastMoveAck,mMoveVec.size());
#endif
// acknowledge only those moves that have been ticked
bstream->writeInt(mLastMoveAck - mMoveVec.size(),32);
// send over the current tick count on the server...
bstream->writeInt(ServerProcessList::get()->getTotalTicks() & TotalTicksMask, TotalTicksBits);
}
void HifiMoveList::clientReadMovePacket(BitStream * bstream)
{
if (!serverTicksInitialized())
resetMoveList();
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("pre move ack: %i", mLastMoveAck);
#endif
mLastMoveAck = bstream->readInt(32);
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("post move ack %i, first move %i, last move %i", mLastMoveAck, mFirstMoveIndex, mLastClientMove);
#endif
// This is how many times we've ticked since last ack -- before adjustments below
S32 ourTicks = mLastMoveAck - mFirstMoveIndex;
if (mLastMoveAck < mFirstMoveIndex)
mLastMoveAck = mFirstMoveIndex;
if(mLastMoveAck > mLastClientMove)
{
ourTicks -= mLastMoveAck-mLastClientMove;
mLastClientMove = mLastMoveAck;
}
while(mFirstMoveIndex < mLastMoveAck)
{
if (mMoveVec.size())
{
mMoveVec.pop_front();
mFirstMoveIndex++;
}
else
{
AssertWarn(1, "Popping off too many moves!");
mFirstMoveIndex = mLastMoveAck;
}
}
// get server ticks using total number of ticks on server to date...
U32 serverTickNum = bstream->readInt(TotalTicksBits);
S32 serverTicks = getServerTicks(serverTickNum);
S32 tickDiff = serverTicks - ourTicks;
#ifdef TORQUE_DEBUG_NET_MOVES
Con::printf("server ticks: %i, client ticks: %i, diff: %i%s", serverTicks, ourTicks, tickDiff, !tickDiff ? "" : " (ticks mis-match)");
#endif
// Apply the first (of two) client-side synchronization mechanisms. Key is that
// we need to both synchronize client/server move streams (so first move in list is made
// at same "time" on both client and server) and maintain the "time" at which the most
// recent move was made on the server. In both cases, "time" is the number of ticks
// it took to get to that move.
updateClientServerTickDiff(tickDiff);
// Apply the second (and final) client-side synchronization mechanism. The tickDiff adjustments above
// make sure time is preserved on client. But that assumes that a future (or previous) update will adjust
// time in the other direction, so that we don't get too far behind or ahead of the server. The updateMoveSync
// mechanism tracks us over time to make sure we eventually return to be in sync, and makes adjustments
// if we don't after a certain time period (number of updates). Unlike the tickDiff mechanism, when
// the updateMoveSync acts time is not preserved on the client.
HifiClientProcessList * processList = dynamic_cast<HifiClientProcessList*>(ClientProcessList::get());
if (processList)
{
processList->updateMoveSync(mLastSentMove-mLastClientMove);
// set catchup parameters...
U32 totalCatchup = mLastClientMove - mFirstMoveIndex;
processList->ageTickCache(ourTicks + (tickDiff>0 ? tickDiff : 0), totalCatchup+1);
processList->forceHifiReset(tickDiff!=0);
processList->setCatchup(totalCatchup);
}
}
void HifiMoveList::ghostPreRead(NetObject * nobj, bool newGhost)
{
GameBase* obj = dynamic_cast<GameBase*>(nobj);
if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) && !newGhost )
{
// set next cache entry to start
obj->getTickCache().beginCacheList();
// reset to old state because we are about to unpack (and then tick forward)
TickCacheEntry * tce = obj->getTickCache().incCacheList(false);
if (tce)
{
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->readPacketData(mConnection, &bs);
}
}
}
void HifiMoveList::ghostReadExtra(NetObject * nobj, BitStream * bstream, bool newGhost)
{
// Receive additional per ghost information.
// Get pending moves for ghosts that have them and add the moves to
// the tick cache.
GameBase* obj = dynamic_cast<GameBase*>(nobj);
if ( obj && ( obj->getTypeMask() & GameBaseHiFiObjectType ) )
{
// mark ghost so that it updates correctly
obj->setGhostUpdated(true);
obj->setNewGhost(newGhost);
// set next cache entry to start
obj->getTickCache().beginCacheList();
// save state for future update
TickCacheEntry * tce = obj->getTickCache().incCacheList();
BitStream bs(tce->packetData,TickCacheEntry::MaxPacketSize);
obj->writePacketData(mConnection, &bs);
}
}

View file

@ -0,0 +1,76 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _MOVELIST_HIFI_H_
#define _MOVELIST_HIFI_H_
#ifndef _MOVELIST_H_
#include "T3D/gameBase/moveList.h"
#endif
class HifiMoveList : public MoveList
{
typedef MoveList Parent;
public:
HifiMoveList();
void init() { mTotalServerTicks = ServerTicksUninitialized; }
void ghostReadExtra(NetObject *,BitStream *, bool newGhost);
void ghostPreRead(NetObject *, bool newGhost);
void clientWriteMovePacket(BitStream *bstream);
void clientReadMovePacket(BitStream *);
void serverWriteMovePacket(BitStream *);
void serverReadMovePacket(BitStream *bstream);
void markControlDirty();
U32 getMoves(Move**,U32* numMoves);
void onAdvanceObjects() { if (mMoveVec.size() > mLastSentMove-mFirstMoveIndex) mLastSentMove++; }
void advanceMove();
protected:
void resetMoveList();
S32 getServerTicks(U32 serverTickNum);
void updateClientServerTickDiff(S32 & tickDiff);
bool serverTicksInitialized() { return mTotalServerTicks!=ServerTicksUninitialized; }
protected:
U32 mLastSentMove;
F32 mAvgMoveQueueSize;
// server side move list management
U32 mTargetMoveListSize; // Target size of move buffer on server
U32 mMaxMoveListSize; // Max size move buffer allowed to grow to
F32 mSmoothMoveAvg; // Smoothing parameter for move list size running average
F32 mMoveListSizeSlack; // Amount above/below target size move list running average allowed to diverge
bool mSuppressMove; // If true, don't return move on server
// client side tracking of server ticks
enum { TotalTicksBits=10, TotalTicksMask = (1<<TotalTicksBits)-1, ServerTicksUninitialized=0xFFFFFFFF };
U32 mTotalServerTicks;
};
#endif // _MOVELIST_HIFI_H_