1106 lines
41 KiB
C++
Executable File
1106 lines
41 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#ifndef _NETCONNECTION_H_
|
|
#define _NETCONNECTION_H_
|
|
|
|
#ifndef _MPOINT_H_
|
|
#include "math/mPoint.h"
|
|
#endif
|
|
#ifndef _NETOBJECT_H_
|
|
#include "sim/netObject.h"
|
|
#endif
|
|
#ifndef _NETSTRINGTABLE_H_
|
|
#include "sim/netStringTable.h"
|
|
#endif
|
|
#ifndef _EVENT_H_
|
|
#include "platform/event.h"
|
|
#endif
|
|
#ifndef _DNET_H_
|
|
#include "core/dnet.h"
|
|
#endif
|
|
|
|
#ifndef _H_CONNECTIONSTRINGTABLE
|
|
#include "sim/connectionStringTable.h"
|
|
#endif
|
|
|
|
class NetConnection;
|
|
class NetObject;
|
|
class BitStream;
|
|
class ResizeBitStream;
|
|
class Stream;
|
|
class Point3F;
|
|
|
|
struct GhostInfo;
|
|
struct SubPacketRef; // defined in NetConnection subclass
|
|
|
|
//#define TORQUE_DEBUG_NET
|
|
|
|
#ifdef TORQUE_DEBUG_NET
|
|
#define DEBUG_LOG(x) if(mLogging){Con::printf x;}
|
|
#else
|
|
#define DEBUG_LOG(x)
|
|
#endif
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
class NetEvent;
|
|
|
|
struct NetEventNote
|
|
{
|
|
NetEvent *mEvent;
|
|
S32 mSeqCount;
|
|
NetEventNote *mNextEvent;
|
|
};
|
|
|
|
/// An event to be sent over the network.
|
|
///
|
|
/// @note Torque implements two methods of network data passing; this is one of them.
|
|
/// See NetConnection for details of the other, which is referred to as ghosting.
|
|
///
|
|
/// Torque's network layer lets you pass events to/from the server. There are three
|
|
/// types of events:
|
|
/// - <b>Unguaranteed events</b> are events which are sent once. If they don't
|
|
/// make it through the link, they are not resent. This is good for quick,
|
|
/// frequent status updates which are of transient interest, like position
|
|
/// updates or voice communication.
|
|
/// - <b>Guaranteed events</b> are events which are guaranteed to be
|
|
/// delivered. If they don't make it through the link, they are sent as
|
|
/// needed. This is good for important, one-time information,
|
|
/// like which team a user wants to play on, or the current weather.
|
|
/// - <b>GuaranteedOrdered events</b> are events which are guaranteed not
|
|
/// only to be delivered, but to be delivered in order. This is good for
|
|
/// information which is not only important, but also order-critical, like
|
|
/// chat messages.
|
|
///
|
|
/// There are 6 methods that you need to implement if you want to make a
|
|
/// basic NetEvent subclass, and 2 macros you need to call.
|
|
///
|
|
/// @code
|
|
/// // A simple NetEvent to transmit a string over the network.
|
|
/// // This is based on the code in netTest.cc
|
|
/// class SimpleMessageEvent : public NetEvent
|
|
/// {
|
|
/// typedef NetEvent Parent;
|
|
/// char *msg;
|
|
/// public:
|
|
/// SimpleMessageEvent(const char *message = NULL);
|
|
/// ~SimpleMessageEvent();
|
|
///
|
|
/// virtual void pack (NetConnection *conn, BitStream *bstream);
|
|
/// virtual void write (NetConnection *conn, BitStream *bstream);
|
|
/// virtual void unpack (NetConnection *conn, BitStream *bstream);
|
|
/// virtual void process(NetConnection *conn);
|
|
///
|
|
/// DECLARE_CONOBJECT(SimpleMessageEvent);
|
|
/// };
|
|
///
|
|
/// IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent);
|
|
/// @endcode
|
|
///
|
|
/// Notice the two macros which we call. The first, DECLARE_CONOBJECT() is there
|
|
/// because we're a ConsoleObject. The second, IMPLEMENT_CO_NETEVENT_V1(), is there
|
|
/// to register this event type with Torque's networking layer, so that it can be
|
|
/// properly transmitted over the wire. There are three macros which you might use:
|
|
/// - <b>IMPLEMENT_CO_NETEVENT_V1</b>, which indicates an event which may be sent
|
|
/// in either direction, from the client to the server, or from the server to the
|
|
/// client.
|
|
/// - <b>IMPLEMENT_CO_CLIENTEVENT_V1</b>, which indicates an event which may only
|
|
/// be sent to the client.
|
|
/// - <b>IMPLEMENT_CO_SERVEREVENT_V1</b>, which indicates an event which may only
|
|
/// be sent to the server.
|
|
///
|
|
/// Choosing the right macro is a good way to make your game more resistant to hacking; for instance,
|
|
/// PathManager events are marked as CLIENTEVENTs, because they would cause the server to crash if
|
|
/// a client sent them.
|
|
///
|
|
/// @note Torque allows you to call NetConnection::setLastError() on the NetConnection passed to
|
|
/// your NetEvent. You can cause the connection to abort if invalid data is received, specifying
|
|
/// a reason to the user.
|
|
///
|
|
/// Now, the 6 methods which we have above; the constructor and destructor need only do
|
|
/// whatever book-keeping is needed for your specific implementation. In our case, we
|
|
/// just need to allocate/deallocate the space for our string:
|
|
///
|
|
/// @code
|
|
/// SimpleMessageEvent::SimpleMessageEvent(const char *message = NULL)
|
|
/// {
|
|
/// // If we wanted to make this not be a GuaranteedOrdered event, we'd
|
|
/// // put a line like this in the constructor:
|
|
/// // mGuaranteeType = Guaranteed;
|
|
/// // (or whatever type you wanted.)
|
|
/// if(message)
|
|
/// msg = dStrdup(message);
|
|
/// else
|
|
/// msg = NULL;
|
|
/// }
|
|
///
|
|
/// SimpleMessageEvent::~SimpleMessageEvent()
|
|
/// {
|
|
/// dFree(msg);
|
|
/// }
|
|
/// @endcode
|
|
///
|
|
/// Simple as that! Now, onto pack(), write(), unpack(), process().
|
|
///
|
|
/// <b>pack()</b> is responsible for packing the event over the wire:
|
|
///
|
|
/// @code
|
|
/// void SimpleMessageEvent::pack(NetConnection* conn, BitStream *bstream)
|
|
/// {
|
|
/// bstream->writeString(msg);
|
|
/// }
|
|
/// @endcode
|
|
///
|
|
/// <b>unpack()</b> is responsible for unpacking the event on the other end:
|
|
///
|
|
/// @code
|
|
/// // The networking layer takes care of instantiating a new
|
|
/// // SimpleMessageEvent, which saves us a bit of effort.
|
|
/// void SimpleMessageEvent::unpack(NetConnection *conn, BitStream *bstream)
|
|
/// {
|
|
/// char buf[256];
|
|
/// bstream->readString(buf);
|
|
/// msg = dStrdup(buf);
|
|
/// }
|
|
/// @endcode
|
|
///
|
|
/// <b>process()</b> is called when the network layer is finished with things.
|
|
/// A typical case is that a GuaranteedOrdered event is unpacked and stored, but
|
|
/// not processed until the events preceding it in the sequence have also been
|
|
/// dealt with.
|
|
///
|
|
/// @code
|
|
/// // This just prints the event in the console. You might
|
|
/// // want to do something more clever here -- BJG
|
|
/// void SimpleMessageEvent::process(NetConnection *conn)
|
|
/// {
|
|
/// Con::printf("RMSG %d %s", mSourceId, msg);
|
|
/// }
|
|
/// @endcode
|
|
///
|
|
/// <b>write()</b> is called if a demo recording is started, and the event has not yet been
|
|
/// processed, but it has been unpacked. It should be identical in its output to the bitstream
|
|
/// compared to pack(), but since it is called after unpack() some lookups may not need to be
|
|
/// performed. In normal demo recording, whole network packets are recorded, meaning that most
|
|
/// of the time write() will not be called.
|
|
///
|
|
/// In our case, it's entirely identical to pack():
|
|
///
|
|
/// @code
|
|
/// virtual void write(NetConnection*, BitStream *bstream)
|
|
/// {
|
|
/// bstream->writeString(msg);
|
|
/// }
|
|
/// @endcode
|
|
///
|
|
/// The NetEvent is sent over the wire in a straightforward way (assuming you have a
|
|
/// handle to a NetConnection):
|
|
///
|
|
/// @code
|
|
/// NetConnection *conn; // We assume you have filled this in.
|
|
///
|
|
/// con->postNetEvent(new SimpleMessageEvent("This is a test!"));
|
|
/// @endcode
|
|
///
|
|
/// @see GhostAlwaysObjectEvent for an example of dissimilar write()/pack() methods.
|
|
///
|
|
/// Finally, for more advanced applications, notifySent() is called whenever the event is
|
|
/// sent over the wire, in NetConnection::eventWritePacket(). notifyDelivered() is called
|
|
/// when the packet is finally received or (in the case of Unguaranteed packets) dropped.
|
|
///
|
|
/// @note IMPLEMENT_CO_NETEVENT_V1 and co. have sibling macros which allow you to specify a
|
|
/// groupMask; see ConsoleObject for a further discussion of this.
|
|
class NetEvent : public ConsoleObject
|
|
{
|
|
public:
|
|
/// @name Implementation Details
|
|
///
|
|
/// These are internal fields which you won't need to manipulate, except for mGuaranteeType.
|
|
/// @{
|
|
|
|
///
|
|
S32 mRefCount;
|
|
typedef ConsoleObject Parent;
|
|
enum {
|
|
GuaranteedOrdered = 0,
|
|
Guaranteed = 1,
|
|
Unguaranteed = 2
|
|
} mGuaranteeType;
|
|
NetConnectionId mSourceId;
|
|
|
|
void incRef()
|
|
{
|
|
mRefCount++;
|
|
}
|
|
void decRef()
|
|
{
|
|
mRefCount--;
|
|
if(!mRefCount)
|
|
delete this;
|
|
}
|
|
|
|
#ifdef TORQUE_DEBUG_NET
|
|
virtual const char *getDebugName();
|
|
#endif
|
|
/// @}
|
|
|
|
/// @name Things To Subclass
|
|
/// @{
|
|
|
|
///
|
|
NetEvent() { mGuaranteeType = GuaranteedOrdered; mRefCount = 0; }
|
|
virtual ~NetEvent();
|
|
|
|
virtual void write(NetConnection *ps, BitStream *bstream) = 0;
|
|
virtual void pack(NetConnection *ps, BitStream *bstream) = 0;
|
|
virtual void unpack(NetConnection *ps, BitStream *bstream) = 0;
|
|
virtual void process(NetConnection *ps) = 0;
|
|
virtual void notifySent(NetConnection *ps);
|
|
virtual void notifyDelivered(NetConnection *ps, bool madeit);
|
|
/// @}
|
|
};
|
|
|
|
#define IMPLEMENT_CO_NETEVENT_V1(className) \
|
|
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
|
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
|
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
|
ConcreteClassRep<className> className::dynClassRep(#className,NetClassGroupGameMask, NetClassTypeEvent, NetEventDirAny, className::getParentStaticClassRep())
|
|
|
|
#define IMPLEMENT_CO_CLIENTEVENT_V1(className) \
|
|
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
|
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
|
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
|
ConcreteClassRep<className> className::dynClassRep(#className,NetClassGroupGameMask, NetClassTypeEvent, NetEventDirServerToClient, className::getParentStaticClassRep())
|
|
|
|
#define IMPLEMENT_CO_SERVEREVENT_V1(className) \
|
|
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
|
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
|
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
|
ConcreteClassRep<className> className::dynClassRep(#className,NetClassGroupGameMask, NetClassTypeEvent, NetEventDirClientToServer, className::getParentStaticClassRep())
|
|
|
|
#define IMPLEMENT_CO_NETEVENT(className,groupMask) \
|
|
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
|
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
|
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
|
ConcreteClassRep<className> className::dynClassRep(#className,groupMask, NetClassTypeEvent, NetEventDirAny, className::getParentStaticClassRep())
|
|
|
|
#define IMPLEMENT_CO_CLIENTEVENT(className,groupMask) \
|
|
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
|
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
|
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
|
ConcreteClassRep<className> className::dynClassRep(#className,groupMask, NetClassTypeEvent, NetEventDirServerToClient, className::getParentStaticClassRep())
|
|
|
|
#define IMPLEMENT_CO_SERVEREVENT(className,groupMask) \
|
|
AbstractClassRep* className::getClassRep() const { return &className::dynClassRep; } \
|
|
AbstractClassRep* className::getStaticClassRep() { return &dynClassRep; } \
|
|
AbstractClassRep* className::getParentStaticClassRep() { return Parent::getStaticClassRep(); } \
|
|
ConcreteClassRep<className> className::dynClassRep(#className,groupMask, NetClassTypeEvent, NetEventDirClientToServer, className::getParentStaticClassRep())
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
/// Torque network connection.
|
|
///
|
|
/// @section NetConnection_intro Introduction
|
|
///
|
|
/// NetConnection is the glue that binds a networked Torque game together. It combines
|
|
/// the low-level notify protocol implemented in ConnectionProtocol with a SimGroup to
|
|
/// provide a powerful basis for implementing a multiplayer game protocol.
|
|
///
|
|
/// On top of this basis it implements several distinct subsystems:
|
|
/// - <b>Event manager</b>, which 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.
|
|
/// - <b>Move manager</b>, which 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).
|
|
/// - <b>Ghost manager</b>, which is responsible for doing scoping calculations (on the server
|
|
/// side) and transmitting most-recent ghost information to the client.
|
|
/// - <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.
|
|
/// - <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.
|
|
/// - <b>Demo Recording</b> is also implemented in NetConnection. 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.
|
|
/// - The <b>Connection Database</b> 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.
|
|
///
|
|
/// @section NetConnection_events On Events
|
|
///
|
|
/// The Event Manager is exposed to the outside world via postNetEvent(), which accepts NetEvents.
|
|
///
|
|
/// @see NetEvent for a more thorough explanation of how to use events.
|
|
///
|
|
/// @section NetConnection_ghosting On Ghosting and Scoping
|
|
///
|
|
/// Ghosting is the most complex, and most powerful, part of Torque's networking capabilities. It
|
|
/// allows the information sent to clients to be very precisely matched to what they need, so that
|
|
/// no excess bandwidth is wasted. The control object's onCameraScopeQuery() is called, to determine
|
|
/// scoping information for the client; then objects which are in scope are then transmitted to the
|
|
/// client, prioritized by the results of their getPriority() method.
|
|
///
|
|
/// There is a cap on the maximum number of ghosts; ghost IDs are currently sent via a 10-bit field,
|
|
/// ergo, there is a cap of 1024 objects ghosted per client. This can be easily raised; see the
|
|
/// GhostConstants enum.
|
|
///
|
|
/// Each object ghosted is assigned a ghost ID; the client is _only_ aware of the ghost ID. This acts
|
|
/// to enhance game security, as it becomes difficult to map objects from one connection to another, or
|
|
/// to reliably identify objects from ID alone. IDs are also reassigned based on need, making it hard
|
|
/// to track objects that have fallen out of scope (as any object which the player shouldn't see would).
|
|
///
|
|
/// resolveGhost() is used on the client side, and resolveObjectFromGhostIndex() on the server side, to
|
|
/// turn ghost IDs into object references.
|
|
///
|
|
/// 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.
|
|
///
|
|
/// @see NetObject, which is the superclass for ghostable objects, and ShapeBase, which is the base
|
|
/// for player and vehicle classes.
|
|
///
|
|
/// @nosubgrouping
|
|
class NetConnection : public ConnectionProtocol, public SimGroup
|
|
{
|
|
friend class NetInterface;
|
|
|
|
typedef SimGroup Parent;
|
|
|
|
public:
|
|
/// Structure to track ghost references in packets.
|
|
///
|
|
/// Every packet we send out with an update from a ghost causes one of these to be
|
|
/// allocated. mask is used to track what states were sent; that way if a packet is
|
|
/// dropped, we can easily manipulate the stored states and figure out what if any data
|
|
/// we need to resend.
|
|
///
|
|
struct GhostRef
|
|
{
|
|
U32 mask; ///< States we transmitted.
|
|
U32 ghostInfoFlags; ///< Flags from GhostInfo::Flags
|
|
GhostInfo *ghost; ///< Reference to the GhostInfo we're from.
|
|
GhostRef *nextRef; ///< Next GhostRef in this packet.
|
|
GhostRef *nextUpdateChain; ///< Next update we sent for this ghost.
|
|
};
|
|
|
|
enum Constants
|
|
{
|
|
HashTableSize = 127,
|
|
};
|
|
|
|
void sendDisconnectPacket(const char *reason);
|
|
|
|
virtual bool canRemoteCreate();
|
|
|
|
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);
|
|
|
|
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);
|
|
|
|
void connect(const NetAddress *address);
|
|
|
|
//----------------------------------------------------------------
|
|
/// @name Global Connection List
|
|
/// @{
|
|
|
|
private:
|
|
///
|
|
NetConnection *mNextConnection; ///< Next item in list.
|
|
NetConnection *mPrevConnection; ///< Previous item in list.
|
|
static NetConnection *mConnectionList; ///< Head of list.
|
|
public:
|
|
static NetConnection *getConnectionList() { return mConnectionList; }
|
|
NetConnection *getNext() { return mNextConnection; }
|
|
/// @}
|
|
//----------------------------------------------------------------
|
|
|
|
enum NetConnectionFlags
|
|
{
|
|
ConnectionToServer = BIT(0),
|
|
ConnectionToClient = BIT(1),
|
|
LocalClientConnection = BIT(2),
|
|
NetworkConnection = BIT(3),
|
|
};
|
|
|
|
private:
|
|
BitSet32 mTypeFlags;
|
|
|
|
U32 mNetClassGroup; ///< The NetClassGroup of this connection.
|
|
|
|
/// @name Statistics
|
|
/// @{
|
|
|
|
U32 mLastUpdateTime;
|
|
F32 mRoundTripTime;
|
|
F32 mPacketLoss;
|
|
U32 mSimulatedPing;
|
|
F32 mSimulatedPacketLoss;
|
|
|
|
/// @}
|
|
|
|
/// @name State
|
|
/// @{
|
|
|
|
U32 mProtocolVersion;
|
|
U32 mSendDelayCredit;
|
|
U32 mConnectSequence;
|
|
U32 mAddressDigest[4];
|
|
|
|
bool mEstablished;
|
|
bool mMissionPathsSent;
|
|
|
|
struct NetRate
|
|
{
|
|
U32 updateDelay;
|
|
S32 packetSize;
|
|
bool changed;
|
|
};
|
|
|
|
NetRate mCurRate;
|
|
NetRate mMaxRate;
|
|
|
|
/// If we're doing a "short circuited" connection, this stores
|
|
/// a pointer to the other side.
|
|
SimObjectPtr<NetConnection> mRemoteConnection;
|
|
|
|
NetAddress mNetAddress;
|
|
|
|
/// @}
|
|
|
|
|
|
/// @name Timeout Management
|
|
/// @{
|
|
|
|
U32 mPingSendCount;
|
|
U32 mPingRetryCount;
|
|
U32 mLastPingSendTime;
|
|
/// @}
|
|
|
|
/// @name Connection Table
|
|
///
|
|
/// We store our connections on a hash table so we can
|
|
/// quickly find them.
|
|
/// @{
|
|
|
|
NetConnection *mNextTableHash;
|
|
static NetConnection *mHashTable[HashTableSize];
|
|
|
|
/// @}
|
|
|
|
protected:
|
|
static SimObjectPtr<NetConnection> mServerConnection;
|
|
static SimObjectPtr<NetConnection> mLocalClientConnection;
|
|
|
|
static bool mFilesWereDownloaded;
|
|
|
|
U32 mConnectSendCount;
|
|
U32 mConnectLastSendTime;
|
|
|
|
public:
|
|
static NetConnection *getConnectionToServer() { return mServerConnection; }
|
|
|
|
static NetConnection *getLocalClientConnection() { return mLocalClientConnection; }
|
|
static void setLocalClientConnection(NetConnection *conn) { mLocalClientConnection = conn; }
|
|
|
|
U32 getNetClassGroup() { return mNetClassGroup; }
|
|
static bool filesWereDownloaded() { return mFilesWereDownloaded; }
|
|
static char *getErrorBuffer() { return mErrorBuffer; }
|
|
|
|
#ifdef TORQUE_DEBUG_NET
|
|
bool mLogging;
|
|
void setLogging(bool logging) { mLogging = logging; }
|
|
#endif
|
|
|
|
void setSimulatedNetParams(F32 packetLoss, U32 ping)
|
|
{ mSimulatedPacketLoss = packetLoss; mSimulatedPing = ping; }
|
|
|
|
bool isConnectionToServer() { return mTypeFlags.test(ConnectionToServer); }
|
|
bool isLocalConnection() { return !mRemoteConnection.isNull() ; }
|
|
bool isNetworkConnection() { return mTypeFlags.test(NetworkConnection); }
|
|
|
|
void setIsConnectionToServer() { mTypeFlags.set(ConnectionToServer); }
|
|
void setIsLocalClientConnection() { mTypeFlags.set(LocalClientConnection); }
|
|
void setNetworkConnection(bool net) { mTypeFlags.set(BitSet32(NetworkConnection), net); }
|
|
|
|
virtual void setEstablished();
|
|
|
|
/// Call this if the "connection" is local to this app. This short-circuits the protocol layer.
|
|
void setRemoteConnectionObject(NetConnection *connection) { mRemoteConnection = connection; };
|
|
|
|
void setSequence(U32 connectSequence);
|
|
|
|
void setAddressDigest(U32 digest[4]);
|
|
void getAddressDigest(U32 digest[4]);
|
|
|
|
U32 getSequence();
|
|
|
|
void setProtocolVersion(U32 protocolVersion) { mProtocolVersion = protocolVersion; }
|
|
U32 getProtocolVersion() { return mProtocolVersion; }
|
|
F32 getRoundTripTime() { return mRoundTripTime; }
|
|
F32 getPacketLoss() { return( mPacketLoss ); }
|
|
|
|
static char mErrorBuffer[256];
|
|
static void setLastError(const char *fmt,...);
|
|
|
|
void checkMaxRate();
|
|
void handlePacket(BitStream *stream);
|
|
void processRawPacket(BitStream *stream);
|
|
void handleNotify(bool recvd);
|
|
void handleConnectionEstablished();
|
|
void keepAlive();
|
|
|
|
const NetAddress *getNetAddress();
|
|
void setNetAddress(const NetAddress *address);
|
|
Net::Error sendPacket(BitStream *stream);
|
|
|
|
private:
|
|
void netAddressTableInsert();
|
|
void netAddressTableRemove();
|
|
|
|
public:
|
|
/// Find a NetConnection, if any, with the specified address.
|
|
static NetConnection *lookup(const NetAddress *remoteAddress);
|
|
|
|
bool checkTimeout(U32 time); ///< returns true if the connection timed out
|
|
|
|
void checkPacketSend(bool force);
|
|
|
|
bool missionPathsSent() const { return mMissionPathsSent; }
|
|
void setMissionPathsSent(const bool s) { mMissionPathsSent = s; }
|
|
|
|
static void consoleInit();
|
|
|
|
void onRemove();
|
|
|
|
NetConnection();
|
|
~NetConnection();
|
|
|
|
public:
|
|
enum NetConnectionState
|
|
{
|
|
NotConnected,
|
|
AwaitingChallengeResponse, ///< We've sent a challenge request, awaiting the response.
|
|
AwaitingConnectRequest, ///< We've received a challenge request and sent a challenge response.
|
|
AwaitingConnectResponse, ///< We've received a challenge response and sent a connect request.
|
|
Connected, ///< We've accepted a connect request, or we've received a connect response accept.
|
|
};
|
|
|
|
U32 mConnectionSendCount; ///< number of connection messages we've sent.
|
|
U32 mConnectionState; ///< State of the connection, from NetConnectionState.
|
|
|
|
void setConnectionState(U32 state) { mConnectionState = state; }
|
|
U32 getConnectionState() { return mConnectionState; }
|
|
|
|
|
|
void setGhostFrom(bool ghostFrom); ///< Sets whether ghosts transmit from this side of the connection.
|
|
void setGhostTo(bool ghostTo); ///< Sets whether ghosts are allowed from the other side of the connection.
|
|
void setSendingEvents(bool sending); ///< Sets whether this side actually sends the events that are posted to it.
|
|
void setTranslatesStrings(bool xl); ///< Sets whether this connection is capable of translating strings.
|
|
void setNetClassGroup(U32 group); ///< Sets the group of NetClasses this connection traffics in.
|
|
bool isEstablished() { return mEstablished; } ///< Is the connection established?
|
|
|
|
DECLARE_CONOBJECT(NetConnection);
|
|
|
|
/// Structure to track packets and what we sent over them.
|
|
///
|
|
/// We need to know what is sent in each packet, so that if a packet is
|
|
/// dropped, we know what to resend. This is the structure we use to track
|
|
/// this data.
|
|
struct PacketNotify
|
|
{
|
|
bool rateChanged; ///< Did the rate change on this packet?
|
|
bool maxRateChanged; ///< Did the max rate change on this packet?
|
|
U32 sendTime; ///< Timestampe, when we sent this packet.
|
|
|
|
NetEventNote *eventList; ///< Linked list of events sent over this packet.
|
|
GhostRef *ghostList; ///< Linked list of ghost updates we sent in this packet.
|
|
SubPacketRef *subList; ///< Defined by subclass - used as desired.
|
|
|
|
PacketNotify *nextPacket; ///< Next packet sent.
|
|
PacketNotify();
|
|
};
|
|
virtual PacketNotify *allocNotify();
|
|
PacketNotify *mNotifyQueueHead; ///< Head of packet notify list.
|
|
PacketNotify *mNotifyQueueTail; ///< Tail of packet notify list.
|
|
|
|
protected:
|
|
virtual void readPacket(BitStream *bstream);
|
|
virtual void writePacket(BitStream *bstream, PacketNotify *note);
|
|
virtual void packetReceived(PacketNotify *note);
|
|
virtual void packetDropped(PacketNotify *note);
|
|
virtual void connectionError(const char *errorString);
|
|
|
|
//----------------------------------------------------------------
|
|
/// @name Event Manager
|
|
/// @{
|
|
|
|
private:
|
|
NetEventNote *mSendEventQueueHead;
|
|
NetEventNote *mSendEventQueueTail;
|
|
NetEventNote *mUnorderedSendEventQueueHead;
|
|
NetEventNote *mUnorderedSendEventQueueTail;
|
|
NetEventNote *mWaitSeqEvents;
|
|
NetEventNote *mNotifyEventList;
|
|
|
|
static FreeListChunker<NetEventNote> mEventNoteChunker;
|
|
|
|
bool mSendingEvents;
|
|
|
|
S32 mNextSendEventSeq;
|
|
S32 mNextRecvEventSeq;
|
|
S32 mLastAckedEventSeq;
|
|
|
|
enum NetEventConstants {
|
|
InvalidSendEventSeq = -1,
|
|
FirstValidSendEventSeq = 0
|
|
};
|
|
|
|
void eventOnRemove();
|
|
|
|
void eventPacketDropped(PacketNotify *notify);
|
|
void eventPacketReceived(PacketNotify *notify);
|
|
|
|
void eventWritePacket(BitStream *bstream, PacketNotify *notify);
|
|
void eventReadPacket(BitStream *bstream);
|
|
|
|
void eventWriteStartBlock(ResizeBitStream *stream);
|
|
void eventReadStartBlock(BitStream *stream);
|
|
public:
|
|
/// Post an event to this connection.
|
|
bool postNetEvent(NetEvent *event);
|
|
|
|
/// @}
|
|
|
|
//----------------------------------------------------------------
|
|
/// @name Networked string table
|
|
/// @{
|
|
|
|
private:
|
|
bool mTranslateStrings;
|
|
ConnectionStringTable *mStringTable;
|
|
public:
|
|
void mapString(U32 netId, StringHandle &string)
|
|
{ mStringTable->mapString(netId, string); }
|
|
U32 checkString(StringHandle &string, bool *isOnOtherSide = NULL)
|
|
{ if(mStringTable) return mStringTable->checkString(string, isOnOtherSide); else return 0; }
|
|
U32 getNetSendId(StringHandle &string)
|
|
{ if(mStringTable) return mStringTable->getNetSendId(string); else return 0;}
|
|
void confirmStringReceived(StringHandle &string, U32 index)
|
|
{ if(!isRemoved()) mStringTable->confirmStringReceived(string, index); }
|
|
|
|
StringHandle translateRemoteStringId(U32 id) { return mStringTable->lookupString(id); }
|
|
void validateSendString(const char *str);
|
|
|
|
void packString(BitStream *stream, const char *str);
|
|
void unpackString(BitStream *stream, char readBuffer[1024]);
|
|
|
|
void packStringHandleU(BitStream *stream, StringHandle &h);
|
|
StringHandle unpackStringHandleU(BitStream *stream);
|
|
/// @}
|
|
|
|
//----------------------------------------------------------------
|
|
/// @name Ghost manager
|
|
/// @{
|
|
|
|
protected:
|
|
enum GhostStates
|
|
{
|
|
GhostAlwaysDone,
|
|
ReadyForNormalGhosts,
|
|
EndGhosting,
|
|
GhostAlwaysStarting,
|
|
SendNextDownloadRequest,
|
|
FileDownloadSizeMessage,
|
|
NumConnectionMessages,
|
|
};
|
|
GhostInfo **mGhostArray; ///< Linked list of ghostInfos ghosted by this side of the connection
|
|
|
|
U32 mGhostZeroUpdateIndex; ///< Index in mGhostArray of first ghost with 0 update mask.
|
|
U32 mGhostFreeIndex; ///< Index in mGhostArray of first free ghost.
|
|
|
|
U32 mGhostsActive; ///- Track actve ghosts on client side
|
|
|
|
bool mGhosting; ///< Am I currently ghosting objects?
|
|
bool mScoping; ///< am I currently scoping objects?
|
|
U32 mGhostingSequence; ///< Sequence number describing this ghosting session.
|
|
|
|
NetObject **mLocalGhosts; ///< Local ghost for remote object.
|
|
///
|
|
/// mLocalGhosts pointer is NULL if mGhostTo is false
|
|
|
|
GhostInfo *mGhostRefs; ///< Allocated array of ghostInfos. Null if ghostFrom is false.
|
|
GhostInfo **mGhostLookupTable; ///< Table indexed by object id to GhostInfo. Null if ghostFrom is false.
|
|
|
|
/// The object around which we are scoping this connection.
|
|
///
|
|
/// This is usually the player object, or a related object, like a vehicle
|
|
/// that the player is driving.
|
|
SimObjectPtr<NetObject> mScopeObject;
|
|
|
|
void clearGhostInfo();
|
|
bool validateGhostArray();
|
|
|
|
void ghostPacketDropped(PacketNotify *notify);
|
|
void ghostPacketReceived(PacketNotify *notify);
|
|
|
|
void ghostWritePacket(BitStream *bstream, PacketNotify *notify);
|
|
void ghostReadPacket(BitStream *bstream);
|
|
void freeGhostInfo(GhostInfo *);
|
|
|
|
void ghostWriteStartBlock(ResizeBitStream *stream);
|
|
void ghostReadStartBlock(BitStream *stream);
|
|
|
|
public:
|
|
/// Some configuration values.
|
|
enum GhostConstants
|
|
{
|
|
GhostIdBitSize = 12,
|
|
MaxGhostCount = 1 << GhostIdBitSize, //4096,
|
|
GhostLookupTableSize = 1 << GhostIdBitSize, //4096
|
|
GhostIndexBitSize = 4 // number of bits GhostIdBitSize-3 fits into
|
|
};
|
|
|
|
U32 getGhostsActive() { return mGhostsActive;};
|
|
|
|
/// Are we ghosting to someone?
|
|
bool isGhostingTo() { return mLocalGhosts != NULL; };
|
|
|
|
/// Are we ghosting from someone?
|
|
bool isGhostingFrom() { return mGhostArray != NULL; };
|
|
|
|
/// Called by onRemove, to shut down the ghost subsystem.
|
|
void ghostOnRemove();
|
|
|
|
/// Called when we're done with normal scoping.
|
|
///
|
|
/// This gives subclasses a chance to shove things into scope, such as
|
|
/// the results of a sensor network calculation, that would otherwise
|
|
/// be awkward to add.
|
|
virtual void doneScopingScene() { /* null */ }
|
|
|
|
/// Set the object around which we are currently scoping network traffic.
|
|
void setScopeObject(NetObject *object);
|
|
|
|
/// Get the object aorund which we are currently scoping network traffic.
|
|
NetObject *getScopeObject();
|
|
|
|
/// Add an object to scope.
|
|
void objectInScope(NetObject *object);
|
|
|
|
/// Add an object to scope, marking that it should always be scoped to this connection.
|
|
void objectLocalScopeAlways(NetObject *object);
|
|
|
|
/// Mark an object that is being ghosted as not always needing to be scoped.
|
|
///
|
|
/// This undoes objectLocalScopeAlways(), but doesn't immediately flush it from scope.
|
|
///
|
|
/// Instead, the standard scoping mechanisms will clear it from scope when it is appropos
|
|
/// to do so.
|
|
void objectLocalClearAlways(NetObject *object);
|
|
|
|
/// Get a NetObject* from a ghost ID (on client side).
|
|
NetObject *resolveGhost(S32 id);
|
|
|
|
/// Get a NetObject* from a ghost index (on the server side).
|
|
NetObject *resolveObjectFromGhostIndex(S32 id);
|
|
|
|
/// Get the ghost index corresponding to a given NetObject. This is only
|
|
/// meaningful on the server side.
|
|
S32 getGhostIndex(NetObject *object);
|
|
|
|
/// Move a GhostInfo into the nonzero portion of the list (so that we know to update it).
|
|
void ghostPushNonZero(GhostInfo *gi);
|
|
|
|
/// Move a GhostInfo into the zero portion of the list (so that we know not to update it).
|
|
void ghostPushToZero(GhostInfo *gi);
|
|
|
|
/// Move a GhostInfo from the zero portion of the list to the free portion.
|
|
void ghostPushZeroToFree(GhostInfo *gi);
|
|
|
|
/// Move a GhostInfo from the free portion of the list to the zero portion.
|
|
inline void ghostPushFreeToZero(GhostInfo *info);
|
|
|
|
/// Stop all ghosting activity and inform the other side about this.
|
|
///
|
|
/// Turns off ghosting.
|
|
void resetGhosting();
|
|
|
|
/// Activate ghosting, once it's enabled.
|
|
void activateGhosting();
|
|
|
|
/// Are we ghosting?
|
|
bool isGhosting() { return mGhosting; }
|
|
|
|
/// Begin to stop ghosting an object.
|
|
void detachObject(GhostInfo *info);
|
|
|
|
/// Mark an object to be always ghosted. Index is the ghost index of the object.
|
|
void setGhostAlwaysObject(NetObject *object, U32 index);
|
|
|
|
|
|
/// Send ghost connection handshake message.
|
|
///
|
|
/// As part of the ghoost connection process, extensive hand-shaking must be performed.
|
|
///
|
|
/// This is done by passing ConnectionMessageEvents; this is a helper function
|
|
/// to more effectively perform this task. Messages are dealt with by
|
|
/// handleConnectionMessage().
|
|
///
|
|
/// @param message One of GhostStates
|
|
/// @param sequence A sequence number, if any.
|
|
/// @param ghostCount A count of ghosts relating to this message.
|
|
void sendConnectionMessage(U32 message, U32 sequence = 0, U32 ghostCount = 0);
|
|
|
|
/// Handle message from sendConnectionMessage().
|
|
///
|
|
/// This is called to handle messages sent via sendConnectionMessage.
|
|
///
|
|
/// @param message One of GhostStates
|
|
/// @param sequence A sequence number, if any.
|
|
/// @param ghostCount A count of ghosts relating to this message.
|
|
virtual void handleConnectionMessage(U32 message, U32 sequence, U32 ghostCount);
|
|
|
|
/// @}
|
|
public:
|
|
//----------------------------------------------------------------
|
|
/// @name File transfer
|
|
/// @{
|
|
|
|
protected:
|
|
/// List of files missing for this connection.
|
|
///
|
|
/// The currently downloading file is always first in the list (ie, [0]).
|
|
Vector<char *> mMissingFileList;
|
|
|
|
/// Stream for currently uploading file (if any).
|
|
Stream *mCurrentDownloadingFile;
|
|
|
|
/// Storage for currently downloading file.
|
|
void *mCurrentFileBuffer;
|
|
|
|
/// Size of currently downloading file in bytes.
|
|
U32 mCurrentFileBufferSize;
|
|
|
|
/// Our position in the currently downloading file in bytes.
|
|
U32 mCurrentFileBufferOffset;
|
|
|
|
/// Number of files we have downloaded.
|
|
U32 mNumDownloadedFiles;
|
|
|
|
/// Error storage for file transfers.
|
|
char mLastFileErrorBuffer[256];
|
|
|
|
/// Structure to track ghost-always objects and their ghost indices.
|
|
struct GhostSave {
|
|
NetObject *ghost;
|
|
U32 index;
|
|
};
|
|
|
|
/// List of objects to ghost-always.
|
|
Vector<GhostSave> mGhostAlwaysSaveList;
|
|
|
|
public:
|
|
/// Start sending the specified file over the link.
|
|
bool startSendingFile(const char *fileName);
|
|
|
|
/// Called when we receive a FileChunkEvent.
|
|
void chunkReceived(U8 *chunkData, U32 chunkLen);
|
|
|
|
/// Get the next file...
|
|
void sendNextFileDownloadRequest();
|
|
|
|
/// Post the next FileChunkEvent.
|
|
void sendFileChunk();
|
|
|
|
/// Called when we finish downloading file data.
|
|
virtual void fileDownloadSegmentComplete();
|
|
|
|
/// This is part of the file transfer logic; basically, we call this
|
|
/// every time we finish downloading new files. It attempts to load
|
|
/// the GhostAlways objects; if they fail, it marks an error and we
|
|
/// have chance to retry.
|
|
void loadNextGhostAlwaysObject(bool hadNewFiles);
|
|
/// @}
|
|
|
|
//----------------------------------------------------------------
|
|
/// @name Demo Recording
|
|
/// @{
|
|
|
|
private:
|
|
Stream *mDemoWriteStream;
|
|
Stream *mDemoReadStream;
|
|
U32 mDemoNextBlockType;
|
|
U32 mDemoNextBlockSize;
|
|
|
|
U32 mDemoWriteStartTime;
|
|
U32 mDemoReadStartTime;
|
|
U32 mDemoLastWriteTime;
|
|
|
|
U32 mDemoRealStartTime;
|
|
|
|
public:
|
|
enum DemoBlockTypes {
|
|
BlockTypePacket,
|
|
BlockTypeSendPacket,
|
|
NetConnectionBlockTypeCount
|
|
};
|
|
|
|
enum DemoConstants {
|
|
MaxNumBlockTypes = 16,
|
|
MaxBlockSize = 0x1000,
|
|
};
|
|
|
|
bool isRecording()
|
|
{ return mDemoWriteStream != NULL; }
|
|
bool isPlayingBack()
|
|
{ return mDemoReadStream != NULL; }
|
|
|
|
U32 getNextBlockType() { return mDemoNextBlockType; }
|
|
void recordBlock(U32 type, U32 size, void *data);
|
|
virtual void handleRecordedBlock(U32 type, U32 size, void *data);
|
|
bool processNextBlock();
|
|
|
|
bool startDemoRecord(const char *fileName);
|
|
bool replayDemoRecord(const char *fileName);
|
|
void startDemoRead();
|
|
void stopRecording();
|
|
void stopDemoPlayback();
|
|
|
|
virtual void writeDemoStartBlock(ResizeBitStream *stream);
|
|
virtual bool readDemoStartBlock(BitStream *stream);
|
|
virtual void demoPlaybackComplete();
|
|
/// @}
|
|
};
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
/// Information about a ghosted object.
|
|
///
|
|
/// @note If the size of this structure changes, the
|
|
/// NetConnection::getGhostIndex function MUST be changed
|
|
/// to reflect the new size.
|
|
struct GhostInfo
|
|
{
|
|
public: // required for MSVC
|
|
NetObject *obj; ///< The object being ghosted.
|
|
U32 updateMask; ///< Flags indicating what state data needs to be transferred.
|
|
|
|
U32 updateSkipCount; ///< How many updates have we skipped this guy?
|
|
U32 flags; ///< Flags from GhostInfo::Flags
|
|
F32 priority; ///< A float value indicating the priority of this object for
|
|
/// updates.
|
|
|
|
/// @name References
|
|
///
|
|
/// The GhostInfo structure is used in several linked lists; these members are
|
|
/// the implementation for this.
|
|
/// @{
|
|
|
|
NetConnection::GhostRef *updateChain; ///< List of references in NetConnections to us.
|
|
|
|
GhostInfo *nextObjectRef; ///< Next ghosted object.
|
|
GhostInfo *prevObjectRef; ///< Previous ghosted object.
|
|
NetConnection *connection; ///< Connection that we're ghosting over.
|
|
GhostInfo *nextLookupInfo; ///< GhostInfo references are stored in a hash; this is the bucket
|
|
/// implementation.
|
|
|
|
/// @}
|
|
|
|
U32 index;
|
|
U32 arrayIndex;
|
|
|
|
/// Flags relating to the state of the object.
|
|
enum Flags
|
|
{
|
|
Valid = BIT(0),
|
|
InScope = BIT(1),
|
|
ScopeAlways = BIT(2),
|
|
NotYetGhosted = BIT(3),
|
|
Ghosting = BIT(4),
|
|
KillGhost = BIT(5),
|
|
KillingGhost = BIT(6),
|
|
ScopedEvent = BIT(7),
|
|
ScopeLocalAlways = BIT(8),
|
|
};
|
|
};
|
|
|
|
inline void NetConnection::ghostPushNonZero(GhostInfo *info)
|
|
{
|
|
AssertFatal(info->arrayIndex >= mGhostZeroUpdateIndex && info->arrayIndex < mGhostFreeIndex, "Out of range arrayIndex.");
|
|
AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object.");
|
|
if(info->arrayIndex != mGhostZeroUpdateIndex)
|
|
{
|
|
mGhostArray[mGhostZeroUpdateIndex]->arrayIndex = info->arrayIndex;
|
|
mGhostArray[info->arrayIndex] = mGhostArray[mGhostZeroUpdateIndex];
|
|
mGhostArray[mGhostZeroUpdateIndex] = info;
|
|
info->arrayIndex = mGhostZeroUpdateIndex;
|
|
}
|
|
mGhostZeroUpdateIndex++;
|
|
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
|
|
}
|
|
|
|
inline void NetConnection::ghostPushToZero(GhostInfo *info)
|
|
{
|
|
AssertFatal(info->arrayIndex < mGhostZeroUpdateIndex, "Out of range arrayIndex.");
|
|
AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object.");
|
|
mGhostZeroUpdateIndex--;
|
|
if(info->arrayIndex != mGhostZeroUpdateIndex)
|
|
{
|
|
mGhostArray[mGhostZeroUpdateIndex]->arrayIndex = info->arrayIndex;
|
|
mGhostArray[info->arrayIndex] = mGhostArray[mGhostZeroUpdateIndex];
|
|
mGhostArray[mGhostZeroUpdateIndex] = info;
|
|
info->arrayIndex = mGhostZeroUpdateIndex;
|
|
}
|
|
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
|
|
}
|
|
|
|
inline void NetConnection::ghostPushZeroToFree(GhostInfo *info)
|
|
{
|
|
AssertFatal(info->arrayIndex >= mGhostZeroUpdateIndex && info->arrayIndex < mGhostFreeIndex, "Out of range arrayIndex.");
|
|
AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object.");
|
|
mGhostFreeIndex--;
|
|
if(info->arrayIndex != mGhostFreeIndex)
|
|
{
|
|
mGhostArray[mGhostFreeIndex]->arrayIndex = info->arrayIndex;
|
|
mGhostArray[info->arrayIndex] = mGhostArray[mGhostFreeIndex];
|
|
mGhostArray[mGhostFreeIndex] = info;
|
|
info->arrayIndex = mGhostFreeIndex;
|
|
}
|
|
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
|
|
}
|
|
|
|
inline void NetConnection::ghostPushFreeToZero(GhostInfo *info)
|
|
{
|
|
AssertFatal(info->arrayIndex >= mGhostFreeIndex, "Out of range arrayIndex.");
|
|
AssertFatal(mGhostArray[info->arrayIndex] == info, "Invalid array object.");
|
|
if(info->arrayIndex != mGhostFreeIndex)
|
|
{
|
|
mGhostArray[mGhostFreeIndex]->arrayIndex = info->arrayIndex;
|
|
mGhostArray[info->arrayIndex] = mGhostArray[mGhostFreeIndex];
|
|
mGhostArray[mGhostFreeIndex] = info;
|
|
info->arrayIndex = mGhostFreeIndex;
|
|
}
|
|
mGhostFreeIndex++;
|
|
//AssertFatal(validateGhostArray(), "Invalid ghost array!");
|
|
}
|
|
|
|
#endif
|
|
|
|
|