mirror of
https://github.com/TorqueGameEngines/Torque3D.git
synced 2026-07-11 14:44:36 +00:00
Engine directory for ticket #1
This commit is contained in:
parent
352279af7a
commit
7dbfe6994d
3795 changed files with 1363358 additions and 0 deletions
2293
Engine/source/sim/actionMap.cpp
Normal file
2293
Engine/source/sim/actionMap.cpp
Normal file
File diff suppressed because it is too large
Load diff
188
Engine/source/sim/actionMap.h
Normal file
188
Engine/source/sim/actionMap.h
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _ACTIONMAP_H_
|
||||
#define _ACTIONMAP_H_
|
||||
|
||||
#ifndef _PLATFORM_H_
|
||||
#include "platform/platform.h"
|
||||
#endif
|
||||
#ifndef _TVECTOR_H_
|
||||
#include "core/util/tVector.h"
|
||||
#endif
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
|
||||
struct InputEventInfo;
|
||||
|
||||
struct EventDescriptor
|
||||
{
|
||||
U8 flags; ///< Combination of any modifier flags.
|
||||
U8 eventType; ///< SI_KEY, etc.
|
||||
U16 eventCode; ///< From event.h
|
||||
};
|
||||
|
||||
/// Map raw inputs to a variety of actions. This is used for all keymapping
|
||||
/// in the engine.
|
||||
/// @see ActionMap::Node
|
||||
class ActionMap : public SimObject
|
||||
{
|
||||
typedef SimObject Parent;
|
||||
|
||||
protected:
|
||||
bool onAdd();
|
||||
|
||||
struct Node {
|
||||
U32 modifiers;
|
||||
U32 action;
|
||||
|
||||
enum Flags {
|
||||
Ranged = BIT(0), ///< Ranged input.
|
||||
HasScale = BIT(1), ///< Scaled input.
|
||||
HasDeadZone = BIT(2), ///< Dead zone is present.
|
||||
Inverted = BIT(3), ///< Input is inverted.
|
||||
NonLinear = BIT(4), ///< Input should be re-fit to a non-linear scale
|
||||
BindCmd = BIT(5) ///< Bind a console command to this.
|
||||
};
|
||||
|
||||
U32 flags; ///< @see Node::Flags
|
||||
F32 deadZoneBegin;
|
||||
F32 deadZoneEnd;
|
||||
F32 scaleFactor;
|
||||
|
||||
SimObject* object; ///< Object to call consoleFunction on.
|
||||
StringTableEntry consoleFunction; ///< Console function to call with new values.
|
||||
|
||||
char *makeConsoleCommand; ///< Console command to execute when we make this command.
|
||||
char *breakConsoleCommand; ///< Console command to execute when we break this command.
|
||||
};
|
||||
|
||||
/// Used to represent a devices.
|
||||
struct DeviceMap
|
||||
{
|
||||
U32 deviceType;
|
||||
U32 deviceInst;
|
||||
|
||||
Vector<Node> nodeMap;
|
||||
DeviceMap() {
|
||||
VECTOR_SET_ASSOCIATION(nodeMap);
|
||||
}
|
||||
~DeviceMap();
|
||||
};
|
||||
struct BreakEntry
|
||||
{
|
||||
U32 deviceType;
|
||||
U32 deviceInst;
|
||||
U32 objInst;
|
||||
SimObject* object;
|
||||
StringTableEntry consoleFunction;
|
||||
char *breakConsoleCommand;
|
||||
|
||||
// It's possible that the node could be deleted (unlikely, but possible,
|
||||
// so we replicate the node flags here...
|
||||
//
|
||||
U32 flags;
|
||||
F32 deadZoneBegin;
|
||||
F32 deadZoneEnd;
|
||||
F32 scaleFactor;
|
||||
};
|
||||
|
||||
|
||||
Vector<DeviceMap*> mDeviceMaps;
|
||||
static Vector<BreakEntry> smBreakTable;
|
||||
|
||||
// Find: return NULL if not found in current map, Get: create if not
|
||||
// found.
|
||||
const Node* findNode(const U32 inDeviceType, const U32 inDeviceInst,
|
||||
const U32 inModifiers, const U32 inAction);
|
||||
bool findBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
|
||||
bool nextBoundNode( const char* function, U32 &devMapIndex, U32 &nodeIndex );
|
||||
Node* getNode(const U32 inDeviceType, const U32 inDeviceInst,
|
||||
const U32 inModifiers, const U32 inAction,
|
||||
SimObject* object = NULL);
|
||||
|
||||
void removeNode(const U32 inDeviceType, const U32 inDeviceInst,
|
||||
const U32 inModifiers, const U32 inAction,
|
||||
SimObject* object = NULL);
|
||||
|
||||
void enterBreakEvent(const InputEventInfo* pEvent, const Node* pNode);
|
||||
|
||||
static const char* getModifierString(const U32 modifiers);
|
||||
|
||||
/// Pass index to a break entry, and this function will fire it off.
|
||||
static void fireBreakEvent(U32 idx, F32 value = 0.f);
|
||||
|
||||
public:
|
||||
ActionMap();
|
||||
~ActionMap();
|
||||
|
||||
void dumpActionMap(const char* fileName, const bool append) const;
|
||||
|
||||
static bool createEventDescriptor(const char* pEventString, EventDescriptor* pDescriptor);
|
||||
|
||||
bool processBind(const U32 argc, const char** argv, SimObject* object = NULL);
|
||||
bool processBindCmd(const char *device, const char *action, const char *makeCmd, const char *breakCmd);
|
||||
bool processUnbind(const char *device, const char *action, SimObject* object = NULL);
|
||||
|
||||
/// @name Console Interface Functions
|
||||
/// @{
|
||||
const char* getBinding ( const char* command ); ///< Find what the given command is bound to.
|
||||
const char* getCommand ( const char* device, const char* action ); ///< Find what command is bound to the given event descriptor .
|
||||
bool isInverted ( const char* device, const char* action );
|
||||
F32 getScale ( const char* device, const char* action );
|
||||
const char* getDeadZone( const char* device, const char* action );
|
||||
/// @}
|
||||
|
||||
|
||||
static bool getKeyString(const U32 action, char* buffer);
|
||||
static bool getDeviceName(const U32 deviceType, const U32 deviceInstance, char* buffer);
|
||||
static const char* buildActionString( const InputEventInfo* event );
|
||||
|
||||
bool processAction(const InputEventInfo*);
|
||||
|
||||
/// Return true if the given event triggers is bound to an action in this map.
|
||||
bool isAction( U32 deviceType, U32 deviceInst, U32 modifiers, U32 action );
|
||||
|
||||
/// Returns the global ActionMap.
|
||||
static ActionMap* getGlobalMap();
|
||||
|
||||
static bool checkBreakTable(const InputEventInfo*);
|
||||
static bool handleEvent(const InputEventInfo*);
|
||||
static bool handleEventGlobal(const InputEventInfo*);
|
||||
|
||||
/// Called when we lose focus, to make sure we have no dangling inputs.
|
||||
///
|
||||
/// This fires a break event for every currently pending item in the break
|
||||
/// table.
|
||||
static void clearAllBreaks();
|
||||
|
||||
/// Returns true if the specified key + modifiers are bound to something
|
||||
/// on the global action map.
|
||||
static bool checkAsciiGlobal(U16 key, U32 modifiers);
|
||||
|
||||
static bool getDeviceTypeAndInstance(const char *device, U32 &deviceType, U32 &deviceInstance);
|
||||
|
||||
DECLARE_CONOBJECT(ActionMap);
|
||||
};
|
||||
|
||||
#endif // _ACTIONMAP_H_
|
||||
198
Engine/source/sim/connectionStringTable.cpp
Normal file
198
Engine/source/sim/connectionStringTable.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include "core/dnet.h"
|
||||
#include "console/simBase.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "console/consoleTypes.h"
|
||||
|
||||
class NetStringEvent : public NetEvent
|
||||
{
|
||||
NetStringHandle mString;
|
||||
U32 mIndex;
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
NetStringEvent(U32 index = 0, NetStringHandle string = NetStringHandle())
|
||||
{
|
||||
mIndex = index;
|
||||
mString = string;
|
||||
}
|
||||
virtual void pack(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize);
|
||||
bstream->writeString(mString.getString());
|
||||
}
|
||||
virtual void write(NetConnection* /*ps*/, BitStream *bstream)
|
||||
{
|
||||
bstream->writeInt(mIndex, ConnectionStringTable::EntryBitSize);
|
||||
bstream->writeString(mString.getString());
|
||||
}
|
||||
virtual void unpack(NetConnection* /*con*/, BitStream *bstream)
|
||||
{
|
||||
char buf[256];
|
||||
mIndex = bstream->readInt(ConnectionStringTable::EntryBitSize);
|
||||
bstream->readString(buf);
|
||||
mString = NetStringHandle(buf);
|
||||
}
|
||||
virtual void notifyDelivered(NetConnection *ps, bool madeit)
|
||||
{
|
||||
if(madeit)
|
||||
ps->confirmStringReceived(mString, mIndex);
|
||||
}
|
||||
virtual void process(NetConnection *connection)
|
||||
{
|
||||
Con::printf("Mapping string: %s to index: %d", mString.getString(), mIndex);
|
||||
connection->mapString(mIndex, mString);
|
||||
}
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *getDebugName()
|
||||
{
|
||||
static char buffer[512];
|
||||
dSprintf(buffer, sizeof(buffer), "%s - \"", getClassName());
|
||||
expandEscape(buffer + dStrlen(buffer), mString.getString());
|
||||
dStrcat(buffer, "\"");
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
DECLARE_CONOBJECT(NetStringEvent);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(NetStringEvent);
|
||||
|
||||
ConsoleDocClass( NetStringEvent,
|
||||
"@brief Internal event used for transmitting strings across the server.\n\n"
|
||||
|
||||
"For internal use only, not intended for TorqueScript or game development\n\n"
|
||||
|
||||
"@internal\n"
|
||||
);
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
|
||||
ConnectionStringTable::ConnectionStringTable(NetConnection *parent)
|
||||
{
|
||||
mParent = parent;
|
||||
for(U32 i = 0; i < EntryCount; i++)
|
||||
{
|
||||
mEntryTable[i].nextHash = NULL;
|
||||
mEntryTable[i].nextLink = &mEntryTable[i+1];
|
||||
mEntryTable[i].prevLink = &mEntryTable[i-1];
|
||||
mEntryTable[i].index = i;
|
||||
|
||||
mHashTable[i] = NULL;
|
||||
}
|
||||
mLRUHead.nextLink = &mEntryTable[0];
|
||||
mEntryTable[0].prevLink = &mLRUHead;
|
||||
mLRUTail.prevLink = &mEntryTable[EntryCount-1];
|
||||
mEntryTable[EntryCount-1].nextLink = &mLRUTail;
|
||||
}
|
||||
|
||||
U32 ConnectionStringTable::getNetSendId(NetStringHandle &string)
|
||||
{
|
||||
// see if the entry is in the hash table right now
|
||||
U32 hashIndex = string.getIndex() % EntryCount;
|
||||
for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash)
|
||||
if(walk->string == string)
|
||||
return walk->index;
|
||||
AssertFatal(0, "Net send id is not in the table. Error!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
U32 ConnectionStringTable::checkString(NetStringHandle &string, bool *isOnOtherSide)
|
||||
{
|
||||
// see if the entry is in the hash table right now
|
||||
U32 hashIndex = string.getIndex() % EntryCount;
|
||||
for(Entry *walk = mHashTable[hashIndex]; walk; walk = walk->nextHash)
|
||||
{
|
||||
if(walk->string == string)
|
||||
{
|
||||
pushBack(walk);
|
||||
if(isOnOtherSide)
|
||||
*isOnOtherSide = walk->receiveConfirmed;
|
||||
return walk->index;
|
||||
}
|
||||
}
|
||||
|
||||
// not in the hash table, means we have to add it
|
||||
Entry *newEntry;
|
||||
|
||||
// pull the new entry from the LRU list.
|
||||
newEntry = mLRUHead.nextLink;
|
||||
pushBack(newEntry);
|
||||
|
||||
// remove the string from the hash table
|
||||
Entry **hashWalk;
|
||||
for (hashWalk = &mHashTable[newEntry->string.getIndex() % EntryCount]; *hashWalk; hashWalk = &((*hashWalk)->nextHash))
|
||||
{
|
||||
if(*hashWalk == newEntry)
|
||||
{
|
||||
*hashWalk = newEntry->nextHash;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
newEntry->string = string;
|
||||
newEntry->receiveConfirmed = false;
|
||||
newEntry->nextHash = mHashTable[hashIndex];
|
||||
mHashTable[hashIndex] = newEntry;
|
||||
|
||||
mParent->postNetEvent(new NetStringEvent(newEntry->index, string));
|
||||
if(isOnOtherSide)
|
||||
*isOnOtherSide = false;
|
||||
return newEntry->index;
|
||||
}
|
||||
|
||||
void ConnectionStringTable::mapString(U32 netId, NetStringHandle &string)
|
||||
{
|
||||
// the netId is sent by the other side... throw it in our mapping table:
|
||||
mRemoteStringTable[netId] = string;
|
||||
}
|
||||
|
||||
void ConnectionStringTable::readDemoStartBlock(BitStream *stream)
|
||||
{
|
||||
// ok, reading the demo start block
|
||||
for(U32 i = 0; i < EntryCount; i++)
|
||||
{
|
||||
if(stream->readFlag())
|
||||
{
|
||||
char buffer[256];
|
||||
stream->readString(buffer);
|
||||
mRemoteStringTable[i] = NetStringHandle(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionStringTable::writeDemoStartBlock(ResizeBitStream *stream)
|
||||
{
|
||||
// ok, writing the demo start block
|
||||
for(U32 i = 0; i < EntryCount; i++)
|
||||
{
|
||||
if(stream->writeFlag(mRemoteStringTable[i].isValidString()))
|
||||
{
|
||||
stream->writeString(mRemoteStringTable[i].getString());
|
||||
stream->validate();
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Engine/source/sim/connectionStringTable.h
Normal file
109
Engine/source/sim/connectionStringTable.h
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _H_CONNECTIONSTRINGTABLE
|
||||
#define _H_CONNECTIONSTRINGTABLE
|
||||
|
||||
/// Maintain a table of strings which are shared across the network.
|
||||
///
|
||||
/// This allows us to reference strings in our network streams more efficiently.
|
||||
class ConnectionStringTable
|
||||
{
|
||||
public:
|
||||
enum Constants {
|
||||
EntryCount = 32,
|
||||
EntryBitSize = 5,
|
||||
InvalidEntryId = 32,
|
||||
};
|
||||
private:
|
||||
struct Entry {
|
||||
NetStringHandle string; ///< Global string table entry of this string
|
||||
/// will be 0 if this string is unused.
|
||||
|
||||
U32 index; ///< index of this entry
|
||||
Entry *nextHash; ///< the next hash entry for this id
|
||||
Entry *nextLink; ///< the next in the LRU list
|
||||
Entry *prevLink; ///< the prev entry in the LRU list
|
||||
bool receiveConfirmed; ///< The other side now has this string.
|
||||
};
|
||||
|
||||
Entry mEntryTable[EntryCount];
|
||||
Entry *mHashTable[EntryCount];
|
||||
NetStringHandle mRemoteStringTable[EntryCount];
|
||||
Entry mLRUHead, mLRUTail;
|
||||
|
||||
/// Connection over which we are maintaining this string table.
|
||||
NetConnection *mParent;
|
||||
|
||||
inline void pushBack(Entry *entry) // pushes an entry to the back of the LRU list
|
||||
{
|
||||
entry->prevLink->nextLink = entry->nextLink;
|
||||
entry->nextLink->prevLink = entry->prevLink;
|
||||
entry->nextLink = &mLRUTail;
|
||||
entry->prevLink = mLRUTail.prevLink;
|
||||
entry->nextLink->prevLink = entry;
|
||||
entry->prevLink->nextLink = entry;
|
||||
}
|
||||
|
||||
public:
|
||||
/// Initialize the connection string table.
|
||||
///
|
||||
/// @param parent Connection over which we are maintaining this string table.
|
||||
ConnectionStringTable(NetConnection *parent);
|
||||
|
||||
/// Has the specified string been received on the other side?
|
||||
inline void confirmStringReceived(NetStringHandle &string, U32 index)
|
||||
{
|
||||
if(mEntryTable[index].string == string)
|
||||
mEntryTable[index].receiveConfirmed = true;
|
||||
}
|
||||
|
||||
U32 checkString(NetStringHandle &stringTableId, bool *stringOnOtherSide = NULL); ///< Checks if the global string ID is
|
||||
/// currently valid for this connection
|
||||
/// and returns the table ID.
|
||||
/// Sends a string event to the other side
|
||||
/// if it is not active.
|
||||
/// It will fill in stringOnOtherSide.
|
||||
|
||||
U32 getNetSendId(NetStringHandle &stringTableId); ///< Same return value as checkString
|
||||
/// but will assert if the string is not
|
||||
/// valid.
|
||||
|
||||
void mapString(U32 netId, NetStringHandle &string); ///< Maps a string that
|
||||
/// was just sent over the net
|
||||
/// to the corresponding net ID.
|
||||
|
||||
inline NetStringHandle lookupString(U32 netId) ///< looks up the string ID and returns
|
||||
{ /// the global string table ID for that string.
|
||||
return mRemoteStringTable[netId];
|
||||
}
|
||||
|
||||
/// @name Demo functionality
|
||||
/// @{
|
||||
|
||||
void readDemoStartBlock(BitStream *stream);
|
||||
void writeDemoStartBlock(ResizeBitStream *stream);
|
||||
/// @}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
1494
Engine/source/sim/netConnection.cpp
Normal file
1494
Engine/source/sim/netConnection.cpp
Normal file
File diff suppressed because it is too large
Load diff
1160
Engine/source/sim/netConnection.h
Normal file
1160
Engine/source/sim/netConnection.h
Normal file
File diff suppressed because it is too large
Load diff
273
Engine/source/sim/netDownload.cpp
Normal file
273
Engine/source/sim/netDownload.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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"
|
||||
#include "sim/netObject.h"
|
||||
|
||||
class FileDownloadRequestEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
enum
|
||||
{
|
||||
MaxFileNames = 31,
|
||||
};
|
||||
|
||||
U32 nameCount;
|
||||
char mFileNames[MaxFileNames][256];
|
||||
|
||||
FileDownloadRequestEvent(Vector<char *> *nameList = NULL)
|
||||
{
|
||||
nameCount = 0;
|
||||
if(nameList)
|
||||
{
|
||||
nameCount = nameList->size();
|
||||
|
||||
if(nameCount > MaxFileNames)
|
||||
nameCount = MaxFileNames;
|
||||
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
{
|
||||
dStrcpy(mFileNames[i], (*nameList)[i]);
|
||||
//Con::printf("Sending request for file %s", mFileNames[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void pack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(nameCount, 0, MaxFileNames);
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
bstream->writeString(mFileNames[i]);
|
||||
}
|
||||
|
||||
virtual void write(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(nameCount, 0, MaxFileNames);
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
bstream->writeString(mFileNames[i]);
|
||||
}
|
||||
|
||||
virtual void unpack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
nameCount = bstream->readRangedU32(0, MaxFileNames);
|
||||
for(U32 i = 0; i < nameCount; i++)
|
||||
bstream->readString(mFileNames[i]);
|
||||
}
|
||||
|
||||
virtual void process(NetConnection *connection)
|
||||
{
|
||||
U32 i;
|
||||
for(i = 0; i < nameCount; i++)
|
||||
if(connection->startSendingFile(mFileNames[i]))
|
||||
break;
|
||||
if(i == nameCount)
|
||||
connection->startSendingFile(NULL); // none of the files were sent
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(FileDownloadRequestEvent);
|
||||
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(FileDownloadRequestEvent);
|
||||
|
||||
ConsoleDocClass( FileDownloadRequestEvent,
|
||||
"@brief Used by NetConnection for transmitting requests to obtain files from server during loading.\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal");
|
||||
|
||||
class FileChunkEvent : public NetEvent
|
||||
{
|
||||
public:
|
||||
typedef NetEvent Parent;
|
||||
enum
|
||||
{
|
||||
ChunkSize = 63,
|
||||
};
|
||||
|
||||
U8 chunkData[ChunkSize];
|
||||
U32 chunkLen;
|
||||
|
||||
FileChunkEvent(U8 *data = NULL, U32 len = 0)
|
||||
{
|
||||
if(data)
|
||||
dMemcpy(chunkData, data, len);
|
||||
chunkLen = len;
|
||||
}
|
||||
|
||||
virtual void pack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(chunkLen, 0, ChunkSize);
|
||||
bstream->write(chunkLen, chunkData);
|
||||
}
|
||||
|
||||
virtual void write(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
bstream->writeRangedU32(chunkLen, 0, ChunkSize);
|
||||
bstream->write(chunkLen, chunkData);
|
||||
}
|
||||
|
||||
virtual void unpack(NetConnection *, BitStream *bstream)
|
||||
{
|
||||
chunkLen = bstream->readRangedU32(0, ChunkSize);
|
||||
bstream->read(chunkLen, chunkData);
|
||||
}
|
||||
|
||||
virtual void process(NetConnection *connection)
|
||||
{
|
||||
connection->chunkReceived(chunkData, chunkLen);
|
||||
}
|
||||
|
||||
virtual void notifyDelivered(NetConnection *nc, bool madeIt)
|
||||
{
|
||||
if(!nc->isRemoved())
|
||||
nc->sendFileChunk();
|
||||
}
|
||||
|
||||
DECLARE_CONOBJECT(FileChunkEvent);
|
||||
};
|
||||
|
||||
IMPLEMENT_CO_NETEVENT_V1(FileChunkEvent);
|
||||
|
||||
ConsoleDocClass( FileChunkEvent,
|
||||
"@brief Used by NetConnection for sending/receiving chunks of data.\n\n"
|
||||
"Not intended for game development, for editors or internal use only.\n\n "
|
||||
"@internal");
|
||||
|
||||
void NetConnection::sendFileChunk()
|
||||
{
|
||||
U8 buffer[FileChunkEvent::ChunkSize];
|
||||
U32 len = FileChunkEvent::ChunkSize;
|
||||
if(len + mCurrentFileBufferOffset > mCurrentFileBufferSize)
|
||||
len = mCurrentFileBufferSize - mCurrentFileBufferOffset;
|
||||
|
||||
if(!len)
|
||||
{
|
||||
delete mCurrentDownloadingFile;
|
||||
mCurrentDownloadingFile = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
mCurrentFileBufferOffset += len;
|
||||
mCurrentDownloadingFile->read(len, buffer);
|
||||
postNetEvent(new FileChunkEvent(buffer, len));
|
||||
}
|
||||
|
||||
bool NetConnection::startSendingFile(const char *fileName)
|
||||
{
|
||||
if(!fileName || Con::getBoolVariable("$NetConnection::neverUploadFiles"))
|
||||
{
|
||||
sendConnectionMessage(SendNextDownloadRequest);
|
||||
return false;
|
||||
}
|
||||
|
||||
mCurrentDownloadingFile = FileStream::createAndOpen( fileName, Torque::FS::File::Read );
|
||||
if(!mCurrentDownloadingFile)
|
||||
{
|
||||
// the server didn't have the file, so send a 0 byte chunk:
|
||||
Con::printf("No such file '%s'.", fileName);
|
||||
postNetEvent(new FileChunkEvent(NULL, 0));
|
||||
return false;
|
||||
}
|
||||
|
||||
Con::printf("Sending file '%s'.", fileName);
|
||||
mCurrentFileBufferSize = mCurrentDownloadingFile->getStreamSize();
|
||||
mCurrentFileBufferOffset = 0;
|
||||
|
||||
// always have 32 file chunks (64 bytes each) in transit
|
||||
sendConnectionMessage(FileDownloadSizeMessage, mCurrentFileBufferSize);
|
||||
for(U32 i = 0; i < 32; i++)
|
||||
sendFileChunk();
|
||||
return true;
|
||||
}
|
||||
|
||||
void NetConnection::sendNextFileDownloadRequest()
|
||||
{
|
||||
// see if we've already downloaded this file...
|
||||
while(mMissingFileList.size() && (Torque::FS::IsFile(mMissingFileList[0]) || Con::getBoolVariable("$NetConnection::neverDownloadFiles")))
|
||||
{
|
||||
dFree(mMissingFileList[0]);
|
||||
mMissingFileList.pop_front();
|
||||
}
|
||||
|
||||
if(mMissingFileList.size())
|
||||
{
|
||||
postNetEvent(new FileDownloadRequestEvent(&mMissingFileList));
|
||||
}
|
||||
else
|
||||
{
|
||||
fileDownloadSegmentComplete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void NetConnection::chunkReceived(U8 *chunkData, U32 chunkLen)
|
||||
{
|
||||
if(chunkLen == 0)
|
||||
{
|
||||
// the server didn't have the file... apparently it's one we don't need...
|
||||
dFree(mCurrentFileBuffer);
|
||||
mCurrentFileBuffer = NULL;
|
||||
dFree(mMissingFileList[0]);
|
||||
mMissingFileList.pop_front();
|
||||
return;
|
||||
}
|
||||
if(chunkLen + mCurrentFileBufferOffset > mCurrentFileBufferSize)
|
||||
{
|
||||
setLastError("Invalid file chunk from server.");
|
||||
return;
|
||||
}
|
||||
dMemcpy(((U8 *) mCurrentFileBuffer) + mCurrentFileBufferOffset, chunkData, chunkLen);
|
||||
mCurrentFileBufferOffset += chunkLen;
|
||||
if(mCurrentFileBufferOffset == mCurrentFileBufferSize)
|
||||
{
|
||||
// this file's done...
|
||||
// save it to disk:
|
||||
FileStream *stream;
|
||||
|
||||
Con::printf("Saving file %s.", mMissingFileList[0]);
|
||||
if((stream = FileStream::createAndOpen( mMissingFileList[0], Torque::FS::File::Write )) == NULL)
|
||||
{
|
||||
setLastError("Couldn't open file downloaded by server.");
|
||||
return;
|
||||
}
|
||||
|
||||
dFree(mMissingFileList[0]);
|
||||
mMissingFileList.pop_front();
|
||||
stream->write(mCurrentFileBufferSize, mCurrentFileBuffer);
|
||||
delete stream;
|
||||
mNumDownloadedFiles++;
|
||||
dFree(mCurrentFileBuffer);
|
||||
mCurrentFileBuffer = NULL;
|
||||
sendNextFileDownloadRequest();
|
||||
}
|
||||
else
|
||||
{
|
||||
Con::executef("onFileChunkReceived", mMissingFileList[0], Con::getIntArg(mCurrentFileBufferOffset), Con::getIntArg(mCurrentFileBufferSize));
|
||||
}
|
||||
}
|
||||
|
||||
453
Engine/source/sim/netEvent.cpp
Normal file
453
Engine/source/sim/netEvent.cpp
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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"
|
||||
|
||||
#define DebugChecksum 0xF00DBAAD
|
||||
|
||||
FreeListChunker<NetEventNote> NetConnection::mEventNoteChunker;
|
||||
|
||||
NetEvent::~NetEvent()
|
||||
{
|
||||
}
|
||||
|
||||
void NetEvent::notifyDelivered(NetConnection *, bool)
|
||||
{
|
||||
}
|
||||
|
||||
void NetEvent::notifySent(NetConnection *)
|
||||
{
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
const char *NetEvent::getDebugName()
|
||||
{
|
||||
return getClassName();
|
||||
}
|
||||
#endif
|
||||
|
||||
void NetConnection::eventOnRemove()
|
||||
{
|
||||
while(mNotifyEventList)
|
||||
{
|
||||
NetEventNote *temp = mNotifyEventList;
|
||||
mNotifyEventList = temp->mNextEvent;
|
||||
|
||||
temp->mEvent->notifyDelivered(this, true);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
}
|
||||
|
||||
while(mUnorderedSendEventQueueHead)
|
||||
{
|
||||
NetEventNote *temp = mUnorderedSendEventQueueHead;
|
||||
mUnorderedSendEventQueueHead = temp->mNextEvent;
|
||||
|
||||
temp->mEvent->notifyDelivered(this, true);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
}
|
||||
|
||||
while(mSendEventQueueHead)
|
||||
{
|
||||
NetEventNote *temp = mSendEventQueueHead;
|
||||
mSendEventQueueHead = temp->mNextEvent;
|
||||
|
||||
temp->mEvent->notifyDelivered(this, true);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
}
|
||||
}
|
||||
|
||||
void NetConnection::eventPacketDropped(PacketNotify *notify)
|
||||
{
|
||||
NetEventNote *walk = notify->eventList;
|
||||
NetEventNote **insertList = &mSendEventQueueHead;
|
||||
NetEventNote *temp;
|
||||
|
||||
while(walk)
|
||||
{
|
||||
switch(walk->mEvent->mGuaranteeType)
|
||||
{
|
||||
// It was a guaranteed ordered packet, reinsert it back into
|
||||
// mSendEventQueueHead in the right place (based on seq numbers)
|
||||
case NetEvent::GuaranteedOrdered:
|
||||
|
||||
//Con::printf("EVT %d: DROP - %d", getId(), walk->mSeqCount);
|
||||
|
||||
while(*insertList && (*insertList)->mSeqCount < walk->mSeqCount)
|
||||
insertList = &((*insertList)->mNextEvent);
|
||||
|
||||
temp = walk->mNextEvent;
|
||||
walk->mNextEvent = *insertList;
|
||||
if(!walk->mNextEvent)
|
||||
mSendEventQueueTail = walk;
|
||||
*insertList = walk;
|
||||
insertList = &(walk->mNextEvent);
|
||||
walk = temp;
|
||||
break;
|
||||
|
||||
// It was a guaranteed packet, put it at the top of
|
||||
// mUnorderedSendEventQueueHead.
|
||||
case NetEvent::Guaranteed:
|
||||
temp = walk->mNextEvent;
|
||||
walk->mNextEvent = mUnorderedSendEventQueueHead;
|
||||
mUnorderedSendEventQueueHead = walk;
|
||||
if(!walk->mNextEvent)
|
||||
mUnorderedSendEventQueueTail = walk;
|
||||
walk = temp;
|
||||
break;
|
||||
|
||||
// Or else it was an unguaranteed packet, notify that
|
||||
// it was _not_ delivered and blast it.
|
||||
case NetEvent::Unguaranteed:
|
||||
walk->mEvent->notifyDelivered(this, false);
|
||||
walk->mEvent->decRef();
|
||||
temp = walk->mNextEvent;
|
||||
mEventNoteChunker.free(walk);
|
||||
walk = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetConnection::eventPacketReceived(PacketNotify *notify)
|
||||
{
|
||||
NetEventNote *walk = notify->eventList;
|
||||
NetEventNote **noteList = &mNotifyEventList;
|
||||
|
||||
while(walk)
|
||||
{
|
||||
NetEventNote *next = walk->mNextEvent;
|
||||
if(walk->mEvent->mGuaranteeType != NetEvent::GuaranteedOrdered)
|
||||
{
|
||||
walk->mEvent->notifyDelivered(this, true);
|
||||
walk->mEvent->decRef();
|
||||
mEventNoteChunker.free(walk);
|
||||
walk = next;
|
||||
}
|
||||
else
|
||||
{
|
||||
while(*noteList && (*noteList)->mSeqCount < walk->mSeqCount)
|
||||
noteList = &((*noteList)->mNextEvent);
|
||||
|
||||
walk->mNextEvent = *noteList;
|
||||
*noteList = walk;
|
||||
noteList = &walk->mNextEvent;
|
||||
walk = next;
|
||||
}
|
||||
}
|
||||
while(mNotifyEventList && mNotifyEventList->mSeqCount == mLastAckedEventSeq + 1)
|
||||
{
|
||||
mLastAckedEventSeq++;
|
||||
NetEventNote *next = mNotifyEventList->mNextEvent;
|
||||
//Con::printf("EVT %d: ACK - %d", getId(), mNotifyEventList->mSeqCount);
|
||||
mNotifyEventList->mEvent->notifyDelivered(this, true);
|
||||
mNotifyEventList->mEvent->decRef();
|
||||
mEventNoteChunker.free(mNotifyEventList);
|
||||
mNotifyEventList = next;
|
||||
}
|
||||
}
|
||||
|
||||
void NetConnection::eventWritePacket(BitStream *bstream, PacketNotify *notify)
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(DebugChecksum, 32);
|
||||
#endif
|
||||
|
||||
NetEventNote *packQueueHead = NULL, *packQueueTail = NULL;
|
||||
|
||||
while(mUnorderedSendEventQueueHead)
|
||||
{
|
||||
if(bstream->isFull())
|
||||
break;
|
||||
// dequeue the first event
|
||||
NetEventNote *ev = mUnorderedSendEventQueueHead;
|
||||
mUnorderedSendEventQueueHead = ev->mNextEvent;
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 start = bstream->getCurPos();
|
||||
#endif
|
||||
|
||||
bstream->writeFlag(true);
|
||||
S32 classId = ev->mEvent->getClassId(getNetClassGroup());
|
||||
AssertFatal(classId>=0, "NetConnection::eventWritePacket - event not in group!");
|
||||
bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
|
||||
|
||||
#ifdef TORQUE_NET_STATS
|
||||
U32 beginSize = bstream->getBitPosition();
|
||||
#endif
|
||||
ev->mEvent->pack(this, bstream);
|
||||
#ifdef TORQUE_NET_STATS
|
||||
ev->mEvent->getClassRep()->updateNetStatPack(0, bstream->getBitPosition() - beginSize);
|
||||
#endif
|
||||
DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getBitPosition() - start, ev->mEvent->getDebugName()) );
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(classId ^ DebugChecksum, 32);
|
||||
#endif
|
||||
// add this event onto the packet queue
|
||||
ev->mNextEvent = NULL;
|
||||
if(!packQueueHead)
|
||||
packQueueHead = ev;
|
||||
else
|
||||
packQueueTail->mNextEvent = ev;
|
||||
packQueueTail = ev;
|
||||
}
|
||||
|
||||
bstream->writeFlag(false);
|
||||
S32 prevSeq = -2;
|
||||
|
||||
while(mSendEventQueueHead)
|
||||
{
|
||||
if(bstream->isFull())
|
||||
break;
|
||||
|
||||
// if the event window is full, stop processing
|
||||
if(mSendEventQueueHead->mSeqCount > mLastAckedEventSeq + 126)
|
||||
break;
|
||||
|
||||
// dequeue the first event
|
||||
NetEventNote *ev = mSendEventQueueHead;
|
||||
mSendEventQueueHead = ev->mNextEvent;
|
||||
|
||||
//Con::printf("EVT %d: SEND - %d", getId(), ev->mSeqCount);
|
||||
|
||||
bstream->writeFlag(true);
|
||||
|
||||
ev->mNextEvent = NULL;
|
||||
if(!packQueueHead)
|
||||
packQueueHead = ev;
|
||||
else
|
||||
packQueueTail->mNextEvent = ev;
|
||||
packQueueTail = ev;
|
||||
if(!bstream->writeFlag(ev->mSeqCount == prevSeq + 1))
|
||||
bstream->writeInt(ev->mSeqCount, 7);
|
||||
|
||||
prevSeq = ev->mSeqCount;
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 start = bstream->getCurPos();
|
||||
#endif
|
||||
|
||||
S32 classId = ev->mEvent->getClassId(getNetClassGroup());
|
||||
bstream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
|
||||
#ifdef TORQUE_NET_STATS
|
||||
U32 beginSize = bstream->getBitPosition();
|
||||
#endif
|
||||
ev->mEvent->pack(this, bstream);
|
||||
#ifdef TORQUE_NET_STATS
|
||||
ev->mEvent->getClassRep()->updateNetStatPack(0, bstream->getBitPosition() - beginSize);
|
||||
#endif
|
||||
DEBUG_LOG(("PKLOG %d EVENT %d: %s", getId(), bstream->getBitPosition() - start, ev->mEvent->getDebugName()) );
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
bstream->writeInt(classId ^ DebugChecksum, 32);
|
||||
#endif
|
||||
}
|
||||
for(NetEventNote *ev = packQueueHead; ev; ev = ev->mNextEvent)
|
||||
ev->mEvent->notifySent(this);
|
||||
|
||||
notify->eventList = packQueueHead;
|
||||
bstream->writeFlag(false);
|
||||
}
|
||||
|
||||
void NetConnection::eventReadPacket(BitStream *bstream)
|
||||
{
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 sum = bstream->readInt(32);
|
||||
AssertISV(sum == DebugChecksum, "Invalid checksum.");
|
||||
#endif
|
||||
|
||||
S32 prevSeq = -2;
|
||||
NetEventNote **waitInsert = &mWaitSeqEvents;
|
||||
bool unguaranteedPhase = true;
|
||||
|
||||
while(true)
|
||||
{
|
||||
bool bit = bstream->readFlag();
|
||||
if(unguaranteedPhase && !bit)
|
||||
{
|
||||
unguaranteedPhase = false;
|
||||
bit = bstream->readFlag();
|
||||
}
|
||||
if(!unguaranteedPhase && !bit)
|
||||
break;
|
||||
|
||||
S32 seq = -1;
|
||||
|
||||
if(!unguaranteedPhase) // get the sequence
|
||||
{
|
||||
if(bstream->readFlag())
|
||||
seq = (prevSeq + 1) & 0x7f;
|
||||
else
|
||||
seq = bstream->readInt(7);
|
||||
prevSeq = seq;
|
||||
}
|
||||
S32 classId = bstream->readClassId(NetClassTypeEvent, getNetClassGroup());
|
||||
if(classId == -1)
|
||||
{
|
||||
setLastError("Invalid packet. (bad event class id)");
|
||||
return;
|
||||
}
|
||||
NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classId);
|
||||
if(!evt)
|
||||
{
|
||||
setLastError("Invalid packet. (bad ghost class id)");
|
||||
return;
|
||||
}
|
||||
AbstractClassRep *rep = evt->getClassRep();
|
||||
if((rep->mNetEventDir == NetEventDirServerToClient && !isConnectionToServer())
|
||||
|| (rep->mNetEventDir == NetEventDirClientToServer && isConnectionToServer()) )
|
||||
{
|
||||
setLastError("Invalid Packet. (invalid direction)");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
evt->mSourceId = getId();
|
||||
#ifdef TORQUE_NET_STATS
|
||||
U32 beginSize = bstream->getBitPosition();
|
||||
#endif
|
||||
evt->unpack(this, bstream);
|
||||
#ifdef TORQUE_NET_STATS
|
||||
evt->getClassRep()->updateNetStatUnpack(bstream->getBitPosition() - beginSize);
|
||||
#endif
|
||||
if(mErrorBuffer.isNotEmpty())
|
||||
return;
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
U32 checksum = bstream->readInt(32);
|
||||
AssertISV( (checksum ^ DebugChecksum) == (U32)classId,
|
||||
avar("unpack did not match pack for event of class %s.",
|
||||
evt->getClassName()) );
|
||||
#endif
|
||||
if(unguaranteedPhase)
|
||||
{
|
||||
evt->process(this);
|
||||
evt->decRef();
|
||||
if(mErrorBuffer.isNotEmpty())
|
||||
return;
|
||||
continue;
|
||||
}
|
||||
seq |= (mNextRecvEventSeq & ~0x7F);
|
||||
if(seq < mNextRecvEventSeq)
|
||||
seq += 128;
|
||||
|
||||
NetEventNote *note = mEventNoteChunker.alloc();
|
||||
note->mEvent = evt;
|
||||
note->mEvent->incRef();
|
||||
|
||||
note->mSeqCount = seq;
|
||||
//Con::printf("EVT %d: RECV - %d", getId(), evt->mSeqCount);
|
||||
while(*waitInsert && (*waitInsert)->mSeqCount < seq)
|
||||
waitInsert = &((*waitInsert)->mNextEvent);
|
||||
|
||||
note->mNextEvent = *waitInsert;
|
||||
*waitInsert = note;
|
||||
waitInsert = &(note->mNextEvent);
|
||||
}
|
||||
while(mWaitSeqEvents && mWaitSeqEvents->mSeqCount == mNextRecvEventSeq)
|
||||
{
|
||||
mNextRecvEventSeq++;
|
||||
NetEventNote *temp = mWaitSeqEvents;
|
||||
mWaitSeqEvents = temp->mNextEvent;
|
||||
|
||||
//Con::printf("EVT %d: PROCESS - %d", getId(), temp->mSeqCount);
|
||||
temp->mEvent->process(this);
|
||||
temp->mEvent->decRef();
|
||||
mEventNoteChunker.free(temp);
|
||||
if(mErrorBuffer.isNotEmpty())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool NetConnection::postNetEvent(NetEvent *theEvent)
|
||||
{
|
||||
if(!mSendingEvents)
|
||||
{
|
||||
theEvent->decRef();
|
||||
return false;
|
||||
}
|
||||
NetEventNote *event = mEventNoteChunker.alloc();
|
||||
event->mEvent = theEvent;
|
||||
theEvent->incRef();
|
||||
|
||||
event->mNextEvent = NULL;
|
||||
if(theEvent->mGuaranteeType == NetEvent::GuaranteedOrdered)
|
||||
{
|
||||
event->mSeqCount = mNextSendEventSeq++;
|
||||
if(!mSendEventQueueHead)
|
||||
mSendEventQueueHead = event;
|
||||
else
|
||||
mSendEventQueueTail->mNextEvent = event;
|
||||
mSendEventQueueTail = event;
|
||||
}
|
||||
else
|
||||
{
|
||||
event->mSeqCount = InvalidSendEventSeq;
|
||||
if(!mUnorderedSendEventQueueHead)
|
||||
mUnorderedSendEventQueueHead = event;
|
||||
else
|
||||
mUnorderedSendEventQueueTail->mNextEvent = event;
|
||||
mUnorderedSendEventQueueTail = event;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void NetConnection::eventWriteStartBlock(ResizeBitStream *stream)
|
||||
{
|
||||
stream->write(mNextRecvEventSeq);
|
||||
for(NetEventNote *walk = mWaitSeqEvents; walk; walk = walk->mNextEvent)
|
||||
{
|
||||
stream->writeFlag(true);
|
||||
S32 classId = walk->mEvent->getClassId(getNetClassGroup());
|
||||
stream->writeClassId(classId, NetClassTypeEvent, getNetClassGroup());
|
||||
walk->mEvent->write(this, stream);
|
||||
stream->validate();
|
||||
}
|
||||
stream->writeFlag(false);
|
||||
}
|
||||
|
||||
void NetConnection::eventReadStartBlock(BitStream *stream)
|
||||
{
|
||||
stream->read(&mNextRecvEventSeq);
|
||||
|
||||
NetEventNote *lastEvent = NULL;
|
||||
while(stream->readFlag())
|
||||
{
|
||||
S32 classTag = stream->readClassId(NetClassTypeEvent, getNetClassGroup());
|
||||
NetEvent *evt = (NetEvent *) ConsoleObject::create(getNetClassGroup(), NetClassTypeEvent, classTag);
|
||||
evt->unpack(this, stream);
|
||||
NetEventNote *add = mEventNoteChunker.alloc();
|
||||
add->mEvent = evt;
|
||||
evt->incRef();
|
||||
add->mNextEvent = NULL;
|
||||
|
||||
if(!lastEvent)
|
||||
mWaitSeqEvents = add;
|
||||
else
|
||||
lastEvent->mNextEvent = add;
|
||||
lastEvent = add;
|
||||
}
|
||||
}
|
||||
1294
Engine/source/sim/netGhost.cpp
Normal file
1294
Engine/source/sim/netGhost.cpp
Normal file
File diff suppressed because it is too large
Load diff
656
Engine/source/sim/netInterface.cpp
Normal file
656
Engine/source/sim/netInterface.cpp
Normal file
|
|
@ -0,0 +1,656 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "platform/event.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "sim/netInterface.h"
|
||||
#include "core/stream/bitStream.h"
|
||||
#include "math/mRandom.h"
|
||||
#include "core/util/journal/journal.h"
|
||||
|
||||
#ifdef GGC_PLUGIN
|
||||
#include "GGCNatTunnel.h"
|
||||
extern void HandleGGCPacket(NetAddress* addr, unsigned char* data, U32 dataSize);
|
||||
#endif
|
||||
|
||||
NetInterface *GNet = NULL;
|
||||
|
||||
NetInterface::NetInterface()
|
||||
{
|
||||
AssertFatal(GNet == NULL, "ERROR: Multiple net interfaces declared.");
|
||||
GNet = this;
|
||||
|
||||
mLastTimeoutCheckTime = 0;
|
||||
mAllowConnections = true;
|
||||
|
||||
}
|
||||
|
||||
void NetInterface::initRandomData()
|
||||
{
|
||||
mRandomDataInitialized = true;
|
||||
U32 seed = Platform::getRealMilliseconds();
|
||||
|
||||
if(Journal::IsPlaying())
|
||||
Journal::Read(&seed);
|
||||
else if(Journal::IsRecording())
|
||||
Journal::Write(seed);
|
||||
|
||||
MRandomR250 myRandom(seed);
|
||||
for(U32 i = 0; i < 12; i++)
|
||||
mRandomHashData[i] = myRandom.randI();
|
||||
}
|
||||
|
||||
void NetInterface::addPendingConnection(NetConnection *connection)
|
||||
{
|
||||
Con::printf("Adding a pending connection");
|
||||
mPendingConnections.push_back(connection);
|
||||
}
|
||||
|
||||
void NetInterface::removePendingConnection(NetConnection *connection)
|
||||
{
|
||||
for(U32 i = 0; i < mPendingConnections.size(); i++)
|
||||
if(mPendingConnections[i] == connection)
|
||||
mPendingConnections.erase(i);
|
||||
}
|
||||
|
||||
NetConnection *NetInterface::findPendingConnection(const NetAddress *address, U32 connectSequence)
|
||||
{
|
||||
for(U32 i = 0; i < mPendingConnections.size(); i++)
|
||||
if(Net::compareAddresses(address, mPendingConnections[i]->getNetAddress()) &&
|
||||
connectSequence == mPendingConnections[i]->getSequence())
|
||||
return mPendingConnections[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void NetInterface::processPacketReceiveEvent(NetAddress srcAddress, RawData packetData)
|
||||
{
|
||||
|
||||
U32 dataSize = packetData.size;
|
||||
BitStream pStream(packetData.data, dataSize);
|
||||
|
||||
// Determine what to do with this packet:
|
||||
|
||||
if(packetData.data[0] & 0x01) // it's a protocol packet...
|
||||
{
|
||||
// if the LSB of the first byte is set, it's a game data packet
|
||||
// so pass it to the appropriate connection.
|
||||
|
||||
// lookup the connection in the addressTable
|
||||
NetConnection *conn = NetConnection::lookup(&srcAddress);
|
||||
if(conn)
|
||||
conn->processRawPacket(&pStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise, it's either a game info packet or a
|
||||
// connection handshake packet.
|
||||
|
||||
U8 packetType;
|
||||
pStream.read(&packetType);
|
||||
NetAddress *addr = &srcAddress;
|
||||
|
||||
if(packetType <= GameHeartbeat)
|
||||
handleInfoPacket(addr, packetType, &pStream);
|
||||
#ifdef GGC_PLUGIN
|
||||
else if (packetType == GGCPacket)
|
||||
{
|
||||
HandleGGCPacket(addr, (U8*)packetData.data, dataSize);
|
||||
}
|
||||
#endif
|
||||
else
|
||||
{
|
||||
// check if there's a connection already:
|
||||
switch(packetType)
|
||||
{
|
||||
case ConnectChallengeRequest:
|
||||
handleConnectChallengeRequest(addr, &pStream);
|
||||
break;
|
||||
case ConnectRequest:
|
||||
handleConnectRequest(addr, &pStream);
|
||||
break;
|
||||
case ConnectChallengeResponse:
|
||||
handleConnectChallengeResponse(addr, &pStream);
|
||||
break;
|
||||
case ConnectAccept:
|
||||
handleConnectAccept(addr, &pStream);
|
||||
break;
|
||||
case Disconnect:
|
||||
handleDisconnect(addr, &pStream);
|
||||
break;
|
||||
case ConnectReject:
|
||||
handleConnectReject(addr, &pStream);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
// Connection handshaking basic overview:
|
||||
// The torque engine does a two phase connect handshake to
|
||||
// prevent a spoofed source address Denial-of-Service (DOS) attack
|
||||
//
|
||||
// Basically, the initiator of a connection (client) sends a
|
||||
// Connect Challenge Request packet to the server to initiate the connection
|
||||
// The server then hashes the source address of the client request
|
||||
// with some random magic server data to come up with a 16-byte key that
|
||||
// the client can then use to gain entry to the server.
|
||||
// This way there are no partially active connection records on the
|
||||
// server at all.
|
||||
//
|
||||
// The client then sends a Connect Request packet to the server,
|
||||
// including any game specific data necessary to start a connection (a
|
||||
// server password, for instance), along with the key the server sent
|
||||
// on the Connect Challenge Response packet.
|
||||
//
|
||||
// The server, on receipt of the Connect Request, compares the
|
||||
// entry key with a computed key, makes sure it can create the requested
|
||||
// NetConnection subclass, and then passes all processing on to the connection
|
||||
// instance.
|
||||
//
|
||||
// If the subclass reads and accepts he connect request successfully, the
|
||||
// server sends a Connect Accept packet - otherwise the connection
|
||||
// is rejected with the sendConnectReject function
|
||||
//-----------------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
void NetInterface::sendConnectChallengeRequest(NetConnection *conn)
|
||||
{
|
||||
Con::printf("Sending Connect challenge Request");
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
|
||||
out->write(U8(ConnectChallengeRequest));
|
||||
out->write(conn->getSequence());
|
||||
|
||||
conn->mConnectSendCount++;
|
||||
conn->mConnectLastSendTime = Platform::getVirtualMilliseconds();
|
||||
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream)
|
||||
{
|
||||
char buf[256];
|
||||
Net::addressToString(addr, buf);
|
||||
Con::printf("Got Connect challenge Request from %s", buf);
|
||||
if(!mAllowConnections)
|
||||
return;
|
||||
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
|
||||
if(!mRandomDataInitialized)
|
||||
initRandomData();
|
||||
|
||||
U32 addressDigest[4];
|
||||
computeNetMD5(addr, connectSequence, addressDigest);
|
||||
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectChallengeResponse));
|
||||
out->write(connectSequence);
|
||||
out->write(addressDigest[0]);
|
||||
out->write(addressDigest[1]);
|
||||
out->write(addressDigest[2]);
|
||||
out->write(addressDigest[3]);
|
||||
|
||||
BitStream::sendPacketStream(addr);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::handleConnectChallengeResponse(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
Con::printf("Got Connect challenge Response");
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
|
||||
NetConnection *conn = findPendingConnection(address, connectSequence);
|
||||
if(!conn || conn->getConnectionState() != NetConnection::AwaitingChallengeResponse)
|
||||
return;
|
||||
|
||||
U32 addressDigest[4];
|
||||
stream->read(&addressDigest[0]);
|
||||
stream->read(&addressDigest[1]);
|
||||
stream->read(&addressDigest[2]);
|
||||
stream->read(&addressDigest[3]);
|
||||
conn->setAddressDigest(addressDigest);
|
||||
|
||||
conn->setConnectionState(NetConnection::AwaitingConnectResponse);
|
||||
conn->mConnectSendCount = 0;
|
||||
Con::printf("Sending Connect Request");
|
||||
sendConnectRequest(conn);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::sendConnectRequest(NetConnection *conn)
|
||||
{
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectRequest));
|
||||
out->write(conn->getSequence());
|
||||
|
||||
U32 addressDigest[4];
|
||||
conn->getAddressDigest(addressDigest);
|
||||
out->write(addressDigest[0]);
|
||||
out->write(addressDigest[1]);
|
||||
out->write(addressDigest[2]);
|
||||
out->write(addressDigest[3]);
|
||||
|
||||
out->writeString(conn->getClassName());
|
||||
conn->writeConnectRequest(out);
|
||||
conn->mConnectSendCount++;
|
||||
conn->mConnectLastSendTime = Platform::getVirtualMilliseconds();
|
||||
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::handleConnectRequest(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
if(!mAllowConnections)
|
||||
return;
|
||||
Con::printf("Got Connect Request");
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
|
||||
// see if the connection is in the main connection table:
|
||||
|
||||
NetConnection *connect = NetConnection::lookup(address);
|
||||
if(connect && connect->getSequence() == connectSequence)
|
||||
{
|
||||
sendConnectAccept(connect);
|
||||
return;
|
||||
}
|
||||
U32 addressDigest[4];
|
||||
U32 computedAddressDigest[4];
|
||||
|
||||
stream->read(&addressDigest[0]);
|
||||
stream->read(&addressDigest[1]);
|
||||
stream->read(&addressDigest[2]);
|
||||
stream->read(&addressDigest[3]);
|
||||
|
||||
computeNetMD5(address, connectSequence, computedAddressDigest);
|
||||
if(addressDigest[0] != computedAddressDigest[0] ||
|
||||
addressDigest[1] != computedAddressDigest[1] ||
|
||||
addressDigest[2] != computedAddressDigest[2] ||
|
||||
addressDigest[3] != computedAddressDigest[3])
|
||||
return; // bogus connection attempt
|
||||
|
||||
if(connect)
|
||||
{
|
||||
if(connect->getSequence() > connectSequence)
|
||||
return; // the existing connection should be kept - the incoming request is stale.
|
||||
else
|
||||
connect->deleteObject(); // disconnect this one, and allow the new one to be created.
|
||||
}
|
||||
|
||||
char connectionClass[255];
|
||||
stream->readString(connectionClass);
|
||||
|
||||
ConsoleObject *co = ConsoleObject::create(connectionClass);
|
||||
NetConnection *conn = dynamic_cast<NetConnection *>(co);
|
||||
if(!conn || !conn->canRemoteCreate())
|
||||
{
|
||||
delete co;
|
||||
return;
|
||||
}
|
||||
conn->registerObject();
|
||||
conn->setNetAddress(address);
|
||||
conn->setNetworkConnection(true);
|
||||
conn->setSequence(connectSequence);
|
||||
|
||||
const char *errorString = NULL;
|
||||
if(!conn->readConnectRequest(stream, &errorString))
|
||||
{
|
||||
sendConnectReject(conn, errorString);
|
||||
conn->deleteObject();
|
||||
return;
|
||||
}
|
||||
conn->setNetworkConnection(true);
|
||||
conn->onConnectionEstablished(false);
|
||||
conn->setEstablished();
|
||||
conn->setConnectSequence(connectSequence);
|
||||
sendConnectAccept(conn);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetInterface::sendConnectAccept(NetConnection *conn)
|
||||
{
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectAccept));
|
||||
out->write(conn->getSequence());
|
||||
conn->writeConnectAccept(out);
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::handleConnectAccept(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
NetConnection *conn = findPendingConnection(address, connectSequence);
|
||||
if(!conn || conn->getConnectionState() != NetConnection::AwaitingConnectResponse)
|
||||
return;
|
||||
const char *errorString = NULL;
|
||||
if(!conn->readConnectAccept(stream, &errorString))
|
||||
{
|
||||
conn->handleStartupError(errorString);
|
||||
removePendingConnection(conn);
|
||||
conn->deleteObject();
|
||||
return;
|
||||
}
|
||||
|
||||
removePendingConnection(conn); // remove from the pending connection list
|
||||
conn->setNetworkConnection(true);
|
||||
conn->onConnectionEstablished(true); // notify the connection that it has been established
|
||||
conn->setEstablished(); // installs the connection in the connection table, and causes pings/timeouts to happen
|
||||
conn->setConnectSequence(connectSequence);
|
||||
}
|
||||
|
||||
void NetInterface::sendConnectReject(NetConnection *conn, const char *reason)
|
||||
{
|
||||
if(!reason)
|
||||
return; // if the stream is NULL, we reject silently
|
||||
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(ConnectReject));
|
||||
out->write(conn->getSequence());
|
||||
out->writeString(reason);
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::handleConnectReject(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
U32 connectSequence;
|
||||
stream->read(&connectSequence);
|
||||
NetConnection *conn = findPendingConnection(address, connectSequence);
|
||||
if(!conn || (conn->getConnectionState() != NetConnection::AwaitingChallengeResponse &&
|
||||
conn->getConnectionState() != NetConnection::AwaitingConnectResponse))
|
||||
return;
|
||||
removePendingConnection(conn);
|
||||
char reason[256];
|
||||
stream->readString(reason);
|
||||
conn->onConnectionRejected(reason);
|
||||
conn->deleteObject();
|
||||
}
|
||||
|
||||
void NetInterface::handleDisconnect(const NetAddress *address, BitStream *stream)
|
||||
{
|
||||
NetConnection *conn = NetConnection::lookup(address);
|
||||
if(!conn)
|
||||
return;
|
||||
|
||||
U32 connectSequence;
|
||||
char reason[256];
|
||||
|
||||
stream->read(&connectSequence);
|
||||
stream->readString(reason);
|
||||
|
||||
if(conn->getSequence() != connectSequence)
|
||||
return;
|
||||
|
||||
conn->onDisconnect(reason);
|
||||
conn->deleteObject();
|
||||
}
|
||||
|
||||
void NetInterface::handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream)
|
||||
{
|
||||
}
|
||||
|
||||
void NetInterface::processClient()
|
||||
{
|
||||
NetObject::collapseDirtyList(); // collapse all the mask bits...
|
||||
for(NetConnection *walk = NetConnection::getConnectionList();
|
||||
walk; walk = walk->getNext())
|
||||
{
|
||||
if(walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection()))
|
||||
walk->checkPacketSend(false);
|
||||
}
|
||||
}
|
||||
|
||||
void NetInterface::processServer()
|
||||
{
|
||||
NetObject::collapseDirtyList(); // collapse all the mask bits...
|
||||
for(NetConnection *walk = NetConnection::getConnectionList();
|
||||
walk; walk = walk->getNext())
|
||||
{
|
||||
if(!walk->isConnectionToServer() && (walk->isLocalConnection() || walk->isNetworkConnection()))
|
||||
walk->checkPacketSend(false);
|
||||
}
|
||||
}
|
||||
|
||||
void NetInterface::startConnection(NetConnection *conn)
|
||||
{
|
||||
addPendingConnection(conn);
|
||||
conn->mConnectionSendCount = 0;
|
||||
conn->setConnectSequence(Platform::getVirtualMilliseconds());
|
||||
conn->setConnectionState(NetConnection::AwaitingChallengeResponse);
|
||||
|
||||
// This is a the client side of the connection, so set the connection to
|
||||
// server flag. We need to set this early so that if the connection times
|
||||
// out, its onRemove() will handle the cleanup properly.
|
||||
conn->setIsConnectionToServer();
|
||||
|
||||
// Everything set, so send off the request.
|
||||
sendConnectChallengeRequest(conn);
|
||||
}
|
||||
|
||||
void NetInterface::sendDisconnectPacket(NetConnection *conn, const char *reason)
|
||||
{
|
||||
Con::printf("Issuing Disconnect packet.");
|
||||
|
||||
// send a disconnect packet...
|
||||
U32 connectSequence = conn->getSequence();
|
||||
|
||||
BitStream *out = BitStream::getPacketStream();
|
||||
out->write(U8(Disconnect));
|
||||
out->write(connectSequence);
|
||||
out->writeString(reason);
|
||||
|
||||
BitStream::sendPacketStream(conn->getNetAddress());
|
||||
}
|
||||
|
||||
void NetInterface::checkTimeouts()
|
||||
{
|
||||
U32 time = Platform::getVirtualMilliseconds();
|
||||
if(time > mLastTimeoutCheckTime + TimeoutCheckInterval)
|
||||
{
|
||||
for(U32 i = 0; i < mPendingConnections.size();)
|
||||
{
|
||||
NetConnection *pending = mPendingConnections[i];
|
||||
|
||||
if(pending->getConnectionState() == NetConnection::AwaitingChallengeResponse &&
|
||||
time > pending->mConnectLastSendTime + ChallengeRetryTime)
|
||||
{
|
||||
if(pending->mConnectSendCount > ChallengeRetryCount)
|
||||
{
|
||||
pending->onConnectTimedOut();
|
||||
removePendingConnection(pending);
|
||||
pending->deleteObject();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
sendConnectChallengeRequest(pending);
|
||||
}
|
||||
else if(pending->getConnectionState() == NetConnection::AwaitingConnectResponse &&
|
||||
time > pending->mConnectLastSendTime + ConnectRetryTime)
|
||||
{
|
||||
if(pending->mConnectSendCount > ConnectRetryCount)
|
||||
{
|
||||
pending->onConnectTimedOut();
|
||||
removePendingConnection(pending);
|
||||
pending->deleteObject();
|
||||
continue;
|
||||
}
|
||||
else
|
||||
sendConnectRequest(pending);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
mLastTimeoutCheckTime = time;
|
||||
NetConnection *walk = NetConnection::getConnectionList();
|
||||
|
||||
while(walk)
|
||||
{
|
||||
NetConnection *next = walk->getNext();
|
||||
if(walk->checkTimeout(time))
|
||||
{
|
||||
// this baddie timed out
|
||||
walk->onTimedOut();
|
||||
walk->deleteObject();
|
||||
}
|
||||
walk = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#define F1(x, y, z) (z ^ (x & (y ^ z)))
|
||||
#define F2(x, y, z) F1(z, x, y)
|
||||
#define F3(x, y, z) (x ^ y ^ z)
|
||||
#define F4(x, y, z) (y ^ (x | ~z))
|
||||
|
||||
inline U32 rotlFixed(U32 x, unsigned int y)
|
||||
{
|
||||
return (x >> y) | (x << (32 - y));
|
||||
}
|
||||
|
||||
#define MD5STEP(f, w, x, y, z, data, s) w = rotlFixed(w + f(x, y, z) + data, s) + x
|
||||
|
||||
void NetInterface::computeNetMD5(const NetAddress *address, U32 connectSequence, U32 digest[4])
|
||||
{
|
||||
digest[0] = 0x67452301L;
|
||||
digest[1] = 0xefcdab89L;
|
||||
digest[2] = 0x98badcfeL;
|
||||
digest[3] = 0x10325476L;
|
||||
|
||||
|
||||
U32 a, b, c, d;
|
||||
|
||||
a=digest[0];
|
||||
b=digest[1];
|
||||
c=digest[2];
|
||||
d=digest[3];
|
||||
|
||||
U32 in[16];
|
||||
in[0] = address->type;
|
||||
in[1] = (U32(address->netNum[0]) << 24) |
|
||||
(U32(address->netNum[1]) << 16) |
|
||||
(U32(address->netNum[2]) << 8) |
|
||||
(U32(address->netNum[3]));
|
||||
in[2] = address->port;
|
||||
in[3] = connectSequence;
|
||||
for(U32 i = 0; i < 12; i++)
|
||||
in[i + 4] = mRandomHashData[i];
|
||||
|
||||
MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22);
|
||||
MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7);
|
||||
MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12);
|
||||
MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17);
|
||||
MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22);
|
||||
|
||||
MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20);
|
||||
MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5);
|
||||
MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9);
|
||||
MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14);
|
||||
MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20);
|
||||
|
||||
MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23);
|
||||
MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4);
|
||||
MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11);
|
||||
MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16);
|
||||
MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23);
|
||||
|
||||
MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21);
|
||||
MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6);
|
||||
MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10);
|
||||
MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15);
|
||||
MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21);
|
||||
|
||||
digest[0]+=a;
|
||||
digest[1]+=b;
|
||||
digest[2]+=c;
|
||||
digest[3]+=d;
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupBegin(NetInterface, "Global control functions for the netInterfaces.");
|
||||
|
||||
ConsoleFunction(allowConnections,void,2,2,"allowConnections(bool allow);"
|
||||
"@brief Sets whether or not the global NetInterface allows connections from remote hosts.\n\n"
|
||||
|
||||
"@param allow Set to true to allow remote connections.\n"
|
||||
|
||||
"@ingroup Networking\n")
|
||||
{
|
||||
TORQUE_UNUSED(argc);
|
||||
GNet->setAllowsConnections(dAtob(argv[1]));
|
||||
}
|
||||
|
||||
ConsoleFunctionGroupEnd(NetInterface);
|
||||
|
||||
142
Engine/source/sim/netInterface.h
Normal file
142
Engine/source/sim/netInterface.h
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _H_NETINTERFACE
|
||||
#define _H_NETINTERFACE
|
||||
|
||||
/// NetInterface class. Manages all valid and pending notify protocol connections.
|
||||
///
|
||||
/// @see NetConnection, GameConnection, NetObject, NetEvent
|
||||
class NetInterface
|
||||
{
|
||||
public:
|
||||
/// PacketType is encoded as the first byte of each packet. If the LSB of
|
||||
/// the first byte is set (i.e. if the type number is odd), then the packet
|
||||
/// is a data protocol packet, otherwise it's an OOB packet, suitable for
|
||||
/// use in strange protocols, like game querying or connection initialization.
|
||||
enum PacketTypes
|
||||
{
|
||||
MasterServerGameTypesRequest = 2,
|
||||
MasterServerGameTypesResponse = 4,
|
||||
MasterServerListRequest = 6,
|
||||
MasterServerListResponse = 8,
|
||||
GameMasterInfoRequest = 10,
|
||||
GameMasterInfoResponse = 12,
|
||||
GamePingRequest = 14,
|
||||
GamePingResponse = 16,
|
||||
GameInfoRequest = 18,
|
||||
GameInfoResponse = 20,
|
||||
GameHeartbeat = 22,
|
||||
GGCPacket = 24,
|
||||
ConnectChallengeRequest = 26,
|
||||
ConnectChallengeReject = 28,
|
||||
ConnectChallengeResponse = 30,
|
||||
ConnectRequest = 32,
|
||||
ConnectReject = 34,
|
||||
ConnectAccept = 36,
|
||||
Disconnect = 38,
|
||||
};
|
||||
protected:
|
||||
|
||||
Vector<NetConnection *> mPendingConnections; ///< List of connections that are in the startup phase.
|
||||
U32 mLastTimeoutCheckTime; ///< Last time all the active connections were checked for timeouts.
|
||||
U32 mRandomHashData[12]; ///< Data that gets hashed with connect challenge requests to prevent connection spoofing.
|
||||
bool mRandomDataInitialized; ///< Have we initialized our random number generator?
|
||||
bool mAllowConnections; ///< Is this NetInterface allowing connections at this time?
|
||||
|
||||
enum NetInterfaceConstants
|
||||
{
|
||||
MaxPendingConnects = 20, ///< Maximum number of pending connections. If new connection requests come in before
|
||||
ChallengeRetryCount = 4, ///< Number of times to send connect challenge requests before giving up.
|
||||
ChallengeRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect challenge.
|
||||
|
||||
ConnectRetryCount = 4, ///< Number of times to send connect requests before giving up.
|
||||
ConnectRetryTime = 2500, ///< Timeout interval in milliseconds before retrying connect request.
|
||||
TimeoutCheckInterval = 1500, ///< Interval in milliseconds between checking for connection timeouts.
|
||||
};
|
||||
|
||||
/// Initialize random data.
|
||||
void initRandomData();
|
||||
|
||||
/// @name Connection management
|
||||
/// Most of these are pretty self-explanatory.
|
||||
/// @{
|
||||
|
||||
void addPendingConnection(NetConnection *conn);
|
||||
NetConnection *findPendingConnection(const NetAddress *address, U32 packetSequence);
|
||||
void removePendingConnection(NetConnection *conn);
|
||||
|
||||
void sendConnectChallengeRequest(NetConnection *conn);
|
||||
void handleConnectChallengeRequest(const NetAddress *addr, BitStream *stream);
|
||||
|
||||
void handleConnectChallengeResponse(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void sendConnectRequest(NetConnection *conn);
|
||||
void handleConnectRequest(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void sendConnectAccept(NetConnection *conn);
|
||||
void handleConnectAccept(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void sendConnectReject(NetConnection *conn, const char *reason);
|
||||
void handleConnectReject(const NetAddress *address, BitStream *stream);
|
||||
|
||||
void handleDisconnect(const NetAddress *address, BitStream *stream);
|
||||
|
||||
/// @}
|
||||
|
||||
/// Calculate an MD5 sum representing a connection, and store it into addressDigest.
|
||||
void computeNetMD5(const NetAddress *address, U32 connectSequence, U32 addressDigest[4]);
|
||||
|
||||
public:
|
||||
NetInterface();
|
||||
|
||||
/// Returns whether or not this NetInterface allows connections from remote hosts.
|
||||
bool doesAllowConnections() { return mAllowConnections; }
|
||||
|
||||
/// Sets whether or not this NetInterface allows connections from remote hosts.
|
||||
void setAllowsConnections(bool conn) { mAllowConnections = conn; }
|
||||
|
||||
/// Dispatch function for processing all network packets through this NetInterface.
|
||||
virtual void processPacketReceiveEvent(NetAddress srcAddress, RawData packetData);
|
||||
|
||||
/// Handles all packets that don't fall into the category of connection handshake or game data.
|
||||
virtual void handleInfoPacket(const NetAddress *address, U8 packetType, BitStream *stream);
|
||||
|
||||
/// Checks all connections marked as client to server for packet sends.
|
||||
void processClient();
|
||||
|
||||
/// Checks all connections marked as server to client for packet sends.
|
||||
void processServer();
|
||||
|
||||
/// Begins the connection handshaking process for a connection.
|
||||
void startConnection(NetConnection *conn);
|
||||
|
||||
/// Checks for timeouts on all valid and pending connections.
|
||||
void checkTimeouts();
|
||||
|
||||
/// Send a disconnect packet on a connection, along with a reason.
|
||||
void sendDisconnectPacket(NetConnection *conn, const char *reason);
|
||||
};
|
||||
|
||||
/// The global net interface instance.
|
||||
extern NetInterface *GNet;
|
||||
#endif
|
||||
462
Engine/source/sim/netObject.cpp
Normal file
462
Engine/source/sim/netObject.cpp
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "console/simBase.h"
|
||||
#include "core/dnet.h"
|
||||
#include "sim/netConnection.h"
|
||||
#include "sim/netObject.h"
|
||||
#include "console/consoleTypes.h"
|
||||
#include "console/engineAPI.h"
|
||||
|
||||
IMPLEMENT_CONOBJECT(NetObject);
|
||||
|
||||
// More information can be found in the Torque Manual (CHM)
|
||||
ConsoleDocClass( NetObject,
|
||||
"@brief Superclass for all ghostable networked objects.\n\n"
|
||||
"@ingroup Networking\n");
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
NetObject *NetObject::mDirtyList = NULL;
|
||||
|
||||
NetObject::NetObject()
|
||||
{
|
||||
// netFlags will clear itself to 0
|
||||
mNetIndex = U32(-1);
|
||||
mFirstObjectRef = NULL;
|
||||
mPrevDirtyList = NULL;
|
||||
mNextDirtyList = NULL;
|
||||
mDirtyMaskBits = 0;
|
||||
}
|
||||
|
||||
NetObject::~NetObject()
|
||||
{
|
||||
if(mDirtyMaskBits)
|
||||
{
|
||||
if(mPrevDirtyList)
|
||||
mPrevDirtyList->mNextDirtyList = mNextDirtyList;
|
||||
else
|
||||
mDirtyList = mNextDirtyList;
|
||||
if(mNextDirtyList)
|
||||
mNextDirtyList->mPrevDirtyList = mPrevDirtyList;
|
||||
}
|
||||
}
|
||||
|
||||
String NetObject::describeSelf() const
|
||||
{
|
||||
String desc = Parent::describeSelf();
|
||||
|
||||
if( isClientObject() )
|
||||
desc += "|net: client";
|
||||
else
|
||||
desc += "|net: server";
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
void NetObject::setMaskBits(U32 orMask)
|
||||
{
|
||||
AssertFatal(orMask != 0, "Invalid net mask bits set.");
|
||||
AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state.");
|
||||
if(!mDirtyMaskBits)
|
||||
{
|
||||
AssertFatal(mNextDirtyList == NULL && mPrevDirtyList == NULL, "Object with zero mask already in list.");
|
||||
if(mDirtyList)
|
||||
{
|
||||
mNextDirtyList = mDirtyList;
|
||||
mDirtyList->mPrevDirtyList = this;
|
||||
}
|
||||
mDirtyList = this;
|
||||
}
|
||||
mDirtyMaskBits |= orMask;
|
||||
AssertFatal(mDirtyMaskBits == 0 || (mPrevDirtyList != NULL || mNextDirtyList != NULL || mDirtyList == this), "Invalid dirty list state.");
|
||||
}
|
||||
|
||||
void NetObject::clearMaskBits(U32 orMask)
|
||||
{
|
||||
if(isDeleted())
|
||||
return;
|
||||
if(mDirtyMaskBits)
|
||||
{
|
||||
mDirtyMaskBits &= ~orMask;
|
||||
if(!mDirtyMaskBits)
|
||||
{
|
||||
if(mPrevDirtyList)
|
||||
mPrevDirtyList->mNextDirtyList = mNextDirtyList;
|
||||
else
|
||||
mDirtyList = mNextDirtyList;
|
||||
if(mNextDirtyList)
|
||||
mNextDirtyList->mPrevDirtyList = mPrevDirtyList;
|
||||
mNextDirtyList = mPrevDirtyList = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
for(GhostInfo *walk = mFirstObjectRef; walk; walk = walk->nextObjectRef)
|
||||
{
|
||||
if(walk->updateMask && walk->updateMask == orMask)
|
||||
{
|
||||
walk->updateMask = 0;
|
||||
walk->connection->ghostPushToZero(walk);
|
||||
}
|
||||
else
|
||||
walk->updateMask &= ~orMask;
|
||||
}
|
||||
}
|
||||
|
||||
void NetObject::collapseDirtyList()
|
||||
{
|
||||
#ifdef TORQUE_DEBUG
|
||||
Vector<NetObject *> tempV;
|
||||
for(NetObject *t = mDirtyList; t; t = t->mNextDirtyList)
|
||||
tempV.push_back(t);
|
||||
#endif
|
||||
|
||||
for(NetObject *obj = mDirtyList; obj; )
|
||||
{
|
||||
NetObject *next = obj->mNextDirtyList;
|
||||
U32 dirtyMask = obj->mDirtyMaskBits;
|
||||
|
||||
obj->mNextDirtyList = NULL;
|
||||
obj->mPrevDirtyList = NULL;
|
||||
obj->mDirtyMaskBits = 0;
|
||||
|
||||
if(!obj->isDeleted() && dirtyMask)
|
||||
{
|
||||
for(GhostInfo *walk = obj->mFirstObjectRef; walk; walk = walk->nextObjectRef)
|
||||
{
|
||||
U32 orMask = obj->filterMaskBits(dirtyMask,walk->connection);
|
||||
if(!walk->updateMask && orMask)
|
||||
{
|
||||
walk->updateMask = orMask;
|
||||
walk->connection->ghostPushNonZero(walk);
|
||||
}
|
||||
else
|
||||
walk->updateMask |= orMask;
|
||||
}
|
||||
}
|
||||
obj = next;
|
||||
}
|
||||
mDirtyList = NULL;
|
||||
#ifdef TORQUE_DEBUG
|
||||
for(U32 i = 0; i < tempV.size(); i++)
|
||||
{
|
||||
AssertFatal(tempV[i]->mNextDirtyList == NULL && tempV[i]->mPrevDirtyList == NULL && tempV[i]->mDirtyMaskBits == 0, "Error in collapse");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
DefineEngineMethod( NetObject, scopeToClient, void, ( NetConnection* client),,
|
||||
"@brief Cause the NetObject to be forced as scoped on the specified NetConnection.\n\n"
|
||||
|
||||
"@param client The connection this object will always be scoped to\n\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Called to create new cameras in TorqueScript\n"
|
||||
"// %this - The active GameConnection\n"
|
||||
"// %spawnPoint - The spawn point location where we creat the camera\n"
|
||||
"function GameConnection::spawnCamera(%this, %spawnPoint)\n"
|
||||
"{\n"
|
||||
" // If this connection's camera exists\n"
|
||||
" if(isObject(%this.camera))\n"
|
||||
" {\n"
|
||||
" // Add it to the mission group to be cleaned up later\n"
|
||||
" MissionCleanup.add( %this.camera );\n\n"
|
||||
" // Force it to scope to the client side\n"
|
||||
" %this.camera.scopeToClient(%this);\n"
|
||||
" }\n"
|
||||
"}\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see clearScopeToClient()\n")
|
||||
{
|
||||
if(!client)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::General, "NetObject::scopeToClient: Couldn't find connection %s", client);
|
||||
return;
|
||||
}
|
||||
client->objectLocalScopeAlways(object);
|
||||
}
|
||||
|
||||
//ConsoleMethod(NetObject,scopeToClient,void,3,3,"(NetConnection %client)"
|
||||
// "Cause the NetObject to be forced as scoped on the specified NetConnection.")
|
||||
//{
|
||||
// TORQUE_UNUSED(argc);
|
||||
// NetConnection *conn;
|
||||
// if(!Sim::findObject(argv[2], conn))
|
||||
// {
|
||||
// Con::errorf(ConsoleLogEntry::General, "NetObject::scopeToClient: Couldn't find connection %s", argv[2]);
|
||||
// return;
|
||||
// }
|
||||
// conn->objectLocalScopeAlways(object);
|
||||
//}
|
||||
|
||||
DefineEngineMethod( NetObject, clearScopeToClient, void, ( NetConnection* client),,
|
||||
"@brief Undo the effects of a scopeToClient() call.\n\n"
|
||||
|
||||
"@param client The connection to remove this object's scoping from \n\n"
|
||||
|
||||
"@see scopeToClient()\n")
|
||||
{
|
||||
if(!client)
|
||||
{
|
||||
Con::errorf(ConsoleLogEntry::General, "NetObject::clearScopeToClient: Couldn't find connection %s", client);
|
||||
return;
|
||||
}
|
||||
client->objectLocalClearAlways(object);
|
||||
}
|
||||
|
||||
//ConsoleMethod(NetObject,clearScopeToClient,void,3,3,"clearScopeToClient(%client)"
|
||||
// "Undo the effects of a scopeToClient() call.")
|
||||
//{
|
||||
// TORQUE_UNUSED(argc);
|
||||
// NetConnection *conn;
|
||||
// if(!Sim::findObject(argv[2], conn))
|
||||
// {
|
||||
// Con::errorf(ConsoleLogEntry::General, "NetObject::clearScopeToClient: Couldn't find connection %s", argv[2]);
|
||||
// return;
|
||||
// }
|
||||
// conn->objectLocalClearAlways(object);
|
||||
//}
|
||||
|
||||
DefineEngineMethod( NetObject, setScopeAlways, void, (),,
|
||||
"@brief Always scope this object on all connections.\n\n"
|
||||
|
||||
"The object is marked as ScopeAlways and is immediately ghosted to "
|
||||
"all active connections. This function has no effect if the object "
|
||||
"is not marked as Ghostable.\n\n")
|
||||
{
|
||||
object->setScopeAlways();
|
||||
}
|
||||
|
||||
//ConsoleMethod(NetObject,setScopeAlways,void,2,2,"Always scope this object on all connections.")
|
||||
//{
|
||||
// TORQUE_UNUSED(argc); TORQUE_UNUSED(argv);
|
||||
// object->setScopeAlways();
|
||||
//}
|
||||
|
||||
void NetObject::setScopeAlways()
|
||||
{
|
||||
if(mNetFlags.test(Ghostable) && !mNetFlags.test(IsGhost))
|
||||
{
|
||||
mNetFlags.set(ScopeAlways);
|
||||
|
||||
// if it's a ghost always object, add it to the ghost always set
|
||||
// for ClientReps created later.
|
||||
|
||||
Sim::getGhostAlwaysSet()->addObject(this);
|
||||
|
||||
// add it to all Connections that already exist.
|
||||
|
||||
SimGroup *clientGroup = Sim::getClientGroup();
|
||||
SimGroup::iterator i;
|
||||
for(i = clientGroup->begin(); i != clientGroup->end(); i++)
|
||||
{
|
||||
NetConnection *con = (NetConnection *) (*i);
|
||||
if(con->isGhosting())
|
||||
con->objectInScope(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NetObject::clearScopeAlways()
|
||||
{
|
||||
if(!mNetFlags.test(IsGhost))
|
||||
{
|
||||
mNetFlags.clear(ScopeAlways);
|
||||
Sim::getGhostAlwaysSet()->removeObject(this);
|
||||
|
||||
// Un ghost this object from all the connections
|
||||
while(mFirstObjectRef)
|
||||
mFirstObjectRef->connection->detachObject(mFirstObjectRef);
|
||||
}
|
||||
}
|
||||
|
||||
bool NetObject::onAdd()
|
||||
{
|
||||
if(mNetFlags.test(ScopeAlways))
|
||||
setScopeAlways();
|
||||
|
||||
return Parent::onAdd();
|
||||
}
|
||||
|
||||
void NetObject::onRemove()
|
||||
{
|
||||
while(mFirstObjectRef)
|
||||
mFirstObjectRef->connection->detachObject(mFirstObjectRef);
|
||||
|
||||
Parent::onRemove();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
F32 NetObject::getUpdatePriority(CameraScopeQuery*, U32, S32 updateSkips)
|
||||
{
|
||||
return F32(updateSkips) * 0.1;
|
||||
}
|
||||
|
||||
U32 NetObject::packUpdate(NetConnection* conn, U32 mask, BitStream* stream)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void NetObject::unpackUpdate(NetConnection*, BitStream*)
|
||||
{
|
||||
}
|
||||
|
||||
void NetObject::onCameraScopeQuery(NetConnection *cr, CameraScopeQuery* /*camInfo*/)
|
||||
{
|
||||
// default behavior -
|
||||
// ghost everything that is ghostable
|
||||
|
||||
for (SimSetIterator obj(Sim::getRootGroup()); *obj; ++obj)
|
||||
{
|
||||
NetObject* nobj = dynamic_cast<NetObject*>(*obj);
|
||||
if (nobj)
|
||||
{
|
||||
AssertFatal(!nobj->mNetFlags.test(NetObject::Ghostable) || !nobj->mNetFlags.test(NetObject::IsGhost),
|
||||
"NetObject::onCameraScopeQuery: object marked both ghostable and as ghost");
|
||||
|
||||
// Some objects don't ever want to be ghosted
|
||||
if (!nobj->mNetFlags.test(NetObject::Ghostable))
|
||||
continue;
|
||||
if (!nobj->mNetFlags.test(NetObject::ScopeAlways))
|
||||
{
|
||||
// it's in scope...
|
||||
cr->objectInScope(nobj);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
void NetObject::initPersistFields()
|
||||
{
|
||||
Parent::initPersistFields();
|
||||
}
|
||||
|
||||
DefineEngineMethod( NetObject, getGhostID, S32, (),,
|
||||
"@brief Get the ghost index of this object from the server.\n\n"
|
||||
|
||||
"@returns The ghost ID of this NetObject on the server\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"%ghostID = LocalClientConnection.getGhostId( %serverObject );\n"
|
||||
"@endtsexample\n\n")
|
||||
{
|
||||
return object->getNetIndex();
|
||||
}
|
||||
|
||||
//ConsoleMethod( NetObject, getGhostID, S32, 2, 2, "")
|
||||
//{
|
||||
// return object->getNetIndex();
|
||||
//}
|
||||
|
||||
DefineEngineMethod( NetObject, getClientObject, S32, (),,
|
||||
"@brief Returns a pointer to the client object when on a local connection.\n\n"
|
||||
|
||||
"Short-Circuit-Networking: this is only valid for a local-client / singleplayer situation.\n\n"
|
||||
|
||||
"@returns the SimObject ID of the client object.\n"
|
||||
|
||||
"@tsexample\n"
|
||||
"// Psuedo-code, some values left out for this example\n"
|
||||
"%node = new ParticleEmitterNode(){};\n"
|
||||
"%clientObject = %node.getClientObject();\n"
|
||||
"if(isObject(%clientObject)\n"
|
||||
" %clientObject.setTransform(\"0 0 0\");\n\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see @ref local_connections")
|
||||
{
|
||||
NetObject *obj = object->getClientObject();
|
||||
if ( obj )
|
||||
return obj->getId();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//ConsoleMethod( NetObject, getClientObject, S32, 2, 2, "Short-Circuit-Netorking: this is only valid for a local-client / singleplayer situation." )
|
||||
//{
|
||||
// NetObject *obj = object->getClientObject();
|
||||
// if ( obj )
|
||||
// return obj->getId();
|
||||
//
|
||||
// return NULL;
|
||||
//}
|
||||
|
||||
DefineEngineMethod( NetObject, getServerObject, S32, (),,
|
||||
"@brief Returns a pointer to the client object when on a local connection.\n\n"
|
||||
|
||||
"Short-Circuit-Netorking: this is only valid for a local-client / singleplayer situation.\n\n"
|
||||
|
||||
"@returns The SimObject ID of the server object.\n"
|
||||
"@tsexample\n"
|
||||
"// Psuedo-code, some values left out for this example\n"
|
||||
"%node = new ParticleEmitterNode(){};\n"
|
||||
"%serverObject = %node.getServerObject();\n"
|
||||
"if(isObject(%serverObject)\n"
|
||||
" %serverObject.setTransform(\"0 0 0\");\n\n"
|
||||
"@endtsexample\n\n"
|
||||
|
||||
"@see @ref local_connections")
|
||||
{
|
||||
NetObject *obj = object->getServerObject();
|
||||
if ( obj )
|
||||
return obj->getId();
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//ConsoleMethod( NetObject, getServerObject, S32, 2, 2, "Short-Circuit-Netorking: this is only valid for a local-client / singleplayer situation." )
|
||||
//{
|
||||
// NetObject *obj = object->getServerObject();
|
||||
// if ( obj )
|
||||
// return obj->getId();
|
||||
//
|
||||
// return NULL;
|
||||
//}
|
||||
|
||||
DefineEngineMethod( NetObject, isClientObject, bool, (),,
|
||||
"@brief Called to check if an object resides on the clientside.\n\n"
|
||||
"@return True if the object resides on the client, false otherwise.")
|
||||
{
|
||||
return object->isClientObject();
|
||||
}
|
||||
|
||||
//ConsoleMethod( NetObject, isClientObject, bool, 2, 2, "Return true for client-side objects." )
|
||||
//{
|
||||
// return object->isClientObject();
|
||||
//}
|
||||
|
||||
DefineEngineMethod( NetObject, isServerObject, bool, (),,
|
||||
"@brief Checks if an object resides on the server.\n\n"
|
||||
"@return True if the object resides on the server, false otherwise.")
|
||||
{
|
||||
return object->isServerObject();
|
||||
}
|
||||
|
||||
//ConsoleMethod( NetObject, isServerObject, bool, 2, 2, "Return true for client-side objects." )
|
||||
//{
|
||||
// return object->isServerObject();
|
||||
//}
|
||||
449
Engine/source/sim/netObject.h
Normal file
449
Engine/source/sim/netObject.h
Normal file
|
|
@ -0,0 +1,449 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _NETOBJECT_H_
|
||||
#define _NETOBJECT_H_
|
||||
|
||||
#ifndef _SIMBASE_H_
|
||||
#include "console/simBase.h"
|
||||
#endif
|
||||
#ifndef _MMATH_H_
|
||||
#include "math/mMath.h"
|
||||
#endif
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
class NetConnection;
|
||||
class NetObject;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
struct CameraScopeQuery
|
||||
{
|
||||
NetObject *camera; ///< Pointer to the viewing object.
|
||||
Point3F pos; ///< Position in world space
|
||||
Point3F orientation; ///< Viewing vector in world space
|
||||
F32 fov; ///< Viewing angle/2
|
||||
F32 sinFov; ///< sin(fov/2);
|
||||
F32 cosFov; ///< cos(fov/2);
|
||||
F32 visibleDistance; ///< Visible distance.
|
||||
};
|
||||
|
||||
struct GhostInfo;
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
/// Superclass for ghostable networked objects.
|
||||
///
|
||||
/// @section NetObject_intro Introduction To NetObject And Ghosting
|
||||
///
|
||||
/// One of the most powerful aspects of Torque's networking code is its support
|
||||
/// for ghosting and prioritized, most-recent-state network updates. The way
|
||||
/// this works is a bit complex, but it is immensely efficient. Let's run
|
||||
/// through the steps that the server goes through for each client in this part
|
||||
/// of Torque's networking:
|
||||
/// - First, the server determines what objects are in-scope for the client.
|
||||
/// This is done by calling onCameraScopeQuery() on the object which is
|
||||
/// considered the "scope" object. This is usually the player object, but
|
||||
/// it can be something else. (For instance, the current vehicle, or a
|
||||
/// object we're remote controlling.)
|
||||
/// - Second, it ghosts them to the client; this is implemented in netGhost.cc.
|
||||
/// - Finally, it sends updates as needed, by checking the dirty list and packing
|
||||
/// updates.
|
||||
///
|
||||
/// There several significant advantages to using this networking system:
|
||||
/// - Efficient network usage, since we only send data that has changed. In addition,
|
||||
/// since we only care about most-recent data, if a packet is dropped, we don't waste
|
||||
/// effort trying to deliver stale data.
|
||||
/// - Cheating protection; since we don't deliver information about game objects which
|
||||
/// aren't in scope, we dramatically reduce the ability of clients to hack the game and
|
||||
/// gain a meaningful advantage. (For instance, they can't find out about things behind
|
||||
/// them, since objects behind them don't fall in scope.) In addition, since ghost IDs are
|
||||
/// assigned per-client, it's difficult for any sort of co-ordination between cheaters to
|
||||
/// occur.
|
||||
///
|
||||
/// NetConnection contains the Ghost Manager implementation, which deals with transferring data to
|
||||
/// the appropriate clients and keeping state in synch.
|
||||
///
|
||||
/// @section NetObject_Implementation An Example Implementation
|
||||
///
|
||||
/// The basis of the ghost implementation in Torque is NetObject. It tracks the dirty flags for the
|
||||
/// various states that the object trackers, and does some other book-keeping to allow more efficient
|
||||
/// operation of the networking layer.
|
||||
///
|
||||
/// Using a NetObject is very simple; let's go through a simple example implementation:
|
||||
///
|
||||
/// @code
|
||||
/// class SimpleNetObject : public NetObject
|
||||
/// {
|
||||
/// public:
|
||||
/// typedef NetObject Parent;
|
||||
/// DECLARE_CONOBJECT(SimpleNetObject);
|
||||
/// @endcode
|
||||
///
|
||||
/// Above is the standard boilerplate code for a Torque class. You can find out more about this in SimObject.
|
||||
///
|
||||
/// @code
|
||||
/// char message1[256];
|
||||
/// char message2[256];
|
||||
/// enum States {
|
||||
/// Message1Mask = BIT(0),
|
||||
/// Message2Mask = BIT(1),
|
||||
/// };
|
||||
/// @endcode
|
||||
///
|
||||
/// For our example, we're having two "states" that we keep track of, message1 and message2. In a real
|
||||
/// object, we might map our states to health and position, or some other set of fields. You have 32
|
||||
/// bits to work with, so it's possible to be very specific when defining states. In general, you
|
||||
/// should try to use as few states as possible (you never know when you'll need to expand your object's
|
||||
/// functionality!), and in fact, most of your fields will end up changing all at once, so it's not worth
|
||||
/// it to be too fine-grained. (As an example, position and velocity on Player are controlled by the same
|
||||
/// bit, as one rarely changes without the other changing, too.)
|
||||
///
|
||||
/// @code
|
||||
/// SimpleNetObject()
|
||||
/// {
|
||||
/// // in order for an object to be considered by the network system,
|
||||
/// // the Ghostable net flag must be set.
|
||||
/// // the ScopeAlways flag indicates that the object is always scoped
|
||||
/// // on all active connections.
|
||||
/// mNetFlags.set(ScopeAlways | Ghostable);
|
||||
/// dStrcpy(message1, "Hello World 1!");
|
||||
/// dStrcpy(message2, "Hello World 2!");
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Here is the constructor. Here, you see that we initialize our net flags to show that
|
||||
/// we should always be scoped, and that we're to be taken into consideration for ghosting. We
|
||||
/// also provide some initial values for the message fields.
|
||||
///
|
||||
/// @code
|
||||
/// U32 packUpdate(NetConnection *, U32 mask, BitStream *stream)
|
||||
/// {
|
||||
/// // check which states need to be updated, and update them
|
||||
/// if(stream->writeFlag(mask & Message1Mask))
|
||||
/// stream->writeString(message1);
|
||||
/// if(stream->writeFlag(mask & Message2Mask))
|
||||
/// stream->writeString(message2);
|
||||
///
|
||||
/// // the return value from packUpdate can set which states still
|
||||
/// // need to be updated for this object.
|
||||
/// return 0;
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Here's half of the meat of the networking code, the packUpdate() function. (The other half, unpackUpdate(),
|
||||
/// we'll get to in a second.) The comments in the code pretty much explain everything, however, notice that the
|
||||
/// code follows a pattern of if(writeFlag(mask & StateMask)) { ... write data ... }. The packUpdate()/unpackUpdate()
|
||||
/// functions are responsible for reading and writing the dirty bits to the bitstream by themselves.
|
||||
///
|
||||
/// @code
|
||||
/// void unpackUpdate(NetConnection *, BitStream *stream)
|
||||
/// {
|
||||
/// // the unpackUpdate function must be symmetrical to packUpdate
|
||||
/// if(stream->readFlag())
|
||||
/// {
|
||||
/// stream->readString(message1);
|
||||
/// Con::printf("Got message1: %s", message1);
|
||||
/// }
|
||||
/// if(stream->readFlag())
|
||||
/// {
|
||||
/// stream->readString(message2);
|
||||
/// Con::printf("Got message2: %s", message2);
|
||||
/// }
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// The other half of the networking code in any NetObject, unpackUpdate(). In our simple example, all that
|
||||
/// the code does is print the new messages to the console; however, in a more advanced object, you might
|
||||
/// trigger animations, update complex object properties, or even spawn new objects, based on what packet
|
||||
/// data you unpack.
|
||||
///
|
||||
/// @code
|
||||
/// void setMessage1(const char *msg)
|
||||
/// {
|
||||
/// setMaskBits(Message1Mask);
|
||||
/// dStrcpy(message1, msg);
|
||||
/// }
|
||||
/// void setMessage2(const char *msg)
|
||||
/// {
|
||||
/// setMaskBits(Message2Mask);
|
||||
/// dStrcpy(message2, msg);
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Here are the accessors for the two properties. It is good to encapsulate your state
|
||||
/// variables, so that you don't have to remember to make a call to setMaskBits every time you change
|
||||
/// anything; the accessors can do it for you. In a more complex object, you might need to set
|
||||
/// multiple mask bits when you change something; this can be done using the | operator, for instance,
|
||||
/// setMaskBits( Message1Mask | Message2Mask ); if you changed both messages.
|
||||
///
|
||||
/// @code
|
||||
/// IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
|
||||
///
|
||||
/// ConsoleMethod(SimpleNetObject, setMessage1, void, 3, 3, "(string msg) Set message 1.")
|
||||
/// {
|
||||
/// object->setMessage1(argv[2]);
|
||||
/// }
|
||||
///
|
||||
/// ConsoleMethod(SimpleNetObject, setMessage2, void, 3, 3, "(string msg) Set message 2.")
|
||||
/// {
|
||||
/// object->setMessage2(argv[2]);
|
||||
/// }
|
||||
/// @endcode
|
||||
///
|
||||
/// Finally, we use the NetObject implementation macro, IMPLEMENT_CO_NETOBJECT_V1(), to implement our
|
||||
/// NetObject. It is important that we use this, as it makes Torque perform certain initialization tasks
|
||||
/// that allow us to send the object over the network. IMPLEMENT_CONOBJECT() doesn't perform these tasks, see
|
||||
/// the documentation on AbstractClassRep for more details.
|
||||
///
|
||||
/// @nosubgrouping
|
||||
class NetObject: public SimObject
|
||||
{
|
||||
// The Ghost Manager needs read/write access
|
||||
friend class NetConnection;
|
||||
friend struct GhostInfo;
|
||||
friend class ProcessList;
|
||||
|
||||
// Not the best way to do this, but the event needs access to mNetFlags
|
||||
friend class GhostAlwaysObjectEvent;
|
||||
|
||||
private:
|
||||
typedef SimObject Parent;
|
||||
|
||||
/// Mask indicating which states are dirty and need to be retransmitted on this
|
||||
/// object.
|
||||
U32 mDirtyMaskBits;
|
||||
|
||||
/// @name Dirty List
|
||||
///
|
||||
/// Whenever a NetObject becomes "dirty", we add it to the dirty list.
|
||||
/// We also remove ourselves on the destructor.
|
||||
///
|
||||
/// This is done so that when we want to send updates (in NetConnection),
|
||||
/// it's very fast to find the objects that need to be updated.
|
||||
/// @{
|
||||
|
||||
/// Static pointer to the head of the dirty NetObject list.
|
||||
static NetObject *mDirtyList;
|
||||
|
||||
/// Next item in the dirty list...
|
||||
NetObject *mPrevDirtyList;
|
||||
|
||||
/// Previous item in the dirty list...
|
||||
NetObject *mNextDirtyList;
|
||||
|
||||
/// @}
|
||||
protected:
|
||||
|
||||
/// Pointer to the server object on a local connection.
|
||||
/// @see getServerObject
|
||||
SimObjectPtr<NetObject> mServerObject;
|
||||
|
||||
/// Pointer to the client object on a local connection.
|
||||
/// @see getClientObject
|
||||
SimObjectPtr<NetObject> mClientObject;
|
||||
|
||||
enum NetFlags
|
||||
{
|
||||
IsGhost = BIT(1), ///< This is a ghost.
|
||||
ScopeAlways = BIT(6), ///< Object always ghosts to clients.
|
||||
ScopeLocal = BIT(7), ///< Ghost only to local client.
|
||||
Ghostable = BIT(8), ///< Set if this object CAN ghost.
|
||||
|
||||
MaxNetFlagBit = 15
|
||||
};
|
||||
|
||||
BitSet32 mNetFlags; ///< Flag values from NetFlags
|
||||
U32 mNetIndex; ///< The index of this ghost in the GhostManager on the server.
|
||||
|
||||
GhostInfo *mFirstObjectRef; ///< Head of a linked list storing GhostInfos referencing this NetObject.
|
||||
|
||||
public:
|
||||
NetObject();
|
||||
~NetObject();
|
||||
|
||||
virtual String describeSelf() const;
|
||||
|
||||
/// @name Miscellaneous
|
||||
/// @{
|
||||
DECLARE_CONOBJECT(NetObject);
|
||||
static void initPersistFields();
|
||||
bool onAdd();
|
||||
void onRemove();
|
||||
/// @}
|
||||
|
||||
static void collapseDirtyList();
|
||||
|
||||
/// Used to mark a bit as dirty; ie, that its corresponding set of fields need to be transmitted next update.
|
||||
///
|
||||
/// @param orMask Bit(s) to set
|
||||
virtual void setMaskBits(U32 orMask);
|
||||
|
||||
/// Clear the specified bits from the dirty mask.
|
||||
///
|
||||
/// @param orMask Bits to clear
|
||||
virtual void clearMaskBits(U32 orMask);
|
||||
virtual U32 filterMaskBits(U32 mask, NetConnection * connection) { return mask; }
|
||||
|
||||
/// Scope the object to all connections.
|
||||
///
|
||||
/// The object is marked as ScopeAlways and is immediately ghosted to
|
||||
/// all active connections. This function has no effect if the object
|
||||
/// is not marked as Ghostable.
|
||||
void setScopeAlways();
|
||||
|
||||
/// Stop scoping the object to all connections.
|
||||
///
|
||||
/// The object's ScopeAlways flag is cleared and the object is removed from
|
||||
/// all current active connections.
|
||||
void clearScopeAlways();
|
||||
|
||||
/// This returns a value which is used to prioritize which objects need to be updated.
|
||||
///
|
||||
/// In NetObject, our returned priority is 0.1 * updateSkips, so that less recently
|
||||
/// updated objects are more likely to be updated.
|
||||
///
|
||||
/// In subclasses, this can be adjusted. For instance, ShapeBase provides priority
|
||||
/// based on proximity to the camera.
|
||||
///
|
||||
/// @param focusObject Information from a previous call to onCameraScopeQuery.
|
||||
/// @param updateMask Current update mask.
|
||||
/// @param updateSkips Number of ticks we haven't been updated for.
|
||||
/// @returns A floating point value indicating priority. These are typically < 5.0.
|
||||
virtual F32 getUpdatePriority(CameraScopeQuery *focusObject, U32 updateMask, S32 updateSkips);
|
||||
|
||||
/// Instructs this object to pack its state for transfer over the network.
|
||||
///
|
||||
/// @param conn Net connection being used
|
||||
/// @param mask Mask indicating fields to transmit.
|
||||
/// @param stream Bitstream to pack data to
|
||||
///
|
||||
/// @returns Any bits which were not dealt with. The value is stored by the networking
|
||||
/// system. Don't set bits you weren't passed.
|
||||
virtual U32 packUpdate(NetConnection * conn, U32 mask, BitStream *stream);
|
||||
|
||||
/// Instructs this object to read state data previously packed with packUpdate.
|
||||
///
|
||||
/// @param conn Net connection being used
|
||||
/// @param stream stream to read from
|
||||
virtual void unpackUpdate(NetConnection * conn, BitStream *stream);
|
||||
|
||||
/// Queries the object about information used to determine scope.
|
||||
///
|
||||
/// Something that is 'in scope' is somehow interesting to the client.
|
||||
///
|
||||
/// If we are a NetConnection's scope object, it calls this method to determine
|
||||
/// how things should be scoped; basically, we tell it our field of view with camInfo,
|
||||
/// and have the opportunity to manually mark items as "in scope" as we see fit.
|
||||
///
|
||||
/// By default, we just mark all ghostable objects as in scope.
|
||||
///
|
||||
/// @param cr Net connection requesting scope information.
|
||||
/// @param camInfo Information about what this object can see.
|
||||
virtual void onCameraScopeQuery(NetConnection *cr, CameraScopeQuery *camInfo);
|
||||
|
||||
/// Get the ghost index of this object.
|
||||
U32 getNetIndex() { return mNetIndex; }
|
||||
|
||||
bool isServerObject() const; ///< Is this a server object?
|
||||
bool isClientObject() const; ///< Is this a client object?
|
||||
|
||||
bool isGhost() const; ///< Is this is a ghost?
|
||||
bool isScopeLocal() const; ///< Should this object only be visible to the client which created it?
|
||||
bool isScopeable() const; ///< Is this object subject to scoping?
|
||||
bool isGhostable() const; ///< Is this object ghostable?
|
||||
bool isGhostAlways() const; ///< Should this object always be ghosted?
|
||||
|
||||
|
||||
/// @name Short-Circuited Networking
|
||||
///
|
||||
/// When we are running with client and server on the same system (which can happen be either
|
||||
/// when we are doing a single player game, or if we're hosting a multiplayer game and having
|
||||
/// someone playing on the same instance), we can do some short circuited code to enhance
|
||||
/// performance.
|
||||
///
|
||||
/// These variables are used to make it simpler; if we are running in short-circuited mode,
|
||||
/// the ghosted client gets the server object while the server gets the client object.
|
||||
///
|
||||
/// @note "Premature optimization is the root of all evil" - Donald Knuth. The current codebase
|
||||
/// uses this feature in three small places, mostly for non-speed-related purposes.
|
||||
///
|
||||
/// @{
|
||||
|
||||
/// Returns a pointer to the server object when on a local connection.
|
||||
NetObject* getServerObject() const { return mServerObject; }
|
||||
|
||||
/// Returns a pointer to the client object when on a local connection.
|
||||
NetObject* getClientObject() const { return mClientObject; }
|
||||
|
||||
/// Template form for the callers convenience.
|
||||
template < class T >
|
||||
static T* getServerObject( T *netObj ) { return static_cast<T*>( netObj->getServerObject() ); }
|
||||
|
||||
/// Template form for the callers convenience.
|
||||
template < class T >
|
||||
static T* getClientObject( T *netObj ) { return static_cast<T*>( netObj->getClientObject() ); }
|
||||
|
||||
/// @}
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
inline bool NetObject::isGhost() const
|
||||
{
|
||||
return mNetFlags.test(IsGhost);
|
||||
}
|
||||
|
||||
inline bool NetObject::isClientObject() const
|
||||
{
|
||||
return mNetFlags.test(IsGhost);
|
||||
}
|
||||
|
||||
inline bool NetObject::isServerObject() const
|
||||
{
|
||||
return !mNetFlags.test(IsGhost);
|
||||
}
|
||||
|
||||
inline bool NetObject::isScopeLocal() const
|
||||
{
|
||||
return mNetFlags.test(ScopeLocal);
|
||||
}
|
||||
|
||||
inline bool NetObject::isScopeable() const
|
||||
{
|
||||
return mNetFlags.test(Ghostable) && !mNetFlags.test(ScopeAlways);
|
||||
}
|
||||
|
||||
inline bool NetObject::isGhostable() const
|
||||
{
|
||||
return mNetFlags.test(Ghostable);
|
||||
}
|
||||
|
||||
inline bool NetObject::isGhostAlways() const
|
||||
{
|
||||
AssertFatal(mNetFlags.test(Ghostable) || mNetFlags.test(ScopeAlways) == false,
|
||||
"That's strange, a ScopeAlways non-ghostable object? Something wrong here");
|
||||
return mNetFlags.test(Ghostable) && mNetFlags.test(ScopeAlways);
|
||||
}
|
||||
|
||||
#endif
|
||||
281
Engine/source/sim/netStringTable.cpp
Normal file
281
Engine/source/sim/netStringTable.cpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// 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 "core/dnet.h"
|
||||
#include "core/strings/stringFunctions.h"
|
||||
#include "core/stringTable.h"
|
||||
#include "console/engineAPI.h"
|
||||
#include "sim/netStringTable.h"
|
||||
|
||||
|
||||
NetStringTable *gNetStringTable = NULL;
|
||||
|
||||
NetStringTable::NetStringTable()
|
||||
{
|
||||
firstFree = 1;
|
||||
firstValid = 1;
|
||||
|
||||
table = (Entry *) dMalloc(sizeof(Entry) * InitialSize);
|
||||
size = InitialSize;
|
||||
for(U32 i = 0; i < InitialSize; i++)
|
||||
{
|
||||
table[i].next = i + 1;
|
||||
table[i].refCount = 0;
|
||||
table[i].scriptRefCount = 0;
|
||||
}
|
||||
table[InitialSize-1].next = InvalidEntry;
|
||||
for(U32 j = 0; j < HashTableSize; j++)
|
||||
hashTable[j] = 0;
|
||||
allocator = new DataChunker(DataChunkerSize);
|
||||
}
|
||||
|
||||
NetStringTable::~NetStringTable()
|
||||
{
|
||||
delete allocator;
|
||||
dFree( table );
|
||||
}
|
||||
|
||||
void NetStringTable::incStringRef(U32 id)
|
||||
{
|
||||
AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero.");
|
||||
table[id].refCount++;
|
||||
}
|
||||
|
||||
void NetStringTable::incStringRefScript(U32 id)
|
||||
{
|
||||
AssertFatal(table[id].refCount != 0 || table[id].scriptRefCount != 0 , "Cannot inc ref count from zero.");
|
||||
table[id].scriptRefCount++;
|
||||
}
|
||||
|
||||
U32 NetStringTable::addString(const char *string)
|
||||
{
|
||||
U32 hash = _StringTable::hashString(string);
|
||||
U32 bucket = hash % HashTableSize;
|
||||
for(U32 walk = hashTable[bucket];walk; walk = table[walk].next)
|
||||
{
|
||||
if(!dStrcmp(table[walk].string, string))
|
||||
{
|
||||
table[walk].refCount++;
|
||||
return walk;
|
||||
}
|
||||
}
|
||||
U32 e = firstFree;
|
||||
firstFree = table[e].next;
|
||||
if(firstFree == InvalidEntry)
|
||||
{
|
||||
// in this case, we should expand the table for next time...
|
||||
U32 newSize = size * 2;
|
||||
table = (Entry *) dRealloc(table, newSize * sizeof(Entry));
|
||||
for(U32 i = size; i < newSize; i++)
|
||||
{
|
||||
table[i].next = i + 1;
|
||||
table[i].refCount = 0;
|
||||
table[i].scriptRefCount = 0;
|
||||
}
|
||||
firstFree = size;
|
||||
table[newSize - 1].next = InvalidEntry;
|
||||
size = newSize;
|
||||
}
|
||||
table[e].refCount++;
|
||||
table[e].string = (char *) allocator->alloc(dStrlen(string) + 1);
|
||||
dStrcpy(table[e].string, string);
|
||||
table[e].next = hashTable[bucket];
|
||||
hashTable[bucket] = e;
|
||||
table[e].link = firstValid;
|
||||
table[firstValid].prevLink = e;
|
||||
firstValid = e;
|
||||
table[e].prevLink = 0;
|
||||
return e;
|
||||
}
|
||||
|
||||
U32 GameAddTaggedString(const char *string)
|
||||
{
|
||||
return gNetStringTable->addString(string);
|
||||
}
|
||||
|
||||
const char *NetStringTable::lookupString(U32 id)
|
||||
{
|
||||
if(table[id].refCount == 0 && table[id].scriptRefCount == 0)
|
||||
return NULL;
|
||||
return table[id].string;
|
||||
}
|
||||
|
||||
void NetStringTable::removeString(U32 id, bool script)
|
||||
{
|
||||
if(!script)
|
||||
{
|
||||
AssertFatal(table[id].refCount != 0, "Error, ref count is already 0!!");
|
||||
if(--table[id].refCount)
|
||||
return;
|
||||
if(table[id].scriptRefCount)
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If both ref counts are already 0, this id is not valid. Ignore
|
||||
// the remove
|
||||
if (table[id].scriptRefCount == 0 && table[id].refCount == 0)
|
||||
return;
|
||||
|
||||
if(table[id].scriptRefCount == 0 && table[id].refCount)
|
||||
{
|
||||
Con::errorf("removeTaggedString failed! Ref count is already 0 for string: %s", table[id].string);
|
||||
return;
|
||||
}
|
||||
if(--table[id].scriptRefCount)
|
||||
return;
|
||||
if(table[id].refCount)
|
||||
return;
|
||||
}
|
||||
// unlink first:
|
||||
U32 prev = table[id].prevLink;
|
||||
U32 next = table[id].link;
|
||||
if(next)
|
||||
table[next].prevLink = prev;
|
||||
if(prev)
|
||||
table[prev].link = next;
|
||||
else
|
||||
firstValid = next;
|
||||
// remove it from the hash table
|
||||
U32 hash = _StringTable::hashString(table[id].string);
|
||||
U32 bucket = hash % HashTableSize;
|
||||
for(U32 *walk = &hashTable[bucket];*walk; walk = &table[*walk].next)
|
||||
{
|
||||
if(*walk == id)
|
||||
{
|
||||
*walk = table[id].next;
|
||||
break;
|
||||
}
|
||||
}
|
||||
table[id].next = firstFree;
|
||||
firstFree = id;
|
||||
}
|
||||
|
||||
void NetStringTable::repack()
|
||||
{
|
||||
DataChunker *newAllocator = new DataChunker(DataChunkerSize);
|
||||
for(U32 walk = firstValid; walk; walk = table[walk].link)
|
||||
{
|
||||
const char *prevStr = table[walk].string;
|
||||
|
||||
|
||||
table[walk].string = (char *) newAllocator->alloc(dStrlen(prevStr) + 1);
|
||||
dStrcpy(table[walk].string, prevStr);
|
||||
}
|
||||
delete allocator;
|
||||
allocator = newAllocator;
|
||||
}
|
||||
|
||||
void NetStringTable::create()
|
||||
{
|
||||
AssertFatal(gNetStringTable == NULL, "Error, calling NetStringTable::create twice.");
|
||||
gNetStringTable = new NetStringTable();
|
||||
}
|
||||
|
||||
void NetStringTable::destroy()
|
||||
{
|
||||
AssertFatal(gNetStringTable != NULL, "Error, not calling NetStringTable::create.");
|
||||
delete gNetStringTable;
|
||||
gNetStringTable = NULL;
|
||||
}
|
||||
|
||||
void NetStringTable::expandString(NetStringHandle &inString, char *buf, U32 bufSize, U32 argc, const char **argv)
|
||||
{
|
||||
buf[0] = StringTagPrefixByte;
|
||||
dSprintf(buf + 1, bufSize - 1, "%d ", inString.getIndex());
|
||||
|
||||
const char *string = inString.getString();
|
||||
if (string != NULL) {
|
||||
U32 index = dStrlen(buf);
|
||||
while(index < bufSize)
|
||||
{
|
||||
char c = *string++;
|
||||
if(c == '%')
|
||||
{
|
||||
c = *string++;
|
||||
if(c >= '1' && c <= '9')
|
||||
{
|
||||
U32 strIndex = c - '1';
|
||||
if(strIndex >= argc)
|
||||
continue;
|
||||
// start copying out of arg index
|
||||
const char *copy = argv[strIndex];
|
||||
// skip past any tags:
|
||||
if(*copy == StringTagPrefixByte)
|
||||
{
|
||||
while(*copy && *copy != ' ')
|
||||
copy++;
|
||||
if(*copy)
|
||||
copy++;
|
||||
}
|
||||
|
||||
while(*copy && index < bufSize)
|
||||
buf[index++] = *copy++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
buf[index++] = c;
|
||||
if(!c)
|
||||
break;
|
||||
}
|
||||
buf[bufSize - 1] = 0;
|
||||
} else {
|
||||
dStrcat(buf, "<NULL>");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef TORQUE_DEBUG
|
||||
|
||||
void NetStringTable::dumpToConsole()
|
||||
{
|
||||
U32 count = 0;
|
||||
S32 maxIndex = -1;
|
||||
for ( U32 i = 0; i < size; i++ )
|
||||
{
|
||||
if ( table[i].refCount > 0 || table[i].scriptRefCount > 0)
|
||||
{
|
||||
Con::printf( "%d: \"%c%s%c\" REF: %d", i, 0x10, table[i].string, 0x11, table[i].refCount );
|
||||
if ( maxIndex == -1 || table[i].refCount > table[maxIndex].refCount )
|
||||
maxIndex = i;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
Con::printf( ">> STRINGS: %d MAX REF COUNT: %d \"%c%s%c\" <<",
|
||||
count,
|
||||
( maxIndex == -1 ) ? 0 : table[maxIndex].refCount,
|
||||
0x10,
|
||||
( maxIndex == -1 ) ? "" : table[maxIndex].string,
|
||||
0x11 );
|
||||
}
|
||||
|
||||
DefineEngineFunction( dumpNetStringTable, void, (),,
|
||||
"@brief Dump the current contents of the networked string table to the console.\n\n"
|
||||
"The results are returned in three columns. The first column is the network string ID. "
|
||||
"The second column is the string itself. The third column is the reference count to the "
|
||||
"network string.\n\n"
|
||||
"@note This function is available only in debug builds.\n\n"
|
||||
"@ingroup Networking" )
|
||||
{
|
||||
gNetStringTable->dumpToConsole();
|
||||
}
|
||||
|
||||
#endif // DEBUG
|
||||
161
Engine/source/sim/netStringTable.h
Normal file
161
Engine/source/sim/netStringTable.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
//-----------------------------------------------------------------------------
|
||||
// Copyright (c) 2012 GarageGames, LLC
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
// IN THE SOFTWARE.
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
#ifndef _NETSTRINGTABLE_H_
|
||||
#define _NETSTRINGTABLE_H_
|
||||
|
||||
#ifndef _DATACHUNKER_H_
|
||||
#include "core/dataChunker.h"
|
||||
#endif
|
||||
#ifndef _CONSOLE_H_
|
||||
#include "console/console.h"
|
||||
#endif
|
||||
|
||||
class NetConnection;
|
||||
|
||||
class NetStringHandle;
|
||||
extern U32 GameAddTaggedString(const char *string);
|
||||
|
||||
class NetStringTable
|
||||
{
|
||||
friend class NetStringHandle;
|
||||
friend U32 GameAddTaggedString(const char *string);
|
||||
|
||||
#ifdef TORQUE_DEBUG_NET
|
||||
friend class RemoteCommandEvent;
|
||||
#endif
|
||||
|
||||
enum Constants {
|
||||
InitialSize = 16,
|
||||
InvalidEntry = 0xFFFFFFFF,
|
||||
HashTableSize = 2128,
|
||||
DataChunkerSize = 65536
|
||||
};
|
||||
struct Entry
|
||||
{
|
||||
char *string;
|
||||
U32 refCount;
|
||||
U32 scriptRefCount;
|
||||
U32 next;
|
||||
U32 link;
|
||||
U32 prevLink;
|
||||
U32 seq;
|
||||
};
|
||||
U32 size;
|
||||
U32 firstFree;
|
||||
U32 firstValid;
|
||||
U32 sequenceCount;
|
||||
|
||||
Entry *table;
|
||||
U32 hashTable[HashTableSize];
|
||||
DataChunker *allocator;
|
||||
|
||||
NetStringTable();
|
||||
~NetStringTable();
|
||||
|
||||
U32 addString(const char *string);
|
||||
|
||||
// XA: Moved this ones to public to avoid using the friend_ConsoleMethod hack.
|
||||
public:
|
||||
const char *lookupString(U32 id);
|
||||
void removeString(U32 id, bool script = false);
|
||||
void incStringRefScript(U32 id);
|
||||
|
||||
private:
|
||||
void incStringRef(U32 id);
|
||||
|
||||
void repack();
|
||||
public:
|
||||
static void create();
|
||||
static void destroy();
|
||||
|
||||
static void expandString(NetStringHandle &string, char *buf, U32 bufSize, U32 argc, const char **argv);
|
||||
|
||||
#if defined(TORQUE_DEBUG)
|
||||
void dumpToConsole();
|
||||
#endif // DEBUG
|
||||
};
|
||||
|
||||
extern NetStringTable *gNetStringTable;
|
||||
|
||||
// This class represents what is known as a "tagged string" in script.
|
||||
// It is essentially a networked version of the string table. The full string
|
||||
// is only transmitted once; then future references only need to transmit
|
||||
// the id.
|
||||
|
||||
class NetStringHandle
|
||||
{
|
||||
U32 index;
|
||||
public:
|
||||
NetStringHandle() { index = 0; }
|
||||
NetStringHandle(const NetStringHandle &string) {
|
||||
index = string.index;
|
||||
if(index)
|
||||
gNetStringTable->incStringRef(index);
|
||||
}
|
||||
NetStringHandle(const char *string) {
|
||||
index = gNetStringTable->addString(string);
|
||||
}
|
||||
NetStringHandle(U32 initIndex)
|
||||
{
|
||||
index = initIndex;
|
||||
if(index)
|
||||
gNetStringTable->incStringRef(index);
|
||||
}
|
||||
~NetStringHandle()
|
||||
{
|
||||
if(index)
|
||||
gNetStringTable->removeString(index);
|
||||
}
|
||||
|
||||
void setFromIndex(U32 newIndex)
|
||||
{
|
||||
if(index)
|
||||
gNetStringTable->removeString(index);
|
||||
index = newIndex;
|
||||
}
|
||||
|
||||
bool operator==(const NetStringHandle &s) const { return index == s.index; }
|
||||
bool operator!=(const NetStringHandle &s) const { return index != s.index; }
|
||||
|
||||
NetStringHandle &operator=(const NetStringHandle &s)
|
||||
{
|
||||
if(index)
|
||||
gNetStringTable->removeString(index);
|
||||
index = s.index;
|
||||
if(index)
|
||||
gNetStringTable->incStringRef(index);
|
||||
return *this;
|
||||
}
|
||||
const char *getString() const
|
||||
{
|
||||
if(index)
|
||||
return gNetStringTable->lookupString(index);
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
bool isNull() const { return index == 0; }
|
||||
bool isValidString() const { return index != 0; }
|
||||
U32 getIndex() const { return index; }
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue