Engine directory for ticket #1

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

View file

@ -0,0 +1,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 );
}

View 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_

File diff suppressed because it is too large Load diff

View 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

View 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 );
}

View 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

View 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 )
{
}

View 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_

View file

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

View file

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

View file

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

View file

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

View 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);
}
}

View 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_

View 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;
}

View 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

View 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();
}

View 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_

View 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
}

View 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_

View 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;
}
}
}

View 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_

View 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;
}

View 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_