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

View file

@ -0,0 +1,455 @@
//-----------------------------------------------------------------------------
// 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 "app/net/httpObject.h"
#include "platform/platform.h"
#include "platform/event.h"
#include "core/stream/fileStream.h"
#include "console/simBase.h"
#include "console/consoleInternal.h"
#include "console/engineAPI.h"
IMPLEMENT_CONOBJECT(HTTPObject);
ConsoleDocClass( HTTPObject,
"@brief Allows communications between the game and a server using HTTP.\n\n"
"HTTPObject is derrived from TCPObject and makes use of the same callbacks for dealing with "
"connections and received data. However, the way in which you use HTTPObject to connect "
"with a server is different than TCPObject. Rather than opening a connection, sending data, "
"waiting to receive data, and then closing the connection, you issue a get() or post() and "
"handle the response. The connection is automatically created and destroyed for you.\n\n"
"@tsexample\n"
"// In this example we'll retrieve the weather in Las Vegas using\n"
"// Google's API. The response is in XML which could be processed\n"
"// and used by the game using SimXMLDocument, but we'll just output\n"
"// the results to the console in this example.\n\n"
"// Define callbacks for our specific HTTPObject using our instance's\n"
"// name (WeatherFeed) as the namespace.\n\n"
"// Handle an issue with resolving the server's name\n"
"function WeatherFeed::onDNSFailed(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"DNSFailed\";\n\n"
" // Handle DNS failure\n"
"}\n\n"
"function WeatherFeed::onConnectFailed(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"ConnectFailed\";\n\n"
" // Handle connection failure\n"
"}\n\n"
"function WeatherFeed::onDNSResolved(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"DNSResolved\";\n\n"
"}\n\n"
"function WeatherFeed::onConnected(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"Connected\";\n\n"
" // Clear our buffer\n"
" %this.buffer = \"\";\n"
"}\n\n"
"function WeatherFeed::onDisconnect(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"Disconnected\";\n\n"
" // Output the buffer to the console\n"
" echo(\"Google Weather Results:\");\n"
" echo(%this.buffer);\n"
"}\n\n"
"// Handle a line from the server\n"
"function WeatherFeed::onLine(%this, %line)\n"
"{\n"
" // Store this line in out buffer\n"
" %this.buffer = %this.buffer @ %line;\n"
"}\n\n"
"// Create the HTTPObject\n"
"%feed = new HTTPObject(WeatherFeed);\n\n"
"// Define a dynamic field to store the last connection state\n"
"%feed.lastState = \"None\";\n\n"
"// Send the GET command\n"
"%feed.get(\"www.google.com:80\", \"/ig/api\", \"weather=Las-Vegas,US\");\n"
"@endtsexample\n\n"
"@see TCPObject\n"
"@ingroup Networking\n"
);
//--------------------------------------
HTTPObject::HTTPObject()
{
mHostName = 0;
mPath = 0;
mQuery = 0;
mPost = 0;
mBufferSave = 0;
}
HTTPObject::~HTTPObject()
{
dFree(mHostName);
dFree(mPath);
dFree(mQuery);
dFree(mPost);
mHostName = 0;
mPath = 0;
mQuery = 0;
mPost = 0;
dFree(mBufferSave);
}
//--------------------------------------
//--------------------------------------
void HTTPObject::get(const char *host, const char *path, const char *query)
{
if(mHostName)
dFree(mHostName);
if(mPath)
dFree(mPath);
if(mQuery)
dFree(mQuery);
if(mPost)
dFree(mPost);
if(mBufferSave)
dFree(mBufferSave);
mBufferSave = 0;
mHostName = dStrdup(host);
mPath = dStrdup(path);
if(query)
mQuery = dStrdup(query);
else
mQuery = NULL;
mPost = NULL;
connect(host);
}
void HTTPObject::post(const char *host, const char *path, const char *query, const char *post)
{
if(mHostName)
dFree(mHostName);
if(mPath)
dFree(mPath);
if(mQuery)
dFree(mQuery);
if(mPost)
dFree(mPost);
if(mBufferSave)
dFree(mBufferSave);
mBufferSave = 0;
mHostName = dStrdup(host);
mPath = dStrdup(path);
if(query && query[0])
mQuery = dStrdup(query);
else
mQuery = NULL;
mPost = dStrdup(post);
connect(host);
}
static char getHex(char c)
{
if(c <= 9)
return c + '0';
return c - 10 + 'A';
}
static S32 getHexVal(char c)
{
if(c >= '0' && c <= '9')
return c - '0';
else if(c >= 'A' && c <= 'Z')
return c - 'A' + 10;
else if(c >= 'a' && c <= 'z')
return c - 'a' + 10;
return -1;
}
void HTTPObject::expandPath(char *dest, const char *path, U32 destSize)
{
static bool asciiEscapeTableBuilt = false;
static bool asciiEscapeTable[256];
if(!asciiEscapeTableBuilt)
{
asciiEscapeTableBuilt = true;
U32 i;
for(i = 0; i <= ' '; i++)
asciiEscapeTable[i] = true;
for(;i <= 0x7F; i++)
asciiEscapeTable[i] = false;
for(;i <= 0xFF; i++)
asciiEscapeTable[i] = true;
asciiEscapeTable[static_cast<U32>('\"')] = true;
asciiEscapeTable[static_cast<U32>('_')] = true;
asciiEscapeTable[static_cast<U32>('\'')] = true;
asciiEscapeTable[static_cast<U32>('#')] = true;
asciiEscapeTable[static_cast<U32>('$')] = true;
asciiEscapeTable[static_cast<U32>('%')] = true;
asciiEscapeTable[static_cast<U32>('&')] = false;
asciiEscapeTable[static_cast<U32>('+')] = true;
asciiEscapeTable[static_cast<U32>('-')] = true;
asciiEscapeTable[static_cast<U32>('~')] = true;
}
U32 destIndex = 0;
U32 srcIndex = 0;
while(path[srcIndex] && destIndex < destSize - 3)
{
char c = path[srcIndex++];
if(asciiEscapeTable[static_cast<U32>(c)])
{
dest[destIndex++] = '%';
dest[destIndex++] = getHex((c >> 4) & 0xF);
dest[destIndex++] = getHex(c & 0xF);
}
else
dest[destIndex++] = c;
}
dest[destIndex] = 0;
}
//--------------------------------------
void HTTPObject::onConnected()
{
Parent::onConnected();
char expPath[8192];
char buffer[8192];
if(mQuery)
{
dSprintf(buffer, sizeof(buffer), "%s?%s", mPath, mQuery);
expandPath(expPath, buffer, sizeof(expPath));
}
else
expandPath(expPath, mPath, sizeof(expPath));
char *pt = dStrchr(mHostName, ':');
if(pt)
*pt = 0;
dSprintf(buffer, sizeof(buffer), "GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", expPath, mHostName);
if(pt)
*pt = ':';
send((U8*)buffer, dStrlen(buffer));
mParseState = ParsingStatusLine;
mChunkedEncoding = false;
}
void HTTPObject::onConnectFailed()
{
dFree(mHostName);
dFree(mPath);
dFree(mQuery);
mHostName = 0;
mPath = 0;
mQuery = 0;
Parent::onConnectFailed();
}
void HTTPObject::onDisconnect()
{
dFree(mHostName);
dFree(mPath);
dFree(mQuery);
mHostName = 0;
mPath = 0;
mQuery = 0;
Parent::onDisconnect();
}
bool HTTPObject::processLine(U8 *line)
{
if(mParseState == ParsingStatusLine)
{
mParseState = ParsingHeader;
}
else if(mParseState == ParsingHeader)
{
if(!dStricmp((char *) line, "transfer-encoding: chunked"))
mChunkedEncoding = true;
if(line[0] == 0)
{
if(mChunkedEncoding)
mParseState = ParsingChunkHeader;
else
mParseState = ProcessingBody;
return true;
}
}
else if(mParseState == ParsingChunkHeader)
{
if(line[0]) // strip off the crlf if necessary
{
mChunkSize = 0;
S32 hexVal;
while((hexVal = getHexVal(*line++)) != -1)
{
mChunkSize *= 16;
mChunkSize += hexVal;
}
if(mBufferSave)
{
mBuffer = mBufferSave;
mBufferSize = mBufferSaveSize;
mBufferSave = 0;
}
if(mChunkSize)
mParseState = ProcessingBody;
else
{
mParseState = ProcessingDone;
finishLastLine();
}
}
}
else
{
return Parent::processLine((UTF8*)line);
}
return true;
}
U32 HTTPObject::onDataReceive(U8 *buffer, U32 bufferLen)
{
U32 start = 0;
parseLine(buffer, &start, bufferLen);
return start;
}
//--------------------------------------
U32 HTTPObject::onReceive(U8 *buffer, U32 bufferLen)
{
if(mParseState == ProcessingBody)
{
if(mChunkedEncoding && bufferLen >= mChunkSize)
{
U32 ret = onDataReceive(buffer, mChunkSize);
mChunkSize -= ret;
if(mChunkSize == 0)
{
if(mBuffer)
{
mBufferSaveSize = mBufferSize;
mBufferSave = mBuffer;
mBuffer = 0;
mBufferSize = 0;
}
mParseState = ParsingChunkHeader;
}
return ret;
}
else
{
U32 ret = onDataReceive(buffer, bufferLen);
mChunkSize -= ret;
return ret;
}
}
else if(mParseState != ProcessingDone)
{
U32 start = 0;
parseLine(buffer, &start, bufferLen);
return start;
}
return bufferLen;
}
//--------------------------------------
DefineEngineMethod( HTTPObject, get, void, ( const char* Address, const char* requirstURI, const char* query ), ( "" ),
"@brief Send a GET command to a server to send or retrieve data.\n\n"
"@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
"@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
"@param query Optional. Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. "
"If you were building the URL manually, this is the text that follows the question mark. For example: http://www.google.com/ig/api?<b>weather=Las-Vegas,US</b>\n"
"@tsexample\n"
"// Create an HTTP object for communications\n"
"%httpObj = new HTTPObject();\n\n"
"// Specify a URL to transmit to\n"
"%url = \"www.garagegames.com:80\";\n\n"
"// Specify a URI to communicate with\n"
"%URI = \"/index.php\";\n\n"
"// Specify a query to send.\n"
"%query = \"\";\n\n"
"// Send the GET command to the server\n"
"%httpObj.get(%url,%URI,%query);\n"
"@endtsexample\n\n"
)
{
if( !query || !query[ 0 ] )
object->get(Address, requirstURI, NULL);
else
object->get(Address, requirstURI, query);
}
DefineEngineMethod( HTTPObject, post, void, ( const char* Address, const char* requirstURI, const char* query, const char* post ),,
"@brief Send POST command to a server to send or retrieve data.\n\n"
"@param Address HTTP web address to send this get call to. Be sure to include the port at the end (IE: \"www.garagegames.com:80\").\n"
"@param requirstURI Specific location on the server to access (IE: \"index.php\".)\n"
"@param query Actual data to transmit to the server. Can be anything required providing it sticks with limitations of the HTTP protocol. \n"
"@param post Submission data to be processed.\n"
"@note The post() method is currently non-functional.\n"
"@tsexample\n"
"// Create an HTTP object for communications\n"
"%httpObj = new HTTPObject();\n\n"
"// Specify a URL to transmit to\n"
"%url = \"www.garagegames.com:80\";\n\n"
"// Specify a URI to communicate with\n"
"%URI = \"/index.php\";\n\n"
"// Specify a query to send.\n"
"%query = \"\";\n\n"
"// Specify the submission data.\n"
"%post = \"\";\n\n"
"// Send the POST command to the server\n"
"%httpObj.POST(%url,%URI,%query,%post);\n"
"@endtsexample\n\n"
)
{
object->post(Address, requirstURI, query, post);
}

View file

@ -0,0 +1,81 @@
//-----------------------------------------------------------------------------
// 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 _HTTPOBJECT_H_
#define _HTTPOBJECT_H_
#ifndef _TVECTOR_H_
#include "core/util/tVector.h"
#endif
#ifndef _TCPOBJECT_H_
#include "app/net/tcpObject.h"
#endif
class HTTPObject : public TCPObject
{
private:
typedef TCPObject Parent;
protected:
enum ParseState {
ParsingStatusLine,
ParsingHeader,
ParsingChunkHeader,
ProcessingBody,
ProcessingDone,
};
ParseState mParseState;
U32 mTotalBytes;
U32 mBytesRemaining;
public:
U32 mStatus;
F32 mVersion;
U32 mContentLength;
bool mChunkedEncoding;
U32 mChunkSize;
const char *mContentType;
char *mHostName;
char *mPath;
char *mQuery;
char *mPost;
U8 *mBufferSave;
U32 mBufferSaveSize;
public:
static void expandPath(char *dest, const char *path, U32 destSize);
void get(const char *hostName, const char *urlName, const char *query);
void post(const char *host, const char *path, const char *query, const char *post);
HTTPObject();
~HTTPObject();
//static HTTPObject *find(U32 tag);
virtual U32 onDataReceive(U8 *buffer, U32 bufferLen);
virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data
virtual void onConnected();
virtual void onConnectFailed();
virtual void onDisconnect();
bool processLine(U8 *line);
DECLARE_CONOBJECT(HTTPObject);
};
#endif // _H_HTTPOBJECT_

View file

@ -0,0 +1,417 @@
//-----------------------------------------------------------------------------
// 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 "core/idGenerator.h"
#include "core/stream/bitStream.h"
#include "console/simBase.h"
#include "console/console.h"
#include "console/consoleTypes.h"
#include "sim/netConnection.h"
#include "sim/netObject.h"
#include "app/net/serverQuery.h"
#include "console/engineAPI.h"
//----------------------------------------------------------------
// remote procedure call console functions
//----------------------------------------------------------------
class RemoteCommandEvent : public NetEvent
{
public:
typedef NetEvent Parent;
enum {
MaxRemoteCommandArgs = 20,
CommandArgsBits = 5
};
private:
S32 mArgc;
char *mArgv[MaxRemoteCommandArgs + 1];
NetStringHandle mTagv[MaxRemoteCommandArgs + 1];
static char mBuf[1024];
public:
RemoteCommandEvent(S32 argc=0, const char **argv=NULL, NetConnection *conn = NULL)
{
mArgc = argc;
for(S32 i = 0; i < argc; i++)
{
if(argv[i][0] == StringTagPrefixByte)
{
char buffer[256];
mTagv[i+1] = NetStringHandle(dAtoi(argv[i]+1));
if(conn)
{
dSprintf(buffer + 1, sizeof(buffer) - 1, "%d", conn->getNetSendId(mTagv[i+1]));
buffer[0] = StringTagPrefixByte;
mArgv[i+1] = dStrdup(buffer);
}
}
else
mArgv[i+1] = dStrdup(argv[i]);
}
}
#ifdef TORQUE_DEBUG_NET
const char *getDebugName()
{
static char buffer[256];
dSprintf(buffer, sizeof(buffer), "%s [%s]", getClassName(), mTagv[1].isValidString() ? mTagv[1].getString() : "--unknown--" );
return buffer;
}
#endif
~RemoteCommandEvent()
{
for(S32 i = 0; i < mArgc; i++)
dFree(mArgv[i+1]);
}
virtual void pack(NetConnection* conn, BitStream *bstream)
{
bstream->writeInt(mArgc, CommandArgsBits);
// write it out reversed... why?
// automatic string substitution with later arguments -
// handled automatically by the system.
for(S32 i = 0; i < mArgc; i++)
conn->packString(bstream, mArgv[i+1]);
}
virtual void write(NetConnection* conn, BitStream *bstream)
{
pack(conn, bstream);
}
virtual void unpack(NetConnection* conn, BitStream *bstream)
{
mArgc = bstream->readInt(CommandArgsBits);
// read it out backwards
for(S32 i = 0; i < mArgc; i++)
{
conn->unpackString(bstream, mBuf);
mArgv[i+1] = dStrdup(mBuf);
}
}
virtual void process(NetConnection *conn)
{
static char idBuf[10];
// de-tag the command name
for(S32 i = mArgc - 1; i >= 0; i--)
{
char *arg = mArgv[i+1];
if(*arg == StringTagPrefixByte)
{
// it's a tag:
U32 localTag = dAtoi(arg + 1);
NetStringHandle tag = conn->translateRemoteStringId(localTag);
NetStringTable::expandString( tag,
mBuf,
sizeof(mBuf),
(mArgc - 1) - i,
(const char**)(mArgv + i + 2) );
dFree(mArgv[i+1]);
mArgv[i+1] = dStrdup(mBuf);
}
}
const char *rmtCommandName = dStrchr(mArgv[1], ' ') + 1;
if(conn->isConnectionToServer())
{
dStrcpy(mBuf, "clientCmd");
dStrcat(mBuf, rmtCommandName);
char *temp = mArgv[1];
mArgv[1] = mBuf;
Con::execute(mArgc, (const char **) mArgv+1);
mArgv[1] = temp;
}
else
{
dStrcpy(mBuf, "serverCmd");
dStrcat(mBuf, rmtCommandName);
char *temp = mArgv[1];
dSprintf(idBuf, sizeof(idBuf), "%d", conn->getId());
mArgv[0] = mBuf;
mArgv[1] = idBuf;
Con::execute(mArgc+1, (const char **) mArgv);
mArgv[1] = temp;
}
}
DECLARE_CONOBJECT(RemoteCommandEvent);
};
char RemoteCommandEvent::mBuf[1024];
IMPLEMENT_CO_NETEVENT_V1(RemoteCommandEvent);
ConsoleDocClass( RemoteCommandEvent,
"@brief Object used for remote procedure calls.\n\n"
"Not intended for game development, for exposing ConsoleFunctions (such as commandToClient) only.\n\n"
"@internal");
static void sendRemoteCommand(NetConnection *conn, S32 argc, const char **argv)
{
if(U8(argv[0][0]) != StringTagPrefixByte)
{
Con::errorf(ConsoleLogEntry::Script, "Remote Command Error - command must be a tag.");
return;
}
S32 i;
for(i = argc - 1; i >= 0; i--)
{
if(argv[i][0] != 0)
break;
argc = i;
}
for(i = 0; i < argc; i++)
conn->validateSendString(argv[i]);
RemoteCommandEvent *cevt = new RemoteCommandEvent(argc, argv, conn);
conn->postNetEvent(cevt);
}
ConsoleFunctionGroupBegin( Net, "Functions for use with the network; tagged strings and remote commands.");
ConsoleFunction( commandToServer, void, 2, RemoteCommandEvent::MaxRemoteCommandArgs + 1, "(string func, ...)"
"@brief Send a command to the server.\n\n"
"@param func Name of the server command being called\n"
"@param ... Various parameters being passed to server command\n\n"
"@tsexample\n"
"// Create a standard function. Needs to be executed on the client, such \n"
"// as within scripts/client/default.bind.cs\n"
"function toggleCamera(%val)\n"
"{\n"
" // If key was down, call a server command named 'ToggleCamera'\n"
" if (%val)\n"
" commandToServer('ToggleCamera');\n"
"}\n\n"
"// Server command being called from above. Needs to be executed on the \n"
"// server, such as within scripts/server/commands.cs\n"
"function serverCmdToggleCamera(%client)\n"
"{\n"
" if (%client.getControlObject() == %client.player)\n"
" {\n"
" %client.camera.setVelocity(\"0 0 0\");\n"
" %control = %client.camera;\n"
" }\n"
" else\n"
" {\n"
" %client.player.setVelocity(\"0 0 0\");\n"
" %control = %client.player;\n"
" }\n"
" %client.setControlObject(%control);\n"
" clientCmdSyncEditorGui();\n"
"}\n"
"@endtsexample\n\n"
"@ingroup Networking")
{
NetConnection *conn = NetConnection::getConnectionToServer();
if(!conn)
return;
sendRemoteCommand(conn, argc - 1, argv + 1);
}
ConsoleFunction( commandToClient, void, 3, RemoteCommandEvent::MaxRemoteCommandArgs + 2, "(NetConnection client, string func, ...)"
"@brief Send a command from the server to the client\n\n"
"@param client The numeric ID of a client GameConnection\n"
"@param func Name of the client function being called\n"
"@param ... Various parameters being passed to client command\n\n"
"@tsexample\n"
"// Set up the client command. Needs to be executed on the client, such as\n"
"// within scripts/client/client.cs\n"
"// Update the Ammo Counter with current ammo, if not any then hide the counter.\n"
"function clientCmdSetAmmoAmountHud(%amount)\n"
"{\n"
" if (!%amount)\n"
" AmmoAmount.setVisible(false);\n"
" else\n"
" {\n"
" AmmoAmount.setVisible(true);\n"
" AmmoAmount.setText(\"Ammo: \"@%amount);\n"
" }\n"
"}\n\n"
"// Call it from a server function. Needs to be executed on the server, \n"
"//such as within scripts/server/game.cs\n"
"function GameConnection::setAmmoAmountHud(%client, %amount)\n"
"{\n"
" commandToClient(%client, 'SetAmmoAmountHud', %amount);\n"
"}\n"
"@endtsexample\n\n"
"@ingroup Networking\n")
{
NetConnection *conn;
if(!Sim::findObject(argv[1], conn))
return;
sendRemoteCommand(conn, argc - 2, argv + 2);
}
ConsoleFunction(removeTaggedString, void, 2, 2, "(int tag)"
"@brief Remove a tagged string from the Net String Table\n\n"
"@param tag The tag associated with the string\n\n"
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
"@see addTaggedString()\n"
"@see getTaggedString()\n"
"@ingroup Networking\n")
{
gNetStringTable->removeString(dAtoi(argv[1]+1), true);
}
ConsoleFunction( addTaggedString, const char*, 2, 2, "(string str)"
"@brief Use the addTaggedString function to tag a new string and add it to the NetStringTable\n\n"
"@param str The string to be tagged and placed in the NetStringTable. Tagging ignores case, "
"so tagging the same string (excluding case differences) will be ignored as a duplicated tag.\n\n"
"@return Returns a string( containing a numeric value) equivalent to the string ID for the newly tagged string"
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
"@see removeTaggedString()\n"
"@see getTaggedString()\n"
"@ingroup Networking\n")
{
NetStringHandle s(argv[1]);
gNetStringTable->incStringRefScript(s.getIndex());
char *ret = Con::getReturnBuffer(10);
ret[0] = StringTagPrefixByte;
dSprintf(ret + 1, 9, "%d", s.getIndex());
return ret;
}
ConsoleFunction( getTaggedString, const char*, 2, 2, "(int tag)"
"@brief Use the getTaggedString function to convert a tag to a string.\n\n"
"This is not the same as detag() which can only be used within the context "
"of a function that receives a tag. This function can be used any time and "
"anywhere to convert a tag to a string.\n\n"
"@param tag A numeric tag ID.\n"
"@returns The string as found in the Net String table.\n"
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
"@see addTaggedString()\n"
"@see removeTaggedString()\n"
"@ingroup Networking\n")
{
const char *indexPtr = argv[1];
if (*indexPtr == StringTagPrefixByte)
indexPtr++;
return gNetStringTable->lookupString(dAtoi(indexPtr));
}
ConsoleFunction( buildTaggedString, const char*, 2, 11, "(string format, ...)"
"@brief Build a string using the specified tagged string format.\n\n"
"This function takes an already tagged string (passed in as a tagged string ID) and one "
"or more additional strings. If the tagged string contains argument tags that range from "
"%%1 through %%9, then each additional string will be substituted into the tagged string. "
"The final (non-tagged) combined string will be returned. The maximum length of the tagged "
"string plus any inserted additional strings is 511 characters.\n\n"
"@param format A tagged string ID that contains zero or more argument tags, in the form of "
"%%1 through %%9.\n"
"@param ... A variable number of arguments that are insterted into the tagged string "
"based on the argument tags within the format string.\n"
"@returns An ordinary string that is a combination of the original tagged string with any additional "
"strings passed in inserted in place of each argument tag.\n"
"@tsexample\n"
"// Create a tagged string with argument tags\n"
"%taggedStringID = addTaggedString(\"Welcome %1 to the game!\");\n\n"
"// Some point later, combine the tagged string with some other string\n"
"%string = buildTaggedString(%taggedStringID, %playerName);\n"
"echo(%string);\n"
"@endtsexample\n\n"
"@see \\ref syntaxDataTypes under Tagged %Strings\n"
"@see addTaggedString()\n"
"@see getTaggedString()\n"
"@ingroup Networking\n")
{
const char *indexPtr = argv[1];
if (*indexPtr == StringTagPrefixByte)
indexPtr++;
const char *fmtString = gNetStringTable->lookupString(dAtoi(indexPtr));
char *strBuffer = Con::getReturnBuffer(512);
const char *fmtStrPtr = fmtString;
char *strBufPtr = strBuffer;
S32 strMaxLength = 511;
if (!fmtString)
goto done;
//build the string
while (*fmtStrPtr)
{
//look for an argument tag
if (*fmtStrPtr == '%')
{
if (fmtStrPtr[1] >= '1' && fmtStrPtr[1] <= '9')
{
S32 argIndex = S32(fmtStrPtr[1] - '0') + 1;
if (argIndex >= argc)
goto done;
const char *argStr = argv[argIndex];
if (!argStr)
goto done;
S32 strLength = dStrlen(argStr);
if (strLength > strMaxLength)
goto done;
dStrcpy(strBufPtr, argStr);
strBufPtr += strLength;
strMaxLength -= strLength;
fmtStrPtr += 2;
continue;
}
}
//if we don't continue, just copy the character
if (strMaxLength <= 0)
goto done;
*strBufPtr++ = *fmtStrPtr++;
strMaxLength--;
}
done:
*strBufPtr = '\0';
return strBuffer;
}
ConsoleFunctionGroupEnd( Net );

View file

@ -0,0 +1,192 @@
//-----------------------------------------------------------------------------
// 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 "platform/event.h"
#include "sim/netConnection.h"
#include "core/stream/bitStream.h"
#include "sim/netObject.h"
#include "console/engineAPI.h"
class SimpleMessageEvent : public NetEvent
{
char *msg;
public:
typedef NetEvent Parent;
SimpleMessageEvent(const char *message = NULL)
{
if(message)
msg = dStrdup(message);
else
msg = NULL;
}
~SimpleMessageEvent()
{ dFree(msg); }
virtual void pack(NetConnection* /*ps*/, BitStream *bstream)
{ bstream->writeString(msg); }
virtual void write(NetConnection*, BitStream *bstream)
{ bstream->writeString(msg); }
virtual void unpack(NetConnection* /*ps*/, BitStream *bstream)
{ char buf[256]; bstream->readString(buf); msg = dStrdup(buf); }
virtual void process(NetConnection *)
{ Con::printf("RMSG %d %s", mSourceId, msg); }
DECLARE_CONOBJECT(SimpleMessageEvent);
};
IMPLEMENT_CO_NETEVENT_V1(SimpleMessageEvent);
ConsoleDocClass( SimpleMessageEvent,
"@brief A very simple example of a network event.\n\n"
"This object exists purely for instructional purposes. It is primarily "
"geared toward developers that wish to understand the inner-working of "
"Torque 3D's networking system. This is not intended for actual game "
"development.\n\n "
"@see NetEvent for the inner workings of network events\n\n"
"@ingroup Networking\n");
DefineEngineStaticMethod( SimpleMessageEvent, msg, void, (NetConnection* con, const char* message),,
"@brief Send a SimpleMessageEvent message to the specified connection.\n\n"
"The far end that receives the message will print the message out to the console.\n"
"@param con The unique ID of the connection to transmit to\n"
"@param message The string containing the message to transmit\n\n"
"@tsexample\n"
"// Send a message to the other end of the given NetConnection\n"
"SimpleMessageEvent::msg( %conn, \"A message from me!\");\n\n"
"// The far end will see the following in the console\n"
"// (Note: The number may be something other than 1796 as it is the SimObjectID\n"
"// of the received event)\n"
"// \n"
"// RMSG 1796 A message from me!\n"
"@endtsexample\n\n"
)
{
//NetConnection *con = (NetConnection *) Sim::findObject(argv[1]);
if(con)
con->postNetEvent(new SimpleMessageEvent(message));
}
//ConsoleFunction( msg, void, 3, 3, "(NetConnection id, string message)"
// "Send a SimpleNetObject message to the specified connection.")
//{
// NetConnection *con = (NetConnection *) Sim::findObject(argv[1]);
// if(con)
// con->postNetEvent(new SimpleMessageEvent(argv[2]));
//}
class SimpleNetObject : public NetObject
{
typedef NetObject Parent;
public:
char message[256];
SimpleNetObject()
{
mNetFlags.set(ScopeAlways | Ghostable);
dStrcpy(message, "Hello World!");
}
U32 packUpdate(NetConnection *conn, U32 mask, BitStream *stream)
{
stream->writeString(message);
return 0;
}
void unpackUpdate(NetConnection *conn, BitStream *stream)
{
stream->readString(message);
Con::printf("Got message: %s", message);
}
void setMessage(const char *msg)
{
setMaskBits(1);
dStrcpy(message, msg);
}
DECLARE_CONOBJECT(SimpleNetObject);
};
IMPLEMENT_CO_NETOBJECT_V1(SimpleNetObject);
ConsoleDocClass( SimpleNetObject,
"@brief A very simple example of a class derived from NetObject.\n\n"
"This object exists purely for instructional purposes. It is primarily "
"geared toward developers that wish to understand the inner-working of "
"Torque 3D's networking system. This is not intended for actual game "
"development.\n\n "
"@tsexample\n"
"// On the server, create a new SimpleNetObject. This is a ghost always\n"
"// object so it will be immediately ghosted to all connected clients.\n"
"$s = new SimpleNetObject();\n\n"
"// All connected clients will see the following in their console:\n"
"// \n"
"// Got message: Hello World!\n"
"@endtsexample\n\n"
"@see NetObject for a full breakdown of this example object\n"
"@ingroup Networking\n");
DefineEngineMethod( SimpleNetObject, setMessage, void, (const char* msg),,
"@brief Sets the internal message variable.\n\n"
"SimpleNetObject is set up to automatically transmit this new message to "
"all connected clients. It will appear in the clients' console.\n"
"@param msg The new message to send\n\n"
"@tsexample\n"
"// On the server, create a new SimpleNetObject. This is a ghost always\n"
"// object so it will be immediately ghosted to all connected clients.\n"
"$s = new SimpleNetObject();\n\n"
"// All connected clients will see the following in their console:\n"
"// \n"
"// Got message: Hello World!\n\n"
"// Now again on the server, change the message. This will cause it to\n"
"// be sent to all connected clients.\n"
"$s.setMessage(\"A new message from me!\");\n\n"
"// All connected clients will now see in their console:\n"
"// \n"
"// Go message: A new message from me!\n"
"@endtsexample\n\n"
)
{
object->setMessage(msg);
}
//ConsoleMethod( SimpleNetObject, setMessage, void, 3, 3, "(string msg)")
//{
// object->setMessage(argv[2]);
//}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,128 @@
//-----------------------------------------------------------------------------
// 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 _SERVERQUERY_H_
#define _SERVERQUERY_H_
#ifndef _PLATFORM_H_
#include "platform/platform.h"
#endif
#ifndef _EVENT_H_
#include "platform/event.h"
#endif
#ifndef _BITSET_H_
#include "core/bitSet.h"
#endif
#include "platform/platformNet.h"
//-----------------------------------------------------------------------------
// Game Server Information
struct ServerInfo
{
enum StatusFlags
{
// Info flags (0-7):
Status_Dedicated = BIT(0),
Status_Passworded = BIT(1),
Status_Linux = BIT(2),
Status_Xenon = BIT(6),
// Status flags:
Status_New = 0,
Status_Querying = BIT(28),
Status_Updating = BIT(29),
Status_Responded = BIT(30),
Status_TimedOut = BIT(31),
};
U8 numPlayers;
U8 maxPlayers;
U8 numBots;
char* name;
char* gameType;
char* missionName;
char* missionType;
char* statusString;
char* infoString;
NetAddress address;
U32 version;
U32 ping;
U32 cpuSpeed;
bool isFavorite;
BitSet32 status;
ServerInfo()
{
numPlayers = 0;
maxPlayers = 0;
numBots = 0;
name = NULL;
gameType = NULL;
missionType = NULL;
missionName = NULL;
statusString = NULL;
infoString = NULL;
version = 0;
ping = 0;
cpuSpeed = 0;
isFavorite = false;
status = Status_New;
}
~ServerInfo();
bool isNew() { return( status == Status_New ); }
bool isQuerying() { return( status.test( Status_Querying ) ); }
bool isUpdating() { return( status.test( Status_Updating ) ); }
bool hasResponded() { return( status.test( Status_Responded ) ); }
bool isTimedOut() { return( status.test( Status_TimedOut ) ); }
bool isDedicated() { return( status.test( Status_Dedicated ) ); }
bool isPassworded() { return( status.test( Status_Passworded ) ); }
bool isLinux() { return( status.test( Status_Linux ) ); }
bool isXenon() { return( status.test( Status_Xenon ) ); }
};
//-----------------------------------------------------------------------------
extern Vector<ServerInfo> gServerList;
extern bool gServerBrowserDirty;
extern void clearServerList();
extern void queryLanServers(U32 port, U8 flags, const char* gameType, const char* missionType,
U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU,
U8 filterFlags);
extern void queryMasterGameTypes();
extern void queryMasterServer(U8 flags, const char* gameType, const char* missionType,
U8 minPlayers, U8 maxPlayers, U8 maxBots, U32 regionMask, U32 maxPing, U16 minCPU,
U8 filterFlags, U8 buddyCount, U32* buddyList );
extern void queryFavoriteServers( U8 flags );
extern void querySingleServer(const NetAddress* addr, U8 flags);
extern void startHeartbeat();
extern void sendHeartbeat( U8 flags );
#ifdef TORQUE_DEBUG
extern void addFakeServers( S32 howMany );
#endif // DEBUG
#endif

View file

@ -0,0 +1,521 @@
//-----------------------------------------------------------------------------
// 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 "app/net/tcpObject.h"
#include "platform/platform.h"
#include "platform/event.h"
#include "console/simBase.h"
#include "console/consoleInternal.h"
#include "core/strings/stringUnit.h"
#include "console/engineAPI.h"
TCPObject *TCPObject::table[TCPObject::TableSize] = {0, };
IMPLEMENT_CONOBJECT(TCPObject);
ConsoleDocClass( TCPObject,
"@brief Allows communications between the game and a server using TCP/IP protocols.\n\n"
"To use TCPObject you set up a connection to a server, send data to the server, and handle "
"each line of the server's response using a callback. Once you are done communicating with "
"the server, you disconnect.\n\n"
"TCPObject is intended to be used with text based protocols which means you'll need to "
"delineate the server's response with an end-of-line character. i.e. the newline "
"character @\\n. You may optionally include the carriage return character @\\r prior to the newline "
"and TCPObject will strip it out before sending the line to the callback. If a newline "
"character is not included in the server's output, the received data will not be "
"processed until you disconnect from the server (which flushes the internal buffer).\n\n"
"TCPObject may also be set up to listen to a specific port, making Torque into a TCP server. "
"When used in this manner, a callback is received when a client connection is made. Following "
"the outside connection, text may be sent and lines are processed in the usual manner.\n\n"
"If you want to work with HTTP you may wish to use HTTPObject instead as it handles all of the "
"HTTP header setup and parsing.\n\n"
"@tsexample\n"
"// In this example we'll retrieve the new forum threads RSS\n"
"// feed from garagegames.com. As we're using TCPObject, the\n"
"// raw text response will be received from the server, including\n"
"// the HTTP header.\n\n"
"// Define callbacks for our specific TCPObject using our instance's\n"
"// name (RSSFeed) as the namespace.\n\n"
"// Handle an issue with resolving the server's name\n"
"function RSSFeed::onDNSFailed(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"DNSFailed\";\n\n"
" // Handle DNS failure\n"
"}\n\n"
"function RSSFeed::onConnectFailed(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"ConnectFailed\";\n\n"
" // Handle connection failure\n"
"}\n\n"
"function RSSFeed::onDNSResolved(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"DNSResolved\";\n\n"
"}\n\n"
"function RSSFeed::onConnected(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"Connected\";\n\n"
"}\n\n"
"function RSSFeed::onDisconnect(%this)\n"
"{\n"
" // Store this state\n"
" %this.lastState = \"Disconnected\";\n\n"
"}\n\n"
"// Handle a line from the server\n"
"function RSSFeed::onLine(%this, %line)\n"
"{\n"
" // Print the line to the console\n"
" echo( %line );\n"
"}\n\n"
"// Create the TCPObject\n"
"%rss = new TCPObject(RSSFeed);\n\n"
"// Define a dynamic field to store the last connection state\n"
"%rss.lastState = \"None\";\n\n"
"// Connect to the server\n"
"%rss.connect(\"www.garagegames.com:80\");\n\n"
"// Send the RSS feed request to the server. Response will be\n"
"// handled in onLine() callback above\n"
"%rss.send(\"GET /feeds/rss/threads HTTP/1.1\\r\\nHost: www.garagegames.com\\r\\n\\r\\n\");\n"
"@endtsexample\n\n"
"@see HTTPObject\n"
"@ingroup Networking\n"
);
IMPLEMENT_CALLBACK(TCPObject, onConnectionRequest, void, (const char* address, const char* ID), (address, ID),
"@brief Called whenever a connection request is made.\n\n"
"This callback is used when the TCPObject is listening to a port and a client is attempting to connect.\n"
"@param address Server address connecting from.\n"
"@param ID Connection ID\n"
);
IMPLEMENT_CALLBACK(TCPObject, onLine, void, (const char* line), (line),
"@brief Called whenever a line of data is sent to this TCPObject.\n\n"
"This callback is called when the received data contains a newline @\\n character, or "
"the connection has been disconnected and the TCPObject's buffer is flushed.\n"
"@param line Data sent from the server.\n"
);
IMPLEMENT_CALLBACK(TCPObject, onDNSResolved, void, (),(),
"Called whenever the DNS has been resolved.\n"
);
IMPLEMENT_CALLBACK(TCPObject, onDNSFailed, void, (),(),
"Called whenever the DNS has failed to resolve.\n"
);
IMPLEMENT_CALLBACK(TCPObject, onConnected, void, (),(),
"Called whenever a connection is established with a server.\n"
);
IMPLEMENT_CALLBACK(TCPObject, onConnectFailed, void, (),(),
"Called whenever a connection has failed to be established with a server.\n"
);
IMPLEMENT_CALLBACK(TCPObject, onDisconnect, void, (),(),
"Called whenever the TCPObject disconnects from whatever it is currently connected to.\n"
);
TCPObject *TCPObject::find(NetSocket tag)
{
for(TCPObject *walk = table[U32(tag) & TableMask]; walk; walk = walk->mNext)
if(walk->mTag == tag)
return walk;
return NULL;
}
void TCPObject::addToTable(NetSocket newTag)
{
removeFromTable();
mTag = newTag;
mNext = table[U32(mTag) & TableMask];
table[U32(mTag) & TableMask] = this;
}
void TCPObject::removeFromTable()
{
for(TCPObject **walk = &table[U32(mTag) & TableMask]; *walk; walk = &((*walk)->mNext))
{
if(*walk == this)
{
*walk = mNext;
return;
}
}
}
void processConnectedReceiveEvent(NetSocket sock, RawData incomingData);
void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress);
void processConnectedNotifyEvent( NetSocket sock, U32 state );
S32 gTCPCount = 0;
TCPObject::TCPObject()
{
mBuffer = NULL;
mBufferSize = 0;
mPort = 0;
mTag = InvalidSocket;
mNext = NULL;
mState = Disconnected;
gTCPCount++;
if(gTCPCount == 1)
{
Net::smConnectionAccept.notify(processConnectedAcceptEvent);
Net::smConnectionReceive.notify(processConnectedReceiveEvent);
Net::smConnectionNotify.notify(processConnectedNotifyEvent);
}
}
TCPObject::~TCPObject()
{
disconnect();
dFree(mBuffer);
gTCPCount--;
if(gTCPCount == 0)
{
Net::smConnectionAccept.remove(processConnectedAcceptEvent);
Net::smConnectionReceive.remove(processConnectedReceiveEvent);
Net::smConnectionNotify.remove(processConnectedNotifyEvent);
}
}
bool TCPObject::processArguments(S32 argc, const char **argv)
{
if(argc == 0)
return true;
else if(argc == 1)
{
addToTable(U32(dAtoi(argv[0])));
return true;
}
return false;
}
bool TCPObject::onAdd()
{
if(!Parent::onAdd())
return false;
const char *name = getName();
if(name && name[0] && getClassRep())
{
Namespace *parent = getClassRep()->getNameSpace();
Con::linkNamespaces(parent->mName, name);
mNameSpace = Con::lookupNamespace(name);
}
Sim::getTCPGroup()->addObject(this);
return true;
}
U32 TCPObject::onReceive(U8 *buffer, U32 bufferLen)
{
// we got a raw buffer event
// default action is to split the buffer into lines of text
// and call processLine on each
// for any incomplete lines we have mBuffer
U32 start = 0;
parseLine(buffer, &start, bufferLen);
return start;
}
void TCPObject::parseLine(U8 *buffer, U32 *start, U32 bufferLen)
{
// find the first \n in buffer
U32 i;
U8 *line = buffer + *start;
for(i = *start; i < bufferLen; i++)
if(buffer[i] == '\n' || buffer[i] == 0)
break;
U32 len = i - *start;
if(i == bufferLen || mBuffer)
{
// we've hit the end with no newline
mBuffer = (U8 *) dRealloc(mBuffer, mBufferSize + len + 1);
dMemcpy(mBuffer + mBufferSize, line, len);
mBufferSize += len;
*start = i;
// process the line
if(i != bufferLen)
{
mBuffer[mBufferSize] = 0;
if(mBufferSize && mBuffer[mBufferSize-1] == '\r')
mBuffer[mBufferSize - 1] = 0;
U8 *temp = mBuffer;
mBuffer = 0;
mBufferSize = 0;
processLine((UTF8*)temp);
dFree(temp);
}
}
else if(i != bufferLen)
{
line[len] = 0;
if(len && line[len-1] == '\r')
line[len-1] = 0;
processLine((UTF8*)line);
}
if(i != bufferLen)
*start = i + 1;
}
void TCPObject::onConnectionRequest(const NetAddress *addr, U32 connectId)
{
char idBuf[16];
char addrBuf[256];
Net::addressToString(addr, addrBuf);
dSprintf(idBuf, sizeof(idBuf), "%d", connectId);
onConnectionRequest_callback(addrBuf,idBuf);
}
bool TCPObject::processLine(UTF8 *line)
{
onLine_callback(line);
return true;
}
void TCPObject::onDNSResolved()
{
mState = DNSResolved;
onDNSResolved_callback();
}
void TCPObject::onDNSFailed()
{
mState = Disconnected;
onDNSFailed_callback();
}
void TCPObject::onConnected()
{
mState = Connected;
onConnected_callback();
}
void TCPObject::onConnectFailed()
{
mState = Disconnected;
onConnectFailed_callback();
}
void TCPObject::finishLastLine()
{
if(mBufferSize)
{
mBuffer[mBufferSize] = 0;
processLine((UTF8*)mBuffer);
dFree(mBuffer);
mBuffer = 0;
mBufferSize = 0;
}
}
void TCPObject::onDisconnect()
{
finishLastLine();
mState = Disconnected;
onDisconnect_callback();
}
void TCPObject::listen(U16 port)
{
mState = Listening;
U32 newTag = Net::openListenPort(port);
addToTable(newTag);
}
void TCPObject::connect(const char *address)
{
NetSocket newTag = Net::openConnectTo(address);
addToTable(newTag);
}
void TCPObject::disconnect()
{
if( mTag != InvalidSocket ) {
Net::closeConnectTo(mTag);
}
removeFromTable();
}
void TCPObject::send(const U8 *buffer, U32 len)
{
Net::sendtoSocket(mTag, buffer, S32(len));
}
DefineEngineMethod(TCPObject, send, void, (const char *data),,
"@brief Transmits the data string to the connected computer.\n\n"
"This method is used to send text data to the connected computer regardless if we initiated the "
"connection using connect(), or listening to a port using listen().\n"
"@param data The data string to send.\n"
"@tsexample\n"
"// Set the command data\n"
"%data = \"GET \" @ $RSSFeed::serverURL @ \" HTTP/1.0\\r\\n\";\n"
"%data = %data @ \"Host: \" @ $RSSFeed::serverName @ \"\\r\\n\";\n"
"%data = %data @ \"User-Agent: \" @ $RSSFeed::userAgent @ \"\\r\\n\\r\\n\"\n\n"
"// Send the command to the connected server.\n"
"%thisTCPObj.send(%data);\n"
"@endtsexample\n")
{
object->send( (const U8*)data, dStrlen(data) );
}
DefineEngineMethod(TCPObject, listen, void, (int port),,
"@brief Start listening on the specified port for connections.\n\n"
"@param port Port for this TCPObject to start listening for connections on.\n"
"@tsexample\n"
"// Set the port number list\n"
"%portNumber = 80;\n\n"
"// Inform this TCPObject to start listening at the specified port.\n"
"%thisTCPObj.send(%portNumber);\n"
"@endtsexample\n")
{
object->listen(U32(port));
}
DefineEngineMethod(TCPObject, connect, void, (const char* address),,
"@brief Connect to the given address.\n\n"
"@param address Server address (including port) to connect to.\n"
"@tsexample\n"
"// Set the address.\n"
"%address = \"www.garagegames.com:80\";\n\n"
"// Inform this TCPObject to connect to the specified address.\n"
"%thisTCPObj.connect(%address);\n"
"@endtsexample\n")
{
object->connect(address);
}
DefineEngineMethod(TCPObject, disconnect, void, (),,
"@brief Disconnect from whatever this TCPObject is currently connected to, if anything.\n\n"
"@tsexample\n"
"// Inform this TCPObject to disconnect from anything it is currently connected to.\n"
"%thisTCPObj.disconnect();\n"
"@endtsexample\n")
{
object->disconnect();
}
void processConnectedReceiveEvent(NetSocket sock, RawData incomingData)
{
TCPObject *tcpo = TCPObject::find(sock);
if(!tcpo)
{
Con::printf("Got bad connected receive event.");
return;
}
U32 size = incomingData.size;
U8 *buffer = (U8*)incomingData.data;
while(size)
{
U32 ret = tcpo->onReceive(buffer, size);
AssertFatal(ret <= size, "Invalid return size");
size -= ret;
buffer += ret;
}
}
void processConnectedAcceptEvent(NetSocket listeningPort, NetSocket newConnection, NetAddress originatingAddress)
{
TCPObject *tcpo = TCPObject::find(listeningPort);
if(!tcpo)
return;
tcpo->onConnectionRequest(&originatingAddress, newConnection);
}
void processConnectedNotifyEvent( NetSocket sock, U32 state )
{
TCPObject *tcpo = TCPObject::find(sock);
if(!tcpo)
return;
switch(state)
{
case Net::DNSResolved:
tcpo->onDNSResolved();
break;
case Net::DNSFailed:
tcpo->onDNSFailed();
break;
case Net::Connected:
tcpo->onConnected();
break;
case Net::ConnectFailed:
tcpo->onConnectFailed();
break;
case Net::Disconnected:
tcpo->onDisconnect();
break;
}
}

