| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497 | //-----------------------------------------------------------------------------// 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 "console/simBase.h"#include "sim/netConnection.h"#include "core/stream/bitStream.h"#include "core/stream/fileStream.h"#ifndef TORQUE_TGB_ONLY#include "scene/pathManager.h"#endif#include "console/consoleTypes.h"#include "sim/netInterface.h"#include "console/engineAPI.h"#include <stdarg.h>IMPLEMENT_SCOPE( NetAPI, Net,, "Networking functionality." );IMPLEMENT_NONINSTANTIABLE_CLASS( NetEvent,   "An event to be sent over the network." )END_IMPLEMENT_CLASS;S32 gNetBitsSent = 0;extern S32 gNetBitsReceived;U32 gGhostUpdates = 0;enum NetConnectionConstants {   PingTimeout = 4500, ///< milliseconds   DefaultPingRetryCount = 15,};SimObjectPtr<NetConnection> NetConnection::mServerConnection;SimObjectPtr<NetConnection> NetConnection::mLocalClientConnection;//----------------------------------------------------------------------/// ConnectionMessageEvent////// This event is used inside by the connection and subclasses to message/// itself when sequencing events occur.  Right now, the message event/// only uses 6 bits to transmit the message, soclass ConnectionMessageEvent : public NetEvent{   U32 sequence;   U32 message;   U32 ghostCount;public:   typedef NetEvent Parent;   ConnectionMessageEvent(U32 msg=0, U32 seq=0, U32 gc=0)      { message = msg; sequence = seq; ghostCount = gc;}   void pack(NetConnection *, BitStream *bstream)   {      bstream->write(sequence);      bstream->writeInt(message, 3);      bstream->writeInt(ghostCount, NetConnection::GhostIdBitSize + 1);   }   void write(NetConnection *, BitStream *bstream)   {      bstream->write(sequence);      bstream->writeInt(message, 3);      bstream->writeInt(ghostCount, NetConnection::GhostIdBitSize + 1);   }   void unpack(NetConnection *, BitStream *bstream)   {      bstream->read(&sequence);      message = bstream->readInt(3);      ghostCount = bstream->readInt(NetConnection::GhostIdBitSize + 1);   }   void process(NetConnection *ps)   {      ps->handleConnectionMessage(message, sequence, ghostCount);   }   DECLARE_CONOBJECT(ConnectionMessageEvent);};IMPLEMENT_CO_NETEVENT_V1(ConnectionMessageEvent);ConsoleDocClass( ConnectionMessageEvent,				"@brief This event is used inside by the connection and subclasses to message itself when sequencing events occur.\n\n"				"Not intended for game development, for editors or internal use only.\n\n "				"@internal");void NetConnection::sendConnectionMessage(U32 message, U32 sequence, U32 ghostCount){   postNetEvent(new ConnectionMessageEvent(message, sequence, ghostCount));}//--------------------------------------------------------------------IMPLEMENT_CONOBJECT(NetConnection);ConsoleDocClass( NetConnection,   "@brief Provides the basis for implementing a multiplayer game protocol.\n\n"   "NetConnection combines a low-level notify protocol implemented in ConnectionProtocol with a SimGroup, "   "and implements several distinct subsystems:\n\n"   "- <b>Event Manager</b>  This is responsible for transmitting NetEvents over the wire.  "   "It deals with ensuring that the various types of NetEvents are delivered appropriately, "   "and with notifying the event of its delivery status.\n\n"   "- <b>Move Manager</b>  This is responsible for transferring a Move to the server 32 "   "times a second (on the client) and applying it to the control object (on the server).\n\n"   "- <b>Ghost Manager</b>  This is responsible for doing scoping calculations (on the server "   "side) and transmitting most-recent ghost information to the client.\n\n"   "- <b>File Transfer</b>  It is often the case that clients will lack important files when "   "connecting to a server which is running a mod or new map. This subsystem allows the "   "server to transfer such files to the client.\n\n"   "- <b>Networked String Table</b>  String data can easily soak up network bandwidth, so for "   "efficiency, we implement a networked string table. We can then notify the connection "   "of strings we will reference often, such as player names, and transmit only a tag, "   "instead of the whole string.\n\n"   "- <b>Demo Recording</b>  A demo in Torque is a log of the network traffic between client "   "and server; when a NetConnection records a demo, it simply logs this data to a file. When "   "it plays a demo back, it replays the logged data.\n\n"   "- <b>Connection Database</b>  This is used to keep track of all the NetConnections; it can "   "be iterated over (for instance, to send an event to all active connections), or queried "   "by address.\n\n"   "The NetConnection is a SimGroup. On the client side, it contains all the objects which have been "   "ghosted to that client. On the server side, it is empty; it can be used (typically in script) to "   "hold objects related to the connection. For instance, you might place an observation camera in the "   "NetConnnection. In both cases, when the connection is destroyed, so are the contained objects.\n\n"   "The NetConnection also has the concept of local connections.  These are used when the client and "   "server reside in the same process.  A local connection is typically required to use the standard "   "Torque world building tools.  A local connection is also required when building a single player "   "game.\n\n"   "@see @ref Networking, @ref ghosting_scoping, @ref netconnection_simgroup, @ref local_connections, GameConnection, AIConnection, and AIClient.\n\n"   "@ingroup Networking\n");NetConnection* NetConnection::mConnectionList = NULL;NetConnection* NetConnection::mHashTable[NetConnection::HashTableSize] = { NULL, };bool NetConnection::mFilesWereDownloaded = false;static inline U32 HashNetAddress(const NetAddress *addr){   return *((U32 *)addr->netNum) % NetConnection::HashTableSize;}NetConnection *NetConnection::lookup(const NetAddress *addr){   U32 hashIndex = HashNetAddress(addr);   for(NetConnection *walk = mHashTable[hashIndex]; walk; walk = walk->mNextTableHash)      if(Net::compareAddresses(addr, walk->getNetAddress()))         return walk;   return NULL;}void NetConnection::netAddressTableInsert(){   U32 hashIndex = HashNetAddress(&mNetAddress);   mNextTableHash = mHashTable[hashIndex];   mHashTable[hashIndex] = this;}void NetConnection::netAddressTableRemove(){   U32 hashIndex = HashNetAddress(&mNetAddress);   NetConnection **walk = &mHashTable[hashIndex];   while(*walk)   {      if(*walk == this)      {         *walk = mNextTableHash;         mNextTableHash = NULL;         return;      }      walk = &((*walk)->mNextTableHash);   }}void NetConnection::setNetAddress(const NetAddress *addr){   mNetAddress = *addr;}const NetAddress *NetConnection::getNetAddress(){   return &mNetAddress;}void NetConnection::setSequence(U32 sequence){   mConnectSequence = sequence;}U32 NetConnection::getSequence(){   return mConnectSequence;}static U32 gPacketRateToServer = 32;static U32 gPacketUpdateDelayToServer = 32;static U32 gPacketRateToClient = 10;static U32 gPacketSize = 200;void NetConnection::consoleInit(){   Con::addVariable("$pref::Net::PacketRateToServer", TypeS32, &gPacketRateToServer,      "@brief Sets how often packets are sent from the client to the server.\n\n"      "It is possible to control how often packets may be sent to the server.  This may be "      "used to throttle the amount of bandwidth being used, but should be adjusted with "      "caution.\n\n"      "The actual formula used to calculate the delay between sending packets to the server is:\n"      "@code\n"      "Packet Update Delay To Server = 1024 / $pref::Net::PacketRateToServer"      "@endcode\n"      "with the result in ms.  A minimum rate of 8 is enforced in the source code.  The default "      "value is 32.\n\n"      "@note When using a local connection (@ref local_connections) be aware that this variable "      "is always forced to 128.\n\n"      "@ingroup Networking");   Con::addVariable("$pref::Net::PacketRateToClient", TypeS32, &gPacketRateToClient,      "@brief Sets how often packets are sent from the server to a client.\n\n"      "It is possible to control how often packets may be sent to the clients.  This may be "      "used to throttle the amount of bandwidth being used, but should be adjusted with "      "caution.\n\n"      "The actual formula used to calculate the delay between sending packets to a client is:\n"      "@code\n"      "Packet Update Delay To Client = 1024 / $pref::Net::PacketRateToClient"      "@endcode\n"      "with the result in ms.  A minimum rate of 1 is enforced in the source code.  The default "      "value is 10.\n\n"      "@note When using a local connection (@ref local_connections) be aware that this variable "      "is always forced to 128.\n\n"      "@ingroup Networking");   Con::addVariable("$pref::Net::PacketSize", TypeS32, &gPacketSize,      "@brief Sets the maximum size in bytes an individual network packet may be.\n\n"      "It is possible to control how large each individual network packet may be.  Increasing "      "its size from the default allows for more data to be sent on each network send.  "      "However, this value should be changed with caution as too large a value will cause "      "packets to be split up by the networking platform or hardware, which is something "      "Torque cannot handle.\n\n"      "A minimum packet size of 100 bytes is enforced in the source code.  There is no "      "enforced maximum.  The default value is 200 bytes.\n\n"      "@note When using a local connection (@ref local_connections) be aware that this variable "      "is always forced to 1024 bytes.\n\n"      "@ingroup Networking");   Con::addVariable("$Stats::netBitsSent", TypeS32, &gNetBitsSent,      "@brief The number of bytes sent during the last packet send operation.\n\n"      "@note Even though this variable has 'Bits' in it, the value is indeed reported in bytes.  This name "      "is a legacy holdover and remains for compatibility reasons.\n"      "@ingroup Networking");   Con::addVariable("$Stats::netBitsReceived", TypeS32, &gNetBitsReceived,      "@brief The number of bytes received during the last packet process operation.\n\n"      "@note Even though this variable has 'Bits' in it, the value is indeed reported in bytes.  This name "      "is a legacy holdover and remains for compatibility reasons.\n"      "@ingroup Networking");   Con::addVariable("$Stats::netGhostUpdates", TypeS32, &gGhostUpdates,      "@brief The total number of ghosts added, removed, and/or updated on the client "      "during the last packet process operation.\n\n"      "@ingroup Networking");}void NetConnection::checkMaxRate(){   // Enforce some minimum limits to the network settings.   gPacketRateToServer = getMax( gPacketRateToServer, (U32)8 );   gPacketRateToClient = getMax( gPacketRateToClient, (U32)1 );   gPacketSize = getMax( gPacketSize, (U32)100 );   U32 packetRateToServer = gPacketRateToServer;   U32 packetRateToClient = gPacketRateToClient;   U32 packetSize = gPacketSize;   if (isLocalConnection())   {      packetRateToServer = 128;      packetRateToClient = 128;      packetSize = 1024;   }   gPacketUpdateDelayToServer = 1024 / packetRateToServer;   U32 toClientUpdateDelay = 1024 / packetRateToClient;   if(mMaxRate.updateDelay != toClientUpdateDelay || mMaxRate.packetSize != packetSize)   {      mMaxRate.updateDelay = toClientUpdateDelay;      mMaxRate.packetSize = packetSize;      mMaxRate.changed = true;   }}void NetConnection::setSendingEvents(bool sending){   AssertFatal(!mEstablished, "Error, cannot change event behavior after a connection has been established.");   mSendingEvents = sending;}void NetConnection::setTranslatesStrings(bool xl){   AssertFatal(!mEstablished, "Error, cannot change event behavior after a connection has been established.");   mTranslateStrings = xl;   if(mTranslateStrings)      mStringTable = new ConnectionStringTable(this);}void NetConnection::setNetClassGroup(U32 grp){   AssertFatal(!mEstablished, "Error, cannot change net class group after a connection has been established.");   mNetClassGroup = grp;}NetConnection::NetConnection(){   mTranslateStrings = false;   mConnectSequence = 0;   mStringTable = NULL;   mSendingEvents = true;   mNetClassGroup = NetClassGroupGame;   AssertFatal(mNetClassGroup >= NetClassGroupGame && mNetClassGroup < NetClassGroupsCount,            "Invalid net event class type.");   mSimulatedPing = 0;   mSimulatedPacketLoss = 0;#ifdef TORQUE_DEBUG_NET   mLogging = false;#endif   mEstablished = false;   mLastUpdateTime = 0;   mRoundTripTime = 0;   mPacketLoss = 0;   mNextTableHash = NULL;   mSendDelayCredit = 0;   mConnectionState = NotConnected;   mCurrentDownloadingFile = NULL;   mCurrentFileBuffer = NULL;   mNextConnection = NULL;   mPrevConnection = NULL;   mNotifyQueueHead = NULL;   mNotifyQueueTail = NULL;   mCurRate.updateDelay = 102;   mCurRate.packetSize = 200;   mCurRate.changed = false;   mMaxRate.updateDelay = 102;   mMaxRate.packetSize = 200;   mMaxRate.changed = false;   checkMaxRate();   // event management data:   mNotifyEventList = NULL;   mSendEventQueueHead = NULL;   mSendEventQueueTail = NULL;   mUnorderedSendEventQueueHead = NULL;   mUnorderedSendEventQueueTail = NULL;   mWaitSeqEvents = NULL;   mNextSendEventSeq = FirstValidSendEventSeq;   mNextRecvEventSeq = FirstValidSendEventSeq;   mLastAckedEventSeq = -1;   // ghost management data:   mScopeObject = NULL;   mGhostingSequence = 0;   mGhosting = false;   mScoping = false;   mGhostArray = NULL;   mGhostRefs = NULL;   mGhostLookupTable = NULL;   mLocalGhosts = NULL;   mGhostsActive = 0;   mMissionPathsSent = false;   mDemoWriteStream = NULL;   mDemoReadStream = NULL;   mPingSendCount = 0;   mPingRetryCount = DefaultPingRetryCount;   mLastPingSendTime = Platform::getVirtualMilliseconds();   mCurrentDownloadingFile = NULL;   mCurrentFileBuffer = NULL;   mCurrentFileBufferSize = 0;   mCurrentFileBufferOffset = 0;   mNumDownloadedFiles = 0;   // Disable starting a new journal recording or playback from here on   Journal::Disable();   // Ensure NetAddress is cleared   dMemset(&mNetAddress, '\0', sizeof(NetAddress));}NetConnection::~NetConnection(){   AssertFatal(mNotifyQueueHead == NULL, "Uncleared notifies remain.");   netAddressTableRemove();   dFree(mCurrentFileBuffer);   if(mCurrentDownloadingFile)      delete mCurrentDownloadingFile;   delete[] mLocalGhosts;   delete[] mGhostLookupTable;   delete[] mGhostRefs;   delete[] mGhostArray;   delete mStringTable;   if(mDemoWriteStream)      delete mDemoWriteStream;   if(mDemoReadStream)      delete mDemoReadStream;}NetConnection::PacketNotify::PacketNotify(){   rateChanged = false;   maxRateChanged = false;   sendTime = 0;   eventList = 0;   ghostList = 0;}bool NetConnection::checkTimeout(U32 time){   if(!isNetworkConnection())      return false;   if(time > mLastPingSendTime + PingTimeout)   {      if(mPingSendCount >= mPingRetryCount)         return true;      mLastPingSendTime = time;      mPingSendCount++;      sendPingPacket();   }   return false;}void NetConnection::keepAlive(){   mLastPingSendTime = Platform::getVirtualMilliseconds();   mPingSendCount = 0;}void NetConnection::handleConnectionEstablished(){}//--------------------------------------------------------------------------#ifndef TORQUE_TGB_ONLYDefineEngineMethod( NetConnection, transmitPaths, void, (),,   "@brief Sent by the server during phase 2 of the mission download to update motion spline paths.\n\n"   "The server transmits all spline motion paths that are within the mission (Path) separate from "   "other objects.  This is due to the potentially large number of nodes within each path, which may "   "saturate a packet sent to the client.  By managing this step separately, Torque has finer control "   "over how packets are organised vs. doing it during the ghosting stage.\n\n"   "Internally a PathManager is used to track all paths defined within a mission on the server, and each "   "one is transmitted using a PathManagerEvent.  The client side collects these events and builds the "   "given paths within its own PathManager.  This is typically done during the standard mission start "   "phase 2 when following Torque's example mission startup sequence.\n\n"   "When a mission is ended, all paths need to be cleared from their respective path managers."   "@tsexample\n"   "function serverCmdMissionStartPhase2Ack(%client, %seq, %playerDB)\n"   "{\n"   "   // Make sure to ignore calls from a previous mission load\n"   "   if (%seq != $missionSequence || !$MissionRunning)\n"   "      return;\n"   "   if (%client.currentPhase != 1.5)\n"   "      return;\n"   "   %client.currentPhase = 2;\n"   "\n"   "   // Set the player datablock choice\n"   "   %client.playerDB = %playerDB;\n"   "\n"   "   // Update mission paths (SimPath), this needs to get there before the objects.\n"   "   %client.transmitPaths();\n"   "\n"   "   // Start ghosting objects to the client\n"   "   %client.activateGhosting();\n"   "}\n"   "@endtsexample\n"      "@see clearPaths()\n"   "@see Path\n"){   gServerPathManager->transmitPaths(object);   object->setMissionPathsSent(true);}DefineEngineMethod( NetConnection, clearPaths, void, (),,   "@brief On the server, resets the connection to indicate that motion spline paths have not been transmitted.\n\n"   "Typically when a mission has ended on the server, all connected clients are informed of this change "   "and their connections are reset back to a starting state.  This method resets a connection on the "   "server to indicate that motion spline paths have not been transmitted.\n\n"   "@tsexample\n"   "   // Inform the clients\n"   "   for (%clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++)\n"   "   {\n"   "      // clear ghosts and paths from all clients\n"   "      %cl = ClientGroup.getObject(%clientIndex);\n"   "      %cl.endMission();\n"   "      %cl.resetGhosting();\n"   "      %cl.clearPaths();\n"   "   }\n"   "@endtsexample\n"      "@see transmitPaths()\n"   "@see Path\n"){   object->setMissionPathsSent(false);}#endifDefineEngineMethod( NetConnection, getAddress, const char *, (),,   "@brief Returns the far end network address for the connection.\n\n"   "The address will be in one of the following forms:\n"   "- <b>IP:Broadcast:<port></b> for broadcast type addresses\n"   "- <b>IP:<address>:<port></b> for IP addresses\n"   "- <b>local</b> when connected locally (server and client running in same process\n"){   if(object->isLocalConnection())      return "local";   char *buffer = Con::getReturnBuffer(256);   Net::addressToString(object->getNetAddress(), buffer);   return buffer;}DefineEngineMethod( NetConnection, setSimulatedNetParams, void, (F32 packetLoss, S32 delay),,   "@brief Simulate network issues on the connection for testing.\n\n"   "@param packetLoss The fraction of packets that will be lost.  Ranges from 0.0 (no loss) to 1.0 (complete loss)\n"   "@param delay Delays packets being transmitted by simulating a particular ping.  This is an absolute "   "integer, measured in ms.\n"){   object->setSimulatedNetParams(packetLoss, delay);}DefineEngineMethod( NetConnection, getPing, S32, (),,   "@brief Returns the average round trip time (in ms) for the connection.\n\n"   "The round trip time is recalculated every time a notify packet is received.  Notify "   "packets are used to information the connection that the far end successfully received "   "the sent packet.\n"){   return( S32( object->getRoundTripTime() ) );}DefineEngineMethod( NetConnection, getPacketLoss, S32, (),,   "@brief Returns the percentage of packets lost per tick.\n\n"   "@note This method is not yet hooked up.\n"){   return( S32( 100 * object->getPacketLoss() ) );}DefineEngineMethod( NetConnection, checkMaxRate, void, (),,   "@brief Ensures that all configured packet rates and sizes meet minimum requirements.\n\n"   "This method is normally only called when a NetConnection class is first constructed.  It need "   "only be manually called if the global variables that set the packet rate or size have changed.\n\n"   "@note If @$pref::Net::PacketRateToServer, @$pref::Net::PacketRateToClient or @$pref::Net::PacketSize "   "have been changed since a NetConnection has been created, this method must be called on "   "all connections for them to follow the new rates or size.\n"){   object->checkMaxRate();}#ifdef TORQUE_DEBUG_NETDefineEngineMethod( NetConnection, setLogging, void, (bool state),,   "@brief Sets if debug statements should be written to the console log.\n\n"   "@note Only valid if the executable has been compiled with TORQUE_DEBUG_NET.\n"){   object->setLogging(state);}#endif//--------------------------------------------------------------------void NetConnection::setEstablished(){   AssertFatal(!mEstablished, "NetConnection::setEstablished - Error, this NetConnection has already been established.");   mEstablished = true;   mNextConnection = mConnectionList;   if(mConnectionList)      mConnectionList->mPrevConnection = this;   mConnectionList = this;   if(isNetworkConnection())      netAddressTableInsert();}void NetConnection::onRemove(){   // delete any ghosts that may exist for this connection, but aren't added   while(mGhostAlwaysSaveList.size())   {      delete mGhostAlwaysSaveList[0].ghost;      mGhostAlwaysSaveList.pop_front();   }   if(mNextConnection)      mNextConnection->mPrevConnection = mPrevConnection;   if(mPrevConnection)      mPrevConnection->mNextConnection = mNextConnection;   if(mConnectionList == this)      mConnectionList = mNextConnection;   while(mNotifyQueueHead)      handleNotify(false);   ghostOnRemove();   eventOnRemove();   Parent::onRemove();}String NetConnection::mErrorBuffer;void NetConnection::setLastError(const char *fmt, ...){   va_list argptr;   va_start(argptr, fmt);   mErrorBuffer = String::ToString(fmt, argptr);   va_end(argptr);#ifdef TORQUE_DEBUG_NET   // setLastErrors assert in net_debug builds   AssertFatal(false, mErrorBuffer.c_str());#endif}//--------------------------------------------------------------------void NetConnection::handleNotify(bool recvd){//   Con::printf("NET  %d: NOTIFY - %d %s", getId(), gPacketId, recvd ? "RECVD" : "DROPPED");   PacketNotify *note = mNotifyQueueHead;   AssertFatal(note != NULL, "Error: got a notify with a null notify head.");   mNotifyQueueHead = mNotifyQueueHead->nextPacket;   if(note->rateChanged && !recvd)      mCurRate.changed = true;   if(note->maxRateChanged && !recvd)      mMaxRate.changed = true;   if(recvd)    {      // Running average of roundTrip time      U32 curTime = Platform::getVirtualMilliseconds();      mRoundTripTime = (mRoundTripTime + (curTime - note->sendTime)) * 0.5;      packetReceived(note);   }   else      packetDropped(note);   delete note;}void NetConnection::processRawPacket(BitStream *bstream){   if(mDemoWriteStream)      recordBlock(BlockTypePacket, bstream->getReadByteSize(), bstream->getBuffer());   ConnectionProtocol::processRawPacket(bstream);}void NetConnection::handlePacket(BitStream *bstream){//   Con::printf("NET  %d: RECV - %d", getId(), mLastSeqRecvd);   // clear out any errors   mErrorBuffer = String();   if(bstream->readFlag())   {      mCurRate.updateDelay = bstream->readInt(12);      mCurRate.packetSize = bstream->readInt(12);   }   if(bstream->readFlag())   {      U32 omaxDelay = bstream->readInt(12);      S32 omaxSize = bstream->readInt(12);      if(omaxDelay < mMaxRate.updateDelay)         omaxDelay = mMaxRate.updateDelay;      if(omaxSize > mMaxRate.packetSize)         omaxSize = mMaxRate.packetSize;      if(omaxDelay != mCurRate.updateDelay || omaxSize != mCurRate.packetSize)      {         mCurRate.updateDelay = omaxDelay;         mCurRate.packetSize = omaxSize;         mCurRate.changed = true;      }   }   readPacket(bstream);   if(mErrorBuffer.isNotEmpty())      connectionError(mErrorBuffer);}void NetConnection::connectionError(const char *errorString){   TORQUE_UNUSED(errorString);}//--------------------------------------------------------------------NetConnection::PacketNotify *NetConnection::allocNotify(){   return new PacketNotify;}/// Used when simulating lag.////// We post this SimEvent when we want to send a packet; it delays for a bit, then/// sends the actual packet.class NetDelayEvent : public SimEvent{   U8 buffer[Net::MaxPacketDataSize];   BitStream stream;public:   NetDelayEvent(BitStream *inStream) : stream(NULL, 0)   {      dMemcpy(buffer, inStream->getBuffer(), inStream->getPosition());      stream.setBuffer(buffer, inStream->getPosition());      stream.setPosition(inStream->getPosition());   }   void process(SimObject *object)   {      ((NetConnection *) object)->sendPacket(&stream);   }};void NetConnection::checkPacketSend(bool force){   U32 curTime = Platform::getVirtualMilliseconds();   U32 delay = isConnectionToServer() ? gPacketUpdateDelayToServer : mCurRate.updateDelay;   if(!force)   {      if(curTime < mLastUpdateTime + delay - mSendDelayCredit)         return;      mSendDelayCredit = curTime - (mLastUpdateTime + delay - mSendDelayCredit);      if(mSendDelayCredit > 1000)         mSendDelayCredit = 1000;      if(mDemoWriteStream)         recordBlock(BlockTypeSendPacket, 0, 0);   }   if(windowFull())      return;   BitStream *stream = BitStream::getPacketStream(mCurRate.packetSize);   buildSendPacketHeader(stream);   mLastUpdateTime = curTime;   PacketNotify *note = allocNotify();   if(!mNotifyQueueHead)      mNotifyQueueHead = note;   else      mNotifyQueueTail->nextPacket = note;   mNotifyQueueTail = note;   note->nextPacket = NULL;   note->sendTime = curTime;   note->rateChanged = mCurRate.changed;   note->maxRateChanged = mMaxRate.changed;   if(stream->writeFlag(mCurRate.changed))   {      stream->writeInt(mCurRate.updateDelay, 12);      stream->writeInt(mCurRate.packetSize, 12);      mCurRate.changed = false;   }   if(stream->writeFlag(mMaxRate.changed))   {      stream->writeInt(mMaxRate.updateDelay, 12);      stream->writeInt(mMaxRate.packetSize, 12);      mMaxRate.changed = false;   }#ifdef TORQUE_DEBUG_NET   U32 start = stream->getCurPos();#endif   DEBUG_LOG(("PKLOG %d START", getId()) );   writePacket(stream, note);   DEBUG_LOG(("PKLOG %d END - %d", getId(), stream->getCurPos() - start) );   if(mSimulatedPacketLoss && Platform::getRandom() < mSimulatedPacketLoss)   {      //Con::printf("NET  %d: SENDDROP - %d", getId(), mLastSendSeq);      return;   }   if(mSimulatedPing)   {      Sim::postEvent(getId(), new NetDelayEvent(stream), Sim::getCurrentTime() + mSimulatedPing);      return;   }   sendPacket(stream);}Net::Error NetConnection::sendPacket(BitStream *stream){   //Con::printf("NET  %d: SEND - %d", getId(), mLastSendSeq);   // do nothing on send if this is a demo replay.   if(mDemoReadStream)      return Net::NoError;   gNetBitsSent = stream->getPosition();   if(isLocalConnection())   {      // short circuit connection to the other side.      // handle the packet, then force a notify.      stream->setBuffer(stream->getBuffer(), stream->getPosition(), stream->getPosition());      mRemoteConnection->processRawPacket(stream);      return Net::NoError;   }   else   {      return Net::sendto(getNetAddress(), stream->getBuffer(), stream->getPosition());   }}//--------------------------------------------------------------------//--------------------------------------------------------------------// these are the virtual function defs for Connection -// if your subclass has additional data to read / write / notify, add it in these functions.void NetConnection::readPacket(BitStream *bstream){   eventReadPacket(bstream);   ghostReadPacket(bstream);}void NetConnection::writePacket(BitStream *bstream, PacketNotify *note){   eventWritePacket(bstream, note);   ghostWritePacket(bstream, note);}void NetConnection::packetReceived(PacketNotify *note){   eventPacketReceived(note);   ghostPacketReceived(note);}void NetConnection::packetDropped(PacketNotify *note){   eventPacketDropped(note);   ghostPacketDropped(note);}//--------------------------------------------------------------------//--------------------------------------------------------------------void NetConnection::writeDemoStartBlock(ResizeBitStream* stream){   ConnectionProtocol::writeDemoStartBlock(stream);   stream->write(mRoundTripTime);   stream->write(mPacketLoss);#ifndef TORQUE_TGB_ONLY   // Write all the current paths to the stream...   gClientPathManager->dumpState(stream);#endif   stream->validate();   mStringTable->writeDemoStartBlock(stream);   U32 start = 0;   PacketNotify *note = mNotifyQueueHead;   while(note)   {      start++;      note = note->nextPacket;   }   stream->write(start);   eventWriteStartBlock(stream);   ghostWriteStartBlock(stream);}bool NetConnection::readDemoStartBlock(BitStream* stream){   ConnectionProtocol::readDemoStartBlock(stream);   stream->read(&mRoundTripTime);   stream->read(&mPacketLoss);#ifndef TORQUE_TGB_ONLY   // Read   gClientPathManager->readState(stream);#endif   mStringTable->readDemoStartBlock(stream);   U32 pos;   stream->read(&pos); // notify count   for(U32 i = 0; i < pos; i++)   {      PacketNotify *note = allocNotify();      note->nextPacket = NULL;      if(!mNotifyQueueHead)         mNotifyQueueHead = note;      else         mNotifyQueueTail->nextPacket = note;      mNotifyQueueTail = note;   }   eventReadStartBlock(stream);   ghostReadStartBlock(stream);   return true;}bool NetConnection::startDemoRecord(const char *fileName){   FileStream *fs = new FileStream;   if((fs = FileStream::createAndOpen( fileName, Torque::FS::File::Write )) == NULL)      return false;   mDemoWriteStream = fs;   mDemoWriteStream->write(mProtocolVersion);   ResizeBitStream bs;   // then write out the start block   writeDemoStartBlock(&bs);   U32 size = bs.getPosition() + 1;   mDemoWriteStream->write(size);   mDemoWriteStream->write(size, bs.getBuffer());   return true;}bool NetConnection::replayDemoRecord(const char *fileName){   Stream *fs;   if((fs = FileStream::createAndOpen( fileName, Torque::FS::File::Read )) == NULL)      return false;   mDemoReadStream = fs;   mDemoReadStream->read(&mProtocolVersion);   U32 size;   mDemoReadStream->read(&size);   U8 *block = new U8[size];   mDemoReadStream->read(size, block);   BitStream bs(block, size);   bool res = readDemoStartBlock(&bs);   delete[] block;   if(!res)      return false;   // prep for first block read   // type/size stored in U16: [type:4][size:12]   U16 typeSize;   mDemoReadStream->read(&typeSize);   mDemoNextBlockType = typeSize >> 12;   mDemoNextBlockSize = typeSize & 0xFFF;   if(mDemoReadStream->getStatus() != Stream::Ok)      return false;   return true;}void NetConnection::stopRecording(){   if(mDemoWriteStream)   {      delete mDemoWriteStream;      mDemoWriteStream = NULL;   }}void NetConnection::recordBlock(U32 type, U32 size, void *data){   AssertFatal(type < MaxNumBlockTypes, "NetConnection::recordBlock: invalid type");   AssertFatal(size < MaxBlockSize, "NetConnection::recordBlock: invalid size");   if((type >= MaxNumBlockTypes) || (size >= MaxBlockSize))      return;   if(mDemoWriteStream)   {      // store type/size in U16: [type:4][size:12]      U16 typeSize = (type << 12) | size;      mDemoWriteStream->write(typeSize);      if(size)         mDemoWriteStream->write(size, data);   }}void NetConnection::handleRecordedBlock(U32 type, U32 size, void *data){   switch(type)   {      case BlockTypePacket: {         BitStream bs(data, size);         processRawPacket(&bs);         break;      }      case BlockTypeSendPacket:         checkPacketSend(true);         break;   }}void NetConnection::demoPlaybackComplete(){}void NetConnection::stopDemoPlayback(){   demoPlaybackComplete();   deleteObject();}bool NetConnection::processNextBlock(){   U8 buffer[Net::MaxPacketDataSize];   // read in and handle   if(mDemoReadStream->read(mDemoNextBlockSize, buffer))      handleRecordedBlock(mDemoNextBlockType, mDemoNextBlockSize, buffer);   U16 typeSize;   mDemoReadStream->read(&typeSize);   mDemoNextBlockType = typeSize >> 12;   mDemoNextBlockSize = typeSize & 0xFFF;   if(mDemoReadStream->getStatus() != Stream::Ok)   {      stopDemoPlayback();      return false;   }   return true;}//--------------------------------------------------------------------//--------------------------------------------------------------------// some handy string functions for compressing strings over a connection:enum NetStringConstants{   NullString = 0,   CString,   TagString,   Integer};void NetConnection::validateSendString(const char *str){   if(U8(*str) == StringTagPrefixByte)   {      NetStringHandle strHandle(dAtoi(str + 1));      checkString(strHandle);   }}void NetConnection::packString(BitStream *stream, const char *str){   if(!*str)   {      stream->writeInt(NullString, 2);      return;   }   if(U8(str[0]) == StringTagPrefixByte)   {      stream->writeInt(TagString, 2);      stream->writeInt(dAtoi(str + 1), ConnectionStringTable::EntryBitSize);      return;   }   if(str[0] == '-' || (str[0] >= '0' && str[0] <= '9'))   {      char buf[16];      S32 num = dAtoi(str);      dSprintf(buf, sizeof(buf), "%d", num);      if(!dStrcmp(buf, str))      {         stream->writeInt(Integer, 2);         if(stream->writeFlag(num < 0))            num = -num;         if(stream->writeFlag(num < 128))         {            stream->writeInt(num, 7);            return;         }         if(stream->writeFlag(num < 32768))         {            stream->writeInt(num, 15);            return;         }         else         {            stream->writeInt(num, 31);            return;         }      }   }   stream->writeInt(CString, 2);   stream->writeString(str);}void NetConnection::unpackString(BitStream *stream, char readBuffer[1024]){   U32 code = stream->readInt(2);   switch(code)   {      case NullString:         readBuffer[0] = 0;         return;      case CString:         {            stream->readString(readBuffer);            return;         }      case TagString:         U32 tag;         tag = stream->readInt(ConnectionStringTable::EntryBitSize);         readBuffer[0] = StringTagPrefixByte;         dSprintf(readBuffer+1, 1023, "%d", tag);         return;      case Integer:         bool neg;         neg = stream->readFlag();         S32 num;         if(stream->readFlag())            num = stream->readInt(7);         else if(stream->readFlag())            num = stream->readInt(15);         else            num = stream->readInt(31);         if(neg)            num = -num;         dSprintf(readBuffer, 1024, "%d", num);   }}void NetConnection::packNetStringHandleU(BitStream *stream, NetStringHandle &h){   if(stream->writeFlag(h.isValidString() ))   {      bool isReceived;      U32 netIndex = checkString(h, &isReceived);      if(stream->writeFlag(isReceived))         stream->writeInt(netIndex, ConnectionStringTable::EntryBitSize);      else         stream->writeString(h.getString());   }}NetStringHandle NetConnection::unpackNetStringHandleU(BitStream *stream){   NetStringHandle ret;   if(stream->readFlag())   {      if(stream->readFlag())         ret = mStringTable->lookupString(stream->readInt(ConnectionStringTable::EntryBitSize));      else      {         char buf[256];         stream->readString(buf);         ret = NetStringHandle(buf);      }   }   return ret;}//----------------------------------------------------------------------------//----------------------------------------------------------------------------//----------------------------------------------------------------------------void NetConnection::setAddressDigest(U32 digest[4]){   mAddressDigest[0] = digest[0];   mAddressDigest[1] = digest[1];   mAddressDigest[2] = digest[2];   mAddressDigest[3] = digest[3];}void NetConnection::getAddressDigest(U32 digest[4]){   digest[0] = mAddressDigest[0];   digest[1] = mAddressDigest[1];   digest[2] = mAddressDigest[2];   digest[3] = mAddressDigest[3];}bool NetConnection::canRemoteCreate(){   return false;}void NetConnection::onTimedOut(){}void NetConnection::connect(const NetAddress *address){   mNetAddress = *address;   GNet->startConnection(this);}void NetConnection::onConnectTimedOut(){}void NetConnection::sendDisconnectPacket(const char *reason){   GNet->sendDisconnectPacket(this, reason);}void NetConnection::onDisconnect(const char *reason){   TORQUE_UNUSED(reason);}void NetConnection::onConnectionRejected(const char *reason){}void NetConnection::onConnectionEstablished(bool isInitiator){}void NetConnection::handleStartupError(const char *errorString){}void NetConnection::writeConnectRequest(BitStream *stream){   stream->write(mNetClassGroup);   stream->write(U32(AbstractClassRep::getClassCRC(mNetClassGroup)));}bool NetConnection::readConnectRequest(BitStream *stream, const char **errorString){   U32 classGroup, classCRC;   stream->read(&classGroup);   stream->read(&classCRC);   if(classGroup == mNetClassGroup && classCRC == AbstractClassRep::getClassCRC(mNetClassGroup))      return true;   *errorString = "CHR_INVALID";   return false;}void NetConnection::writeConnectAccept(BitStream *stream){   TORQUE_UNUSED(stream);}bool NetConnection::readConnectAccept(BitStream *stream, const char **errorString){   TORQUE_UNUSED(stream);   TORQUE_UNUSED(errorString);   return true;}DefineEngineMethod( NetConnection, resolveGhostID, S32, (S32 ghostID),,   "@brief On the client, convert a ghost ID from this connection to a real SimObject ID.\n\n"   "Torque's network ghosting system only exchanges ghost ID's between the server and client.  Use "   "this method on the client to discover an object's local SimObject ID when you only have a "   "ghost ID.\n"   "@param ghostID The ghost ID of the object as sent by the server.\n"   "@returns The SimObject ID of the object, or 0 if it could not be resolved.\n\n"   "@tsexample\n"      "%object = ServerConnection.resolveGhostID( %ghostId );\n"   "@endtsexample\n\n"   "@see @ref ghosting_scoping for a description of the ghosting system.\n\n"){   // Safety check   if(ghostID < 0 || ghostID > NetConnection::MaxGhostCount) return 0;   NetObject *foo = object->resolveGhost(ghostID);   if(foo)      return foo->getId();   else      return 0;}DefineEngineMethod( NetConnection, resolveObjectFromGhostIndex, S32, (S32 ghostID),,   "@brief On the server, convert a ghost ID from this connection to a real SimObject ID.\n\n"   "Torque's network ghosting system only exchanges ghost ID's between the server and client.  Use "   "this method on the server to discover an object's local SimObject ID when you only have a "   "ghost ID.\n"   "@param ghostID The ghost ID of the object as sent by the server.\n"   "@returns The SimObject ID of the object, or 0 if it could not be resolved.\n\n"   "@tsexample\n"      "%object = %client.resolveObjectFromGhostIndex( %ghostId );\n"   "@endtsexample\n\n"   "@see @ref ghosting_scoping for a description of the ghosting system.\n\n"){   // Safety check   if(ghostID < 0 || ghostID > NetConnection::MaxGhostCount) return 0;   NetObject *foo = object->resolveObjectFromGhostIndex(ghostID);   if(foo)      return foo->getId();   else      return 0;}DefineEngineMethod( NetConnection, getGhostID, S32, (S32 realID),,   "@brief On server or client, convert a real id to the ghost id for this connection.\n\n"   "Torque's network ghosting system only exchanges ghost ID's between the server and client.  Use "   "this method on the server or client to discover an object's ghost ID based on its real SimObject ID.\n"   "@param realID The real SimObject ID of the object.\n"   "@returns The ghost ID of the object for this connection, or -1 if it could not be resolved.\n\n"   "@see @ref ghosting_scoping for a description of the ghosting system.\n\n"){   NetObject * foo;   if(Sim::findObject(realID, foo))   {      return object->getGhostIndex(foo);   }   else   {      Con::errorf("NetConnection::serverToGhostID - could not find specified object");      return -1;   }}DefineEngineMethod( NetConnection, connect, void, (const char* remoteAddress),,   "@brief Connects to the remote address.\n\n"   "Attempts to connect with another NetConnection on the given address.  Typically once "   "connected, a game's information is passed along from the server to the client, followed "   "by the player entering the game world.  The actual procedure is dependent on "   "the NetConnection subclass that is used.  i.e. GameConnection.\n"   "@param remoteAddress The address to connect to in the form of IP:<address>:<port&rt; "   "although the <i>IP:</i> portion is optional.  The <i>address</i> portion may be in the form "   "of w.x.y.z or as a host name, in which case a DNS lookup will be performed.  You may also "   "substitue the word <i>broadcast</i> for the address to broadcast the connect request over "   "the local subnet.\n\n"   "@see NetConnection::connectLocal() to connect to a server running within the same process "   "as the client.\n"   ){   NetAddress addr;   if(!Net::stringToAddress(remoteAddress, &addr))   {      Con::errorf("NetConnection::connect: invalid address - %s", remoteAddress);      return;   }   object->connect(&addr);}DefineEngineMethod( NetConnection, connectLocal, const char*, (),,   "@brief Connects with the server that is running within the same process as the client.\n\n"   "@returns An error text message upon failure, or an empty string when successful.\n\n"   "@see See @ref local_connections for a description of local connections and their use.  See "   "NetConnection::connect() to connect to a server running in another process (on the same machine or not).\n"){   ConsoleObject *co = ConsoleObject::create(object->getClassName());   NetConnection *client = object;   NetConnection *server = dynamic_cast<NetConnection *>(co);   BitStream *stream = BitStream::getPacketStream();   if(!server || !server->canRemoteCreate())   {      delete co;      return "error";   }         server->registerObject();   server->setIsLocalClientConnection();   server->setSequence(0);   client->setSequence(0);   client->setRemoteConnectionObject(server);   server->setRemoteConnectionObject(client);   //We need to reset the maxrate's here, because we   // can't test if it is a local connection until RemoteConnectionObject   // has been set   server->checkMaxRate();     client->checkMaxRate();   stream->setPosition(0);   client->writeConnectRequest(stream);   stream->setPosition(0);      const char* error;   if( !server->readConnectRequest( stream, &error ) )   {      client->onConnectionRejected( error );      server->deleteObject();      return "error";   }   stream->setPosition(0);   server->writeConnectAccept(stream);   stream->setPosition(0);   if( !client->readConnectAccept( stream, &error ) )   {      client->handleStartupError( error );      server->deleteObject();      return "error";   }   client->onConnectionEstablished(true);   server->onConnectionEstablished(false);   client->setEstablished();   server->setEstablished();   client->setConnectSequence(0);   server->setConnectSequence(0);   NetConnection::setLocalClientConnection(server);   server->assignName("LocalClientConnection");      return "";}
 |