Engine directory for ticket #1

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

File diff suppressed because it is too large Load diff

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

View file

@ -0,0 +1,198 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "platform/platform.h"
#include "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();
}
}
}

View 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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

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

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

File diff suppressed because it is too large Load diff

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

View 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

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

View 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

View 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

View file

@ -0,0 +1,161 @@
//-----------------------------------------------------------------------------
// Copyright (c) 2012 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#ifndef _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