Game Development Community

T3D 1.1 Beta 3 - Singleplayer packetsize & packetrate parms not being correctly set - RESOLVED

by Dusty Monk · in Torque 3D Professional · 01/29/2011 (6:34 am) · 12 replies

Bug 1 - Singleplayer packetsize & packetrate not being correctly set

The docs for $pref::net::packsize say the following:
"@brief Sets the maximum size in bytes an individual network packet may be.nn"
      "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.nn"
      "A minimum packet size of 100 bytes is enforced in the source code.  There is no "
      "enforced maximum.  The default value is 200 bytes.nn"
      "@note When using a local connection (@ref local_connections) be aware that this variable "
      "is always forced to 1024 bytes.nn"

There are similar notes for $pref::net::packetratetoserver & $pref::net::packetrateto client, that say they are set to 128 each in a single player game.

However, as of T3D 1.1 B3, this is not actually occuring. Here's why:

The code that looks looks over packetsizes & packetrates and modifies them to use the console variables, and hardcodes them if you are in a singleplayer game, is done in NetConnection::checkMaxRate -

U32 packetRateToServer = gPacketRateToServer;
   U32 packetRateToClient = gPacketRateToClient;
   U32 packetSize = gPacketSize;

   if (isLocalConnection())
   {
      packetRateToServer = 128;
      packetRateToClient = 128;
      packetSize = 1024;
   }

Currently, checkMaxRate is only called during the NetConnection's constructor. The function isLocalConnection returns true or false depending on whether the RemoteConnection pointer has been set for the netconnection object. Unforunately, that pointer will never bet set until after the constructor, so isLocalConnection in this case always returns false. So regardless of whether you're using a singleplayer set up or not, currently you're always getting the values specified by the net parameters.

My Fix
I fixed this by modifying the 'connectLocal' console method to call checkMaxRate again, on both the server & client objects, after the remoteobject pointer has been set, so that the islocalConnection is correctly set. Specifically, in the where DefineEngineMethod(NetConnection, connectLocal... is specified, I added the following:

server->setSequence(0);
   client->setSequence(0);
   client->setRemoteConnectionObject(server);
   server->setRemoteConnectionObject(client);

   // WS START - We need to reset the maxrate's here, because they do special things 
   // based on whether or not they're a local connection or not, but that detection can't
   // occur until the RemoteConnectionObject has been set.
   server->checkMaxRate();
   client->checkMaxRate();
   // WS END

   stream->setPosition(0);
   client->writeConnectRequest(stream);
   stream->setPosition(0);

Bug 2 - Bit size for packet size & rates is not adequate for larger values of packetsize.

Unfortunately, if you activate the above code, you'll encounter a second bug, which may be a bit devious. The short version is simply that the code that writes and reads the values for packetsize in packetrate, in NetConnection::checkPacketSend and NetConnection::handlePacket don't allow enough bits to read and write a large value. That is, they specify 10 bits, and 1024 takes 11. The devious nature of this bug is that what happens is that on the client side, handlePacket quietly reads 0 instead of 1024, which means the when the stream is allocated to do the read, 0 is passed in for rate, which causes bitStream::getStream to allocate the entire buffer. And what that means is there is no room for buffer overruns as event or ghost packets are read in, so you will read packets until you literally overrun the buffer, which will cause all sort of unpleasant things to happen to your game during mission load (when most of the events and ghosts are being transferred).

My fix
If you activate the code above in bug 1, then you must do the following as well. In NetConnection::checkPacketSend, make the following change:

if(stream->writeFlag(mCurRate.changed))
   {
      // WS START - As a singleplayer game, we need bigger packet sizes.
      // So this means we need more bits.
      // stream->writeInt(mCurRate.updateDelay, 10);
      // stream->writeInt(mCurRate.packetSize, 10);
      stream->writeInt(mCurRate.updateDelay, 12);
      stream->writeInt(mCurRate.packetSize, 12);
      mCurRate.changed = false;
   }
   if(stream->writeFlag(mMaxRate.changed))
   {
      // stream->writeInt(mMaxRate.updateDelay, 10);
      // stream->writeInt(mMaxRate.packetSize, 10);
      stream->writeInt(mMaxRate.updateDelay, 12);
      stream->writeInt(mMaxRate.packetSize, 12);
      // WS END
      mMaxRate.changed = false;
   }
#ifdef TORQUE_DEBUG_NET
   U32 start = stream->getCurPos();
#endif

To be safe, I specified 12 bits, and to not send an odd number of bits. And in NetConnection::handlePacket, the corresponding change on the read side:

mErrorBuffer = String();

   if(bstream->readFlag())
   {
      // WS START - modified to handle writing of larger packet sizes & delays
      // mCurRate.updateDelay = bstream->readInt(10);
      // mCurRate.packetSize = bstream->readInt(10);
      mCurRate.updateDelay = bstream->readInt(12);
      mCurRate.packetSize = bstream->readInt(12);
   }

   if(bstream->readFlag())
   {
      // See above
      // U32 omaxDelay = bstream->readInt(10);
      // S32 omaxSize = bstream->readInt(10);
      U32 omaxDelay = bstream->readInt(12);
      S32 omaxSize = bstream->readInt(12);
      // WS END
      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);

Once these changes are made, your singlplayer game can then correctly use a packetsze of 1024, and packetrates of 128 for both client & server.

Hope this helps anyone making a singleplayer game that needs as much throughput as possible..

Cheers,

Dusty



About the author

Dusty Monk is founder and president of Windstorm Studios, an independant game studio. Formerly a sr. programmer at Ensemble Studios, Dusty has worked on AAA titles such as Age of Empires II & III, and Halo Wars.


#1
01/29/2011 (8:03 am)
Just to mention:
In stock T3D when I've forced the check for MaxPacketRate I'd get a crash when spawning more than a few Ai at once.

I'll have a test and see if your code cleans this up when I get chance.
#2
01/31/2011 (9:04 am)
All seems to work nicely Dusty.
:)

Though I've noticed before that the MaxPacketRate is allegedly 1500.

from TorqueConfig.h
/// Enable this define to change the default Net::MaxPacketDataSize
/// Do this at your own risk since it has the potential to cause packets
/// to be split up by old routers and Torque does not have a mechanism to
/// stitch split packets back together. Using this define can be very useful
/// in controlled network hardware environments (like a LAN) or for singleplayer
/// games (like BArricade and its large paths)
//#define MAXPACKETSIZE 1500

And can forced in script with
function GameCore::onClientEnterGame(%game, %client)
{
...
//localclientconnection.checkmaxrate();//yorks
//sets package rate to 1500 but causes a level/mission crash if you try and spawn 50 Ai at once.
}

Which if used will lead to a "bad packet" warning and a return to Main Menu.

And after reading your above post, I understand why.
#3
01/31/2011 (9:37 am)
Yeah the whole thing is actually quite confusing, due to Torque's rather overbinding of the term "maxpacketsize". Those comments in TorqueConfig.h are really misleading, because that constant doesn't really define your packet size, it defines how much space they'll allocate to hold our packets. If you look at the top of platformNet.h, you'll find a tiny bit of code at the top that says oh hey if you didn't define this already, then I'll define it for you:

#ifndef MAXPACKETSIZE
#define MAXPACKETSIZE 1500
#endif

So really regardless of whether you uncomment that line or not, that define is being set and set to 1500. You could uncomment it if you wanted to allocated a smaller (or larger) bitstream buffer.

That constant is used a bit lower in the file to define a static constant MaxPacketDataSize:
static const int MaxPacketDataSize = MAXPACKETSIZE;

   static ConnectionNotifyEvent   smConnectionNotify;
   static ConnectionAcceptedEvent smConnectionAccept;
   static ConnectionReceiveEvent  smConnectionReceive;
   static PacketReceiveEvent      smPacketReceive;

Which in turn is used to define allocation sizes in various places throughout the code, most notably the bitstream buffers that are used to send and receive packets. But the actual size of the packet that's sent across is determined by that $pref::net::packetsize constant. And you always want to make sure that is something *less* than the MAXPACKETSIZE constant, because if you set it to be the same, even with the changes above, due to the way the netconnection code reads and writes packets, you will inevitably get buffer overruns.

Fun!

Dusty

#4
01/31/2011 (11:07 am)
Quote:
Fun!

That's one way to put it ...
#5
03/21/2011 (1:31 pm)
Logged as THREED-1502.
#6
04/05/2011 (6:54 pm)
Thanks for this fix, Dusty!
#7
04/08/2011 (11:33 am)
I applied this patch, and it crashed in a getShapeName call during mission loading (44 objects). Specifically, the lookupString call inside there receives an index of 0xcdcdcdcd, which causes the call to asplode. We get to this sorry state of affairs via the GUISHapeNameHUD setting the shapename.

I've double checked the code changes, and everything is fine there.

I suspect something is going awry with the tagged strings somewhere...

The curious thing is that if I remove a bunch of objects from the mission, the crash doesn't happen...
#8
04/08/2011 (11:52 am)
Okay problem solved - not the fault of this patch. A dev had added staticTSObjectType mask into the loop in the GUIShapeNameHUD:Render function.

tsStatics don't have shapeNames (being mere SceneObjects and not mighty ShapeBases).

Gotta run...
#9
04/08/2011 (11:54 am)
>>A dev had added staticTSObjectType mask

and didn't comment-tag it... grrrr
#10
04/08/2011 (12:23 pm)
Don't you hate when they do that? :) Glad to hear you got it all sorted out.

Dusty
#11
04/08/2011 (1:04 pm)
Glad I saw Ken's posting. Didn't actually have the packet rate problem but the mention of his shapeNameHud problem and addition of the staticTSObjectType mask helped solve an issue I had encountered.
#12
04/24/2011 (4:22 pm)
Confirmed as fixed in 1.1 Final and Preview.