View file

@ -0,0 +1,96 @@
//-----------------------------------------------------------------------------
// 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 _TCPOBJECT_H_
#define _TCPOBJECT_H_
#ifndef _SIMBASE_H_
#include "console/simBase.h"
#endif
#include "platform/platformNet.h"
class TCPObject : public SimObject
{
public:
enum State {Disconnected, DNSResolved, Connected, Listening };
DECLARE_CALLBACK(void, onConnectionRequest, (const char* address, const char* ID));
DECLARE_CALLBACK(void, onLine, (const char* line));
DECLARE_CALLBACK(void, onDNSResolved,());
DECLARE_CALLBACK(void, onDNSFailed, ());
DECLARE_CALLBACK(void, onConnected, ());
DECLARE_CALLBACK(void, onConnectFailed, ());
DECLARE_CALLBACK(void, onDisconnect, ());
private:
NetSocket mTag;
TCPObject *mNext;
enum { TableSize = 256, TableMask = 0xFF };
static TCPObject *table[TableSize];
State mState;
protected:
typedef SimObject Parent;
U8 *mBuffer;
U32 mBufferSize;
U16 mPort;
public:
TCPObject();
virtual ~TCPObject();
void parseLine(U8 *buffer, U32 *start, U32 bufferLen);
void finishLastLine();
static TCPObject *find(NetSocket tag);
// onReceive gets called continuously until all bytes are processed
// return # of bytes processed each time.
virtual U32 onReceive(U8 *buffer, U32 bufferLen); // process a buffer of raw packet data
virtual bool processLine(UTF8 *line); // process a complete line of text... default action is to call into script
virtual void onDNSResolved();
virtual void onDNSFailed();
virtual void onConnected();
virtual void onConnectFailed();
virtual void onConnectionRequest(const NetAddress *addr, U32 connectId);
virtual void onDisconnect();
void connect(const char *address);
void listen(U16 port);
void disconnect();
State getState() { return mState; }
bool processArguments(S32 argc, const char **argv);
void send(const U8 *buffer, U32 bufferLen);
void addToTable(NetSocket newTag);
void removeFromTable();
void setPort(U16 port) { mPort = port; }
bool onAdd();
DECLARE_CONOBJECT(TCPObject);
};
#endif // _H_TCPOBJECT_