mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-15 00:24:40 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
607
Engine/source/T3D/gameBase/hifi/hifiGameProcess.cpp
Normal file
607
Engine/source/T3D/gameBase/hifi/hifiGameProcess.cpp
Normal 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 );
|
||||
}
|
||||
91
Engine/source/T3D/gameBase/hifi/hifiGameProcess.h
Normal file
91
Engine/source/T3D/gameBase/hifi/hifiGameProcess.h
Normal 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_
|
||||
443
Engine/source/T3D/gameBase/hifi/hifiMoveList.cpp
Normal file
443
Engine/source/T3D/gameBase/hifi/hifiMoveList.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
76
Engine/source/T3D/gameBase/hifi/hifiMoveList.h
Normal file
76
Engine/source/T3D/gameBase/hifi/hifiMoveList.h
Normal 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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue