mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-04-28 15:55:39 +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
703
Engine/source/T3D/gameBase/gameBase.cpp
Normal file
703
Engine/source/T3D/gameBase/gameBase.cpp
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/gameBase.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "math/mathIO.h"
|
||||
#include "T3D/gameBase/moveManager.h"
|
||||
#include "T3D/gameBase/gameProcess.h"
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
#include "T3D/aiConnection.h"
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// Ghost update relative priority values
|
||||
|
||||
static F32 sUpFov = 1.0;
|
||||
static F32 sUpDistance = 0.4f;
|
||||
static F32 sUpVelocity = 0.4f;
|
||||
static F32 sUpSkips = 0.2f;
|
||||
static F32 sUpInterest = 0.2f;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
IMPLEMENT_CO_DATABLOCK_V1(GameBaseData);
|
||||
|
||||
ConsoleDocClass( GameBaseData,
|
||||
"@brief Scriptable, demo-able datablock. Used by GameBase objects.\n\n"
|
||||
"@see GameBase\n"
|
||||
"@ingroup gameObjects\n"
|
||||
);
|
||||
|
||||
IMPLEMENT_CALLBACK( GameBaseData, onAdd, void, ( GameBase* obj ), ( obj ),
|
||||
"@brief Called when the object is added to the scene.\n\n"
|
||||
|
||||
"@param obj the GameBase object\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"datablock GameBaseData(MyObjectData)\n"
|
||||
"{\n"
|
||||
" category = \"Misc\";\n"
|
||||
"};\n\n"
|
||||
"function MyObjectData::onAdd( %this, %obj )\n"
|
||||
"{\n"
|
||||
" echo( \"Added \" @ %obj.getName() @ \" to the scene.\" );\n"
|
||||
"}\n\n"
|
||||
"function MyObjectData::onNewDataBlock( %this, %obj )\n"
|
||||
"{\n"
|
||||
" echo( \"Assign \" @ %this.getName() @ \" datablock to \" %obj.getName() );\n"
|
||||
"}\n\n"
|
||||
"function MyObjectData::onRemove( %this, %obj )\n"
|
||||
"{\n"
|
||||
" echo( \"Removed \" @ %obj.getName() @ \" to the scene.\" );\n"
|
||||
"}\n\n"
|
||||
"function MyObjectData::onMount( %this, %obj, %mountObj, %node )\n"
|
||||
"{\n"
|
||||
" echo( %obj.getName() @ \" mounted to \" @ %mountObj.getName() );\n"
|
||||
"}\n\n"
|
||||
"function MyObjectData::onUnmount( %this, %obj, %mountObj, %node )\n"
|
||||
"{\n"
|
||||
" echo( %obj.getName() @ \" unmounted from \" @ %mountObj.getName() );\n"
|
||||
"}\n\n"
|
||||
"@endtsexample\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( GameBaseData, onNewDataBlock, void, ( GameBase* obj ), ( obj ),
|
||||
"@brief Called when the object has a new datablock assigned.\n\n"
|
||||
"@param obj the GameBase object\n\n"
|
||||
"@see onAdd for an example\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( GameBaseData, onRemove, void, ( GameBase* obj ), ( obj ),
|
||||
"@brief Called when the object is removed from the scene.\n\n"
|
||||
"@param obj the GameBase object\n\n"
|
||||
"@see onAdd for an example\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( GameBaseData, onMount, void, ( GameBase* obj, SceneObject* mountObj, S32 node ), ( obj, mountObj, node ),
|
||||
"@brief Called when the object is mounted to another object in the scene.\n\n"
|
||||
"@param obj the GameBase object being mounted\n"
|
||||
"@param mountObj the object we are mounted to\n"
|
||||
"@param node the mountObj node we are mounted to\n\n"
|
||||
"@see onAdd for an example\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( GameBaseData, onUnmount, void, ( GameBase* obj, SceneObject* mountObj, S32 node ), ( obj, mountObj, node ),
|
||||
"@brief Called when the object is unmounted from another object in the scene.\n\n"
|
||||
"@param obj the GameBase object being unmounted\n"
|
||||
"@param mountObj the object we are unmounted from\n"
|
||||
"@param node the mountObj node we are unmounted from\n\n"
|
||||
"@see onAdd for an example\n" );
|
||||
|
||||
IMPLEMENT_CALLBACK( GameBase, setControl, void, ( bool controlled ), ( controlled ),
|
||||
"@brief Called when the client controlling the object changes.\n\n"
|
||||
"@param controlled true if a client now controls this object, false if no "
|
||||
"client controls this object.\n" );
|
||||
|
||||
|
||||
GameBaseData::GameBaseData()
|
||||
{
|
||||
category = "";
|
||||
packed = false;
|
||||
}
|
||||
|
||||
void GameBaseData::inspectPostApply()
|
||||
{
|
||||
Parent::inspectPostApply();
|
||||
|
||||
// Tell interested parties ( like objects referencing this datablock )
|
||||
// that we have been modified and they might want to rebuild...
|
||||
mReloadSignal.trigger();
|
||||
}
|
||||
|
||||
bool GameBaseData::onAdd()
|
||||
{
|
||||
if (!Parent::onAdd())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameBaseData::initPersistFields()
|
||||
{
|
||||
addGroup("Scripting");
|
||||
|
||||
addField( "category", TypeCaseString, Offset( category, GameBaseData ),
|
||||
"The group that this datablock will show up in under the \"Scripted\" "
|
||||
"tab in the World Editor Library." );
|
||||
|
||||
endGroup("Scripting");
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
bool GameBaseData::preload(bool server, String &errorStr)
|
||||
{
|
||||
if (!Parent::preload(server, errorStr))
|
||||
return false;
|
||||
packed = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameBaseData::unpackData(BitStream* stream)
|
||||
{
|
||||
Parent::unpackData(stream);
|
||||
packed = true;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool UNPACK_DB_ID(BitStream * stream, U32 & id)
|
||||
{
|
||||
if (stream->readFlag())
|
||||
{
|
||||
id = stream->readRangedU32(DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PACK_DB_ID(BitStream * stream, U32 id)
|
||||
{
|
||||
if (stream->writeFlag(id))
|
||||
{
|
||||
stream->writeRangedU32(id,DataBlockObjectIdFirst,DataBlockObjectIdLast);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PRELOAD_DB(U32 & id, SimDataBlock ** data, bool server, const char * clientMissing, const char * serverMissing)
|
||||
{
|
||||
if (server)
|
||||
{
|
||||
if (*data)
|
||||
id = (*data)->getId();
|
||||
else if (server && serverMissing)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::General,serverMissing);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (id && !Sim::findObject(id,*data) && clientMissing)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::General,clientMissing);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
bool GameBase::gShowBoundingBox = false;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
IMPLEMENT_CO_NETOBJECT_V1(GameBase);
|
||||
|
||||
ConsoleDocClass( GameBase,
|
||||
"@brief Base class for game objects which use datablocks, networking, are "
|
||||
"editable, and need to process ticks.\n\n"
|
||||
"@ingroup gameObjects\n"
|
||||
);
|
||||
|
||||
GameBase::GameBase()
|
||||
: mDataBlock( NULL ),
|
||||
mControllingClient( NULL ),
|
||||
mCurrentWaterObject( NULL )
|
||||
{
|
||||
mNetFlags.set(Ghostable);
|
||||
mTypeMask |= GameBaseObjectType;
|
||||
mProcessTag = 0;
|
||||
|
||||
// From ProcessObject
|
||||
mIsGameBase = true;
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
mLastMoveId = 0;
|
||||
mTicksSinceLastMove = 0;
|
||||
mIsAiControlled = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
GameBase::~GameBase()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
bool GameBase::onAdd()
|
||||
{
|
||||
if ( !Parent::onAdd() )
|
||||
return false;
|
||||
|
||||
// Datablock must be initialized on the server.
|
||||
// Client datablock are initialized by the initial update.
|
||||
if ( isServerObject() && mDataBlock && !onNewDataBlock( mDataBlock, false ) )
|
||||
return false;
|
||||
|
||||
setProcessTick( true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameBase::onRemove()
|
||||
{
|
||||
// EDITOR FEATURE: Remove us from the reload signal of our datablock.
|
||||
if ( mDataBlock )
|
||||
mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
bool GameBase::onNewDataBlock( GameBaseData *dptr, bool reload )
|
||||
{
|
||||
// EDITOR FEATURE: Remove us from old datablock's reload signal and
|
||||
// add us to the new one.
|
||||
if ( !reload )
|
||||
{
|
||||
if ( mDataBlock )
|
||||
mDataBlock->mReloadSignal.remove( this, &GameBase::_onDatablockModified );
|
||||
if ( dptr )
|
||||
dptr->mReloadSignal.notify( this, &GameBase::_onDatablockModified );
|
||||
}
|
||||
|
||||
|
||||
mDataBlock = dptr;
|
||||
|
||||
if ( !mDataBlock )
|
||||
return false;
|
||||
|
||||
setMaskBits(DataBlockMask);
|
||||
return true;
|
||||
}
|
||||
|
||||
void GameBase::_onDatablockModified()
|
||||
{
|
||||
AssertFatal( mDataBlock, "GameBase::onDatablockModified - mDataBlock is NULL." );
|
||||
onNewDataBlock( mDataBlock, true );
|
||||
}
|
||||
|
||||
void GameBase::inspectPostApply()
|
||||
{
|
||||
Parent::inspectPostApply();
|
||||
setMaskBits(ExtendedInfoMask);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void GameBase::processTick(const Move * move)
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
if (!move)
|
||||
mTicksSinceLastMove++;
|
||||
|
||||
const char * srv = isClientObject() ? "client" : "server";
|
||||
const char * who = "";
|
||||
if (isClientObject())
|
||||
{
|
||||
if (this == (GameBase*)GameConnection::getConnectionToServer()->getControlObject())
|
||||
who = " player";
|
||||
else
|
||||
who = " ghost";
|
||||
if (mIsAiControlled)
|
||||
who = " ai";
|
||||
}
|
||||
if (isServerObject())
|
||||
{
|
||||
if (dynamic_cast<AIConnection*>(getControllingClient()))
|
||||
{
|
||||
who = " ai";
|
||||
mIsAiControlled = true;
|
||||
}
|
||||
else if (getControllingClient())
|
||||
{
|
||||
who = " player";
|
||||
mIsAiControlled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
who = "";
|
||||
mIsAiControlled = false;
|
||||
}
|
||||
}
|
||||
U32 moveid = mLastMoveId+mTicksSinceLastMove;
|
||||
if (move)
|
||||
moveid = move->id;
|
||||
|
||||
if (getTypeMask() & GameBaseHiFiObjectType)
|
||||
{
|
||||
if (move)
|
||||
Con::printf("Processing (%s%s id %i) move %i",srv,who,getId(), move->id);
|
||||
else
|
||||
Con::printf("Processing (%s%s id %i) move %i (%i)",srv,who,getId(),mLastMoveId+mTicksSinceLastMove,mTicksSinceLastMove);
|
||||
}
|
||||
|
||||
if (move)
|
||||
{
|
||||
mLastMoveId = move->id;
|
||||
mTicksSinceLastMove=0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
F32 GameBase::getUpdatePriority(CameraScopeQuery *camInfo, U32 updateMask, S32 updateSkips)
|
||||
{
|
||||
TORQUE_UNUSED(updateMask);
|
||||
|
||||
// Calculate a priority used to decide if this object
|
||||
// will be updated on the client. All the weights
|
||||
// are calculated 0 -> 1 Then weighted together at the
|
||||
// end to produce a priority.
|
||||
Point3F pos;
|
||||
getWorldBox().getCenter(&pos);
|
||||
pos -= camInfo->pos;
|
||||
F32 dist = pos.len();
|
||||
if (dist == 0.0f) dist = 0.001f;
|
||||
pos *= 1.0f / dist;
|
||||
|
||||
// Weight based on linear distance, the basic stuff.
|
||||
F32 wDistance = (dist < camInfo->visibleDistance)?
|
||||
1.0f - (dist / camInfo->visibleDistance): 0.0f;
|
||||
|
||||
// Weight by field of view, objects directly in front
|
||||
// will be weighted 1, objects behind will be 0
|
||||
F32 dot = mDot(pos,camInfo->orientation);
|
||||
bool inFov = dot > camInfo->cosFov;
|
||||
F32 wFov = inFov? 1.0f: 0;
|
||||
|
||||
// Weight by linear velocity parallel to the viewing plane
|
||||
// (if it's the field of view, 0 if it's not).
|
||||
F32 wVelocity = 0.0f;
|
||||
if (inFov)
|
||||
{
|
||||
Point3F vec;
|
||||
mCross(camInfo->orientation,getVelocity(),&vec);
|
||||
wVelocity = (vec.len() * camInfo->fov) /
|
||||
(camInfo->fov * camInfo->visibleDistance);
|
||||
if (wVelocity > 1.0f)
|
||||
wVelocity = 1.0f;
|
||||
}
|
||||
|
||||
// Weight by interest.
|
||||
F32 wInterest;
|
||||
if (getTypeMask() & PlayerObjectType)
|
||||
wInterest = 0.75f;
|
||||
else if (getTypeMask() & ProjectileObjectType)
|
||||
{
|
||||
// Projectiles are more interesting if they
|
||||
// are heading for us.
|
||||
wInterest = 0.30f;
|
||||
F32 dot = -mDot(pos,getVelocity());
|
||||
if (dot > 0.0f)
|
||||
wInterest += 0.20 * dot;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (getTypeMask() & ItemObjectType)
|
||||
wInterest = 0.25f;
|
||||
else
|
||||
// Everything else is less interesting.
|
||||
wInterest = 0.0f;
|
||||
}
|
||||
|
||||
// Weight by updateSkips
|
||||
F32 wSkips = updateSkips * 0.5;
|
||||
|
||||
// Calculate final priority, should total to about 1.0f
|
||||
//
|
||||
return
|
||||
wFov * sUpFov +
|
||||
wDistance * sUpDistance +
|
||||
wVelocity * sUpVelocity +
|
||||
wSkips * sUpSkips +
|
||||
wInterest * sUpInterest;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
bool GameBase::setDataBlock(GameBaseData* dptr)
|
||||
{
|
||||
if (isGhost() || isProperlyAdded()) {
|
||||
if (mDataBlock != dptr)
|
||||
return onNewDataBlock(dptr,false);
|
||||
}
|
||||
else
|
||||
mDataBlock = dptr;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
void GameBase::scriptOnAdd()
|
||||
{
|
||||
// Script onAdd() must be called by the leaf class after
|
||||
// everything is ready.
|
||||
if (mDataBlock && !isGhost())
|
||||
mDataBlock->onAdd_callback( this );
|
||||
}
|
||||
|
||||
void GameBase::scriptOnNewDataBlock()
|
||||
{
|
||||
// Script onNewDataBlock() must be called by the leaf class
|
||||
// after everything is loaded.
|
||||
if (mDataBlock && !isGhost())
|
||||
mDataBlock->onNewDataBlock_callback( this );
|
||||
}
|
||||
|
||||
void GameBase::scriptOnRemove()
|
||||
{
|
||||
// Script onRemove() must be called by leaf class while
|
||||
// the object state is still valid.
|
||||
if (!isGhost() && mDataBlock)
|
||||
mDataBlock->onRemove_callback( this );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void GameBase::setControllingClient(GameConnection* client)
|
||||
{
|
||||
if (isClientObject())
|
||||
{
|
||||
if (mControllingClient)
|
||||
setControl_callback( 0 );
|
||||
if (client)
|
||||
setControl_callback( 1 );
|
||||
}
|
||||
|
||||
mControllingClient = client;
|
||||
}
|
||||
|
||||
U32 GameBase::getPacketDataChecksum(GameConnection * connection)
|
||||
{
|
||||
// just write the packet data into a buffer
|
||||
// then we can CRC the buffer. This should always let us
|
||||
// know when there is a checksum problem.
|
||||
|
||||
static U8 buffer[1500] = { 0, };
|
||||
BitStream stream(buffer, sizeof(buffer));
|
||||
|
||||
writePacketData(connection, &stream);
|
||||
U32 byteCount = stream.getPosition();
|
||||
U32 ret = CRC::calculateCRC(buffer, byteCount, 0xFFFFFFFF);
|
||||
dMemset(buffer, 0, byteCount);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void GameBase::writePacketData(GameConnection*, BitStream*)
|
||||
{
|
||||
}
|
||||
|
||||
void GameBase::readPacketData(GameConnection*, BitStream*)
|
||||
{
|
||||
}
|
||||
|
||||
U32 GameBase::packUpdate( NetConnection *connection, U32 mask, BitStream *stream )
|
||||
{
|
||||
U32 retMask = Parent::packUpdate( connection, mask, stream );
|
||||
|
||||
if ( stream->writeFlag( mask & ScaleMask ) )
|
||||
{
|
||||
// Only write one bit if the scale is one.
|
||||
if ( stream->writeFlag( mObjScale != Point3F::One ) )
|
||||
mathWrite( *stream, mObjScale );
|
||||
}
|
||||
|
||||
if ( stream->writeFlag( ( mask & DataBlockMask ) && mDataBlock != NULL ) )
|
||||
{
|
||||
stream->writeRangedU32( mDataBlock->getId(),
|
||||
DataBlockObjectIdFirst,
|
||||
DataBlockObjectIdLast );
|
||||
if ( stream->writeFlag( mNetFlags.test( NetOrdered ) ) )
|
||||
stream->writeInt( mOrderGUID, 16 );
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
stream->write(mLastMoveId);
|
||||
stream->writeFlag(mIsAiControlled);
|
||||
#endif
|
||||
|
||||
return retMask;
|
||||
}
|
||||
|
||||
void GameBase::unpackUpdate(NetConnection *con, BitStream *stream)
|
||||
{
|
||||
Parent::unpackUpdate( con, stream );
|
||||
|
||||
// ScaleMask
|
||||
if ( stream->readFlag() )
|
||||
{
|
||||
if ( stream->readFlag() )
|
||||
{
|
||||
VectorF scale;
|
||||
mathRead( *stream, &scale );
|
||||
setScale( scale );
|
||||
}
|
||||
else
|
||||
setScale( Point3F::One );
|
||||
}
|
||||
|
||||
// DataBlockMask
|
||||
if ( stream->readFlag() )
|
||||
{
|
||||
GameBaseData *dptr = 0;
|
||||
SimObjectId id = stream->readRangedU32( DataBlockObjectIdFirst,
|
||||
DataBlockObjectIdLast );
|
||||
if ( stream->readFlag() )
|
||||
mOrderGUID = stream->readInt( 16 );
|
||||
|
||||
if ( !Sim::findObject( id, dptr ) || !setDataBlock( dptr ) )
|
||||
con->setLastError( "Invalid packet GameBase::unpackUpdate()" );
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
stream->read(&mLastMoveId);
|
||||
mTicksSinceLastMove = 0;
|
||||
mIsAiControlled = stream->readFlag();
|
||||
#endif
|
||||
}
|
||||
|
||||
void GameBase::onMount( SceneObject *obj, S32 node )
|
||||
{
|
||||
deleteNotify( obj );
|
||||
|
||||
// Are we mounting to a GameBase object?
|
||||
GameBase *gbaseObj = dynamic_cast<GameBase*>( obj );
|
||||
|
||||
if ( gbaseObj && gbaseObj->getControlObject() != this )
|
||||
processAfter( gbaseObj );
|
||||
|
||||
if (!isGhost()) {
|
||||
setMaskBits(MountedMask);
|
||||
mDataBlock->onMount_callback( this, obj, node );
|
||||
}
|
||||
}
|
||||
|
||||
void GameBase::onUnmount( SceneObject *obj, S32 node )
|
||||
{
|
||||
clearNotify(obj);
|
||||
|
||||
GameBase *gbaseObj = dynamic_cast<GameBase*>( obj );
|
||||
|
||||
if ( gbaseObj && gbaseObj->getControlObject() != this )
|
||||
clearProcessAfter();
|
||||
|
||||
if (!isGhost()) {
|
||||
setMaskBits(MountedMask);
|
||||
mDataBlock->onUnmount_callback( this, obj, node );
|
||||
}
|
||||
}
|
||||
|
||||
bool GameBase::setDataBlockProperty( void *obj, const char *index, const char *db)
|
||||
{
|
||||
if( db == NULL || !db || !db[ 0 ] )
|
||||
{
|
||||
Con::errorf( "GameBase::setDataBlockProperty - Can't unset datablock on GameBase objects" );
|
||||
return false;
|
||||
}
|
||||
|
||||
GameBase* object = static_cast< GameBase* >( obj );
|
||||
GameBaseData* data;
|
||||
if( Sim::findObject( db, data ) )
|
||||
return object->setDataBlock( data );
|
||||
|
||||
Con::errorf( "GameBase::setDatablockProperty - Could not find data block \"%s\"", db );
|
||||
return false;
|
||||
}
|
||||
|
||||
MoveList* GameBase::getMoveList()
|
||||
{
|
||||
return mControllingClient ? mControllingClient->mMoveList : NULL;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
DefineEngineMethod( GameBase, getDataBlock, S32, (),,
|
||||
"@brief Get the datablock used by this object.\n\n"
|
||||
"@return the datablock this GameBase is using."
|
||||
"@see setDataBlock()\n")
|
||||
{
|
||||
return object->getDataBlock()? object->getDataBlock()->getId(): 0;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
DefineEngineMethod( GameBase, setDataBlock, bool, ( GameBaseData* data ),,
|
||||
"@brief Assign this GameBase to use the specified datablock.\n\n"
|
||||
"@param data new datablock to use\n"
|
||||
"@return true if successful, false if failed."
|
||||
"@see getDataBlock()\n")
|
||||
{
|
||||
return ( data && object->setDataBlock(data) );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void GameBase::initPersistFields()
|
||||
{
|
||||
addGroup( "Game" );
|
||||
|
||||
addProtectedField( "dataBlock", TYPEID< GameBaseData >(), Offset(mDataBlock, GameBase),
|
||||
&setDataBlockProperty, &defaultProtectedGetFn,
|
||||
"Script datablock used for game objects." );
|
||||
|
||||
endGroup( "Game" );
|
||||
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
void GameBase::consoleInit()
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
Con::addVariable( "GameBase::boundingBox", TypeBool, &gShowBoundingBox,
|
||||
"@brief Toggles on the rendering of the bounding boxes for certain types of objects in scene.\n\n"
|
||||
"@ingroup GameBase" );
|
||||
#endif
|
||||
}
|
||||
|
||||
DefineEngineMethod( GameBase, applyImpulse, bool, ( Point3F pos, VectorF vel ),,
|
||||
"@brief Apply an impulse to this object as defined by a world position and velocity vector.\n\n"
|
||||
|
||||
"@param pos impulse world position\n"
|
||||
"@param vel impulse velocity (impulse force F = m * v)\n"
|
||||
"@return Always true\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
object->applyImpulse(pos,vel);
|
||||
return true;
|
||||
}
|
||||
|
||||
DefineEngineMethod( GameBase, applyRadialImpulse, void, ( Point3F origin, F32 radius, F32 magnitude ),,
|
||||
"@brief Applies a radial impulse to the object using the given origin and force.\n\n"
|
||||
|
||||
"@param origin World point of origin of the radial impulse.\n"
|
||||
"@param radius The radius of the impulse area.\n"
|
||||
"@param magnitude The strength of the impulse.\n"
|
||||
|
||||
"@note Not all objects that derrive from GameBase have this defined.\n")
|
||||
{
|
||||
object->applyRadialImpulse( origin, radius, magnitude );
|
||||
}
|
||||
451
Engine/source/T3D/gameBase/gameBase.h
Normal file
451
Engine/source/T3D/gameBase/gameBase.h
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _GAMEBASE_H_
|
||||
#define _GAMEBASE_H_
|
||||
|
||||
#ifndef _SCENEOBJECT_H_
|
||||
#include "scene/sceneObject.h"
|
||||
#endif
|
||||
#ifndef _PROCESSLIST_H_
|
||||
#include "T3D/gameBase/processList.h"
|
||||
#endif
|
||||
#ifndef _TICKCACHE_H_
|
||||
#include "T3D/gameBase/tickCache.h"
|
||||
#endif
|
||||
#ifndef _DYNAMIC_CONSOLETYPES_H_
|
||||
#include "console/dynamicTypes.h"
|
||||
#endif
|
||||
|
||||
class NetConnection;
|
||||
class ProcessList;
|
||||
class GameBase;
|
||||
struct Move;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/// Scriptable, demo-able datablock.
|
||||
///
|
||||
/// This variant of SimDataBlock performs these additional tasks:
|
||||
/// - Linking datablock's namepsaces to the namespace of their C++ class, so
|
||||
/// that datablocks can expose script functionality.
|
||||
/// - Linking datablocks to a user defined scripting namespace, by setting the
|
||||
/// 'class' field at datablock definition time.
|
||||
/// - Adds a category field; this is used by the world creator in the editor to
|
||||
/// classify creatable shapes. Creatable shapes are placed under the Shapes
|
||||
/// node in the treeview for this; additional levels are created, named after
|
||||
/// the category fields.
|
||||
/// - Adds support for demo stream recording. This support takes the form
|
||||
/// of the member variable packed. When a demo is being recorded by a client,
|
||||
/// data is unpacked, then packed again to the data stream, then, in the case
|
||||
/// of datablocks, preload() is called to process the data. It is occasionally
|
||||
/// the case that certain references in the datablock stream cannot be resolved
|
||||
/// until preload is called, in which case a raw ID field is stored in the variable
|
||||
/// which will eventually be used to store a pointer to the object. However, if
|
||||
/// packData() is called before we resolve this ID, trying to call getID() on the
|
||||
/// objecct ID would be a fatal error. Therefore, in these cases, we test packed;
|
||||
/// if it is true, then we know we have to write the raw data, instead of trying
|
||||
/// to resolve an ID.
|
||||
///
|
||||
/// @see SimDataBlock for further details about datablocks.
|
||||
/// @see http://hosted.tribalwar.com/t2faq/datablocks.shtml for an excellent
|
||||
/// explanation of the basics of datablocks from a scripting perspective.
|
||||
/// @nosubgrouping
|
||||
struct GameBaseData : public SimDataBlock
|
||||
{
|
||||
private:
|
||||
|
||||
typedef SimDataBlock Parent;
|
||||
|
||||
public:
|
||||
|
||||
bool packed;
|
||||
StringTableEntry category;
|
||||
|
||||
// Signal triggered when this datablock is modified.
|
||||
// GameBase objects referencing this datablock notify with this signal.
|
||||
Signal<void(void)> mReloadSignal;
|
||||
|
||||
// Triggers the reload signal.
|
||||
void inspectPostApply();
|
||||
|
||||
bool onAdd();
|
||||
|
||||
// The derived class should provide the following:
|
||||
DECLARE_CONOBJECT(GameBaseData);
|
||||
GameBaseData();
|
||||
static void initPersistFields();
|
||||
bool preload(bool server, String &errorStr);
|
||||
void unpackData(BitStream* stream);
|
||||
|
||||
/// @name Callbacks
|
||||
/// @{
|
||||
DECLARE_CALLBACK( void, onAdd, ( GameBase* obj ) );
|
||||
DECLARE_CALLBACK( void, onRemove, ( GameBase* obj ) );
|
||||
DECLARE_CALLBACK( void, onNewDataBlock, ( GameBase* obj ) );
|
||||
DECLARE_CALLBACK( void, onMount, ( GameBase* obj, SceneObject* mountObj, S32 node ) );
|
||||
DECLARE_CALLBACK( void, onUnmount, ( GameBase* obj, SceneObject* mountObj, S32 node ) );
|
||||
/// @}
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// A few utility methods for sending datablocks over the net
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
bool UNPACK_DB_ID(BitStream *, U32 & id);
|
||||
bool PACK_DB_ID(BitStream *, U32 id);
|
||||
bool PRELOAD_DB(U32 & id, SimDataBlock **, bool server, const char * clientMissing = NULL, const char * serverMissing = NULL);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
class GameConnection;
|
||||
class WaterObject;
|
||||
class MoveList;
|
||||
|
||||
// For truly it is written: "The wise man extends GameBase for his purposes,
|
||||
// while the fool has the ability to eject shell casings from the belly of his
|
||||
// dragon." -- KillerBunny
|
||||
|
||||
/// Base class for game objects which use datablocks, networking, are editable,
|
||||
/// and need to process ticks.
|
||||
///
|
||||
/// @section GameBase_process GameBase and ProcessList
|
||||
///
|
||||
/// GameBase adds two kinds of time-based updates. Torque works off of a concept
|
||||
/// of ticks. Ticks are slices of time 32 milliseconds in length. There are three
|
||||
/// methods which are used to update GameBase objects that are registered with
|
||||
/// the ProcessLists:
|
||||
/// - processTick(Move*) is called on each object once for every tick, regardless
|
||||
/// of the "real" framerate.
|
||||
/// - interpolateTick(float) is called on client objects when they need to interpolate
|
||||
/// to match the next tick.
|
||||
/// - advanceTime(float) is called on client objects so they can do time-based behaviour,
|
||||
/// like updating animations.
|
||||
///
|
||||
/// Torque maintains a server and a client processing list; in a local game, both
|
||||
/// are populated, while in multiplayer situations, either one or the other is
|
||||
/// populated.
|
||||
///
|
||||
/// You can control whether an object is considered for ticking by means of the
|
||||
/// setProcessTick() method.
|
||||
///
|
||||
/// @section GameBase_datablock GameBase and Datablocks
|
||||
///
|
||||
/// GameBase adds support for datablocks. Datablocks are secondary classes which store
|
||||
/// static data for types of game elements. For instance, this means that all "light human
|
||||
/// male armor" type Players share the same datablock. Datablocks typically store not only
|
||||
/// raw data, but perform precalculations, like finding nodes in the game model, or
|
||||
/// validating movement parameters.
|
||||
///
|
||||
/// There are three parts to the datablock interface implemented in GameBase:
|
||||
/// - <b>getDataBlock()</b>, which gets a pointer to the current datablock. This is
|
||||
/// mostly for external use; for in-class use, it's better to directly access the
|
||||
/// mDataBlock member.
|
||||
/// - <b>setDataBlock()</b>, which sets mDataBlock to point to a new datablock; it
|
||||
/// uses the next part of the interface to inform subclasses of this.
|
||||
/// - <b>onNewDataBlock()</b> is called whenever a new datablock is assigned to a GameBase.
|
||||
///
|
||||
/// Datablocks are also usable through the scripting language.
|
||||
///
|
||||
/// @see SimDataBlock for more details.
|
||||
///
|
||||
/// @section GameBase_networking GameBase and Networking
|
||||
///
|
||||
/// writePacketData() and readPacketData() are called to transfer information needed for client
|
||||
/// side prediction. They are usually used when updating a client of its control object state.
|
||||
///
|
||||
/// Subclasses of GameBase usually transmit positional and basic status data in the packUpdate()
|
||||
/// functions, while giving velocity, momentum, and similar state information in the writePacketData().
|
||||
///
|
||||
/// writePacketData()/readPacketData() are called <i>in addition</i> to packUpdate/unpackUpdate().
|
||||
///
|
||||
/// @nosubgrouping
|
||||
class GameBase : public SceneObject
|
||||
{
|
||||
typedef SceneObject Parent;
|
||||
|
||||
/// @name Datablock
|
||||
/// @{
|
||||
|
||||
GameBaseData* mDataBlock;
|
||||
|
||||
/// @}
|
||||
|
||||
TickCache mTickCache;
|
||||
|
||||
// Control interface
|
||||
GameConnection* mControllingClient;
|
||||
|
||||
public:
|
||||
|
||||
static bool gShowBoundingBox; ///< Should we render bounding boxes?
|
||||
|
||||
protected:
|
||||
|
||||
F32 mCameraFov;
|
||||
|
||||
/// The WaterObject we are currently within.
|
||||
WaterObject *mCurrentWaterObject;
|
||||
|
||||
static bool setDataBlockProperty( void *object, const char *index, const char *data );
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
U32 mLastMoveId;
|
||||
U32 mTicksSinceLastMove;
|
||||
bool mIsAiControlled;
|
||||
#endif
|
||||
|
||||
public:
|
||||
|
||||
GameBase();
|
||||
~GameBase();
|
||||
|
||||
enum GameBaseMasks {
|
||||
DataBlockMask = Parent::NextFreeMask << 0,
|
||||
ExtendedInfoMask = Parent::NextFreeMask << 1,
|
||||
NextFreeMask = Parent::NextFreeMask << 2
|
||||
};
|
||||
|
||||
// net flags added by game base
|
||||
enum
|
||||
{
|
||||
NetOrdered = BIT(Parent::MaxNetFlagBit+1), /// Process in same order on client and server.
|
||||
NetNearbyAdded = BIT(Parent::MaxNetFlagBit+2), /// Is set during client catchup when neighbors have been checked.
|
||||
GhostUpdated = BIT(Parent::MaxNetFlagBit+3), /// Is set whenever ghost updated (and reset) on the client, for hifi objects.
|
||||
TickLast = BIT(Parent::MaxNetFlagBit+4), /// Tick this object after all others.
|
||||
NewGhost = BIT(Parent::MaxNetFlagBit+5), /// This ghost was just added during the last update.
|
||||
HiFiPassive = BIT(Parent::MaxNetFlagBit+6), /// Do not interact with other hifi passive objects.
|
||||
MaxNetFlagBit = Parent::MaxNetFlagBit+6
|
||||
};
|
||||
|
||||
/// @name Inherited Functionality.
|
||||
/// @{
|
||||
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
void inspectPostApply();
|
||||
static void initPersistFields();
|
||||
static void consoleInit();
|
||||
|
||||
/// @}
|
||||
|
||||
///@name Datablock
|
||||
///@{
|
||||
|
||||
/// Assigns this object a datablock and loads attributes with onNewDataBlock.
|
||||
///
|
||||
/// @see onNewDataBlock
|
||||
/// @param dptr Datablock
|
||||
bool setDataBlock( GameBaseData *dptr );
|
||||
|
||||
/// Returns the datablock for this object.
|
||||
GameBaseData* getDataBlock() { return mDataBlock; }
|
||||
|
||||
/// Called when a new datablock is set. This allows subclasses to
|
||||
/// appropriately handle new datablocks.
|
||||
///
|
||||
/// @see setDataBlock()
|
||||
/// @param dptr New datablock
|
||||
/// @param reload Is this a new datablock or are we reloading one
|
||||
/// we already had.
|
||||
virtual bool onNewDataBlock( GameBaseData *dptr, bool reload );
|
||||
///@}
|
||||
|
||||
/// @name Script
|
||||
/// The scriptOnXX methods are invoked by the leaf classes
|
||||
/// @{
|
||||
|
||||
/// Executes the 'onAdd' script function for this object.
|
||||
/// @note This must be called after everything is ready
|
||||
void scriptOnAdd();
|
||||
|
||||
/// Executes the 'onNewDataBlock' script function for this object.
|
||||
///
|
||||
/// @note This must be called after everything is loaded.
|
||||
void scriptOnNewDataBlock();
|
||||
|
||||
/// Executes the 'onRemove' script function for this object.
|
||||
/// @note This must be called while the object is still valid
|
||||
void scriptOnRemove();
|
||||
|
||||
/// @}
|
||||
|
||||
// ProcessObject override
|
||||
void processTick( const Move *move );
|
||||
|
||||
/// @name GameBase NetFlags & Hifi-Net Interface
|
||||
/// @{
|
||||
|
||||
/// Set or clear the GhostUpdated bit in our NetFlags.
|
||||
/// @see GhostUpdated
|
||||
void setGhostUpdated( bool b ) { if (b) mNetFlags.set(GhostUpdated); else mNetFlags.clear(GhostUpdated); }
|
||||
|
||||
/// Returns true if the GhostUpdated bit in our NetFlags is set.
|
||||
/// @see GhostUpdated
|
||||
bool isGhostUpdated() const { return mNetFlags.test(GhostUpdated); }
|
||||
|
||||
/// Set or clear the NewGhost bit in our NetFlags.
|
||||
/// @see NewGhost
|
||||
void setNewGhost( bool n ) { if (n) mNetFlags.set(NewGhost); else mNetFlags.clear(NewGhost); }
|
||||
|
||||
/// Returns true if the NewGhost bit in out NetFlags is set.
|
||||
/// @see NewGhost
|
||||
bool isNewGhost() const { return mNetFlags.test(NewGhost); }
|
||||
|
||||
/// Set or clear the NetNearbyAdded bit in our NetFlags.
|
||||
/// @see NetNearbyAdded
|
||||
void setNetNearbyAdded( bool b ) { if (b) mNetFlags.set(NetNearbyAdded); else mNetFlags.clear(NetNearbyAdded); }
|
||||
|
||||
/// Returns true if the NetNearby bit in our NetFlags is set.
|
||||
/// @see NetNearbyAdded
|
||||
bool isNetNearbyAdded() const { return mNetFlags.test(NetNearbyAdded); }
|
||||
|
||||
/// Returns true if the HiFiPassive bit in our NetFlags is set.
|
||||
/// @see HiFiPassive
|
||||
bool isHifiPassive() const { return mNetFlags.test(HiFiPassive); }
|
||||
|
||||
/// Returns true if the TickLast bit in our NetFlags is set.
|
||||
/// @see TickLast
|
||||
bool isTickLast() const { return mNetFlags.test(TickLast); }
|
||||
|
||||
/// Returns true if the NetOrdered bit in our NetFlags is set.
|
||||
/// @see NetOrdered
|
||||
bool isNetOrdered() const { return mNetFlags.test(NetOrdered); }
|
||||
|
||||
/// Called during client catchup under the hifi-net model.
|
||||
virtual void computeNetSmooth( F32 backDelta ) {}
|
||||
|
||||
/// Returns TickCache used under the hifi-net model.
|
||||
TickCache& getTickCache() { return mTickCache; }
|
||||
/// @}
|
||||
|
||||
/// @name Network
|
||||
/// @see NetObject, NetConnection
|
||||
/// @{
|
||||
|
||||
F32 getUpdatePriority( CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips );
|
||||
U32 packUpdate ( NetConnection *conn, U32 mask, BitStream *stream );
|
||||
void unpackUpdate( NetConnection *conn, BitStream *stream );
|
||||
|
||||
/// Write state information necessary to perform client side prediction of an object.
|
||||
///
|
||||
/// This information is sent only to the controlling object. For example, if you are a client
|
||||
/// controlling a Player, the server uses writePacketData() instead of packUpdate() to
|
||||
/// generate the data you receive.
|
||||
///
|
||||
/// @param conn Connection for which we're generating this data.
|
||||
/// @param stream Bitstream for output.
|
||||
virtual void writePacketData( GameConnection *conn, BitStream *stream );
|
||||
|
||||
/// Read data written with writePacketData() and update the object state.
|
||||
///
|
||||
/// @param conn Connection for which we're generating this data.
|
||||
/// @param stream Bitstream to read.
|
||||
virtual void readPacketData( GameConnection *conn, BitStream *stream );
|
||||
|
||||
/// Gets the checksum for packet data.
|
||||
///
|
||||
/// Basically writes a packet, does a CRC check on it, and returns
|
||||
/// that CRC.
|
||||
///
|
||||
/// @see writePacketData
|
||||
/// @param conn Game connection
|
||||
virtual U32 getPacketDataChecksum( GameConnection *conn );
|
||||
///@}
|
||||
|
||||
|
||||
/// @name Mounted objects ( overrides )
|
||||
/// @{
|
||||
|
||||
public:
|
||||
|
||||
virtual void onMount( SceneObject *obj, S32 node );
|
||||
virtual void onUnmount( SceneObject *obj,S32 node );
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name User control
|
||||
/// @{
|
||||
|
||||
/// Returns the client controlling this object
|
||||
GameConnection *getControllingClient() { return mControllingClient; }
|
||||
const GameConnection *getControllingClient() const { return mControllingClient; }
|
||||
|
||||
/// Returns the MoveList of the client controlling this object.
|
||||
/// If there is no client it returns NULL;
|
||||
MoveList* getMoveList();
|
||||
|
||||
/// Sets the client controlling this object
|
||||
/// @param client Client that is now controlling this object
|
||||
virtual void setControllingClient( GameConnection *client );
|
||||
|
||||
virtual GameBase * getControllingObject() { return NULL; }
|
||||
virtual GameBase * getControlObject() { return NULL; }
|
||||
virtual void setControlObject( GameBase * ) { }
|
||||
/// @}
|
||||
|
||||
virtual F32 getDefaultCameraFov() { return 90.f; }
|
||||
virtual F32 getCameraFov() { return 90.f; }
|
||||
virtual void setCameraFov( F32 fov ) { }
|
||||
virtual bool isValidCameraFov( F32 fov ) { return true; }
|
||||
virtual bool useObjsEyePoint() const { return false; }
|
||||
virtual bool onlyFirstPerson() const { return false; }
|
||||
virtual F32 getDamageFlash() const { return 1.0f; }
|
||||
virtual F32 getWhiteOut() const { return 1.0f; }
|
||||
|
||||
// Not implemented here, but should return the Camera to world transformation matrix
|
||||
virtual void getCameraTransform (F32 *pos, MatrixF *mat ) { *mat = MatrixF::Identity; }
|
||||
|
||||
/// Returns the water object we are colliding with, it is up to derived
|
||||
/// classes to actually set this object.
|
||||
virtual WaterObject* getCurrentWaterObject() { return mCurrentWaterObject; }
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
bool isAIControlled() const { return mIsAiControlled; }
|
||||
#endif
|
||||
|
||||
DECLARE_CONOBJECT (GameBase );
|
||||
|
||||
/// @name Callbacks
|
||||
/// @{
|
||||
DECLARE_CALLBACK( void, setControl, ( bool controlled ) );
|
||||
/// @}
|
||||
|
||||
private:
|
||||
|
||||
/// This is called by the reload signal in our datablock when it is
|
||||
/// modified in the editor.
|
||||
///
|
||||
/// This method is private and is not virtual. To handle a datablock-modified
|
||||
/// even in a child-class specific way you should override onNewDatablock
|
||||
/// and handle the reload( true ) case.
|
||||
///
|
||||
/// Warning: For local-client, editor situations only.
|
||||
///
|
||||
/// Warning: Do not attempt to call .remove or .notify on mDataBlock->mReloadSignal
|
||||
/// within this callback.
|
||||
///
|
||||
void _onDatablockModified();
|
||||
};
|
||||
|
||||
|
||||
#endif // _GAMEBASE_H_
|
||||
2105
Engine/source/T3D/gameBase/gameConnection.cpp
Normal file
2105
Engine/source/T3D/gameBase/gameConnection.cpp
Normal file
File diff suppressed because it is too large
Load diff
345
Engine/source/T3D/gameBase/gameConnection.h
Normal file
345
Engine/source/T3D/gameBase/gameConnection.h
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _GAMECONNECTION_H_
|
||||
#define _GAMECONNECTION_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
#ifndef _GAMEBASE_H_
|
||||
#include "T3D/gameBase/gameBase.h"
|
||||
#endif
|
||||
#ifndef _NETCONNECTION_H_
|
||||
#include "sim/netConnection.h"
|
||||
#endif
|
||||
#ifndef _MOVEMANAGER_H_
|
||||
#include "T3D/gameBase/moveManager.h"
|
||||
#endif
|
||||
#ifndef _BITVECTOR_H_
|
||||
#include "core/bitVector.h"
|
||||
#endif
|
||||
|
||||
enum GameConnectionConstants
|
||||
{
|
||||
MaxClients = 126,
|
||||
DataBlockQueueCount = 16
|
||||
};
|
||||
|
||||
class SFXProfile;
|
||||
class MatrixF;
|
||||
class MatrixF;
|
||||
class Point3F;
|
||||
class MoveManager;
|
||||
class MoveList;
|
||||
struct Move;
|
||||
struct AuthInfo;
|
||||
|
||||
#define GameString TORQUE_APP_NAME
|
||||
|
||||
const F32 MinCameraFov = 1.f; ///< min camera FOV
|
||||
const F32 MaxCameraFov = 179.f; ///< max camera FOV
|
||||
|
||||
class GameConnection : public NetConnection
|
||||
{
|
||||
private:
|
||||
typedef NetConnection Parent;
|
||||
|
||||
SimObjectPtr<GameBase> mControlObject;
|
||||
SimObjectPtr<GameBase> mCameraObject;
|
||||
U32 mDataBlockSequence;
|
||||
char mDisconnectReason[256];
|
||||
|
||||
U32 mMissionCRC; // crc of the current mission file from the server
|
||||
|
||||
private:
|
||||
U32 mLastControlRequestTime;
|
||||
S32 mDataBlockModifiedKey;
|
||||
S32 mMaxDataBlockModifiedKey;
|
||||
|
||||
/// @name Client side first/third person
|
||||
/// @{
|
||||
|
||||
///
|
||||
bool mFirstPerson; ///< Are we currently first person or not.
|
||||
bool mUpdateFirstPerson; ///< Set to notify client or server of first person change.
|
||||
bool mUpdateCameraFov; ///< Set to notify server of camera FOV change.
|
||||
F32 mCameraFov; ///< Current camera fov (in degrees).
|
||||
F32 mCameraPos; ///< Current camera pos (0-1).
|
||||
F32 mCameraSpeed; ///< Camera in/out speed.
|
||||
/// @}
|
||||
|
||||
public:
|
||||
|
||||
/// @name Protocol Versions
|
||||
///
|
||||
/// Protocol versions are used to indicated changes in network traffic.
|
||||
/// These could be changes in how any object transmits or processes
|
||||
/// network information. You can specify backwards compatibility by
|
||||
/// specifying a MinRequireProtocolVersion. If the client
|
||||
/// protocol is >= this min value, the connection is accepted.
|
||||
///
|
||||
/// Torque (V12) SDK 1.0 uses protocol = 1
|
||||
///
|
||||
/// Torque SDK 1.1 uses protocol = 2
|
||||
/// Torque SDK 1.4 uses protocol = 12
|
||||
/// @{
|
||||
static const U32 CurrentProtocolVersion;
|
||||
static const U32 MinRequiredProtocolVersion;
|
||||
/// @}
|
||||
|
||||
/// Configuration
|
||||
enum Constants {
|
||||
BlockTypeMove = NetConnectionBlockTypeCount,
|
||||
GameConnectionBlockTypeCount,
|
||||
MaxConnectArgs = 16,
|
||||
DataBlocksDone = NumConnectionMessages,
|
||||
DataBlocksDownloadDone,
|
||||
};
|
||||
|
||||
/// Set connection arguments; these are passed to the server when we connect.
|
||||
void setConnectArgs(U32 argc, const char **argv);
|
||||
|
||||
/// Set the server password to use when we join.
|
||||
void setJoinPassword(const char *password);
|
||||
|
||||
/// @name Event Handling
|
||||
/// @{
|
||||
|
||||
virtual void onTimedOut();
|
||||
virtual void onConnectTimedOut();
|
||||
virtual void onDisconnect(const char *reason);
|
||||
virtual void onConnectionRejected(const char *reason);
|
||||
virtual void onConnectionEstablished(bool isInitiator);
|
||||
virtual void handleStartupError(const char *errorString);
|
||||
/// @}
|
||||
|
||||
/// @name Packet I/O
|
||||
/// @{
|
||||
|
||||
virtual void writeConnectRequest(BitStream *stream);
|
||||
virtual bool readConnectRequest(BitStream *stream, const char **errorString);
|
||||
virtual void writeConnectAccept(BitStream *stream);
|
||||
virtual bool readConnectAccept(BitStream *stream, const char **errorString);
|
||||
/// @}
|
||||
|
||||
bool canRemoteCreate();
|
||||
|
||||
private:
|
||||
/// @name Connection State
|
||||
/// This data is set with setConnectArgs() and setJoinPassword(), and
|
||||
/// sent across the wire when we connect.
|
||||
/// @{
|
||||
|
||||
U32 mConnectArgc;
|
||||
char *mConnectArgv[MaxConnectArgs];
|
||||
char *mJoinPassword;
|
||||
/// @}
|
||||
|
||||
protected:
|
||||
struct GamePacketNotify : public NetConnection::PacketNotify
|
||||
{
|
||||
S32 cameraFov;
|
||||
GamePacketNotify();
|
||||
};
|
||||
PacketNotify *allocNotify();
|
||||
|
||||
bool mControlForceMismatch;
|
||||
|
||||
Vector<SimDataBlock *> mDataBlockLoadList;
|
||||
|
||||
public:
|
||||
|
||||
MoveList *mMoveList;
|
||||
|
||||
protected:
|
||||
bool mAIControlled;
|
||||
AuthInfo * mAuthInfo;
|
||||
|
||||
static S32 mLagThresholdMS;
|
||||
S32 mLastPacketTime;
|
||||
bool mLagging;
|
||||
|
||||
/// @name Flashing
|
||||
////
|
||||
/// Note, these variables are not networked, they are for the local connection only.
|
||||
/// @{
|
||||
F32 mDamageFlash;
|
||||
F32 mWhiteOut;
|
||||
|
||||
F32 mBlackOut;
|
||||
S32 mBlackOutTimeMS;
|
||||
S32 mBlackOutStartTimeMS;
|
||||
bool mFadeToBlack;
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Packet I/O
|
||||
/// @{
|
||||
|
||||
void readPacket (BitStream *bstream);
|
||||
void writePacket (BitStream *bstream, PacketNotify *note);
|
||||
void packetReceived (PacketNotify *note);
|
||||
void packetDropped (PacketNotify *note);
|
||||
void connectionError (const char *errorString);
|
||||
|
||||
void writeDemoStartBlock (ResizeBitStream *stream);
|
||||
bool readDemoStartBlock (BitStream *stream);
|
||||
void handleRecordedBlock (U32 type, U32 size, void *data);
|
||||
/// @}
|
||||
void ghostWriteExtra(NetObject *,BitStream *);
|
||||
void ghostReadExtra(NetObject *,BitStream *, bool newGhost);
|
||||
void ghostPreRead(NetObject *, bool newGhost);
|
||||
|
||||
virtual void onEndGhosting();
|
||||
|
||||
public:
|
||||
|
||||
DECLARE_CONOBJECT(GameConnection);
|
||||
void handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount);
|
||||
void preloadDataBlock(SimDataBlock *block);
|
||||
void fileDownloadSegmentComplete();
|
||||
void preloadNextDataBlock(bool hadNew);
|
||||
|
||||
static void consoleInit();
|
||||
|
||||
void setDisconnectReason(const char *reason);
|
||||
GameConnection();
|
||||
~GameConnection();
|
||||
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
|
||||
static GameConnection *getConnectionToServer()
|
||||
{
|
||||
return dynamic_cast<GameConnection*>((NetConnection *) mServerConnection);
|
||||
}
|
||||
|
||||
static GameConnection *getLocalClientConnection()
|
||||
{
|
||||
return dynamic_cast<GameConnection*>((NetConnection *) mLocalClientConnection);
|
||||
}
|
||||
|
||||
/// @name Control object
|
||||
/// @{
|
||||
|
||||
///
|
||||
void setControlObject(GameBase *);
|
||||
GameBase* getControlObject() { return mControlObject; }
|
||||
const GameBase* getControlObject() const { return mControlObject; }
|
||||
|
||||
void setCameraObject(GameBase *);
|
||||
GameBase* getCameraObject();
|
||||
|
||||
bool getControlCameraTransform(F32 dt,MatrixF* mat);
|
||||
bool getControlCameraVelocity(Point3F *vel);
|
||||
|
||||
bool getControlCameraDefaultFov(F32 *fov);
|
||||
bool getControlCameraFov(F32 *fov);
|
||||
bool setControlCameraFov(F32 fov);
|
||||
bool isValidControlCameraFov(F32 fov);
|
||||
|
||||
// Used by editor
|
||||
bool isControlObjectRotDampedCamera();
|
||||
|
||||
void setFirstPerson(bool firstPerson);
|
||||
|
||||
/// @}
|
||||
|
||||
void detectLag();
|
||||
|
||||
/// @name Datablock management
|
||||
/// @{
|
||||
|
||||
S32 getDataBlockModifiedKey () { return mDataBlockModifiedKey; }
|
||||
void setDataBlockModifiedKey (S32 key) { mDataBlockModifiedKey = key; }
|
||||
S32 getMaxDataBlockModifiedKey () { return mMaxDataBlockModifiedKey; }
|
||||
void setMaxDataBlockModifiedKey (S32 key) { mMaxDataBlockModifiedKey = key; }
|
||||
|
||||
/// Return the datablock sequence number that this game connection is on.
|
||||
/// The datablock sequence number is synchronized to the mission sequence number
|
||||
/// on each datablock transmission.
|
||||
U32 getDataBlockSequence() { return mDataBlockSequence; }
|
||||
|
||||
/// Set the datablock sequence number.
|
||||
void setDataBlockSequence(U32 seq) { mDataBlockSequence = seq; }
|
||||
|
||||
/// @}
|
||||
|
||||
/// @name Fade control
|
||||
/// @{
|
||||
|
||||
F32 getDamageFlash() const { return mDamageFlash; }
|
||||
F32 getWhiteOut() const { return mWhiteOut; }
|
||||
|
||||
void setBlackOut(bool fadeToBlack, S32 timeMS);
|
||||
F32 getBlackOut();
|
||||
/// @}
|
||||
|
||||
/// @name Authentication
|
||||
///
|
||||
/// This is remnant code from Tribes 2.
|
||||
/// @{
|
||||
|
||||
void setAuthInfo(const AuthInfo *info);
|
||||
const AuthInfo *getAuthInfo();
|
||||
/// @}
|
||||
|
||||
/// @name Sound
|
||||
/// @{
|
||||
|
||||
void play2D(SFXProfile *profile);
|
||||
void play3D(SFXProfile *profile, const MatrixF *transform);
|
||||
/// @}
|
||||
|
||||
/// @name Misc.
|
||||
/// @{
|
||||
|
||||
bool isFirstPerson() const { return mCameraPos == 0; }
|
||||
bool isAIControlled() { return mAIControlled; }
|
||||
|
||||
void doneScopingScene();
|
||||
void demoPlaybackComplete();
|
||||
|
||||
void setMissionCRC(U32 crc) { mMissionCRC = crc; }
|
||||
U32 getMissionCRC() { return(mMissionCRC); }
|
||||
/// @}
|
||||
|
||||
static Signal<void(F32)> smFovUpdate;
|
||||
static Signal<void()> smPlayingDemo;
|
||||
|
||||
protected:
|
||||
DECLARE_CALLBACK( void, onConnectionTimedOut, () );
|
||||
DECLARE_CALLBACK( void, onConnectionAccepted, () );
|
||||
DECLARE_CALLBACK( void, onConnectRequestTimedOut, () );
|
||||
DECLARE_CALLBACK( void, onConnectionDropped, (const char* reason) );
|
||||
DECLARE_CALLBACK( void, onConnectRequestRejected, (const char* reason) );
|
||||
DECLARE_CALLBACK( void, onConnectionError, (const char* errorString) );
|
||||
DECLARE_CALLBACK( void, onDrop, (const char* disconnectReason) );
|
||||
DECLARE_CALLBACK( void, initialControlSet, () );
|
||||
DECLARE_CALLBACK( void, onControlObjectChange, () );
|
||||
DECLARE_CALLBACK( void, setLagIcon, (bool state) );
|
||||
DECLARE_CALLBACK( void, onDataBlocksDone, (U32 sequence) );
|
||||
DECLARE_CALLBACK( void, onFlash, (bool state) );
|
||||
};
|
||||
|
||||
#endif
|
||||
382
Engine/source/T3D/gameBase/gameConnectionEvents.cpp
Normal file
382
Engine/source/T3D/gameBase/gameConnectionEvents.cpp
Normal file
|
|
@ -0,0 +1,382 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "core/dnet.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/simBase.h"
|
||||
#include "scene/pathManager.h"
|
||||
#include "scene/sceneManager.h"
|
||||
#include "sfx/sfxSystem.h"
|
||||
#include "sfx/sfxDescription.h"
|
||||
#include "app/game.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/gameBase/gameConnectionEvents.h"
|
||||
|
||||
#define DebugChecksum 0xF00DBAAD
|
||||
|
||||
|
||||
//#define DEBUG_SPEW
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
IMPLEMENT_CO_CLIENTEVENT_V1(SimDataBlockEvent);
|
||||
IMPLEMENT_CO_CLIENTEVENT_V1(Sim2DAudioEvent);
|
||||
IMPLEMENT_CO_CLIENTEVENT_V1(Sim3DAudioEvent);
|
||||
IMPLEMENT_CO_CLIENTEVENT_V1(SetMissionCRCEvent);
|
||||
|
||||
ConsoleDocClass( SimDataBlockEvent,
|
||||
"@brief Use by GameConnection to process incoming datablocks.\n\n"
|
||||
"Not intended for game development, internal use only, but does expose onDataBlockObjectReceived.\n\n "
|
||||
"@internal");
|
||||
|
||||
ConsoleDocClass( Sim2DAudioEvent,
|
||||
"@brief Use by GameConnection to send a 2D sound event over the network.\n\n"
|
||||
"Not intended for game development, internal use only, but does expose GameConnection::play2D.\n\n "
|
||||
"@internal");
|
||||
|
||||
ConsoleDocClass( Sim3DAudioEvent,
|
||||
"@brief Use by GameConnection to send a 3D sound event over the network.\n\n"
|
||||
"Not intended for game development, internal use only, but does expose GameConnection::play3D.\n\n "
|
||||
"@internal");
|
||||
|
||||
ConsoleDocClass( SetMissionCRCEvent,
|
||||
"@brief Use by GameConnection to send a 3D sound event over the network.\n\n"
|
||||
"Not intended for game development, internal use only, but does expose GameConnection::setMissionCRC.\n\n "
|
||||
"@internal");
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
SimDataBlockEvent::SimDataBlockEvent(SimDataBlock* obj, U32 index, U32 total, U32 missionSequence)
|
||||
{
|
||||
mObj = NULL;
|
||||
mIndex = index;
|
||||
mTotal = total;
|
||||
mMissionSequence = missionSequence;
|
||||
mProcess = false;
|
||||
|
||||
if(obj)
|
||||
{
|
||||
id = obj->getId();
|
||||
AssertFatal(id >= DataBlockObjectIdFirst && id <= DataBlockObjectIdLast,
|
||||
"Out of range event data block id... check simBase.h");
|
||||
|
||||
#ifdef DEBUG_SPEW
|
||||
Con::printf("queuing data block: %d", mIndex);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
SimDataBlockEvent::~SimDataBlockEvent()
|
||||
{
|
||||
if( mObj )
|
||||
delete mObj;
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *SimDataBlockEvent::getDebugName()
|
||||
{
|
||||
SimObject *obj = Sim::findObject(id);
|
||||
static char buffer[256];
|
||||
dSprintf(buffer, sizeof(buffer), "%s [%s - %s]",
|
||||
getClassName(),
|
||||
obj ? obj->getName() : "",
|
||||
obj ? obj->getClassName() : "NONE");
|
||||
return buffer;
|
||||
}
|
||||
#endif // TORQUE_DEBUG_NET
|
||||
|
||||
void SimDataBlockEvent::notifyDelivered(NetConnection *conn, bool )
|
||||
{
|
||||
// if the modified key for this event is not the current one,
|
||||
// we've already resorted and resent some blocks, so fall out.
|
||||
if(conn->isRemoved())
|
||||
return;
|
||||
|
||||
GameConnection *gc = (GameConnection *) conn;
|
||||
if(gc->getDataBlockSequence() != mMissionSequence)
|
||||
return;
|
||||
|
||||
U32 nextIndex = mIndex + DataBlockQueueCount;
|
||||
SimDataBlockGroup *g = Sim::getDataBlockGroup();
|
||||
|
||||
if(mIndex == g->size() - 1)
|
||||
{
|
||||
gc->setDataBlockModifiedKey(gc->getMaxDataBlockModifiedKey());
|
||||
gc->sendConnectionMessage(GameConnection::DataBlocksDone, mMissionSequence);
|
||||
}
|
||||
|
||||
if(g->size() <= nextIndex)
|
||||
return;
|
||||
|
||||
SimDataBlock *blk = (SimDataBlock *) (*g)[nextIndex];
|
||||
gc->postNetEvent(new SimDataBlockEvent(blk, nextIndex, g->size(), mMissionSequence));
|
||||
}
|
||||
|
||||
void SimDataBlockEvent::pack(NetConnection *conn, BitStream *bstream)
|
||||
{
|
||||
SimDataBlock* obj;
|
||||
Sim::findObject(id,obj);
|
||||
GameConnection *gc = (GameConnection *) conn;
|
||||
if(bstream->writeFlag(gc->getDataBlockModifiedKey() < obj->getModifiedKey()))
|
||||
{
|
||||
if(obj->getModifiedKey() > gc->getMaxDataBlockModifiedKey())
|
||||
gc->setMaxDataBlockModifiedKey(obj->getModifiedKey());
|
||||
|
||||
AssertFatal(obj,
|
||||
"SimDataBlockEvent:: Data blocks cannot be deleted");
|
||||
bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize);
|
||||
|
||||
S32 classId = obj->getClassId(conn->getNetClassGroup());
|
||||
bstream->writeClassId(classId, NetClassTypeDataBlock, conn->getNetClassGroup());
|
||||
bstream->writeInt(mIndex, DataBlockObjectIdBitSize);
|
||||
bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1);
|
||||
obj->packData(bstream);
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(classId ^ DebugChecksum, 32);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void SimDataBlockEvent::unpack(NetConnection *cptr, BitStream *bstream)
|
||||
{
|
||||
if(bstream->readFlag())
|
||||
{
|
||||
mProcess = true;
|
||||
id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst;
|
||||
S32 classId = bstream->readClassId(NetClassTypeDataBlock, cptr->getNetClassGroup());
|
||||
mIndex = bstream->readInt(DataBlockObjectIdBitSize);
|
||||
mTotal = bstream->readInt(DataBlockObjectIdBitSize + 1);
|
||||
|
||||
SimObject* ptr;
|
||||
if( Sim::findObject( id, ptr ) )
|
||||
{
|
||||
// An object with the given ID already exists. Make sure it has the right class.
|
||||
|
||||
AbstractClassRep* classRep = AbstractClassRep::findClassRep( cptr->getNetClassGroup(), NetClassTypeDataBlock, classId );
|
||||
if( classRep && dStrcmp( classRep->getClassName(), ptr->getClassName() ) != 0 )
|
||||
{
|
||||
Con::warnf( "A '%s' datablock with id: %d already existed. "
|
||||
"Clobbering it with new '%s' datablock from server.",
|
||||
ptr->getClassName(), id, classRep->getClassName() );
|
||||
ptr->deleteObject();
|
||||
ptr = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if( !ptr )
|
||||
ptr = ( SimObject* ) ConsoleObject::create( cptr->getNetClassGroup(), NetClassTypeDataBlock, classId );
|
||||
|
||||
mObj = dynamic_cast< SimDataBlock* >( ptr );
|
||||
if( mObj != NULL )
|
||||
{
|
||||
#ifdef DEBUG_SPEW
|
||||
Con::printf(" - SimDataBlockEvent: unpacking event of type: %s", mObj->getClassName());
|
||||
#endif
|
||||
|
||||
mObj->unpackData( bstream );
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef DEBUG_SPEW
|
||||
Con::printf(" - SimDataBlockEvent: INVALID PACKET! Could not create class with classID: %d", classId);
|
||||
#endif
|
||||
|
||||
delete ptr;
|
||||
cptr->setLastError("Invalid packet in SimDataBlockEvent::unpack()");
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 checksum = bstream->readInt(32);
|
||||
AssertISV( (checksum ^ DebugChecksum) == (U32)classId,
|
||||
avar("unpack did not match pack for event of class %s.",
|
||||
mObj->getClassName()) );
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void SimDataBlockEvent::write(NetConnection *cptr, BitStream *bstream)
|
||||
{
|
||||
if(bstream->writeFlag(mProcess))
|
||||
{
|
||||
bstream->writeInt(id - DataBlockObjectIdFirst,DataBlockObjectIdBitSize);
|
||||
S32 classId = mObj->getClassId(cptr->getNetClassGroup());
|
||||
bstream->writeClassId(classId, NetClassTypeDataBlock, cptr->getNetClassGroup());
|
||||
bstream->writeInt(mIndex, DataBlockObjectIdBitSize);
|
||||
bstream->writeInt(mTotal, DataBlockObjectIdBitSize + 1);
|
||||
mObj->packData(bstream);
|
||||
}
|
||||
}
|
||||
|
||||
void SimDataBlockEvent::process(NetConnection *cptr)
|
||||
{
|
||||
if(mProcess)
|
||||
{
|
||||
//call the console function to set the number of blocks to be sent
|
||||
Con::executef("onDataBlockObjectReceived", Con::getIntArg(mIndex), Con::getIntArg(mTotal));
|
||||
|
||||
String &errorBuffer = NetConnection::getErrorBuffer();
|
||||
|
||||
// Register the datablock object if this is a new DB
|
||||
// and not for a modified datablock event.
|
||||
|
||||
if( !mObj->isProperlyAdded() )
|
||||
{
|
||||
// This is a fresh datablock object.
|
||||
// Perform preload on datablock and register
|
||||
// the object.
|
||||
|
||||
GameConnection* conn = dynamic_cast< GameConnection* >( cptr );
|
||||
if( conn )
|
||||
conn->preloadDataBlock( mObj );
|
||||
|
||||
if( mObj->registerObject(id) )
|
||||
{
|
||||
cptr->addObject( mObj );
|
||||
mObj = NULL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is an update to an existing datablock. Preload
|
||||
// to finish this.
|
||||
|
||||
mObj->preload( false, errorBuffer );
|
||||
mObj = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
|
||||
Sim2DAudioEvent::Sim2DAudioEvent(SFXProfile *profile)
|
||||
{
|
||||
mProfile = profile;
|
||||
}
|
||||
|
||||
void Sim2DAudioEvent::pack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt( mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize);
|
||||
}
|
||||
|
||||
void Sim2DAudioEvent::write(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt( mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize);
|
||||
}
|
||||
|
||||
void Sim2DAudioEvent::unpack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
SimObjectId id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst;
|
||||
Sim::findObject(id, mProfile);
|
||||
}
|
||||
|
||||
void Sim2DAudioEvent::process(NetConnection *)
|
||||
{
|
||||
if (mProfile)
|
||||
SFX->playOnce( mProfile );
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
static F32 SoundPosAccuracy = 0.5;
|
||||
static S32 SoundRotBits = 8;
|
||||
|
||||
Sim3DAudioEvent::Sim3DAudioEvent(SFXProfile *profile,const MatrixF* mat)
|
||||
{
|
||||
mProfile = profile;
|
||||
if (mat)
|
||||
mTransform = *mat;
|
||||
}
|
||||
|
||||
void Sim3DAudioEvent::pack(NetConnection *con, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mProfile->getId() - DataBlockObjectIdFirst, DataBlockObjectIdBitSize);
|
||||
|
||||
// If the sound has cone parameters, the orientation is
|
||||
// transmitted as well.
|
||||
SFXDescription* ad = mProfile->getDescription();
|
||||
if ( bstream->writeFlag( ad->mConeInsideAngle || ad->mConeOutsideAngle ) )
|
||||
{
|
||||
QuatF q(mTransform);
|
||||
q.normalize();
|
||||
|
||||
// LH - we can get a valid quat that's very slightly over 1 in and so
|
||||
// this fails (barely) check against zero. So use some error-
|
||||
AssertFatal((1.0 - ((q.x * q.x) + (q.y * q.y) + (q.z * q.z))) >= (0.0 - 0.001),
|
||||
"QuatF::normalize() is broken in Sim3DAudioEvent");
|
||||
|
||||
bstream->writeFloat(q.x,SoundRotBits);
|
||||
bstream->writeFloat(q.y,SoundRotBits);
|
||||
bstream->writeFloat(q.z,SoundRotBits);
|
||||
bstream->writeFlag(q.w < 0.0);
|
||||
}
|
||||
|
||||
Point3F pos;
|
||||
mTransform.getColumn(3,&pos);
|
||||
bstream->writeCompressedPoint(pos,SoundPosAccuracy);
|
||||
}
|
||||
|
||||
void Sim3DAudioEvent::write(NetConnection *con, BitStream *bstream)
|
||||
{
|
||||
// Just do the normal pack...
|
||||
pack(con,bstream);
|
||||
}
|
||||
|
||||
void Sim3DAudioEvent::unpack(NetConnection *con, BitStream *bstream)
|
||||
{
|
||||
SimObjectId id = bstream->readInt(DataBlockObjectIdBitSize) + DataBlockObjectIdFirst;
|
||||
Sim::findObject(id, mProfile);
|
||||
|
||||
if (bstream->readFlag()) {
|
||||
QuatF q;
|
||||
q.x = bstream->readFloat(SoundRotBits);
|
||||
q.y = bstream->readFloat(SoundRotBits);
|
||||
q.z = bstream->readFloat(SoundRotBits);
|
||||
F32 value = ((q.x * q.x) + (q.y * q.y) + (q.z * q.z));
|
||||
// #ifdef __linux
|
||||
// Hmm, this should never happen, but it does...
|
||||
if ( value > 1.f )
|
||||
value = 1.f;
|
||||
// #endif
|
||||
q.w = mSqrt(1.f - value);
|
||||
if (bstream->readFlag())
|
||||
q.w = -q.w;
|
||||
q.setMatrix(&mTransform);
|
||||
}
|
||||
else
|
||||
mTransform.identity();
|
||||
|
||||
Point3F pos;
|
||||
bstream->readCompressedPoint(&pos,SoundPosAccuracy);
|
||||
mTransform.setColumn(3, pos);
|
||||
}
|
||||
|
||||
void Sim3DAudioEvent::process(NetConnection *)
|
||||
{
|
||||
if (mProfile)
|
||||
SFX->playOnce( mProfile, &mTransform );
|
||||
}
|
||||
|
||||
161
Engine/source/T3D/gameBase/gameConnectionEvents.h
Normal file
161
Engine/source/T3D/gameBase/gameConnectionEvents.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _GAMECONNECTIONEVENTS_H_
|
||||
#define _GAMECONNECTIONEVENTS_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
#ifndef _GAMECONNECTION_H_
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#endif
|
||||
|
||||
#ifndef _SFXPROFILE_H_
|
||||
#include "sfx/sfxProfile.h"
|
||||
#endif
|
||||
|
||||
#ifndef _BITSTREAM_H_
|
||||
#include "core/stream/bitStream.h"
|
||||
#endif
|
||||
|
||||
|
||||
class QuitEvent : public SimEvent
|
||||
{
|
||||
void process(SimObject *object)
|
||||
{
|
||||
Platform::postQuitMessage(0);
|
||||
}
|
||||
};
|
||||
|
||||
/// Event for sending a datablock over the net from the server to the client.
|
||||
///
|
||||
/// Datablock events are GuaranteedOrdered client events.
|
||||
///
|
||||
class SimDataBlockEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
|
||||
typedef NetEvent Parent;
|
||||
|
||||
protected:
|
||||
|
||||
/// Id of the datablock object to be sent. This must be a datablock ID
|
||||
/// (as opposed to a normal object ID).
|
||||
SimObjectId id;
|
||||
|
||||
///
|
||||
U32 mIndex;
|
||||
|
||||
/// Total number of datablocks that are part of this datablock transmission.
|
||||
/// Each datablock is transmitted in an independent datablock event.
|
||||
U32 mTotal;
|
||||
|
||||
/// The mission sequence number to which this datablock transmission
|
||||
/// belongs.
|
||||
///
|
||||
/// @see GameConnection::getDataBlockSequence
|
||||
U32 mMissionSequence;
|
||||
|
||||
/// Datablock object constructed on the client side.
|
||||
SimDataBlock *mObj;
|
||||
|
||||
///
|
||||
bool mProcess;
|
||||
|
||||
public:
|
||||
|
||||
SimDataBlockEvent(SimDataBlock* obj = NULL, U32 index = 0, U32 total = 0, U32 missionSequence = 0);
|
||||
~SimDataBlockEvent();
|
||||
|
||||
void pack(NetConnection *, BitStream *bstream);
|
||||
void write(NetConnection *, BitStream *bstream);
|
||||
void unpack(NetConnection *cptr, BitStream *bstream);
|
||||
void process(NetConnection*);
|
||||
void notifyDelivered(NetConnection *, bool);
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *getDebugName();
|
||||
#endif
|
||||
|
||||
DECLARE_CONOBJECT( SimDataBlockEvent );
|
||||
DECLARE_CATEGORY( "Game Networking" );
|
||||
};
|
||||
|
||||
class Sim2DAudioEvent: public NetEvent
|
||||
{
|
||||
private:
|
||||
SFXProfile *mProfile;
|
||||
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
Sim2DAudioEvent(SFXProfile *profile=NULL);
|
||||
void pack(NetConnection *, BitStream *bstream);
|
||||
void write(NetConnection *, BitStream *bstream);
|
||||
void unpack(NetConnection *, BitStream *bstream);
|
||||
void process(NetConnection *);
|
||||
DECLARE_CONOBJECT(Sim2DAudioEvent);
|
||||
};
|
||||
|
||||
class Sim3DAudioEvent: public NetEvent
|
||||
{
|
||||
private:
|
||||
SFXProfile *mProfile;
|
||||
MatrixF mTransform;
|
||||
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
Sim3DAudioEvent(SFXProfile *profile=NULL,const MatrixF* mat=NULL);
|
||||
void pack(NetConnection *, BitStream *bstream);
|
||||
void write(NetConnection *, BitStream *bstream);
|
||||
void unpack(NetConnection *, BitStream *bstream);
|
||||
void process(NetConnection *);
|
||||
DECLARE_CONOBJECT(Sim3DAudioEvent);
|
||||
};
|
||||
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
// used to set the crc for the current mission (mission lighting)
|
||||
//----------------------------------------------------------------------------
|
||||
class SetMissionCRCEvent : public NetEvent
|
||||
{
|
||||
private:
|
||||
U32 mCrc;
|
||||
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
SetMissionCRCEvent(U32 crc = 0xffffffff)
|
||||
{ mCrc = crc; }
|
||||
void pack(NetConnection *, BitStream * bstream)
|
||||
{ bstream->write(mCrc); }
|
||||
void write(NetConnection * con, BitStream * bstream)
|
||||
{ pack(con, bstream); }
|
||||
void unpack(NetConnection *, BitStream * bstream)
|
||||
{ bstream->read(&mCrc); }
|
||||
void process(NetConnection * con)
|
||||
{ static_cast<GameConnection*>(con)->setMissionCRC(mCrc); }
|
||||
|
||||
DECLARE_CONOBJECT(SetMissionCRCEvent);
|
||||
};
|
||||
|
||||
#endif
|
||||
184
Engine/source/T3D/gameBase/gameProcess.cpp
Normal file
184
Engine/source/T3D/gameBase/gameProcess.cpp
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/gameProcess.h"
|
||||
|
||||
#include "T3D/gameBase/gameBase.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/gameBase/moveList.h"
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
ClientProcessList* ClientProcessList::smClientProcessList = NULL;
|
||||
ServerProcessList* ServerProcessList::smServerProcessList = NULL;
|
||||
static U32 gNetOrderNextId = 0;
|
||||
|
||||
ConsoleFunction( dumpProcessList, void, 1, 1,
|
||||
"Dumps all ProcessObjects in ServerProcessList and ClientProcessList to the console." )
|
||||
{
|
||||
Con::printf( "client process list:" );
|
||||
ClientProcessList::get()->dumpToConsole();
|
||||
Con::printf( "server process list:" );
|
||||
ServerProcessList::get()->dumpToConsole();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// ClientProcessList
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ClientProcessList::ClientProcessList()
|
||||
{
|
||||
}
|
||||
|
||||
void ClientProcessList::addObject( ProcessObject *pobj )
|
||||
{
|
||||
AssertFatal( static_cast<SceneObject*>( pobj )->isClientObject(), "Tried to add server object to ClientProcessList." );
|
||||
|
||||
GameBase *obj = getGameBase( pobj );
|
||||
|
||||
if ( obj && obj->isNetOrdered() )
|
||||
{
|
||||
if ( ( gNetOrderNextId & 0xFFFF ) == 0 )
|
||||
// don't let it be zero
|
||||
gNetOrderNextId++;
|
||||
|
||||
pobj->mOrderGUID = ( gNetOrderNextId++ ) & 0xFFFF; // 16 bits should be enough
|
||||
pobj->plLinkBefore( &mHead );
|
||||
mDirty = true;
|
||||
}
|
||||
else if ( obj && obj->isTickLast() )
|
||||
{
|
||||
pobj->mOrderGUID = 0xFFFFFFFF;
|
||||
pobj->plLinkBefore( &mHead );
|
||||
// not dirty
|
||||
}
|
||||
else
|
||||
{
|
||||
pobj->plLinkAfter( &mHead );
|
||||
// not dirty
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientProcessList::doBacklogged( SimTime timeDelta )
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
static bool backlogged = false;
|
||||
static U32 backloggedTime = 0;
|
||||
#endif
|
||||
|
||||
// See if the control object has pending moves.
|
||||
GameConnection *connection = GameConnection::getConnectionToServer();
|
||||
|
||||
if ( connection )
|
||||
{
|
||||
// If the connection to the server is backlogged
|
||||
// the simulation is frozen.
|
||||
if ( connection->mMoveList->isBacklogged() )
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
if ( !backlogged )
|
||||
{
|
||||
Con::printf( "client is backlogged, time is frozen" );
|
||||
backlogged = true;
|
||||
}
|
||||
|
||||
backloggedTime += timeDelta;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
if ( backlogged )
|
||||
{
|
||||
Con::printf( "client is no longer backlogged, time is unfrozen (%i ms elapsed)", backloggedTime );
|
||||
backlogged = false;
|
||||
backloggedTime = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClientProcessList::onPreTickObject( ProcessObject *pobj )
|
||||
{
|
||||
// reset to tick boundary
|
||||
pobj->interpolateTick( 0.0f );
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// ServerProcessList
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ServerProcessList::ServerProcessList()
|
||||
{
|
||||
}
|
||||
|
||||
void ServerProcessList::addObject( ProcessObject *pobj )
|
||||
{
|
||||
AssertFatal( static_cast<SceneObject*>( pobj )->isServerObject(), "Tried to add client object to ServerProcessList." );
|
||||
|
||||
GameBase *obj = getGameBase( pobj );
|
||||
|
||||
if ( obj && obj->isNetOrdered() )
|
||||
{
|
||||
if ( ( gNetOrderNextId & 0xFFFF ) == 0)
|
||||
// don't let it be zero
|
||||
gNetOrderNextId++;
|
||||
|
||||
pobj->mOrderGUID = ( gNetOrderNextId++ ) & 0xFFFF; // 16 bits should be enough
|
||||
pobj->plLinkBefore( &mHead );
|
||||
mDirty = true;
|
||||
}
|
||||
else if ( obj && obj->isTickLast() )
|
||||
{
|
||||
pobj->mOrderGUID = 0xFFFFFFFF;
|
||||
pobj->plLinkBefore( &mHead );
|
||||
// not dirty
|
||||
}
|
||||
else
|
||||
{
|
||||
pobj->plLinkAfter( &mHead );
|
||||
// not dirty
|
||||
}
|
||||
}
|
||||
|
||||
void ServerProcessList::advanceObjects()
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("Advance server time...");
|
||||
#endif
|
||||
|
||||
Parent::advanceObjects();
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("---------");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ServerProcessList::onPreTickObject( ProcessObject *pobj )
|
||||
{
|
||||
}
|
||||
|
||||
96
Engine/source/T3D/gameBase/gameProcess.h
Normal file
96
Engine/source/T3D/gameBase/gameProcess.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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_H_
|
||||
#define _GAMEPROCESS_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _PROCESSLIST_H_
|
||||
#include "T3D/gameBase/processList.h"
|
||||
#endif
|
||||
|
||||
|
||||
class GameBase;
|
||||
class GameConnection;
|
||||
struct Move;
|
||||
|
||||
|
||||
class ClientProcessList : public ProcessList
|
||||
{
|
||||
typedef ProcessList Parent;
|
||||
|
||||
public:
|
||||
|
||||
ClientProcessList();
|
||||
|
||||
// ProcessList
|
||||
void addObject( ProcessObject *pobj );
|
||||
|
||||
/// Called after a correction packet is received from the server.
|
||||
/// If the control object was corrected it will now play back any moves
|
||||
/// which were rolled back.
|
||||
virtual void clientCatchup( GameConnection *conn ) {}
|
||||
|
||||
static ClientProcessList* get() { return smClientProcessList; }
|
||||
|
||||
protected:
|
||||
|
||||
// ProcessList
|
||||
void onPreTickObject( ProcessObject *pobj );
|
||||
|
||||
/// Returns true if backlogged.
|
||||
bool doBacklogged( SimTime timeDelta );
|
||||
|
||||
protected:
|
||||
|
||||
static ClientProcessList* smClientProcessList;
|
||||
};
|
||||
|
||||
|
||||
class ServerProcessList : public ProcessList
|
||||
{
|
||||
typedef ProcessList Parent;
|
||||
|
||||
public:
|
||||
|
||||
ServerProcessList();
|
||||
|
||||
// ProcessList
|
||||
void addObject( ProcessObject *pobj );
|
||||
|
||||
static ServerProcessList* get() { return smServerProcessList; }
|
||||
|
||||
protected:
|
||||
|
||||
// ProcessList
|
||||
void onPreTickObject( ProcessObject *pobj );
|
||||
void advanceObjects();
|
||||
|
||||
protected:
|
||||
|
||||
static ServerProcessList* smServerProcessList;
|
||||
};
|
||||
|
||||
|
||||
#endif // _GAMEPROCESS_H_
|
||||
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_
|
||||
223
Engine/source/T3D/gameBase/moveList.cpp
Normal file
223
Engine/source/T3D/gameBase/moveList.cpp
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/moveList.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
|
||||
MoveList::MoveList()
|
||||
{
|
||||
mControlMismatch = false;
|
||||
reset();
|
||||
}
|
||||
|
||||
void MoveList::reset()
|
||||
{
|
||||
mLastMoveAck = 0;
|
||||
mLastClientMove = 0;
|
||||
mFirstMoveIndex = 0;
|
||||
mMoveVec.clear();
|
||||
}
|
||||
|
||||
bool MoveList::getNextMove(Move &curMove)
|
||||
{
|
||||
if ( mMoveVec.size() > MaxMoveQueueSize )
|
||||
return false;
|
||||
|
||||
F32 pitchAdd = MoveManager::mPitchUpSpeed - MoveManager::mPitchDownSpeed;
|
||||
F32 yawAdd = MoveManager::mYawLeftSpeed - MoveManager::mYawRightSpeed;
|
||||
F32 rollAdd = MoveManager::mRollRightSpeed - MoveManager::mRollLeftSpeed;
|
||||
|
||||
curMove.pitch = MoveManager::mPitch + pitchAdd;
|
||||
curMove.yaw = MoveManager::mYaw + yawAdd;
|
||||
curMove.roll = MoveManager::mRoll + rollAdd;
|
||||
|
||||
MoveManager::mPitch = 0;
|
||||
MoveManager::mYaw = 0;
|
||||
MoveManager::mRoll = 0;
|
||||
|
||||
curMove.x = MoveManager::mRightAction - MoveManager::mLeftAction + MoveManager::mXAxis_L;
|
||||
curMove.y = MoveManager::mForwardAction - MoveManager::mBackwardAction + MoveManager::mYAxis_L;
|
||||
curMove.z = MoveManager::mUpAction - MoveManager::mDownAction;
|
||||
|
||||
curMove.freeLook = MoveManager::mFreeLook;
|
||||
curMove.deviceIsKeyboardMouse = MoveManager::mDeviceIsKeyboardMouse;
|
||||
|
||||
for(U32 i = 0; i < MaxTriggerKeys; i++)
|
||||
{
|
||||
curMove.trigger[i] = false;
|
||||
if(MoveManager::mTriggerCount[i] & 1)
|
||||
curMove.trigger[i] = true;
|
||||
else if(!(MoveManager::mPrevTriggerCount[i] & 1) && MoveManager::mPrevTriggerCount[i] != MoveManager::mTriggerCount[i])
|
||||
curMove.trigger[i] = true;
|
||||
MoveManager::mPrevTriggerCount[i] = MoveManager::mTriggerCount[i];
|
||||
}
|
||||
|
||||
if (mConnection->getControlObject())
|
||||
mConnection->getControlObject()->preprocessMove(&curMove);
|
||||
|
||||
curMove.clamp(); // clamp for net traffic
|
||||
return true;
|
||||
}
|
||||
|
||||
void MoveList::pushMove(const Move &mv)
|
||||
{
|
||||
U32 id = mFirstMoveIndex + mMoveVec.size();
|
||||
U32 sz = mMoveVec.size();
|
||||
mMoveVec.push_back(mv);
|
||||
mMoveVec[sz].id = id;
|
||||
mMoveVec[sz].sendCount = 0;
|
||||
}
|
||||
|
||||
U32 MoveList::getMoves(Move** movePtr,U32* numMoves)
|
||||
{
|
||||
if (mConnection->isConnectionToServer())
|
||||
{
|
||||
// give back moves starting at the last client move...
|
||||
|
||||
AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
|
||||
AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move.");
|
||||
*numMoves = mMoveVec.size() - mLastClientMove + mFirstMoveIndex;
|
||||
*movePtr = mMoveVec.address() + mLastClientMove - mFirstMoveIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
// return the full list
|
||||
*numMoves = mMoveVec.size();
|
||||
*movePtr = mMoveVec.begin();
|
||||
}
|
||||
|
||||
return *numMoves;
|
||||
}
|
||||
|
||||
void MoveList::collectMove()
|
||||
{
|
||||
Move mv;
|
||||
if (mConnection)
|
||||
{
|
||||
if(!mConnection->isPlayingBack() && getNextMove(mv))
|
||||
{
|
||||
mv.checksum=Move::ChecksumMismatch;
|
||||
pushMove(mv);
|
||||
mConnection->recordBlock(GameConnection::BlockTypeMove, sizeof(Move), &mv);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(getNextMove(mv))
|
||||
{
|
||||
mv.checksum=Move::ChecksumMismatch;
|
||||
pushMove(mv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MoveList::clearMoves(U32 count)
|
||||
{
|
||||
if (mConnection->isConnectionToServer())
|
||||
{
|
||||
mLastClientMove += count;
|
||||
AssertFatal(mLastClientMove >= mFirstMoveIndex, "Bad move request");
|
||||
AssertFatal(mLastClientMove - mFirstMoveIndex <= mMoveVec.size(), "Desynched first and last move.");
|
||||
if (!mConnection)
|
||||
// drop right away if no connection
|
||||
ackMoves(count);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertFatal(count <= mMoveVec.size(),"GameConnection: Clearing too many moves");
|
||||
for (S32 i=0; i<count; i++)
|
||||
if (mMoveVec[i].checksum == Move::ChecksumMismatch)
|
||||
mControlMismatch = true;
|
||||
else
|
||||
mControlMismatch = false;
|
||||
if (count == mMoveVec.size())
|
||||
mMoveVec.clear();
|
||||
else
|
||||
while (count--)
|
||||
mMoveVec.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
bool MoveList::areMovesPending()
|
||||
{
|
||||
return mConnection->isConnectionToServer() ?
|
||||
mMoveVec.size() - mLastClientMove + mFirstMoveIndex :
|
||||
mMoveVec.size();
|
||||
}
|
||||
|
||||
bool MoveList::isBacklogged()
|
||||
{
|
||||
if ( !mConnection->isConnectionToServer() )
|
||||
return false;
|
||||
|
||||
return mLastClientMove - mFirstMoveIndex == mMoveVec.size() &&
|
||||
mMoveVec.size() >= MaxMoveCount;
|
||||
}
|
||||
|
||||
void MoveList::ackMoves(U32 count)
|
||||
{
|
||||
mLastMoveAck += count;
|
||||
if(mLastMoveAck > mLastClientMove)
|
||||
mLastClientMove = mLastMoveAck;
|
||||
while(mFirstMoveIndex < mLastMoveAck)
|
||||
{
|
||||
if (mMoveVec.size())
|
||||
{
|
||||
mMoveVec.pop_front();
|
||||
mFirstMoveIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertWarn(1, "Popping off too many moves!");
|
||||
mFirstMoveIndex = mLastMoveAck;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MoveList::writeDemoStartBlock(ResizeBitStream *stream)
|
||||
{
|
||||
stream->write(mLastMoveAck);
|
||||
stream->write(mLastClientMove);
|
||||
stream->write(mFirstMoveIndex);
|
||||
|
||||
stream->write(U32(mMoveVec.size()));
|
||||
for(U32 j = 0; j < mMoveVec.size(); j++)
|
||||
mMoveVec[j].pack(stream);
|
||||
}
|
||||
|
||||
void MoveList::readDemoStartBlock(BitStream *stream)
|
||||
{
|
||||
stream->read(&mLastMoveAck);
|
||||
stream->read(&mLastClientMove);
|
||||
stream->read(&mFirstMoveIndex);
|
||||
|
||||
U32 size;
|
||||
Move mv;
|
||||
stream->read(&size);
|
||||
mMoveVec.clear();
|
||||
while(size--)
|
||||
{
|
||||
mv.unpack(stream);
|
||||
pushMove(mv);
|
||||
}
|
||||
}
|
||||
122
Engine/source/T3D/gameBase/moveList.h
Normal file
122
Engine/source/T3D/gameBase/moveList.h
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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_H_
|
||||
#define _MOVELIST_H_
|
||||
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
#ifndef _MOVEMANAGER_H_
|
||||
#include "T3D/gameBase/moveManager.h"
|
||||
#endif
|
||||
|
||||
class BitStream;
|
||||
class ResizeBitStream;
|
||||
class NetObject;
|
||||
class GameConnection;
|
||||
class PlayerRep;
|
||||
class ProcessList;
|
||||
|
||||
class MoveList
|
||||
{
|
||||
public:
|
||||
|
||||
MoveList();
|
||||
virtual ~MoveList() {}
|
||||
|
||||
virtual void init() {}
|
||||
|
||||
void setConnection( GameConnection *connection) { mConnection = connection; }
|
||||
|
||||
/// @name Move Packets
|
||||
/// Write/read move data to the packet.
|
||||
/// @{
|
||||
|
||||
virtual void ghostReadExtra( NetObject *, BitStream *, bool newGhost) {};
|
||||
virtual void ghostWriteExtra( NetObject *,BitStream * ) {};
|
||||
virtual void ghostPreRead( NetObject *, bool newGhost ) {};
|
||||
|
||||
virtual void clientWriteMovePacket( BitStream *bstream ) = 0;
|
||||
virtual void clientReadMovePacket( BitStream * ) = 0;
|
||||
|
||||
virtual void serverWriteMovePacket( BitStream * ) = 0;
|
||||
virtual void serverReadMovePacket( BitStream *bstream ) = 0;
|
||||
|
||||
virtual void writeDemoStartBlock( ResizeBitStream *stream );
|
||||
virtual void readDemoStartBlock( BitStream *stream );
|
||||
/// @}
|
||||
|
||||
virtual void advanceMove() = 0;
|
||||
virtual void onAdvanceObjects() = 0;
|
||||
virtual U32 getMoves( Move**, U32 *numMoves );
|
||||
|
||||
/// Reset to beginning of client move list.
|
||||
void resetClientMoves() { mLastClientMove = mFirstMoveIndex; }
|
||||
|
||||
/// Reset move list back to last acknowledged move.
|
||||
void resetCatchup() { mLastClientMove = mLastMoveAck; }
|
||||
|
||||
void collectMove();
|
||||
void pushMove( const Move &mv );
|
||||
virtual void clearMoves( U32 count );
|
||||
|
||||
virtual void markControlDirty() { mLastClientMove = mLastMoveAck; }
|
||||
bool isMismatch() { return mControlMismatch; }
|
||||
void clearMismatch() { mControlMismatch = false; }
|
||||
|
||||
/// Clear out all moves in the list and reset to initial state.
|
||||
void reset();
|
||||
|
||||
/// If there are no pending moves and the input queue is full,
|
||||
/// then the connection to the server must be clogged.
|
||||
bool isBacklogged();
|
||||
|
||||
bool areMovesPending();
|
||||
|
||||
void ackMoves( U32 count );
|
||||
|
||||
protected:
|
||||
|
||||
bool getNextMove( Move &curMove );
|
||||
|
||||
protected:
|
||||
|
||||
enum
|
||||
{
|
||||
MoveCountBits = 5,
|
||||
/// MaxMoveCount should not exceed the MoveManager's
|
||||
/// own maximum (MaxMoveQueueSize)
|
||||
MaxMoveCount = 30,
|
||||
};
|
||||
|
||||
U32 mLastMoveAck;
|
||||
U32 mLastClientMove;
|
||||
U32 mFirstMoveIndex;
|
||||
bool mControlMismatch;
|
||||
|
||||
GameConnection *mConnection;
|
||||
|
||||
Vector<Move> mMoveVec;
|
||||
};
|
||||
|
||||
#endif // _MOVELIST_H_
|
||||
302
Engine/source/T3D/gameBase/moveManager.cpp
Normal file
302
Engine/source/T3D/gameBase/moveManager.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/moveManager.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "core/module.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/strings/stringFunctions.h"
|
||||
#include "math/mConstants.h"
|
||||
|
||||
|
||||
MODULE_BEGIN( MoveManager )
|
||||
|
||||
MODULE_INIT
|
||||
{
|
||||
MoveManager::init();
|
||||
}
|
||||
|
||||
MODULE_END;
|
||||
|
||||
|
||||
bool MoveManager::mDeviceIsKeyboardMouse = false;
|
||||
F32 MoveManager::mForwardAction = 0;
|
||||
F32 MoveManager::mBackwardAction = 0;
|
||||
F32 MoveManager::mUpAction = 0;
|
||||
F32 MoveManager::mDownAction = 0;
|
||||
F32 MoveManager::mLeftAction = 0;
|
||||
F32 MoveManager::mRightAction = 0;
|
||||
|
||||
bool MoveManager::mFreeLook = false;
|
||||
F32 MoveManager::mPitch = 0;
|
||||
F32 MoveManager::mYaw = 0;
|
||||
F32 MoveManager::mRoll = 0;
|
||||
|
||||
F32 MoveManager::mPitchUpSpeed = 0;
|
||||
F32 MoveManager::mPitchDownSpeed = 0;
|
||||
F32 MoveManager::mYawLeftSpeed = 0;
|
||||
F32 MoveManager::mYawRightSpeed = 0;
|
||||
F32 MoveManager::mRollLeftSpeed = 0;
|
||||
F32 MoveManager::mRollRightSpeed = 0;
|
||||
|
||||
F32 MoveManager::mXAxis_L = 0;
|
||||
F32 MoveManager::mYAxis_L = 0;
|
||||
F32 MoveManager::mXAxis_R = 0;
|
||||
F32 MoveManager::mYAxis_R = 0;
|
||||
|
||||
U32 MoveManager::mTriggerCount[MaxTriggerKeys] = { 0, };
|
||||
U32 MoveManager::mPrevTriggerCount[MaxTriggerKeys] = { 0, };
|
||||
|
||||
const Move NullMove =
|
||||
{
|
||||
/*px=*/16, /*py=*/16, /*pz=*/16,
|
||||
/*pyaw=*/0, /*ppitch=*/0, /*proll=*/0,
|
||||
/*x=*/0, /*y=*/0,/*z=*/0,
|
||||
/*yaw=*/0, /*pitch=*/0, /*roll=*/0,
|
||||
/*id=*/0,
|
||||
/*sendCount=*/0,
|
||||
|
||||
/*checksum=*/false,
|
||||
/*deviceIsKeyboardMouse=*/false,
|
||||
/*freeLook=*/false,
|
||||
/*triggers=*/{false,false,false,false,false,false}
|
||||
};
|
||||
|
||||
void MoveManager::init()
|
||||
{
|
||||
Con::addVariable("mvForwardAction", TypeF32, &mForwardAction,
|
||||
"Forwards movement speed for the active player.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvBackwardAction", TypeF32, &mBackwardAction,
|
||||
"Backwards movement speed for the active player.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvUpAction", TypeF32, &mUpAction,
|
||||
"Upwards movement speed for the active player.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvDownAction", TypeF32, &mDownAction,
|
||||
"Downwards movement speed for the active player.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvLeftAction", TypeF32, &mLeftAction,
|
||||
"Left movement speed for the active player.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvRightAction", TypeF32, &mRightAction,
|
||||
"Right movement speed for the active player.\n"
|
||||
"@ingroup Game");
|
||||
|
||||
Con::addVariable("mvFreeLook", TypeBool, &mFreeLook,
|
||||
"Boolean state for if freelook is active or not.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvDeviceIsKeyboardMouse", TypeBool, &mDeviceIsKeyboardMouse,
|
||||
"Boolean state for it the system is using a keyboard and mouse or not.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvPitch", TypeF32, &mPitch,
|
||||
"Current pitch value, typically applied through input devices, such as a mouse.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvYaw", TypeF32, &mYaw,
|
||||
"Current yaw value, typically applied through input devices, such as a mouse.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvRoll", TypeF32, &mRoll,
|
||||
"Current roll value, typically applied through input devices, such as a mouse.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvPitchUpSpeed", TypeF32, &mPitchUpSpeed,
|
||||
"Upwards pitch speed.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvPitchDownSpeed", TypeF32, &mPitchDownSpeed,
|
||||
"Downwards pitch speed.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvYawLeftSpeed", TypeF32, &mYawLeftSpeed,
|
||||
"Left Yaw speed.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvYawRightSpeed", TypeF32, &mYawRightSpeed,
|
||||
"Right Yaw speed.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvRollLeftSpeed", TypeF32, &mRollLeftSpeed,
|
||||
"Left roll speed.\n"
|
||||
"@ingroup Game");
|
||||
Con::addVariable("mvRollRightSpeed", TypeF32, &mRollRightSpeed,
|
||||
"Right roll speed.\n"
|
||||
"@ingroup Game");
|
||||
|
||||
// Dual-analog
|
||||
Con::addVariable( "mvXAxis_L", TypeF32, &mXAxis_L,
|
||||
"Left thumbstick X axis position on a dual-analog gamepad.\n"
|
||||
"@ingroup Game" );
|
||||
Con::addVariable( "mvYAxis_L", TypeF32, &mYAxis_L,
|
||||
"Left thumbstick Y axis position on a dual-analog gamepad.\n"
|
||||
"@ingroup Game" );
|
||||
|
||||
Con::addVariable( "mvXAxis_R", TypeF32, &mXAxis_R,
|
||||
"Right thumbstick X axis position on a dual-analog gamepad.\n"
|
||||
"@ingroup Game" );
|
||||
Con::addVariable( "mvYAxis_R", TypeF32, &mYAxis_R,
|
||||
"Right thumbstick Y axis position on a dual-analog gamepad.\n"
|
||||
"@ingroup Game");
|
||||
|
||||
for(U32 i = 0; i < MaxTriggerKeys; i++)
|
||||
{
|
||||
char varName[256];
|
||||
dSprintf(varName, sizeof(varName), "mvTriggerCount%d", i);
|
||||
Con::addVariable(varName, TypeS32, &mTriggerCount[i],
|
||||
"Used to determine the trigger counts of buttons. Namely used for input actions such as jumping and weapons firing.\n"
|
||||
"@ingroup Game");
|
||||
}
|
||||
}
|
||||
|
||||
static inline F32 clampFloatWrap(F32 val)
|
||||
{
|
||||
return val - F32(S32(val));
|
||||
}
|
||||
|
||||
static inline S32 clampRangeClamp(F32 val)
|
||||
{
|
||||
if(val < -1)
|
||||
return 0;
|
||||
if(val > 1)
|
||||
return 32;
|
||||
|
||||
// 0.5 / 16 = 0.03125 ... this forces a round up to
|
||||
// make the precision near zero equal in the negative
|
||||
// and positive directions. See...
|
||||
//
|
||||
// http://www.garagegames.com/community/forums/viewthread/49714
|
||||
|
||||
return (S32)((val + 1.03125) * 16);
|
||||
}
|
||||
|
||||
|
||||
#define FANG2IANG(x) ((U32)((S16)((F32(0x10000) / M_2PI) * x)) & 0xFFFF)
|
||||
#define IANG2FANG(x) (F32)((M_2PI / F32(0x10000)) * (F32)((S16)x))
|
||||
|
||||
void Move::unclamp()
|
||||
{
|
||||
yaw = IANG2FANG(pyaw);
|
||||
pitch = IANG2FANG(ppitch);
|
||||
roll = IANG2FANG(proll);
|
||||
|
||||
x = (px - 16) / F32(16);
|
||||
y = (py - 16) / F32(16);
|
||||
z = (pz - 16) / F32(16);
|
||||
}
|
||||
|
||||
static inline F32 clampAngleClamp( F32 angle )
|
||||
{
|
||||
const F32 limit = ( M_PI_F / 180.0f ) * 179.999f;
|
||||
if ( angle < -limit )
|
||||
return -limit;
|
||||
if ( angle > limit )
|
||||
return limit;
|
||||
|
||||
return angle;
|
||||
}
|
||||
|
||||
void Move::clamp()
|
||||
{
|
||||
// If yaw/pitch/roll goes equal or greater than -PI/+PI it
|
||||
// flips the direction of the rotation... we protect against
|
||||
// that by clamping before the conversion.
|
||||
|
||||
yaw = clampAngleClamp( yaw );
|
||||
pitch = clampAngleClamp( pitch );
|
||||
roll = clampAngleClamp( roll );
|
||||
|
||||
// angles are all 16 bit.
|
||||
pyaw = FANG2IANG(yaw);
|
||||
ppitch = FANG2IANG(pitch);
|
||||
proll = FANG2IANG(roll);
|
||||
|
||||
px = clampRangeClamp(x);
|
||||
py = clampRangeClamp(y);
|
||||
pz = clampRangeClamp(z);
|
||||
unclamp();
|
||||
}
|
||||
|
||||
void Move::pack(BitStream *stream, const Move * basemove)
|
||||
{
|
||||
bool alwaysWriteAll = basemove!=NULL;
|
||||
if (!basemove)
|
||||
basemove = &NullMove;
|
||||
|
||||
S32 i;
|
||||
bool triggerDifferent = false;
|
||||
for (i=0; i < MaxTriggerKeys; i++)
|
||||
if (trigger[i] != basemove->trigger[i])
|
||||
triggerDifferent = true;
|
||||
bool somethingDifferent = (pyaw!=basemove->pyaw) ||
|
||||
(ppitch!=basemove->ppitch) ||
|
||||
(proll!=basemove->proll) ||
|
||||
(px!=basemove->px) ||
|
||||
(py!=basemove->py) ||
|
||||
(pz!=basemove->pz) ||
|
||||
(deviceIsKeyboardMouse!=basemove->deviceIsKeyboardMouse) ||
|
||||
(freeLook!=basemove->freeLook) ||
|
||||
triggerDifferent;
|
||||
|
||||
if (alwaysWriteAll || stream->writeFlag(somethingDifferent))
|
||||
{
|
||||
if(stream->writeFlag(pyaw != basemove->pyaw))
|
||||
stream->writeInt(pyaw, 16);
|
||||
if(stream->writeFlag(ppitch != basemove->ppitch))
|
||||
stream->writeInt(ppitch, 16);
|
||||
if(stream->writeFlag(proll != basemove->proll))
|
||||
stream->writeInt(proll, 16);
|
||||
|
||||
if (stream->writeFlag(px != basemove->px))
|
||||
stream->writeInt(px, 6);
|
||||
if (stream->writeFlag(py != basemove->py))
|
||||
stream->writeInt(py, 6);
|
||||
if (stream->writeFlag(pz != basemove->pz))
|
||||
stream->writeInt(pz, 6);
|
||||
stream->writeFlag(freeLook);
|
||||
stream->writeFlag(deviceIsKeyboardMouse);
|
||||
|
||||
if (stream->writeFlag(triggerDifferent))
|
||||
for(i = 0; i < MaxTriggerKeys; i++)
|
||||
stream->writeFlag(trigger[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Move::unpack(BitStream *stream, const Move * basemove)
|
||||
{
|
||||
bool alwaysReadAll = basemove!=NULL;
|
||||
if (!basemove)
|
||||
basemove=&NullMove;
|
||||
|
||||
if (alwaysReadAll || stream->readFlag())
|
||||
{
|
||||
pyaw = stream->readFlag() ? stream->readInt(16) : basemove->pyaw;
|
||||
ppitch = stream->readFlag() ? stream->readInt(16) : basemove->ppitch;
|
||||
proll = stream->readFlag() ? stream->readInt(16) : basemove->proll;
|
||||
|
||||
px = stream->readFlag() ? stream->readInt(6) : basemove->px;
|
||||
py = stream->readFlag() ? stream->readInt(6) : basemove->py;
|
||||
pz = stream->readFlag() ? stream->readInt(6) : basemove->pz;
|
||||
freeLook = stream->readFlag();
|
||||
deviceIsKeyboardMouse = stream->readFlag();
|
||||
|
||||
bool triggersDiffer = stream->readFlag();
|
||||
for (S32 i = 0; i< MaxTriggerKeys; i++)
|
||||
trigger[i] = triggersDiffer ? stream->readFlag() : basemove->trigger[i];
|
||||
unclamp();
|
||||
}
|
||||
else
|
||||
*this = *basemove;
|
||||
}
|
||||
95
Engine/source/T3D/gameBase/moveManager.h
Normal file
95
Engine/source/T3D/gameBase/moveManager.h
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _MOVEMANAGER_H_
|
||||
#define _MOVEMANAGER_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
|
||||
enum MoveConstants {
|
||||
MaxTriggerKeys = 6,
|
||||
MaxMoveQueueSize = 45,
|
||||
};
|
||||
|
||||
class BitStream;
|
||||
|
||||
struct Move
|
||||
{
|
||||
enum { ChecksumBits = 16, ChecksumMask = ((1<<ChecksumBits)-1), ChecksumMismatch = U32(-1) };
|
||||
|
||||
// packed storage rep, set in clamp
|
||||
S32 px, py, pz;
|
||||
U32 pyaw, ppitch, proll;
|
||||
F32 x, y, z; // float -1 to 1
|
||||
F32 yaw, pitch, roll; // 0-2PI
|
||||
U32 id; // sync'd between server & client - debugging tool.
|
||||
U32 sendCount;
|
||||
U32 checksum;
|
||||
|
||||
bool deviceIsKeyboardMouse;
|
||||
bool freeLook;
|
||||
bool trigger[MaxTriggerKeys];
|
||||
|
||||
void pack(BitStream *stream, const Move * move = NULL);
|
||||
void unpack(BitStream *stream, const Move * move = NULL);
|
||||
void clamp();
|
||||
void unclamp();
|
||||
};
|
||||
|
||||
extern const Move NullMove;
|
||||
|
||||
class MoveManager
|
||||
{
|
||||
public:
|
||||
static bool mDeviceIsKeyboardMouse;
|
||||
static F32 mForwardAction;
|
||||
static F32 mBackwardAction;
|
||||
static F32 mUpAction;
|
||||
static F32 mDownAction;
|
||||
static F32 mLeftAction;
|
||||
static F32 mRightAction;
|
||||
|
||||
static bool mFreeLook;
|
||||
static F32 mPitch;
|
||||
static F32 mYaw;
|
||||
static F32 mRoll;
|
||||
|
||||
static F32 mPitchUpSpeed;
|
||||
static F32 mPitchDownSpeed;
|
||||
static F32 mYawLeftSpeed;
|
||||
static F32 mYawRightSpeed;
|
||||
static F32 mRollLeftSpeed;
|
||||
static F32 mRollRightSpeed;
|
||||
static F32 mXAxis_L;
|
||||
static F32 mYAxis_L;
|
||||
static F32 mXAxis_R;
|
||||
static F32 mYAxis_R;
|
||||
|
||||
static U32 mTriggerCount[MaxTriggerKeys];
|
||||
static U32 mPrevTriggerCount[MaxTriggerKeys];
|
||||
|
||||
static void init();
|
||||
};
|
||||
|
||||
#endif
|
||||
277
Engine/source/T3D/gameBase/processList.cpp
Normal file
277
Engine/source/T3D/gameBase/processList.cpp
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/processList.h"
|
||||
|
||||
#include "T3D/gameBase/gameBase.h"
|
||||
#include "platform/profiler.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
ProcessObject::ProcessObject()
|
||||
: mProcessTag( 0 ),
|
||||
mOrderGUID( 0 ),
|
||||
mProcessTick( false ),
|
||||
mIsGameBase( false )
|
||||
{
|
||||
mProcessLink.next = mProcessLink.prev = this;
|
||||
}
|
||||
|
||||
void ProcessObject::plUnlink()
|
||||
{
|
||||
mProcessLink.next->mProcessLink.prev = mProcessLink.prev;
|
||||
mProcessLink.prev->mProcessLink.next = mProcessLink.next;
|
||||
mProcessLink.next = mProcessLink.prev = this;
|
||||
}
|
||||
|
||||
void ProcessObject::plLinkAfter(ProcessObject * obj)
|
||||
{
|
||||
AssertFatal(mProcessLink.next == this && mProcessLink.prev == this,"ProcessObject::plLinkAfter: must be unlinked before calling this");
|
||||
#ifdef TORQUE_DEBUG
|
||||
ProcessObject * test1 = obj;
|
||||
ProcessObject * test2 = obj->mProcessLink.next;
|
||||
ProcessObject * test3 = obj->mProcessLink.prev;
|
||||
ProcessObject * test4 = this;
|
||||
#endif
|
||||
|
||||
// Link this after obj
|
||||
mProcessLink.next = obj->mProcessLink.next;
|
||||
mProcessLink.prev = obj;
|
||||
obj->mProcessLink.next = this;
|
||||
mProcessLink.next->mProcessLink.prev = this;
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
AssertFatal(test1->mProcessLink.next->mProcessLink.prev==test1 && test1->mProcessLink.prev->mProcessLink.next==test1,"Doh!");
|
||||
AssertFatal(test2->mProcessLink.next->mProcessLink.prev==test2 && test2->mProcessLink.prev->mProcessLink.next==test2,"Doh!");
|
||||
AssertFatal(test3->mProcessLink.next->mProcessLink.prev==test3 && test3->mProcessLink.prev->mProcessLink.next==test3,"Doh!");
|
||||
AssertFatal(test4->mProcessLink.next->mProcessLink.prev==test4 && test4->mProcessLink.prev->mProcessLink.next==test4,"Doh!");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProcessObject::plLinkBefore(ProcessObject * obj)
|
||||
{
|
||||
AssertFatal(mProcessLink.next == this && mProcessLink.prev == this,"ProcessObject::plLinkBefore: must be unlinked before calling this");
|
||||
#ifdef TORQUE_DEBUG
|
||||
ProcessObject * test1 = obj;
|
||||
ProcessObject * test2 = obj->mProcessLink.next;
|
||||
ProcessObject * test3 = obj->mProcessLink.prev;
|
||||
ProcessObject * test4 = this;
|
||||
#endif
|
||||
|
||||
// Link this before obj
|
||||
mProcessLink.next = obj;
|
||||
mProcessLink.prev = obj->mProcessLink.prev;
|
||||
obj->mProcessLink.prev = this;
|
||||
mProcessLink.prev->mProcessLink.next = this;
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
AssertFatal(test1->mProcessLink.next->mProcessLink.prev==test1 && test1->mProcessLink.prev->mProcessLink.next==test1,"Doh!");
|
||||
AssertFatal(test2->mProcessLink.next->mProcessLink.prev==test2 && test2->mProcessLink.prev->mProcessLink.next==test2,"Doh!");
|
||||
AssertFatal(test3->mProcessLink.next->mProcessLink.prev==test3 && test3->mProcessLink.prev->mProcessLink.next==test3,"Doh!");
|
||||
AssertFatal(test4->mProcessLink.next->mProcessLink.prev==test4 && test4->mProcessLink.prev->mProcessLink.next==test4,"Doh!");
|
||||
#endif
|
||||
}
|
||||
|
||||
void ProcessObject::plJoin(ProcessObject * head)
|
||||
{
|
||||
ProcessObject * tail1 = head->mProcessLink.prev;
|
||||
ProcessObject * tail2 = mProcessLink.prev;
|
||||
tail1->mProcessLink.next = this;
|
||||
mProcessLink.prev = tail1;
|
||||
tail2->mProcessLink.next = head;
|
||||
head->mProcessLink.prev = tail2;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
ProcessList::ProcessList()
|
||||
{
|
||||
mCurrentTag = 0;
|
||||
mDirty = false;
|
||||
|
||||
mTotalTicks = 0;
|
||||
mLastTick = 0;
|
||||
mLastTime = 0;
|
||||
mLastDelta = 0.0f;
|
||||
}
|
||||
|
||||
void ProcessList::addObject( ProcessObject *obj )
|
||||
{
|
||||
obj->plLinkAfter(&mHead);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void ProcessList::orderList()
|
||||
{
|
||||
// ProcessObject tags are initialized to 0, so current tag should never be 0.
|
||||
if (++mCurrentTag == 0)
|
||||
mCurrentTag++;
|
||||
|
||||
// Install a temporary head node
|
||||
ProcessObject list;
|
||||
list.plLinkBefore(mHead.mProcessLink.next);
|
||||
mHead.plUnlink();
|
||||
|
||||
// start out by (bubble) sorting list by GUID
|
||||
for (ProcessObject * cur = list.mProcessLink.next; cur != &list; cur = cur->mProcessLink.next)
|
||||
{
|
||||
if (cur->mOrderGUID == 0)
|
||||
// special case -- can be no lower, so accept as lowest (this is also
|
||||
// a common value since it is what non ordered objects have)
|
||||
continue;
|
||||
|
||||
for (ProcessObject * walk = cur->mProcessLink.next; walk != &list; walk = walk->mProcessLink.next)
|
||||
{
|
||||
if (walk->mOrderGUID < cur->mOrderGUID)
|
||||
{
|
||||
// swap walk and cur -- need to be careful because walk might be just after cur
|
||||
// so insert after item before cur and before item after walk
|
||||
ProcessObject * before = cur->mProcessLink.prev;
|
||||
ProcessObject * after = walk->mProcessLink.next;
|
||||
cur->plUnlink();
|
||||
walk->plUnlink();
|
||||
cur->plLinkBefore(after);
|
||||
walk->plLinkAfter(before);
|
||||
ProcessObject * swap = walk;
|
||||
walk = cur;
|
||||
cur = swap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse topological sort into the original head node
|
||||
while (list.mProcessLink.next != &list)
|
||||
{
|
||||
ProcessObject * ptr = list.mProcessLink.next;
|
||||
ProcessObject * afterObject = ptr->getAfterObject();
|
||||
ptr->mProcessTag = mCurrentTag;
|
||||
ptr->plUnlink();
|
||||
if (afterObject)
|
||||
{
|
||||
// Build chain "stack" of dependent objects and patch
|
||||
// it to the end of the current list.
|
||||
while (afterObject && afterObject->mProcessTag != mCurrentTag)
|
||||
{
|
||||
afterObject->mProcessTag = mCurrentTag;
|
||||
afterObject->plUnlink();
|
||||
afterObject->plLinkBefore(ptr);
|
||||
ptr = afterObject;
|
||||
afterObject = ptr->getAfterObject();
|
||||
}
|
||||
ptr->plJoin(&mHead);
|
||||
}
|
||||
else
|
||||
ptr->plLinkBefore(&mHead);
|
||||
}
|
||||
mDirty = false;
|
||||
}
|
||||
|
||||
GameBase* ProcessList::getGameBase( ProcessObject *obj )
|
||||
{
|
||||
if ( !obj->mIsGameBase )
|
||||
return NULL;
|
||||
|
||||
return static_cast< GameBase* >( obj );
|
||||
}
|
||||
|
||||
|
||||
void ProcessList::dumpToConsole()
|
||||
{
|
||||
for (ProcessObject * pobj = mHead.mProcessLink.next; pobj != &mHead; pobj = pobj->mProcessLink.next)
|
||||
{
|
||||
SimObject * obj = dynamic_cast<SimObject*>(pobj);
|
||||
if (obj)
|
||||
Con::printf("id %i, order guid %i, type %s", obj->getId(), pobj->mOrderGUID, obj->getClassName());
|
||||
else
|
||||
Con::printf("---unknown object type, order guid %i", pobj->mOrderGUID);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
bool ProcessList::advanceTime(SimTime timeDelta)
|
||||
{
|
||||
PROFILE_START(ProcessList_AdvanceTime);
|
||||
|
||||
// some drivers change the FPU control state, which will break our control object simulation
|
||||
// (leading to packet mismatch errors due to small FP differences). So set it to the known
|
||||
// state before advancing.
|
||||
U32 mathState = Platform::getMathControlState();
|
||||
Platform::setMathControlStateKnown();
|
||||
|
||||
if (mDirty)
|
||||
orderList();
|
||||
|
||||
SimTime targetTime = mLastTime + timeDelta;
|
||||
SimTime targetTick = targetTime - (targetTime % TickMs);
|
||||
SimTime tickDelta = targetTick - mLastTick;
|
||||
bool tickPass = mLastTick != targetTick;
|
||||
|
||||
if ( tickPass )
|
||||
mPreTick.trigger();
|
||||
|
||||
// Advance all the objects.
|
||||
for (; mLastTick != targetTick; mLastTick += TickMs)
|
||||
onAdvanceObjects();
|
||||
|
||||
mLastTime = targetTime;
|
||||
mLastDelta = ((TickMs - ((targetTime+1) % TickMs)) % TickMs) / F32(TickMs);
|
||||
|
||||
if ( tickPass )
|
||||
mPostTick.trigger( tickDelta );
|
||||
|
||||
// restore math control state in case others are relying on it being a certain value
|
||||
Platform::setMathControlState(mathState);
|
||||
|
||||
PROFILE_END();
|
||||
return tickPass;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
void ProcessList::advanceObjects()
|
||||
{
|
||||
PROFILE_START(ProcessList_AdvanceObjects);
|
||||
|
||||
// A little link list shuffling is done here to avoid problems
|
||||
// with objects being deleted from within the process method.
|
||||
ProcessObject list;
|
||||
list.plLinkBefore(mHead.mProcessLink.next);
|
||||
mHead.plUnlink();
|
||||
for (ProcessObject * pobj = list.mProcessLink.next; pobj != &list; pobj = list.mProcessLink.next)
|
||||
{
|
||||
pobj->plUnlink();
|
||||
pobj->plLinkBefore(&mHead);
|
||||
|
||||
onTickObject(pobj);
|
||||
}
|
||||
|
||||
mTotalTicks++;
|
||||
|
||||
PROFILE_END();
|
||||
}
|
||||
|
||||
|
||||
|
||||
193
Engine/source/T3D/gameBase/processList.h
Normal file
193
Engine/source/T3D/gameBase/processList.h
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _PROCESSLIST_H_
|
||||
#define _PROCESSLIST_H_
|
||||
|
||||
#ifndef _SIM_H_
|
||||
#include "console/sim.h"
|
||||
#endif
|
||||
#ifndef _TSIGNAL_H_
|
||||
#include "core/util/tSignal.h"
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
#define TickMs 32
|
||||
#define TickSec (F32(TickMs) / 1000.0f)
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
class GameConnection;
|
||||
struct Move;
|
||||
|
||||
|
||||
class ProcessObject
|
||||
{
|
||||
|
||||
public:
|
||||
|
||||
ProcessObject();
|
||||
virtual ~ProcessObject() { removeFromProcessList(); }
|
||||
|
||||
/// Removes this object from the tick-processing list
|
||||
void removeFromProcessList() { plUnlink(); }
|
||||
|
||||
/// Set the status of tick processing.
|
||||
///
|
||||
/// Set true to receive processTick, advanceTime, and interpolateTick calls.
|
||||
///
|
||||
/// @see processTick
|
||||
/// @param t If true, tick processing is enabled.
|
||||
virtual void setProcessTick( bool t ) { mProcessTick = t; }
|
||||
|
||||
/// Returns true if this object processes ticks.
|
||||
bool isTicking() const { return mProcessTick; }
|
||||
|
||||
/// This is really implemented in GameBase and is only here to avoid
|
||||
/// casts within ProcessList.
|
||||
virtual GameConnection* getControllingClient() { return NULL; }
|
||||
|
||||
/// This is really implemented in GameBase and is only here to avoid
|
||||
/// casts within ProcessList.
|
||||
virtual U32 getPacketDataChecksum( GameConnection *conn ) { return -1; }
|
||||
|
||||
/// Force this object to process after some other object.
|
||||
///
|
||||
/// For example, a player mounted to a vehicle would want to process after
|
||||
/// the vehicle to prevent a visible 'lagging' from occurring when the
|
||||
/// vehicle moves. So the player would be set to processAfter(theVehicle).
|
||||
///
|
||||
/// @param obj Object to process after
|
||||
virtual void processAfter( ProcessObject *obj ) {}
|
||||
|
||||
/// Clears the effects of a call to processAfter()
|
||||
virtual void clearProcessAfter() {}
|
||||
|
||||
/// Returns the object that this processes after.
|
||||
///
|
||||
/// @see processAfter
|
||||
virtual ProcessObject* getAfterObject() const { return NULL; }
|
||||
|
||||
/// Processes a move event and updates object state once every 32 milliseconds.
|
||||
///
|
||||
/// This takes place both on the client and server, every 32 milliseconds (1 tick).
|
||||
///
|
||||
/// @see ProcessList
|
||||
/// @param move Move event corresponding to this tick, or NULL.
|
||||
virtual void processTick( const Move *move ) {}
|
||||
|
||||
/// Interpolates between tick events. This takes place on the CLIENT ONLY.
|
||||
///
|
||||
/// @param delta Time since last call to interpolate
|
||||
virtual void interpolateTick( F32 delta ) {}
|
||||
|
||||
/// Advances simulation time for animations. This is called every frame.
|
||||
///
|
||||
/// @param dt Time since last advance call
|
||||
virtual void advanceTime( F32 dt ) {}
|
||||
|
||||
/// Allow object to modify the Move before it is ticked or sent to the server.
|
||||
/// This is only called for the control object on the client-side.
|
||||
virtual void preprocessMove( Move *move ) {}
|
||||
|
||||
//protected:
|
||||
|
||||
struct Link
|
||||
{
|
||||
ProcessObject *next;
|
||||
ProcessObject *prev;
|
||||
};
|
||||
|
||||
// Processing interface
|
||||
void plUnlink();
|
||||
void plLinkAfter(ProcessObject*);
|
||||
void plLinkBefore(ProcessObject*);
|
||||
void plJoin(ProcessObject*);
|
||||
|
||||
U32 mProcessTag; // Tag used during sort
|
||||
U32 mOrderGUID; // UID for keeping order synced (e.g., across network or runs of sim)
|
||||
Link mProcessLink; // Ordered process queue
|
||||
|
||||
bool mProcessTick;
|
||||
|
||||
bool mIsGameBase;
|
||||
};
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
typedef Signal<void()> PreTickSignal;
|
||||
typedef Signal<void(SimTime)> PostTickSignal;
|
||||
class GameBase;
|
||||
|
||||
/// List of ProcessObjects.
|
||||
class ProcessList
|
||||
{
|
||||
public:
|
||||
|
||||
ProcessList();
|
||||
virtual ~ProcessList() {}
|
||||
|
||||
void markDirty() { mDirty = true; }
|
||||
bool isDirty() { return mDirty; }
|
||||
|
||||
SimTime getLastTime() { return mLastTime; }
|
||||
F32 getLastDelta() { return mLastDelta; }
|
||||
F32 getLastInterpDelta() { return mLastDelta / F32(TickMs); }
|
||||
U32 getTotalTicks() { return mTotalTicks; }
|
||||
void dumpToConsole();
|
||||
|
||||
PreTickSignal& preTickSignal() { return mPreTick; }
|
||||
PostTickSignal& postTickSignal() { return mPostTick; }
|
||||
|
||||
virtual void addObject( ProcessObject *obj );
|
||||
|
||||
/// Returns true if a tick was processed.
|
||||
virtual bool advanceTime( SimTime timeDelta );
|
||||
|
||||
protected:
|
||||
|
||||
void orderList();
|
||||
GameBase* getGameBase( ProcessObject *obj );
|
||||
|
||||
virtual void advanceObjects();
|
||||
virtual void onAdvanceObjects() { advanceObjects(); }
|
||||
virtual void onPreTickObject( ProcessObject* ) {}
|
||||
virtual void onTickObject( ProcessObject* ) {}
|
||||
|
||||
protected:
|
||||
|
||||
ProcessObject mHead;
|
||||
|
||||
U32 mCurrentTag;
|
||||
bool mDirty;
|
||||
|
||||
U32 mTotalTicks;
|
||||
SimTime mLastTick;
|
||||
SimTime mLastTime;
|
||||
F32 mLastDelta;
|
||||
|
||||
PreTickSignal mPreTick;
|
||||
PostTickSignal mPostTick;
|
||||
};
|
||||
|
||||
#endif // _PROCESSLIST_H_
|
||||
400
Engine/source/T3D/gameBase/std/stdGameProcess.cpp
Normal file
400
Engine/source/T3D/gameBase/std/stdGameProcess.cpp
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/std/stdGameProcess.h"
|
||||
|
||||
#include "platform/profiler.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "core/dnet.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "core/frameAllocator.h"
|
||||
#include "core/util/refBase.h"
|
||||
#include "math/mPoint3.h"
|
||||
#include "math/mMatrix.h"
|
||||
#include "math/mathUtils.h"
|
||||
#include "T3D/gameBase/gameBase.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "T3D/gameBase/std/stdMoveList.h"
|
||||
#include "T3D/fx/cameraFXMgr.h"
|
||||
|
||||
MODULE_BEGIN( ProcessList )
|
||||
|
||||
MODULE_INIT
|
||||
{
|
||||
StdServerProcessList::init();
|
||||
StdClientProcessList::init();
|
||||
}
|
||||
|
||||
MODULE_SHUTDOWN
|
||||
{
|
||||
StdServerProcessList::shutdown();
|
||||
StdClientProcessList::shutdown();
|
||||
}
|
||||
|
||||
MODULE_END;
|
||||
|
||||
void StdServerProcessList::init()
|
||||
{
|
||||
smServerProcessList = new StdServerProcessList();
|
||||
}
|
||||
|
||||
void StdServerProcessList::shutdown()
|
||||
{
|
||||
delete smServerProcessList;
|
||||
}
|
||||
|
||||
void StdClientProcessList::init()
|
||||
{
|
||||
smClientProcessList = new StdClientProcessList();
|
||||
}
|
||||
|
||||
void StdClientProcessList::shutdown()
|
||||
{
|
||||
delete smClientProcessList;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
// 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;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// ClientProcessList
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
StdClientProcessList::StdClientProcessList()
|
||||
{
|
||||
}
|
||||
|
||||
bool StdClientProcessList::advanceTime( SimTime timeDelta )
|
||||
{
|
||||
PROFILE_SCOPE( StdClientProcessList_AdvanceTime );
|
||||
|
||||
if ( doBacklogged( timeDelta ) )
|
||||
return false;
|
||||
|
||||
bool ret = Parent::advanceTime( timeDelta );
|
||||
ProcessObject *obj = NULL;
|
||||
|
||||
AssertFatal( mLastDelta >= 0.0f && mLastDelta <= 1.0f, "mLastDelta is not zero to one.");
|
||||
|
||||
obj = mHead.mProcessLink.next;
|
||||
while ( obj != &mHead )
|
||||
{
|
||||
if ( obj->isTicking() )
|
||||
obj->interpolateTick( mLastDelta );
|
||||
|
||||
obj = obj->mProcessLink.next;
|
||||
}
|
||||
|
||||
// Inform objects of total elapsed delta so they can advance
|
||||
// client side animations.
|
||||
F32 dt = F32(timeDelta) / 1000;
|
||||
|
||||
// Update camera FX.
|
||||
gCamFXMgr.update( dt );
|
||||
|
||||
obj = mHead.mProcessLink.next;
|
||||
while ( obj != &mHead )
|
||||
{
|
||||
obj->advanceTime( dt );
|
||||
obj = obj->mProcessLink.next;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void StdClientProcessList::onAdvanceObjects()
|
||||
{
|
||||
PROFILE_SCOPE( StdClientProcessList_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 );
|
||||
}
|
||||
|
||||
connection->mMoveList->collectMove();
|
||||
advanceObjects();
|
||||
}
|
||||
else
|
||||
advanceObjects();
|
||||
}
|
||||
|
||||
void StdClientProcessList::onTickObject( ProcessObject *obj )
|
||||
{
|
||||
PROFILE_SCOPE( StdClientProcessList_OnTickObject );
|
||||
|
||||
// In case the object deletes itself during its processTick.
|
||||
SimObjectPtr<SceneObject> safePtr = static_cast<SceneObject*>( obj );
|
||||
|
||||
// Each object is either advanced a single tick, or if it's
|
||||
// being controlled by a client, ticked once for each pending move.
|
||||
Move* movePtr;
|
||||
U32 numMoves;
|
||||
GameConnection* con = obj->getControllingClient();
|
||||
if ( con && con->getControlObject() == obj )
|
||||
{
|
||||
con->mMoveList->getMoves( &movePtr, &numMoves );
|
||||
if ( numMoves )
|
||||
{
|
||||
// Note: should only have a single move at this point
|
||||
AssertFatal(numMoves==1,"ClientProccessList::onTickObject: more than one move in queue");
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient());
|
||||
#endif
|
||||
|
||||
if ( obj->isTicking() )
|
||||
obj->processTick( movePtr );
|
||||
|
||||
if ( bool(safePtr) && 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 ( obj->isTicking() )
|
||||
obj->processTick( 0 );
|
||||
}
|
||||
|
||||
void StdClientProcessList::advanceObjects()
|
||||
{
|
||||
PROFILE_SCOPE( StdClientProcessList_AdvanceObjects );
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("Advance client time...");
|
||||
#endif
|
||||
|
||||
Parent::advanceObjects();
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("---------");
|
||||
#endif
|
||||
}
|
||||
|
||||
void StdClientProcessList::clientCatchup( GameConnection * connection )
|
||||
{
|
||||
SimObjectPtr<GameBase> control = connection->getControlObject();
|
||||
if ( control )
|
||||
{
|
||||
Move * movePtr;
|
||||
U32 numMoves;
|
||||
U32 m = 0;
|
||||
connection->mMoveList->getMoves( &movePtr, &numMoves );
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("client catching up... (%i)", numMoves);
|
||||
#endif
|
||||
|
||||
preTickSignal().trigger();
|
||||
|
||||
if ( control->isTicking() )
|
||||
for ( m = 0; m < numMoves; m++ )
|
||||
control->processTick( movePtr++ );
|
||||
|
||||
connection->mMoveList->clearMoves( m );
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("---------");
|
||||
#endif
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// ServerProcessList
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
StdServerProcessList::StdServerProcessList()
|
||||
{
|
||||
}
|
||||
|
||||
void StdServerProcessList::onPreTickObject( ProcessObject *pobj )
|
||||
{
|
||||
if ( pobj->mIsGameBase )
|
||||
{
|
||||
SimObjectPtr<GameBase> obj = getGameBase( pobj );
|
||||
|
||||
// Each object is either advanced a single tick, or if it's
|
||||
// being controlled by a client, ticked once for each pending move.
|
||||
GameConnection *con = obj->getControllingClient();
|
||||
|
||||
if ( con && con->getControlObject() == obj )
|
||||
{
|
||||
Move* movePtr;
|
||||
U32 numMoves;
|
||||
con->mMoveList->getMoves( &movePtr, &numMoves );
|
||||
|
||||
if ( numMoves == 0 )
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("no moves on object %i, skip tick",obj->getId());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Parent::onPreTickObject (pobj );
|
||||
}
|
||||
|
||||
void StdServerProcessList::onTickObject( ProcessObject *pobj )
|
||||
{
|
||||
PROFILE_SCOPE( StdServerProcessList_OnTickObject );
|
||||
|
||||
// Each object is either advanced a single tick, or if it's
|
||||
// being controlled by a client, ticked once for each pending move.
|
||||
|
||||
GameConnection *con = pobj->getControllingClient();
|
||||
|
||||
if ( pobj->mIsGameBase && con && con->getControlObject() == pobj )
|
||||
{
|
||||
// In case the object is deleted during its own tick.
|
||||
SimObjectPtr<GameBase> obj = getGameBase( pobj );
|
||||
|
||||
Move* movePtr;
|
||||
U32 m, numMoves;
|
||||
con->mMoveList->getMoves( &movePtr, &numMoves );
|
||||
|
||||
// For debugging it can be useful to know when this happens.
|
||||
//if ( numMoves > 1 )
|
||||
// Con::printf( "numMoves: %i", numMoves );
|
||||
|
||||
// Do we really need to test the control object each iteration? Does it change?
|
||||
for ( m = 0; m < numMoves && con && con->getControlObject() == obj; m++, movePtr++ )
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
U32 sum = Move::ChecksumMask & obj->getPacketDataChecksum(obj->getControllingClient());
|
||||
#endif
|
||||
|
||||
if ( obj->isTicking() )
|
||||
obj->processTick( movePtr );
|
||||
|
||||
if ( con && con->getControlObject() == obj )
|
||||
{
|
||||
U32 newsum = Move::ChecksumMask & obj->getPacketDataChecksum( obj->getControllingClient() );
|
||||
|
||||
// check move checksum
|
||||
if ( movePtr->checksum != newsum )
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
if( !obj->isAIControlled() )
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
con->mMoveList->clearMoves( m );
|
||||
}
|
||||
else if ( pobj->isTicking() )
|
||||
pobj->processTick( 0 );
|
||||
}
|
||||
|
||||
void StdServerProcessList::advanceObjects()
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("Advance server time...");
|
||||
#endif
|
||||
|
||||
Parent::advanceObjects();
|
||||
|
||||
// Credit all connections with the elapsed tick
|
||||
SimGroup *clientGroup = Sim::getClientGroup();
|
||||
for(SimGroup::iterator i = clientGroup->begin(); i != clientGroup->end(); i++)
|
||||
{
|
||||
if (GameConnection *con = dynamic_cast<GameConnection *>(*i))
|
||||
{
|
||||
con->mMoveList->advanceMove();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET_MOVES
|
||||
Con::printf("---------");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
82
Engine/source/T3D/gameBase/std/stdGameProcess.h
Normal file
82
Engine/source/T3D/gameBase/std/stdGameProcess.h
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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_STD_H_
|
||||
#define _GAMEPROCESS_STD_H_
|
||||
|
||||
//#include "T3D/gameBase/processList.h"
|
||||
#ifndef _GAMEPROCESS_H_
|
||||
#include "T3D/gameBase/gameProcess.h"
|
||||
#endif
|
||||
|
||||
class GameBase;
|
||||
class GameConnection;
|
||||
struct Move;
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
/// List to keep track of GameBases to process.
|
||||
class StdClientProcessList : public ClientProcessList
|
||||
{
|
||||
typedef ClientProcessList Parent;
|
||||
|
||||
protected:
|
||||
|
||||
// ProcessList
|
||||
void onTickObject(ProcessObject *);
|
||||
void advanceObjects();
|
||||
void onAdvanceObjects();
|
||||
|
||||
public:
|
||||
|
||||
StdClientProcessList();
|
||||
|
||||
// ProcessList
|
||||
bool advanceTime( SimTime timeDelta );
|
||||
|
||||
// ClientProcessList
|
||||
void clientCatchup( GameConnection *conn );
|
||||
|
||||
static void init();
|
||||
static void shutdown();
|
||||
};
|
||||
|
||||
class StdServerProcessList : public ServerProcessList
|
||||
{
|
||||
typedef ServerProcessList Parent;
|
||||
|
||||
protected:
|
||||
|
||||
// ProcessList
|
||||
void onPreTickObject( ProcessObject *pobj );
|
||||
void onTickObject( ProcessObject *pobj );
|
||||
void advanceObjects();
|
||||
|
||||
public:
|
||||
|
||||
StdServerProcessList();
|
||||
|
||||
static void init();
|
||||
static void shutdown();
|
||||
};
|
||||
|
||||
#endif // _GAMEPROCESS_STD_H_
|
||||
198
Engine/source/T3D/gameBase/std/stdMoveList.cpp
Normal file
198
Engine/source/T3D/gameBase/std/stdMoveList.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/std/stdMoveList.h"
|
||||
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
|
||||
#define MAX_MOVE_PACKET_SENDS 4
|
||||
|
||||
|
||||
StdMoveList::StdMoveList()
|
||||
{
|
||||
mMoveCredit = MaxMoveCount;
|
||||
}
|
||||
|
||||
U32 StdMoveList::getMoves(Move** movePtr,U32* numMoves)
|
||||
{
|
||||
if (!mConnection->isConnectionToServer())
|
||||
{
|
||||
if (mMoveVec.size() > mMoveCredit)
|
||||
mMoveVec.setSize(mMoveCredit);
|
||||
}
|
||||
return Parent::getMoves(movePtr,numMoves);
|
||||
}
|
||||
|
||||
void StdMoveList::clearMoves(U32 count)
|
||||
{
|
||||
if (!mConnection->isConnectionToServer())
|
||||
{
|
||||
count = mClamp(count,0,mMoveCredit);
|
||||
mMoveCredit -= count;
|
||||
}
|
||||
|
||||
Parent::clearMoves(count);
|
||||
}
|
||||
|
||||
void StdMoveList::advanceMove()
|
||||
{
|
||||
AssertFatal(!mConnection->isConnectionToServer(), "Cannot inc move credit on the client.");
|
||||
|
||||
// Game tick increment
|
||||
mMoveCredit++;
|
||||
if (mMoveCredit > MaxMoveCount)
|
||||
mMoveCredit = MaxMoveCount;
|
||||
|
||||
// Clear pending moves for the elapsed time if there
|
||||
// is no control object.
|
||||
if ( mConnection->getControlObject() == NULL )
|
||||
mMoveVec.clear();
|
||||
}
|
||||
|
||||
void StdMoveList::clientWriteMovePacket(BitStream *bstream)
|
||||
{
|
||||
AssertFatal(mLastMoveAck == mFirstMoveIndex, "Invalid move index.");
|
||||
U32 count = mMoveVec.size();
|
||||
|
||||
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 StdMoveList::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;
|
||||
}
|
||||
|
||||
void StdMoveList::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);
|
||||
}
|
||||
|
||||
void StdMoveList::clientReadMovePacket(BitStream * bstream)
|
||||
{
|
||||
#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
|
||||
|
||||
if (mLastMoveAck < mFirstMoveIndex)
|
||||
mLastMoveAck = mFirstMoveIndex;
|
||||
|
||||
if(mLastMoveAck > mLastClientMove)
|
||||
mLastClientMove = mLastMoveAck;
|
||||
while(mFirstMoveIndex < mLastMoveAck)
|
||||
{
|
||||
if (mMoveVec.size())
|
||||
{
|
||||
mMoveVec.pop_front();
|
||||
mFirstMoveIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertWarn(1, "Popping off too many moves!");
|
||||
mFirstMoveIndex = mLastMoveAck;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Engine/source/T3D/gameBase/std/stdMoveList.h
Normal file
55
Engine/source/T3D/gameBase/std/stdMoveList.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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_STD_H_
|
||||
#define _MOVELIST_STD_H_
|
||||
|
||||
#ifndef _MOVELIST_H_
|
||||
#include "T3D/gameBase/moveList.h"
|
||||
#endif
|
||||
|
||||
class StdMoveList : public MoveList
|
||||
{
|
||||
typedef MoveList Parent;
|
||||
|
||||
public:
|
||||
|
||||
StdMoveList();
|
||||
|
||||
void clientWriteMovePacket(BitStream *);
|
||||
void clientReadMovePacket(BitStream *);
|
||||
|
||||
void serverWriteMovePacket(BitStream *);
|
||||
void serverReadMovePacket(BitStream *);
|
||||
|
||||
U32 getMoves(Move**,U32* numMoves);
|
||||
void clearMoves(U32 count);
|
||||
|
||||
void advanceMove();
|
||||
void onAdvanceObjects() {}
|
||||
|
||||
protected:
|
||||
|
||||
U32 mMoveCredit;
|
||||
};
|
||||
|
||||
#endif // _MOVELIST_STD_H_
|
||||
174
Engine/source/T3D/gameBase/tickCache.cpp
Normal file
174
Engine/source/T3D/gameBase/tickCache.cpp
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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/gameBase.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/consoleInternal.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "T3D/gameBase/gameConnection.h"
|
||||
#include "math/mathIO.h"
|
||||
#include "T3D/gameBase/moveManager.h"
|
||||
#include "T3D/gameBase/gameProcess.h"
|
||||
|
||||
struct TickCacheHead
|
||||
{
|
||||
TickCacheEntry * oldest;
|
||||
TickCacheEntry * newest;
|
||||
TickCacheEntry * next;
|
||||
U32 numEntry;
|
||||
};
|
||||
|
||||
namespace
|
||||
{
|
||||
FreeListChunker<TickCacheHead> sgTickCacheHeadStore;
|
||||
FreeListChunker<TickCacheEntry> sgTickCacheEntryStore;
|
||||
FreeListChunker<Move> sgMoveStore;
|
||||
|
||||
static TickCacheHead * allocHead() { return sgTickCacheHeadStore.alloc(); }
|
||||
static void freeHead(TickCacheHead * head) { sgTickCacheHeadStore.free(head); }
|
||||
|
||||
static TickCacheEntry * allocEntry() { return sgTickCacheEntryStore.alloc(); }
|
||||
static void freeEntry(TickCacheEntry * entry) { sgTickCacheEntryStore.free(entry); }
|
||||
|
||||
static Move * allocMove() { return sgMoveStore.alloc(); }
|
||||
static void freeMove(Move * move) { sgMoveStore.free(move); }
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
TickCache::~TickCache()
|
||||
{
|
||||
if (mTickCacheHead)
|
||||
{
|
||||
setCacheSize(0);
|
||||
freeHead(mTickCacheHead);
|
||||
mTickCacheHead = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Move * TickCacheEntry::allocateMove()
|
||||
{
|
||||
return allocMove();
|
||||
}
|
||||
|
||||
TickCacheEntry * TickCache::addCacheEntry()
|
||||
{
|
||||
// Add a new entry, creating head if needed
|
||||
if (!mTickCacheHead)
|
||||
{
|
||||
mTickCacheHead = allocHead();
|
||||
mTickCacheHead->newest = mTickCacheHead->oldest = mTickCacheHead->next = NULL;
|
||||
mTickCacheHead->numEntry = 0;
|
||||
}
|
||||
if (!mTickCacheHead->newest)
|
||||
{
|
||||
mTickCacheHead->newest = mTickCacheHead->oldest = allocEntry();
|
||||
}
|
||||
else
|
||||
{
|
||||
mTickCacheHead->newest->next = allocEntry();
|
||||
mTickCacheHead->newest = mTickCacheHead->newest->next;
|
||||
}
|
||||
mTickCacheHead->newest->next = NULL;
|
||||
mTickCacheHead->newest->move = NULL;
|
||||
mTickCacheHead->numEntry++;
|
||||
return mTickCacheHead->newest;
|
||||
}
|
||||
|
||||
void TickCache::setCacheSize(S32 len)
|
||||
{
|
||||
// grow cache to len size, adding to newest side of the list
|
||||
while (!mTickCacheHead || mTickCacheHead->numEntry < len)
|
||||
addCacheEntry();
|
||||
// shrink tick cache down to given size, popping off oldest entries first
|
||||
while (mTickCacheHead && mTickCacheHead->numEntry > len)
|
||||
dropOldest();
|
||||
}
|
||||
|
||||
void TickCache::dropOldest()
|
||||
{
|
||||
AssertFatal(mTickCacheHead->oldest,"Popping off too many tick cache entries");
|
||||
TickCacheEntry * oldest = mTickCacheHead->oldest;
|
||||
mTickCacheHead->oldest = oldest->next;
|
||||
if (oldest->move)
|
||||
freeMove(oldest->move);
|
||||
freeEntry(oldest);
|
||||
mTickCacheHead->numEntry--;
|
||||
if (mTickCacheHead->numEntry < 2)
|
||||
mTickCacheHead->newest = mTickCacheHead->oldest;
|
||||
}
|
||||
|
||||
void TickCache::dropNextOldest()
|
||||
{
|
||||
AssertFatal(mTickCacheHead->oldest && mTickCacheHead->numEntry>1,"Popping off too many tick cache entries");
|
||||
TickCacheEntry * oldest = mTickCacheHead->oldest;
|
||||
TickCacheEntry * nextoldest = mTickCacheHead->oldest->next;
|
||||
oldest->next = nextoldest->next;
|
||||
if (nextoldest->move)
|
||||
freeMove(nextoldest->move);
|
||||
freeEntry(nextoldest);
|
||||
mTickCacheHead->numEntry--;
|
||||
if (mTickCacheHead->numEntry==1)
|
||||
mTickCacheHead->newest = mTickCacheHead->oldest;
|
||||
}
|
||||
|
||||
void TickCache::ageCache(S32 numToAge, S32 len)
|
||||
{
|
||||
AssertFatal(mTickCacheHead,"No tick cache head");
|
||||
AssertFatal(mTickCacheHead->numEntry>=numToAge,"Too few entries!");
|
||||
AssertFatal(mTickCacheHead->numEntry>numToAge,"Too few entries!");
|
||||
|
||||
while (numToAge--)
|
||||
dropOldest();
|
||||
while (mTickCacheHead->numEntry>len)
|
||||
dropNextOldest();
|
||||
while (mTickCacheHead->numEntry<len)
|
||||
addCacheEntry();
|
||||
}
|
||||
|
||||
void TickCache::beginCacheList()
|
||||
{
|
||||
// get ready iterate from oldest to newest entry
|
||||
if (mTickCacheHead)
|
||||
mTickCacheHead->next = mTickCacheHead->oldest;
|
||||
// if no head, that's ok, we'll just add entries as we go
|
||||
}
|
||||
|
||||
TickCacheEntry * TickCache::incCacheList(bool addIfNeeded)
|
||||
{
|
||||
// continue iterating through cache, returning current entry
|
||||
// we'll add new entries if need be
|
||||
TickCacheEntry * ret = NULL;
|
||||
if (mTickCacheHead && mTickCacheHead->next)
|
||||
{
|
||||
ret = mTickCacheHead->next;
|
||||
mTickCacheHead->next = mTickCacheHead->next->next;
|
||||
}
|
||||
else if (addIfNeeded)
|
||||
{
|
||||
addCacheEntry();
|
||||
ret = mTickCacheHead->newest;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
69
Engine/source/T3D/gameBase/tickCache.h
Normal file
69
Engine/source/T3D/gameBase/tickCache.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 _TICKCACHE_H_
|
||||
#define _TICKCACHE_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
|
||||
struct Move;
|
||||
|
||||
struct TickCacheEntry
|
||||
{
|
||||
enum { MaxPacketSize=140 };
|
||||
|
||||
U8 packetData[MaxPacketSize];
|
||||
TickCacheEntry * next;
|
||||
Move * move;
|
||||
|
||||
// If you want to assign moves to tick cache for later playback, allocate them here
|
||||
Move * allocateMove();
|
||||
};
|
||||
|
||||
struct TickCacheHead;
|
||||
|
||||
class TickCache
|
||||
{
|
||||
public:
|
||||
TickCache();
|
||||
~TickCache();
|
||||
|
||||
TickCacheEntry * addCacheEntry();
|
||||
void dropOldest();
|
||||
void dropNextOldest();
|
||||
void ageCache(S32 numToAge, S32 len);
|
||||
void setCacheSize(S32 len);
|
||||
void beginCacheList();
|
||||
TickCacheEntry * incCacheList(bool addIfNeeded=true);
|
||||
|
||||
private:
|
||||
TickCacheHead * mTickCacheHead;
|
||||
};
|
||||
|
||||
inline TickCache::TickCache()
|
||||
{
|
||||
mTickCacheHead = NULL;
|
||||
}
|
||||
|
||||
#endif // _TICKCACHE_H_
|
||||
Loading…
Add table
Add a link
Reference in a new issue