dacian's network files were not yet completely in SVN. added the remaining files, removed obsolete ones
This was SVN commit r6105.
This commit is contained in:
parent
b5987f11e8
commit
8529e2b14f
@ -1,483 +0,0 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "scripting/DOMEvent.h"
|
||||
#include "scripting/JSConversions.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
#include "Client.h"
|
||||
#include "JSEvents.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/CConsole.h"
|
||||
#include "ps/Game.h"
|
||||
#include "ps/Globals.h" // g_frequencyFilter
|
||||
#include "ps/GameAttributes.h"
|
||||
#include "simulation/Simulation.h"
|
||||
|
||||
#define LOG_CAT_NET "net"
|
||||
|
||||
CNetClient *g_NetClient=NULL;
|
||||
|
||||
CNetClient::CServerSession::CServerSession(int sessionID, const CStrW& name):
|
||||
m_SessionID(sessionID),
|
||||
m_Name(name)
|
||||
{
|
||||
}
|
||||
|
||||
void CNetClient::CServerSession::ScriptingInit()
|
||||
{
|
||||
AddProperty(L"id", &CNetClient::CServerSession::m_SessionID, true);
|
||||
AddProperty(L"name", &CNetClient::CServerSession::m_Name, true);
|
||||
|
||||
CJSObject<CServerSession>::ScriptingInit("NetClient_ServerSession");
|
||||
}
|
||||
|
||||
CNetClient::CNetClient(CGame *pGame, CGameAttributes *pGameAttribs):
|
||||
CNetSession(ConnectHandler),
|
||||
m_JSI_ServerSessions(&m_ServerSessions),
|
||||
m_pLocalPlayerSlot(NULL),
|
||||
m_pGame(pGame),
|
||||
m_pGameAttributes(pGameAttribs),
|
||||
m_TurnPending(false)
|
||||
{
|
||||
m_pGame->GetSimulation()->SetTurnManager(this);
|
||||
|
||||
g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript()));
|
||||
}
|
||||
|
||||
CNetClient::~CNetClient()
|
||||
{
|
||||
g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL);
|
||||
|
||||
SessionMap::iterator it=m_ServerSessions.begin();
|
||||
for (; it != m_ServerSessions.end(); ++it)
|
||||
{
|
||||
delete it->second;
|
||||
}
|
||||
}
|
||||
|
||||
void CNetClient::ScriptingInit()
|
||||
{
|
||||
AddMethod<bool, &CNetClient::JSI_BeginConnect>("beginConnect", 1);
|
||||
|
||||
AddProperty(L"onStartGame", &CNetClient::m_OnStartGame);
|
||||
AddProperty(L"onChat", &CNetClient::m_OnChat);
|
||||
AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete);
|
||||
AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect);
|
||||
AddProperty(L"onClientConnect", &CNetClient::m_OnClientConnect);
|
||||
AddProperty(L"onClientDisconnect", &CNetClient::m_OnClientDisconnect);
|
||||
|
||||
AddProperty(L"password", &CNetClient::m_Password);
|
||||
AddProperty<CStrW>(L"playerName", &CNetClient::m_Name);
|
||||
AddProperty(L"sessionId", &CNetClient::m_SessionID);
|
||||
|
||||
AddProperty(L"sessions", &CNetClient::m_JSI_ServerSessions);
|
||||
CJSMap<SessionMap>::ScriptingInit("NetClient_SessionMap");
|
||||
CJSObject<CNetClient>::ScriptingInit("NetClient");
|
||||
|
||||
// Also initialize session objects
|
||||
CNetClient::CServerSession::ScriptingInit();
|
||||
}
|
||||
|
||||
bool CNetClient::JSI_BeginConnect(JSContext* UNUSED(cx), uintN argc, jsval *argv)
|
||||
{
|
||||
CStr connectHostName;
|
||||
unsigned connectPort=PS_DEFAULT_PORT;
|
||||
if (argc >= 1)
|
||||
{
|
||||
connectHostName=g_ScriptingHost.ValueToString(argv[0]);
|
||||
}
|
||||
if (argc >= 2)
|
||||
{
|
||||
connectPort=ToPrimitive<int>(argv[1]);
|
||||
}
|
||||
|
||||
PS_RESULT res=BeginConnect(connectHostName.c_str(), connectPort);
|
||||
if (res != PS_OK)
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::JSI_Connect(): BeginConnect error: %s", res);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
/* TEMPLATE FOR MESSAGE HANDLERS:
|
||||
|
||||
bool CNetClient::<X>Handler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case XXX:
|
||||
break;
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
*/
|
||||
|
||||
#define UNHANDLED(_pMsg) return false;
|
||||
#define HANDLED(_pMsg) delete _pMsg; return true;
|
||||
#define TAKEN(_pMsg) return true;
|
||||
// Uglily assumes the arguments are called pMsg and pSession (which they are
|
||||
// all through this file)
|
||||
#define CHAIN(_chainHandler) STMT(if (_chainHandler(pMsg, pSession)) return true;)
|
||||
|
||||
bool CNetClient::ConnectHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_CONNECT_COMPLETE:
|
||||
pClient->m_pMessageHandler=HandshakeHandler;
|
||||
if (pClient->m_OnConnectComplete.Defined())
|
||||
{
|
||||
CConnectCompleteEvent evt(CStr((char *)PS_OK), true);
|
||||
pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
break;
|
||||
case NMT_ERROR:
|
||||
{
|
||||
CNetErrorMessage *msg=(CNetErrorMessage *)pMsg;
|
||||
LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::ConnectHandler(): Connect Failed: %s", msg->m_Error);
|
||||
if (pClient->m_OnConnectComplete.Defined())
|
||||
{
|
||||
CConnectCompleteEvent evt(CStr(msg->m_Error), false);
|
||||
pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::BaseHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ERROR:
|
||||
{
|
||||
CNetErrorMessage *msg=(CNetErrorMessage *)pMsg;
|
||||
if (msg->m_State == SS_UNCONNECTED)
|
||||
{
|
||||
CStr message=msg->m_Error;
|
||||
CDisconnectEvent evt(message);
|
||||
if (pClient->m_OnDisconnect.Defined())
|
||||
pClient->m_OnDisconnect.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::HandshakeHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ServerHandshake:
|
||||
{
|
||||
CClientHandshake *msg=new CClientHandshake();
|
||||
msg->m_MagicResponse=PS_PROTOCOL_MAGIC_RESPONSE;
|
||||
msg->m_ProtocolVersion=PS_PROTOCOL_VERSION;
|
||||
msg->m_SoftwareVersion=PS_PROTOCOL_VERSION;
|
||||
pClient->Push(msg);
|
||||
break;
|
||||
}
|
||||
case NMT_ServerHandshakeResponse:
|
||||
{
|
||||
CAuthenticate *msg=new CAuthenticate();
|
||||
msg->m_Name=pClient->m_Name;
|
||||
msg->m_Password=pClient->m_Password;
|
||||
|
||||
pClient->m_pMessageHandler=AuthenticateHandler;
|
||||
pClient->Push(msg);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_AuthenticationResult:
|
||||
{
|
||||
CAuthenticationResult *msg=(CAuthenticationResult *)pMsg;
|
||||
if (msg->m_Code != NRC_OK)
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authentication failed: %ls", msg->m_Message.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authenticated!");
|
||||
pClient->m_SessionID=msg->m_SessionID;
|
||||
pClient->m_pMessageHandler=PreGameHandler;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::PreGameHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
CHAIN(ChatHandler);
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_StartGame:
|
||||
{
|
||||
pClient->OnStartGameMessage();
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_ClientConnect:
|
||||
{
|
||||
CClientConnect *msg=(CClientConnect *)pMsg;
|
||||
for (size_t i=0;i<msg->m_Clients.size();i++)
|
||||
{
|
||||
pClient->OnClientConnect(msg->m_Clients[i].m_SessionID,
|
||||
msg->m_Clients[i].m_Name);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_ClientDisconnect:
|
||||
{
|
||||
CClientDisconnect *msg=(CClientDisconnect *)pMsg;
|
||||
pClient->OnClientDisconnect(msg->m_SessionID);
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_SetGameConfig:
|
||||
{
|
||||
CSetGameConfig *msg=(CSetGameConfig *)pMsg;
|
||||
for (size_t i=0;i<msg->m_Values.size();i++)
|
||||
{
|
||||
pClient->m_pGameAttributes->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_AssignPlayerSlot:
|
||||
{
|
||||
CAssignPlayerSlot *msg=(CAssignPlayerSlot *)pMsg;
|
||||
// FIXME Validate slot id to prevent us from going boom
|
||||
CPlayerSlot *pSlot=pClient->m_pGameAttributes->GetSlot(msg->m_SlotID);
|
||||
if (pSlot == pClient->m_pLocalPlayerSlot)
|
||||
pClient->m_pLocalPlayerSlot=NULL;
|
||||
switch (msg->m_Assignment)
|
||||
{
|
||||
case PS_ASSIGN_SESSION:
|
||||
if ((int)msg->m_SessionID == (int)pClient->m_SessionID) // squelch bogus sign mismatch warning
|
||||
pClient->m_pLocalPlayerSlot=pSlot;
|
||||
pSlot->AssignToSessionID(msg->m_SessionID);
|
||||
break;
|
||||
case PS_ASSIGN_CLOSED:
|
||||
pSlot->AssignClosed();
|
||||
break;
|
||||
case PS_ASSIGN_OPEN:
|
||||
pSlot->AssignOpen();
|
||||
break;
|
||||
default:
|
||||
LOG(CLogger::Warning, LOG_CAT_NET, "CNetClient::PreGameHandler(): Received an invalid slot assignment %s", msg->GetString().c_str());
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_SetPlayerConfig:
|
||||
{
|
||||
CSetPlayerConfig *msg=(CSetPlayerConfig *)pMsg;
|
||||
// FIXME Check player ID
|
||||
CPlayer *pPlayer=pClient->m_pGameAttributes->GetPlayer(msg->m_PlayerID);
|
||||
for (size_t i=0;i<msg->m_Values.size();i++)
|
||||
{
|
||||
pPlayer->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
default:
|
||||
{
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
ENetMessageType msgType=pMsg->GetType();
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
CHAIN(ChatHandler);
|
||||
|
||||
if (msgType >= NMT_COMMAND_FIRST && msgType < NMT_COMMAND_LAST)
|
||||
{
|
||||
pClient->QueueIncomingMessage(pMsg);
|
||||
TAKEN(pMsg);
|
||||
}
|
||||
|
||||
switch (msgType)
|
||||
{
|
||||
case NMT_EndCommandBatch:
|
||||
{
|
||||
CEndCommandBatch *msg=(CEndCommandBatch *)pMsg;
|
||||
pClient->SetTurnLength(1, msg->m_TurnLength);
|
||||
|
||||
//debug_printf("Got end of batch, setting NewTurnPending\n");
|
||||
pClient->m_TurnPending = true;
|
||||
// We will ack the turn when our simulation calls NewTurn.
|
||||
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetClient::QueueIncomingMessage(CNetMessage *pMsg)
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
debug_printf("Got a command! queueing it to 2 turns from now\n");
|
||||
QueueMessage(2, pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::ChatHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ChatMessage:
|
||||
{
|
||||
CChatMessage *msg=(CChatMessage *)pMsg;
|
||||
g_Console->ReceivedChatMessage(msg->m_Sender, msg->m_Message);
|
||||
if (pClient->m_OnChat.Defined())
|
||||
{
|
||||
CChatEvent evt(msg->m_Sender, msg->m_Message);
|
||||
pClient->m_OnChat.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetClient::OnClientConnect(int sessionID, const CStrW& name)
|
||||
{
|
||||
// Find existing server session, if any, and delete it
|
||||
SessionMap::iterator it=m_ServerSessions.find(sessionID);
|
||||
if (it != m_ServerSessions.end())
|
||||
{
|
||||
delete it->second;
|
||||
m_ServerSessions.erase(it);
|
||||
}
|
||||
|
||||
// Insert new serversession
|
||||
m_ServerSessions[sessionID]=new CServerSession(sessionID, name);
|
||||
|
||||
// Call JS Callback
|
||||
if (m_OnClientConnect.Defined())
|
||||
{
|
||||
CClientConnectEvent evt(sessionID, name);
|
||||
m_OnClientConnect.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetClient::OnClientDisconnect(int sessionID)
|
||||
{
|
||||
SessionMap::iterator it=m_ServerSessions.find(sessionID);
|
||||
if (it == m_ServerSessions.end())
|
||||
{
|
||||
LOG(CLogger::Warning, LOG_CAT_NET, "Server said session %d disconnected. I don't know of such a session.");
|
||||
}
|
||||
|
||||
// Call JS Callback
|
||||
if (m_OnClientConnect.Defined())
|
||||
{
|
||||
CClientDisconnectEvent evt(it->second->m_SessionID, it->second->m_Name);
|
||||
m_OnClientConnect.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
|
||||
// Delete server session and remove from map
|
||||
delete it->second;
|
||||
m_ServerSessions.erase(it);
|
||||
}
|
||||
|
||||
void CNetClient::OnStartGameMessage()
|
||||
{
|
||||
m_pMessageHandler=InGameHandler;
|
||||
debug_assert( m_OnStartGame.Defined() );
|
||||
CStartGameEvent evt;
|
||||
m_OnStartGame.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
|
||||
int CNetClient::StartGame()
|
||||
{
|
||||
if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK)
|
||||
{
|
||||
// TODO: Send a failed-to-launch-game message and drop out.
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
debug_printf("Client StartGame - sending end-of-batch ack\n");
|
||||
// Send an end-of-batch message for turn 0 to signal that we're ready.
|
||||
CEndCommandBatch *pMsg=new CEndCommandBatch();
|
||||
pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency();
|
||||
Push(pMsg);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CPlayer* CNetClient::GetLocalPlayer()
|
||||
{
|
||||
return m_pLocalPlayerSlot->GetPlayer();
|
||||
}
|
||||
|
||||
bool CNetClient::NewTurnReady()
|
||||
{
|
||||
return m_TurnPending;
|
||||
}
|
||||
|
||||
void CNetClient::NewTurn()
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
RotateBatches();
|
||||
ClearBatch(2);
|
||||
m_TurnPending = false;
|
||||
|
||||
//debug_printf("In NewTurn - sending ack\n");
|
||||
CEndCommandBatch *pMsg=new CEndCommandBatch();
|
||||
pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); // JW: it'd probably be nicer to get the FPS as a parameter
|
||||
Push(pMsg);
|
||||
}
|
||||
|
||||
void CNetClient::QueueLocalCommand(CNetMessage *pMsg)
|
||||
{
|
||||
// Don't save these locally, since they'll be bounced by the server anyway
|
||||
//debug_printf("Sending command from client\n");
|
||||
Push(pMsg);
|
||||
}
|
@ -1,98 +0,0 @@
|
||||
#ifndef INCLUDED_NETWORK_CLIENT
|
||||
#define INCLUDED_NETWORK_CLIENT
|
||||
|
||||
#include "Session.h"
|
||||
|
||||
#include "simulation/TurnManager.h"
|
||||
#include "simulation/ScriptObject.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/ThreadUtil.h"
|
||||
#include "ps/scripting/JSMap.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
class CPlayerSlot;
|
||||
class CPlayer;
|
||||
class CGame;
|
||||
class CGameAttributes;
|
||||
|
||||
class CNetClient: public CNetSession, protected CTurnManager, public CJSObject<CNetClient>
|
||||
{
|
||||
class CServerSession: public CJSObject<CServerSession>
|
||||
{
|
||||
public:
|
||||
CServerSession(int sessionID, const CStrW& name);
|
||||
|
||||
int m_SessionID;
|
||||
CStrW m_Name;
|
||||
|
||||
static void ScriptingInit();
|
||||
};
|
||||
typedef std::map<int, CServerSession *> SessionMap;
|
||||
|
||||
SessionMap m_ServerSessions;
|
||||
CJSMap<SessionMap> m_JSI_ServerSessions;
|
||||
|
||||
CStrW m_Password;
|
||||
int m_SessionID;
|
||||
|
||||
CPlayerSlot *m_pLocalPlayerSlot;
|
||||
CGame *m_pGame;
|
||||
CGameAttributes *m_pGameAttributes;
|
||||
|
||||
// JS event scripts
|
||||
CScriptObject m_OnStartGame;
|
||||
CScriptObject m_OnChat;
|
||||
CScriptObject m_OnConnectComplete;
|
||||
CScriptObject m_OnDisconnect;
|
||||
CScriptObject m_OnClientConnect;
|
||||
CScriptObject m_OnClientDisconnect;
|
||||
|
||||
void OnClientConnect(int sessionID, const CStrW& name);
|
||||
void OnClientDisconnect(int sessionID);
|
||||
void OnStartGameMessage();
|
||||
void QueueIncomingMessage(CNetMessage *pMsg);
|
||||
|
||||
// JS Interface Functions
|
||||
bool JSI_BeginConnect(JSContext *cx, uintN argc, jsval *argv);
|
||||
|
||||
// Are we currently in a locally-yet-unsimulated turn?
|
||||
// This is set to true when we receive a command batch and cleared in NewTurn().
|
||||
// The server also ensures that it does not send a new turn until we ack one.
|
||||
bool m_TurnPending;
|
||||
|
||||
// Mutex for accessing batches
|
||||
CMutex m_Mutex;
|
||||
|
||||
protected:
|
||||
virtual bool NewTurnReady();
|
||||
virtual void NewTurn();
|
||||
virtual void QueueLocalCommand(CNetMessage *pMsg);
|
||||
|
||||
public:
|
||||
CNetClient(CGame *pGame, CGameAttributes *pGameAttribs);
|
||||
virtual ~CNetClient();
|
||||
|
||||
// Launch a game through this client
|
||||
int StartGame();
|
||||
|
||||
// Get a pointer to our player
|
||||
CPlayer* GetLocalPlayer();
|
||||
|
||||
static MessageHandler ConnectHandler;
|
||||
|
||||
static MessageHandler BaseHandler; // Common to all connected states
|
||||
static MessageHandler HandshakeHandler;
|
||||
static MessageHandler AuthenticateHandler;
|
||||
|
||||
static MessageHandler ChatHandler; // Common to pre-game and later
|
||||
static MessageHandler PreGameHandler;
|
||||
static MessageHandler InGameHandler;
|
||||
|
||||
static void ScriptingInit();
|
||||
};
|
||||
|
||||
extern CNetClient *g_NetClient;
|
||||
|
||||
#endif //INCLUDED_NETWORK_CLIENT
|
985
source/network/NetClient.cpp
Normal file
985
source/network/NetClient.cpp
Normal file
@ -0,0 +1,985 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetClient.cpp
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Network client class implementation file
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// INCLUDES
|
||||
#include "precompiled.h"
|
||||
#include "NetClient.h"
|
||||
#include "NetJsEvents.h"
|
||||
#include "network.h"
|
||||
#include "NetServer.h"
|
||||
#include "scripting/DOMEvent.h"
|
||||
#include "scripting/JSConversions.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/CConsole.h"
|
||||
#include "ps/Game.h"
|
||||
#include "ps/Globals.h"
|
||||
#include "ps/GameAttributes.h"
|
||||
#include "simulation/Simulation.h"
|
||||
|
||||
// DECLARATIONS
|
||||
#pragma warning( disable : 4100 )
|
||||
|
||||
#define LOG_CAT_NET "net"
|
||||
|
||||
CNetClient *g_NetClient=NULL;
|
||||
extern int fps;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CServerPlayer()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CServerPlayer::CServerPlayer( uint sessionID, const CStr& nickname )
|
||||
{
|
||||
m_SessionID = sessionID;
|
||||
m_Nickname = nickname;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CServerPlayer()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CServerPlayer::~CServerPlayer( void )
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ScriptingInit()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CServerPlayer::ScriptingInit( void )
|
||||
{
|
||||
AddProperty(L"id", &CServerPlayer::m_SessionID, true);
|
||||
AddProperty(L"name", &CServerPlayer::m_Nickname, true);
|
||||
|
||||
CJSObject<CServerPlayer>::ScriptingInit( "NetClient_ServerSession" );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetClient()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetClient::CNetClient( CGame* pGame, CGameAttributes* pGameAttribs )
|
||||
: m_JsPlayers( &m_Players )
|
||||
{
|
||||
m_pLocalPlayerSlot = NULL;
|
||||
m_pGame = pGame;
|
||||
m_pGameAttributes = pGameAttribs;
|
||||
m_TurnPending = false;
|
||||
|
||||
m_pGame->GetSimulation()->SetTurnManager(this);
|
||||
|
||||
g_ScriptingHost.SetGlobal("g_NetClient", OBJECT_TO_JSVAL(GetScript()));
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CNetClient()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetClient::~CNetClient()
|
||||
{
|
||||
// Release resources
|
||||
PlayerMap::iterator it = m_Players.begin();
|
||||
for ( ; it != m_Players.end(); it++ )
|
||||
{
|
||||
CServerPlayer *pCurrPlayer = it->second;
|
||||
if ( pCurrPlayer ) delete pCurrPlayer;
|
||||
}
|
||||
|
||||
m_Players.clear();
|
||||
|
||||
g_ScriptingHost.SetGlobal("g_NetClient", JSVAL_NULL);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ScriptingInit()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetClient::ScriptingInit()
|
||||
{
|
||||
AddMethod<bool, &CNetClient::SetupConnection>("beginConnect", 1);
|
||||
|
||||
AddProperty(L"onStartGame", &CNetClient::m_OnStartGame);
|
||||
AddProperty(L"onChat", &CNetClient::m_OnChat);
|
||||
AddProperty(L"onConnectComplete", &CNetClient::m_OnConnectComplete);
|
||||
AddProperty(L"onDisconnect", &CNetClient::m_OnDisconnect);
|
||||
AddProperty(L"onClientConnect", &CNetClient::m_OnPlayerJoin);
|
||||
AddProperty(L"onClientDisconnect", &CNetClient::m_OnPlayerLeave);
|
||||
|
||||
AddProperty(L"password", &CNetClient::m_Password);
|
||||
AddProperty<CStr>(L"playerName", &CNetClient::m_Nickname);
|
||||
//AddProperty(L"sessionId", &CNetClient::m_SessionID);
|
||||
|
||||
AddProperty(L"sessions", &CNetClient::m_JsPlayers);
|
||||
|
||||
CJSMap< PlayerMap >::ScriptingInit("NetClient_SessionMap");
|
||||
CJSObject<CNetClient>::ScriptingInit("NetClient");
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Run()
|
||||
// Desc: Connect to server and start main loop
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::SetupConnection( JSContext* pContext, uintN argc, jsval* argv )
|
||||
{
|
||||
uint port = DEFAULT_HOST_PORT;
|
||||
|
||||
// Validate parameters
|
||||
if ( argc == 0 ) return false;
|
||||
|
||||
// Build host information
|
||||
CStr host = g_ScriptingHost.ValueToString( argv[0] );
|
||||
if ( argc == 2 ) port = ToPrimitive< uint >( argv[ 1 ] );
|
||||
|
||||
// Create client host
|
||||
if ( !Create() ) return false;
|
||||
|
||||
// Connect to server
|
||||
return Connect( host, port );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetupSession()
|
||||
// Desc: Setup client session upon creation
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::SetupSession( CNetSession* pSession )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pSession ) return false;
|
||||
|
||||
FsmActionCtx* pContext = new FsmActionCtx;
|
||||
if ( !pContext ) return false;
|
||||
|
||||
pContext->pHost = this;
|
||||
pContext->pSession = pSession;
|
||||
|
||||
// Setup transitions for session
|
||||
pSession->AddTransition( NCS_CONNECT, ( uint )NMT_SERVER_HANDSHAKE, NCS_HANDSHAKE, &OnHandshake, pContext );
|
||||
|
||||
pSession->AddTransition( NCS_HANDSHAKE, ( uint )NMT_ERROR, NCS_CONNECT, &OnError, pContext );
|
||||
pSession->AddTransition( NCS_HANDSHAKE, ( uint )NMT_SERVER_HANDSHAKE_RESPONSE, NCS_AUTHENTICATE, &OnHandshake, pContext );
|
||||
|
||||
pSession->AddTransition( NCS_AUTHENTICATE, ( uint )NMT_ERROR, NCS_CONNECT, &OnError, pContext );
|
||||
pSession->AddTransition( NCS_AUTHENTICATE, ( uint )NMT_AUTHENTICATE_RESULT, NCS_PREGAME, &OnAuthenticate, pContext );
|
||||
|
||||
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ERROR, NCS_CONNECT, &OnError, pContext );
|
||||
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_SETUP, NCS_PREGAME, &OnPreGame, pContext );
|
||||
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_ASSIGN_PLAYER_SLOT, NCS_PREGAME, &OnPreGame, pContext );
|
||||
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_CONFIG, NCS_PREGAME, &OnPreGame, pContext );
|
||||
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_PLAYER_JOIN, NCS_PREGAME, &OnPlayerJoin, pContext );
|
||||
pSession->AddTransition( NCS_PREGAME, ( uint )NMT_GAME_START, NCS_INGAME, &OnStartGame, pContext );
|
||||
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_CHAT, NCS_INGAME, &OnChat, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_GOTO, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_PATROL, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_ADD_WAYPOINT, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_GENERIC, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_PRODUCE, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_PLACE_OBJECT, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_RUN, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_NOTIFY_REQUEST, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GOTO, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_FORMATION_GENERIC, NCS_INGAME, &OnInGame, pContext );
|
||||
pSession->AddTransition( NCS_INGAME, ( uint )NMT_END_COMMAND_BATCH, NCS_INGAME, &OnInGame, pContext );
|
||||
|
||||
// Set first state
|
||||
pSession->SetFirstState( NCS_CONNECT );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleConnect()
|
||||
// Desc: Called when the client successfully connected to server
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::HandleConnect( CNetSession* pSession )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pSession ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleDisconnect()
|
||||
// Desc: Called when the client disconnected from the server
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::HandleDisconnect( CNetSession *pSession )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pSession ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnError()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnError( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
// Error event?
|
||||
if ( pEvent->GetType() != NMT_ERROR ) return true;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
assert( pClient );
|
||||
|
||||
CErrorMessage* pMessage = ( CErrorMessage* )pEvent->GetParamRef();
|
||||
if ( pMessage )
|
||||
{
|
||||
LOG( CLogger::Error, LOG_CAT_NET, "CNetClient::OnError(): Error description %s", pMessage->m_Error );
|
||||
|
||||
if ( pClient->m_OnConnectComplete.Defined() )
|
||||
{
|
||||
CConnectCompleteEvent connectComplete( ( CStrW )pMessage->m_Error, false );
|
||||
pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnPlayer()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnPlayerJoin( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
// Connect event?
|
||||
if ( pEvent->GetType() != NMT_PLAYER_JOIN ) return true;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
assert( pClient );
|
||||
|
||||
CPlayerJoinMessage* pMessage = ( CPlayerJoinMessage* )pEvent->GetParamRef();
|
||||
if ( pMessage )
|
||||
{
|
||||
for ( uint i = 0; i < pMessage->m_Clients.size(); i++ )
|
||||
{
|
||||
pClient->OnPlayer( pMessage->m_Clients[ i ].m_SessionID, pMessage->m_Clients[ i ].m_Name );
|
||||
}
|
||||
|
||||
if ( pClient->m_OnConnectComplete.Defined() )
|
||||
{
|
||||
CConnectCompleteEvent connectComplete( ( CStrW )PS_OK, true );
|
||||
pClient->m_OnConnectComplete.DispatchEvent( pClient->GetScript(), &connectComplete );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnHandshake()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnHandshake( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
|
||||
|
||||
assert( pClient );
|
||||
assert( pSession );
|
||||
|
||||
switch ( pEvent->GetType() )
|
||||
{
|
||||
//case NMT_ERROR:
|
||||
|
||||
// CNetClient::OnError( pContext, pEvent );
|
||||
// break;
|
||||
|
||||
case NMT_SERVER_HANDSHAKE:
|
||||
{
|
||||
CCliHandshakeMessage handshake;
|
||||
handshake.m_MagicResponse = PS_PROTOCOL_MAGIC_RESPONSE;
|
||||
handshake.m_ProtocolVersion = PS_PROTOCOL_VERSION;
|
||||
handshake.m_SoftwareVersion = PS_PROTOCOL_VERSION;
|
||||
( ( CNetHost* )pClient )->SendMessage( pSession, &handshake );
|
||||
}
|
||||
break;
|
||||
|
||||
case NMT_SERVER_HANDSHAKE_RESPONSE:
|
||||
{
|
||||
CAuthenticateMessage authenticate;
|
||||
authenticate.m_Name = pClient->m_Nickname;
|
||||
authenticate.m_Password = pClient->m_Password;
|
||||
( ( CNetHost* )pClient )->SendMessage( pSession, &authenticate );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnAuthenticate()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnAuthenticate( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
CNetSession* pSession = ( ( FsmActionCtx* )pContext )->pSession;
|
||||
|
||||
assert( pClient );
|
||||
assert( pSession );
|
||||
|
||||
if ( pEvent->GetType() == NMT_ERROR )
|
||||
{
|
||||
// return CNetClient::OnError( pContext, pEvent );
|
||||
}
|
||||
else if ( pEvent->GetType() == NMT_AUTHENTICATE_RESULT )
|
||||
{
|
||||
CAuthenticateResultMessage* pMessage =( CAuthenticateResultMessage* )pEvent->GetParamRef();
|
||||
if ( !pMessage ) return true;
|
||||
|
||||
LOG(CLogger::Error, LOG_CAT_NET, "CNetClient::OnAuthenticate(): Authentication result: %s", pMessage->m_Message.c_str() );
|
||||
|
||||
pSession->SetID( pMessage->m_SessionID );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnPreGame()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnPreGame( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
CNetSession* pSession = ( CNetSession* )( ( FsmActionCtx* )pContext )->pSession;
|
||||
|
||||
assert( pClient );
|
||||
assert( pSession );
|
||||
|
||||
switch ( pEvent->GetType() )
|
||||
{
|
||||
|
||||
//CHAIN(BaseHandler);
|
||||
//CHAIN(ChatHandler);
|
||||
|
||||
// case NMT_GAME_START:
|
||||
|
||||
// pClient->StartGame();
|
||||
// break;
|
||||
|
||||
case NMT_PLAYER_LEAVE:
|
||||
{
|
||||
CPlayerLeaveMessage* pMessage = ( CPlayerLeaveMessage* )pEvent->GetParamRef();
|
||||
if ( !pMessage ) return false;
|
||||
|
||||
pClient->OnPlayerLeave( pMessage->m_SessionID );
|
||||
}
|
||||
break;
|
||||
|
||||
case NMT_GAME_SETUP:
|
||||
{
|
||||
CGameSetupMessage* pMessage = ( CGameSetupMessage* )pEvent->GetParamRef();
|
||||
|
||||
for ( uint i = 0; i < pMessage->m_Values.size(); i++ )
|
||||
{
|
||||
pClient->m_pGameAttributes->SetValue( pMessage->m_Values[ i ].m_Name, pMessage->m_Values[ i ].m_Value );
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NMT_ASSIGN_PLAYER_SLOT:
|
||||
{
|
||||
CAssignPlayerSlotMessage* pMessage = ( CAssignPlayerSlotMessage* )pEvent->GetParamRef();
|
||||
|
||||
// FIXME Validate slot id to prevent us from going boom
|
||||
CPlayerSlot* pSlot = pClient->m_pGameAttributes->GetSlot( pMessage->m_SlotID );
|
||||
if ( pSlot == pClient->m_pLocalPlayerSlot )
|
||||
pClient->m_pLocalPlayerSlot = NULL;
|
||||
|
||||
switch ( pMessage->m_Assignment )
|
||||
{
|
||||
case ASSIGN_SESSION:
|
||||
{
|
||||
// TODO: Check where is the best place to assign client's session ID
|
||||
//if ((int)msg->m_SessionID == (int)pClient->m_SessionID)
|
||||
// pClient->m_pLocalPlayerSlot=pSlot;
|
||||
|
||||
pSession->SetID( pMessage->m_SessionID );
|
||||
|
||||
pClient->m_pLocalPlayerSlot = pSlot;
|
||||
|
||||
pSlot->AssignToSessionID( pMessage->m_SessionID );
|
||||
}
|
||||
break;
|
||||
|
||||
case ASSIGN_CLOSED:
|
||||
pSlot->AssignClosed();
|
||||
break;
|
||||
|
||||
case ASSIGN_OPEN:
|
||||
pSlot->AssignOpen();
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG( CLogger::Warning, LOG_CAT_NET, "Invalid slot assignment %s", pMessage->ToString().c_str() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NMT_PLAYER_CONFIG:
|
||||
{
|
||||
CPlayerConfigMessage* pMessage = ( CPlayerConfigMessage* )pEvent->GetParamRef();
|
||||
|
||||
// FIXME Check player ID
|
||||
CPlayer* pPlayer = pClient->m_pGameAttributes->GetPlayer( pMessage->m_PlayerID );
|
||||
|
||||
for ( uint i = 0; i < pMessage->m_Values.size(); i++ )
|
||||
{
|
||||
pPlayer->SetValue( pMessage->m_Values[i].m_Name, pMessage->m_Values[i].m_Value );
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnInGame()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnInGame( void *pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
assert( pClient );
|
||||
|
||||
// ???
|
||||
if ( pEvent->GetType() >= NMT_COMMAND_FIRST && pEvent->GetType() < NMT_COMMAND_LAST )
|
||||
{
|
||||
if ( pEvent->GetParamRef() )
|
||||
{
|
||||
pClient->QueueMessage( 1, ( CNetMessage* )pEvent->GetParamRef() );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
switch ( pEvent->GetType() )
|
||||
{
|
||||
//case NMT_ERROR:
|
||||
|
||||
// CNetClient::OnError( pContext, pEvent );
|
||||
// break;
|
||||
|
||||
//case NMT_CHAT:
|
||||
|
||||
// CNetClient::OnChat( pContext, pEvent );
|
||||
// break;
|
||||
|
||||
case NMT_END_COMMAND_BATCH:
|
||||
{
|
||||
CEndCommandBatchMessage* pMessage = ( CEndCommandBatchMessage* )pEvent->GetParamRef();
|
||||
if ( !pMessage ) return false;
|
||||
|
||||
pClient->SetTurnLength( 1, pMessage->m_TurnLength );
|
||||
|
||||
pClient->m_TurnPending = true;
|
||||
|
||||
// FIXME When the command batch has ended, we should start accepting
|
||||
// commands for the next turn. This will be accomplished by calling
|
||||
// NewTurn. *BUT* we shouldn't prematurely proceed game simulation
|
||||
// since this will produce jerky playback (everything expects a sim
|
||||
// turn to have a certain duration).
|
||||
|
||||
// We should make sure that any commands received after this message
|
||||
// are queued in the next batch (#2 instead of #1). If we're already
|
||||
// putting everything new in batch 2 - we should fast-forward a bit to
|
||||
// catch up with the server.
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnChat()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnChat( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
|
||||
if ( pEvent->GetType() == NMT_CHAT )
|
||||
{
|
||||
CChatMessage* pMessage = ( CChatMessage* )pEvent->GetParamRef();
|
||||
if ( !pMessage ) return false;
|
||||
|
||||
g_Console->ReceivedChatMessage( pMessage->m_Sender, pMessage->m_Message );
|
||||
|
||||
if ( pClient->m_OnChat.Defined() )
|
||||
{
|
||||
CChatEvent evt( pMessage->m_Sender, pMessage->m_Message );
|
||||
pClient->m_OnChat.DispatchEvent( pClient->GetScript(), &evt );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnStartGame()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetClient::OnStartGame( void* pContext, CFsmEvent* pEvent )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pEvent || !pContext ) return false;
|
||||
|
||||
CNetClient* pClient = ( CNetClient* )( ( FsmActionCtx* )pContext )->pHost;
|
||||
|
||||
if ( pClient->m_OnStartGame.Defined() )
|
||||
{
|
||||
CStartGameEvent event;
|
||||
pClient->m_OnStartGame.DispatchEvent( pClient->GetScript(), &event );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnPlayerJoin()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetClient::OnPlayer( uint ID, const CStr& name )
|
||||
{
|
||||
CServerPlayer* pNewPlayer = new CServerPlayer( ID, name );
|
||||
if ( !pNewPlayer ) return;
|
||||
|
||||
// Store new player
|
||||
m_Players[ ID ] = pNewPlayer;
|
||||
|
||||
// Call JS Callback
|
||||
if ( m_OnPlayerJoin.Defined() )
|
||||
{
|
||||
CClientConnectEvent event( ID, name );
|
||||
m_OnPlayerJoin.DispatchEvent( GetScript(), &event );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: OnPlayerLeave()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetClient::OnPlayerLeave( uint ID )
|
||||
{
|
||||
// Lookup player
|
||||
PlayerMap::iterator it = m_Players.find( ID );
|
||||
if ( it == m_Players.end() )
|
||||
{
|
||||
LOG( CLogger::Warning, LOG_CAT_NET, "CNetClient::OnPlayerLeav(): No such player %d.", ID );
|
||||
return;
|
||||
}
|
||||
|
||||
// Call JS Callback
|
||||
if ( m_OnPlayerLeave.Defined() && it->second )
|
||||
{
|
||||
CClientDisconnectEvent event( it->second->GetSessionID(), it->second->GetNickname() );
|
||||
m_OnPlayerLeave.DispatchEvent( GetScript(), &event );
|
||||
}
|
||||
|
||||
// Remove player from internal map
|
||||
m_Players.erase( it );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: StartGame()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
int CNetClient::StartGame( void )
|
||||
{
|
||||
assert ( m_pGame );
|
||||
assert ( m_pGameAttributes );
|
||||
|
||||
if ( m_pGame->StartGame( m_pGameAttributes ) != PSRETURN_OK ) return -1;
|
||||
|
||||
// Send an end-of-batch message for turn 0 to signal that we're ready.
|
||||
CEndCommandBatchMessage endBatch;
|
||||
endBatch.m_TurnLength = 1000 / g_frequencyFilter->StableFrequency();
|
||||
|
||||
CNetSession* pSession = GetSession( 0 );
|
||||
CNetHost::SendMessage( pSession, &endBatch );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetLocalPlayer()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
CPlayer* CNetClient::GetLocalPlayer()
|
||||
{
|
||||
return m_pLocalPlayerSlot->GetPlayer();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: NewTurn()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetClient::NewTurn()
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
RotateBatches();
|
||||
ClearBatch(2);
|
||||
m_TurnPending = false;
|
||||
|
||||
//debug_printf("In NewTurn - sending ack\n");
|
||||
CEndCommandBatchMessage* pMsg = new CEndCommandBatchMessage;
|
||||
pMsg->m_TurnLength=1000/g_frequencyFilter->StableFrequency(); // JW: it'd probably be nicer to get the FPS as a parameter
|
||||
|
||||
CNetSession* pSession = GetSession( 0 );
|
||||
CNetHost::SendMessage( pSession, pMsg );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: QueueLocalCommand()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetClient::QueueLocalCommand( CNetMessage* pMessage )
|
||||
{
|
||||
if ( !pMessage ) return;
|
||||
|
||||
// Don't save these locally, since they'll be bounced by the server anyway
|
||||
CNetSession* pSession = GetSession( 0 );
|
||||
CNetHost::SendMessage( pSession, pMessage );
|
||||
}
|
||||
|
||||
/*
|
||||
void CNetClient::OnStartGameMessage()
|
||||
{
|
||||
m_pMessageHandler=CNetClient::InGameHandler;
|
||||
debug_assert( m_OnStartGame.Defined() );
|
||||
CStartGameEvent evt;
|
||||
m_OnStartGame.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
|
||||
int CNetClient::StartGame()
|
||||
{
|
||||
if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
CPlayer* CNetClient::GetLocalPlayer()
|
||||
{
|
||||
return m_pLocalPlayerSlot->GetPlayer();
|
||||
}
|
||||
|
||||
void CNetClient::NewTurn()
|
||||
{
|
||||
RotateBatches();
|
||||
ClearBatch(2);
|
||||
|
||||
CEndCommandBatchMessage *pMsg=new CEndCommandBatchMessage();
|
||||
pMsg->m_TurnLength=1000/fps;
|
||||
|
||||
if ( m_Session )
|
||||
m_Session->SendMessage( *pMsg );
|
||||
}
|
||||
|
||||
void CNetClient::QueueLocalCommand(CNetMessage *pMsg)
|
||||
{
|
||||
// Don't save these locally, since they'll be bounced by the server anyway
|
||||
if ( m_ServerSession )
|
||||
m_Session->SendMessage( *pMsg );
|
||||
}
|
||||
|
||||
bool CNetClient::ConnectHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
switch (pMsg->GetHeader().GetType())
|
||||
{
|
||||
case NMT_CONNECT_COMPLETE:
|
||||
pClient->m_pMessageHandler=HandshakeHandler;
|
||||
if (pClient->m_OnConnectComplete.Defined())
|
||||
{
|
||||
CConnectCompleteEvent evt(CStr((char *)PS_OK), true);
|
||||
pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
break;
|
||||
case NMT_ERROR:
|
||||
{
|
||||
CNetErrorMessage *msg=(CNetErrorMessage *)pMsg;
|
||||
LOG(ERROR, LOG_CAT_NET, "CNetClient::ConnectHandler(): Connect Failed: %s", msg->m_Error);
|
||||
if (pClient->m_OnConnectComplete.Defined())
|
||||
{
|
||||
CConnectCompleteEvent evt(CStr(msg->m_Error), false);
|
||||
pClient->m_OnConnectComplete.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::BaseHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ERROR:
|
||||
{
|
||||
CNetErrorMessage *msg=(CNetErrorMessage *)pMsg;
|
||||
if (msg->m_State == SS_UNCONNECTED)
|
||||
{
|
||||
CStr message=msg->m_Error;
|
||||
CDisconnectEvent evt(message);
|
||||
if (pClient->m_OnDisconnect.Defined())
|
||||
pClient->m_OnDisconnect.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::HandshakeHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ServerHandshake:
|
||||
{
|
||||
CClientHandshake *msg=new CClientHandshake();
|
||||
msg->m_MagicResponse=PS_PROTOCOL_MAGIC_RESPONSE;
|
||||
msg->m_ProtocolVersion=PS_PROTOCOL_VERSION;
|
||||
msg->m_SoftwareVersion=PS_PROTOCOL_VERSION;
|
||||
pClient->Push(msg);
|
||||
break;
|
||||
}
|
||||
case NMT_ServerHandshakeResponse:
|
||||
{
|
||||
CAuthenticate *msg=new CAuthenticate();
|
||||
msg->m_Name=pClient->m_Name;
|
||||
msg->m_Password=pClient->m_Password;
|
||||
|
||||
pClient->m_pMessageHandler=AuthenticateHandler;
|
||||
pClient->Push(msg);
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_AuthenticationResult:
|
||||
{
|
||||
CAuthenticationResult *msg=(CAuthenticationResult *)pMsg;
|
||||
if (msg->m_Code != NRC_OK)
|
||||
{
|
||||
LOG(ERROR, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authentication failed: %ls", msg->m_Message.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(NORMAL, LOG_CAT_NET, "CNetClient::AuthenticateHandler(): Authenticated!");
|
||||
pClient->m_SessionID=msg->m_SessionID;
|
||||
pClient->m_pMessageHandler=PreGameHandler;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
bool CNetClient::PreGameHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
CHAIN(ChatHandler);
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_StartGame:
|
||||
{
|
||||
pClient->OnStartGameMessage();
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_ClientConnect:
|
||||
{
|
||||
CClientConnect *msg=(CClientConnect *)pMsg;
|
||||
for (uint i=0;i<msg->m_Clients.size();i++)
|
||||
{
|
||||
pClient->OnClientConnect(msg->m_Clients[i].m_SessionID,
|
||||
msg->m_Clients[i].m_Name);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_ClientDisconnect:
|
||||
{
|
||||
CClientDisconnect *msg=(CClientDisconnect *)pMsg;
|
||||
pClient->OnClientDisconnect(msg->m_SessionID);
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_SetGameConfig:
|
||||
{
|
||||
CSetGameConfig *msg=(CSetGameConfig *)pMsg;
|
||||
for (uint i=0;i<msg->m_Values.size();i++)
|
||||
{
|
||||
pClient->m_pGameAttributes->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_AssignPlayerSlot:
|
||||
{
|
||||
CAssignPlayerSlot *msg=(CAssignPlayerSlot *)pMsg;
|
||||
// FIXME Validate slot id to prevent us from going boom
|
||||
CPlayerSlot *pSlot=pClient->m_pGameAttributes->GetSlot(msg->m_SlotID);
|
||||
if (pSlot == pClient->m_pLocalPlayerSlot)
|
||||
pClient->m_pLocalPlayerSlot=NULL;
|
||||
switch (msg->m_Assignment)
|
||||
{
|
||||
case PS_ASSIGN_SESSION:
|
||||
if ((int)msg->m_SessionID == (int)pClient->m_SessionID) // squelch bogus sign mismatch warning
|
||||
pClient->m_pLocalPlayerSlot=pSlot;
|
||||
pSlot->AssignToSessionID(msg->m_SessionID);
|
||||
break;
|
||||
case PS_ASSIGN_CLOSED:
|
||||
pSlot->AssignClosed();
|
||||
break;
|
||||
case PS_ASSIGN_OPEN:
|
||||
pSlot->AssignOpen();
|
||||
break;
|
||||
default:
|
||||
LOG(WARNING, LOG_CAT_NET, "CNetClient::PreGameHandler(): Received an invalid slot assignment %s", msg->GetString().c_str());
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
case NMT_SetPlayerConfig:
|
||||
{
|
||||
CSetPlayerConfig *msg=(CSetPlayerConfig *)pMsg;
|
||||
// FIXME Check player ID
|
||||
CPlayer *pPlayer=pClient->m_pGameAttributes->GetPlayer(msg->m_PlayerID);
|
||||
for (uint i=0;i<msg->m_Values.size();i++)
|
||||
{
|
||||
pPlayer->SetValue(msg->m_Values[i].m_Name, msg->m_Values[i].m_Value);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
default:
|
||||
{
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetClient::InGameHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
ENetMessageType msgType=pMsg->GetType();
|
||||
|
||||
CHAIN(BaseHandler);
|
||||
CHAIN(ChatHandler);
|
||||
|
||||
if (msgType >= NMT_COMMAND_FIRST && msgType < NMT_COMMAND_LAST)
|
||||
{
|
||||
pClient->QueueMessage(1, pMsg);
|
||||
TAKEN(pMsg);
|
||||
}
|
||||
|
||||
switch (msgType)
|
||||
{
|
||||
case NMT_EndCommandBatch:
|
||||
{
|
||||
CEndCommandBatch *msg=(CEndCommandBatch *)pMsg;
|
||||
pClient->SetTurnLength(1, msg->m_TurnLength);
|
||||
|
||||
// FIXME When the command batch has ended, we should start accepting
|
||||
// commands for the next turn. This will be accomplished by calling
|
||||
// NewTurn. *BUT* we shouldn't prematurely proceed game simulation
|
||||
// since this will produce jerky playback (everything expects a sim
|
||||
// turn to have a certain duration).
|
||||
|
||||
// We should make sure that any commands received after this message
|
||||
// are queued in the next batch (#2 instead of #1). If we're already
|
||||
// putting everything new in batch 2 - we should fast-forward a bit to
|
||||
// catch up with the server.
|
||||
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetClient::ChatHandler(CNetMessage *pMsg, CNetSession *pSession)
|
||||
{
|
||||
CNetClient *pClient=(CNetClient *)pSession;
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ChatMessage:
|
||||
{
|
||||
CChatMessage *msg=(CChatMessage *)pMsg;
|
||||
g_Console->ReceivedChatMessage(msg->m_Sender, msg->m_Message);
|
||||
if (pClient->m_OnChat.Defined())
|
||||
{
|
||||
CChatEvent evt(msg->m_Sender, msg->m_Message);
|
||||
pClient->m_OnChat.DispatchEvent(pClient->GetScript(), &evt);
|
||||
}
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}*/
|
||||
|
159
source/network/NetClient.h
Normal file
159
source/network/NetClient.h
Normal file
@ -0,0 +1,159 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetClient.h
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Network client class interface file
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef NETCLIENT_H
|
||||
#define NETCLIENT_H
|
||||
|
||||
// INCLUDES
|
||||
#include "NetSession.h"
|
||||
#include "ps/CStr.h"
|
||||
#include "simulation/TurnManager.h"
|
||||
#include "simulation/ScriptObject.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
#include "ps/scripting/JSMap.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
// DECLARATIONS
|
||||
enum
|
||||
{
|
||||
NCS_CONNECT = 200,
|
||||
NCS_HANDSHAKE = 300,
|
||||
NCS_AUTHENTICATE = 400,
|
||||
NCS_PREGAME = 500,
|
||||
NCS_INGAME = 600
|
||||
};
|
||||
|
||||
class CPlayerSlot;
|
||||
class CPlayer;
|
||||
class CGame;
|
||||
class CGameAttributes;
|
||||
class CServerPlayer;
|
||||
|
||||
//typedef std::map< uint, CStr > PlayerMap;
|
||||
typedef std::map< uint, CServerPlayer* > PlayerMap;
|
||||
|
||||
/*
|
||||
CLASS : CServerPlayer
|
||||
DESCRIPTION :
|
||||
NOTES :
|
||||
*/
|
||||
class CServerPlayer : public CJSObject< CServerPlayer >
|
||||
{
|
||||
public:
|
||||
|
||||
CServerPlayer( uint sessionID, const CStr& nickname );
|
||||
~CServerPlayer( void );
|
||||
|
||||
static void ScriptingInit( void );
|
||||
uint GetSessionID( void ) const { return m_SessionID; }
|
||||
const CStr GetNickname( void ) const { return m_Nickname; }
|
||||
protected:
|
||||
|
||||
private:
|
||||
|
||||
CServerPlayer( const CServerPlayer& );
|
||||
CServerPlayer& operator=( const CServerPlayer& );
|
||||
|
||||
uint m_SessionID; // Player session ID
|
||||
CStr m_Nickname; // Player nickname
|
||||
};
|
||||
|
||||
/*
|
||||
CLASS : CNetClient
|
||||
DESCRIPTION :
|
||||
NOTES :
|
||||
*/
|
||||
|
||||
class CNetClient: public CNetHost,
|
||||
public CJSObject<CNetClient>,
|
||||
protected CTurnManager
|
||||
{
|
||||
public:
|
||||
|
||||
CNetClient( CGame* pGame, CGameAttributes* pGameAttributes );
|
||||
~CNetClient( void );
|
||||
|
||||
bool CreateSession ( void );
|
||||
void OnPlayer ( uint ID, const CStr& name );
|
||||
void OnPlayerLeave ( uint ID );
|
||||
|
||||
/**
|
||||
* Returns true indicating the host acts as a client
|
||||
*
|
||||
* @return Always true
|
||||
*/
|
||||
virtual bool IsClient( void ) const { return true; }
|
||||
|
||||
// Get a pointer to our player
|
||||
CPlayer* GetLocalPlayer();
|
||||
|
||||
CJSMap< PlayerMap > m_JsPlayers;
|
||||
|
||||
CStr m_Nickname;
|
||||
CStr m_Password;
|
||||
//int m_SessionID;
|
||||
|
||||
CPlayerSlot *m_pLocalPlayerSlot;
|
||||
CGame *m_pGame;
|
||||
CGameAttributes *m_pGameAttributes;
|
||||
|
||||
// Are we currently in a locally-yet-unsimulated turn?
|
||||
// This is set to true when we receive a command batch and cleared in NewTurn().
|
||||
// The server also ensures that it does not send a new turn until we ack one.
|
||||
bool m_TurnPending;
|
||||
|
||||
// Mutex for accessing batches
|
||||
CMutex m_Mutex;
|
||||
|
||||
// JS event scripts
|
||||
CScriptObject m_OnStartGame;
|
||||
CScriptObject m_OnChat;
|
||||
CScriptObject m_OnConnectComplete;
|
||||
CScriptObject m_OnDisconnect;
|
||||
CScriptObject m_OnPlayerJoin;
|
||||
CScriptObject m_OnPlayerLeave;
|
||||
|
||||
static void ScriptingInit( void );
|
||||
int StartGame( void );
|
||||
|
||||
protected:
|
||||
|
||||
virtual bool SetupSession ( CNetSession* pSession );
|
||||
virtual bool HandleConnect ( CNetSession* pSession );
|
||||
virtual bool HandleDisconnect ( CNetSession *pSession );
|
||||
|
||||
virtual void NewTurn ( void );
|
||||
virtual bool NewTurnReady ( void ) { return m_TurnPending; }
|
||||
virtual void QueueLocalCommand ( CNetMessage* pMessage );
|
||||
|
||||
private:
|
||||
|
||||
// Not implemented
|
||||
CNetClient( const CNetClient& );
|
||||
CNetClient& operator=( const CNetClient& );
|
||||
|
||||
static bool OnError ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnHandshake ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnAuthenticate ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnPreGame ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnInGame ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnChat ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnStartGame ( void* pContext, CFsmEvent* pEvent );
|
||||
|
||||
|
||||
bool SetupConnection( JSContext *cx, uintN argc, jsval *argv );
|
||||
|
||||
//CNetSession* m_Session; // Server session
|
||||
PlayerMap m_Players; // List of online players
|
||||
};
|
||||
|
||||
extern CNetClient *g_NetClient;
|
||||
|
||||
#endif // NETCLIENT_H
|
147
source/network/NetJsEvents.h
Normal file
147
source/network/NetJsEvents.h
Normal file
@ -0,0 +1,147 @@
|
||||
/*
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetJsEvents.h
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Definitions for JavaScript events used by the network
|
||||
* system
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef NETJSEVENTS_H
|
||||
#define NETJSEVENTS_H
|
||||
|
||||
// INCLUDES
|
||||
#include "NetSession.h"
|
||||
#include "scripting/DOMEvent.h"
|
||||
|
||||
// DEFINES
|
||||
enum NetJsEvents
|
||||
{
|
||||
JS_EVENT_CLIENT_CONNECT,
|
||||
JS_EVENT_CONNECT_COMPLETE,
|
||||
JS_EVENT_CLIENT_DISCONNECT,
|
||||
JS_EVENT_DISCONNECT,
|
||||
JS_EVENT_START_GAME,
|
||||
JS_EVENT_CHAT,
|
||||
JS_EVENT_LAST
|
||||
};
|
||||
|
||||
class CStartGameEvent: public CScriptEvent
|
||||
{
|
||||
public:
|
||||
CStartGameEvent():
|
||||
CScriptEvent(L"startGame", JS_EVENT_START_GAME, false)
|
||||
{}
|
||||
};
|
||||
|
||||
class CChatEvent: public CScriptEvent
|
||||
{
|
||||
CStr m_Sender;
|
||||
CStr m_Message;
|
||||
|
||||
public:
|
||||
CChatEvent(const CStr& sender, const CStr& message):
|
||||
CScriptEvent(L"chat", JS_EVENT_CHAT, false ),
|
||||
m_Sender(sender),
|
||||
m_Message(message)
|
||||
{
|
||||
AddLocalProperty(L"sender", &m_Sender, true);
|
||||
AddLocalProperty(L"message", &m_Message, true);
|
||||
}
|
||||
};
|
||||
|
||||
class CConnectCompleteEvent: public CScriptEvent
|
||||
{
|
||||
CStr m_Message;
|
||||
bool m_Success;
|
||||
|
||||
public:
|
||||
CConnectCompleteEvent(const CStr& message, bool success):
|
||||
CScriptEvent(L"connectComplete", JS_EVENT_CONNECT_COMPLETE, false),
|
||||
m_Message(message),
|
||||
m_Success(success)
|
||||
{
|
||||
AddLocalProperty(L"message", &m_Message, true);
|
||||
AddLocalProperty(L"success", &m_Success, true);
|
||||
}
|
||||
};
|
||||
|
||||
class CDisconnectEvent: public CScriptEvent
|
||||
{
|
||||
CStrW m_Message;
|
||||
|
||||
public:
|
||||
CDisconnectEvent(const CStr& message):
|
||||
CScriptEvent(L"disconnect", JS_EVENT_DISCONNECT, false),
|
||||
m_Message(message)
|
||||
{
|
||||
AddLocalProperty(L"message", &m_Message, true);
|
||||
}
|
||||
};
|
||||
|
||||
class CClientConnectDisconnectCommon: public CScriptEvent
|
||||
{
|
||||
uint m_SessionID;
|
||||
CStr m_Name;
|
||||
CNetSession *m_Session;
|
||||
|
||||
public:
|
||||
|
||||
CClientConnectDisconnectCommon(const wchar_t* UNUSED(eventName), int UNUSED(eventType),
|
||||
int sessionID, const CStr& name, CNetSession* pSession)
|
||||
: CScriptEvent(L"clientConnect", JS_EVENT_CLIENT_CONNECT, false),
|
||||
m_SessionID(sessionID),
|
||||
m_Name(name),
|
||||
m_Session(pSession)
|
||||
{
|
||||
AddLocalProperty(L"id", &m_SessionID, true);
|
||||
AddLocalProperty(L"name", &m_Name, true);
|
||||
if (m_Session)
|
||||
AddLocalProperty(L"session", &m_Session, true);
|
||||
}
|
||||
};
|
||||
|
||||
struct CClientConnectEvent: public CClientConnectDisconnectCommon
|
||||
{
|
||||
CClientConnectEvent(int sessionID, const CStr& name):
|
||||
CClientConnectDisconnectCommon(
|
||||
L"clientConnect",
|
||||
JS_EVENT_CLIENT_CONNECT,
|
||||
sessionID,
|
||||
name,
|
||||
NULL)
|
||||
{}
|
||||
|
||||
CClientConnectEvent(CNetSession *pSession):
|
||||
CClientConnectDisconnectCommon(
|
||||
L"clientConnect",
|
||||
JS_EVENT_CLIENT_CONNECT,
|
||||
pSession->GetID(),
|
||||
pSession->GetName(),
|
||||
pSession)
|
||||
{}
|
||||
};
|
||||
|
||||
struct CClientDisconnectEvent: public CClientConnectDisconnectCommon
|
||||
{
|
||||
CClientDisconnectEvent(int sessionID, const CStr& name):
|
||||
CClientConnectDisconnectCommon(
|
||||
L"clientDisconnect",
|
||||
JS_EVENT_CLIENT_DISCONNECT,
|
||||
sessionID,
|
||||
name,
|
||||
NULL)
|
||||
{}
|
||||
|
||||
CClientDisconnectEvent(CNetSession *pSession):
|
||||
CClientConnectDisconnectCommon(
|
||||
L"clientDisconnect",
|
||||
JS_EVENT_CLIENT_DISCONNECT,
|
||||
pSession->GetID(),
|
||||
pSession->GetName(),
|
||||
pSession)
|
||||
{}
|
||||
};
|
||||
|
||||
#endif // NETJSEVENTS_H
|
||||
|
247
source/network/NetMessages.h
Normal file
247
source/network/NetMessages.h
Normal file
@ -0,0 +1,247 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetMessages.h
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : The list of messages used by the network subsystem
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef NETMESSAGES_H
|
||||
#define NETMESSAGES_H
|
||||
|
||||
// INCLUDES
|
||||
#include "ps/CStr.h"
|
||||
#include "scripting/JSSerialization.h"
|
||||
#include "simulation/EntityHandles.h"
|
||||
|
||||
// DEFINES
|
||||
#define PS_PROTOCOL_MAGIC 0x5073013f // 'P', 's', 0x01, '?'
|
||||
#define PS_PROTOCOL_MAGIC_RESPONSE 0x50630121 // 'P', 'c', 0x01, '!'
|
||||
#define PS_PROTOCOL_VERSION 0x01010002 // Arbitrary protocol
|
||||
#define PS_DEFAULT_PORT 0x5073 // 'P', 's'
|
||||
|
||||
// Defines the list of message types. The order of the list must not change
|
||||
// The message types having a negative value are used internally and not sent
|
||||
// over the network. The message types used for network communication have
|
||||
// positive values.
|
||||
enum NetMessageType
|
||||
{
|
||||
NMT_ERROR = -256, // Delivery of error states
|
||||
NMT_CONNECT_COMPLETE, // Connection is complete
|
||||
NMT_CLOSE_REQUEST, // Close connection request
|
||||
NMT_INVALID = 0, // Invalid message
|
||||
NMT_SERVER_HANDSHAKE, // Handshake stage
|
||||
NMT_CLIENT_HANDSHAKE,
|
||||
NMT_SERVER_HANDSHAKE_RESPONSE,
|
||||
NMT_AUTHENTICATE, // Authentication stage
|
||||
NMT_AUTHENTICATE_RESULT,
|
||||
NMT_CHAT, // Common chat message
|
||||
NMT_PLAYER_JOIN, // Pre-game stage
|
||||
NMT_PLAYER_LEAVE,
|
||||
NMT_GAME_SETUP,
|
||||
NMT_ASSIGN_PLAYER_SLOT,
|
||||
NMT_PLAYER_CONFIG,
|
||||
NMT_FILES_REQUIRED,
|
||||
NMT_FILE_REQUEST,
|
||||
NMT_FILE_CHUNK,
|
||||
NMT_FILE_CHUNK_ACK,
|
||||
NMT_FILE_PROGRESS,
|
||||
NMT_GAME_START,
|
||||
NMT_END_COMMAND_BATCH, // In-game stage
|
||||
NMT_GOTO,
|
||||
NMT_COMMAND_FIRST = NMT_GOTO,
|
||||
NMT_PATROL,
|
||||
NMT_ADD_WAYPOINT,
|
||||
NMT_GENERIC,
|
||||
NMT_PRODUCE,
|
||||
NMT_PLACE_OBJECT,
|
||||
NMT_RUN,
|
||||
NMT_NOTIFY_REQUEST,
|
||||
NMT_FORMATION_GOTO,
|
||||
NMT_FORMATION_GENERIC,
|
||||
NMT_COMMAND_LAST,
|
||||
NMT_LAST // Last message in the list
|
||||
};
|
||||
|
||||
// Authentication result codes
|
||||
enum AuthenticateResultCode
|
||||
{
|
||||
ARC_OK,
|
||||
ARC_PASSWORD_INVALID,
|
||||
ARC_NICK_TAKEN,
|
||||
ARC_NICK_INVALID,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
CHAT_RECIPIENT_FIRST = 0xFFFD,
|
||||
CHAT_RECIPIENT_ENEMIES = 0xFFFD,
|
||||
CHAT_RECIPIENT_ALLIES = 0xFFFE,
|
||||
CHAT_RECIPIENT_ALL = 0xFFFF
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
ASSIGN_OPEN,
|
||||
ASSIGN_CLOSED,
|
||||
ASSIGN_AI,
|
||||
ASSIGN_SESSION
|
||||
};
|
||||
|
||||
#endif // NETMESSAGES_H
|
||||
|
||||
#ifdef CREATING_NMT
|
||||
|
||||
#define ALLNETMSGS_DONT_CREATE_NMTS
|
||||
#define START_NMT_CLASS_(_nm, _message) START_NMT_CLASS(C##_nm##Message, _message)
|
||||
#define DERIVE_NMT_CLASS_(_base, _nm, _message) START_NMT_CLASS_DERIVED(C ## _base ## Message, C ## _nm ## Message, _message)
|
||||
|
||||
START_NMTS()
|
||||
|
||||
START_NMT_CLASS_(SrvHandshake, NMT_SERVER_HANDSHAKE)
|
||||
NMT_FIELD_INT(m_Magic, u32, 4)
|
||||
NMT_FIELD_INT(m_ProtocolVersion, u32, 4)
|
||||
NMT_FIELD_INT(m_SoftwareVersion, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(CliHandshake,NMT_CLIENT_HANDSHAKE)
|
||||
NMT_FIELD_INT(m_MagicResponse, u32, 4)
|
||||
NMT_FIELD_INT(m_ProtocolVersion, u32, 4)
|
||||
NMT_FIELD_INT(m_SoftwareVersion, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(SrvHandshakeResponse, NMT_SERVER_HANDSHAKE_RESPONSE)
|
||||
NMT_FIELD_INT(m_UseProtocolVersion, u32, 4)
|
||||
NMT_FIELD_INT(m_Flags, u32, 4)
|
||||
NMT_FIELD(CStrW, m_Message)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(Authenticate, NMT_AUTHENTICATE)
|
||||
NMT_FIELD(CStrW, m_Name)
|
||||
//NMT_FIELD(CPasswordHash, m_Password)
|
||||
NMT_FIELD(CStrW, m_Password)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(AuthenticateResult, NMT_AUTHENTICATE_RESULT)
|
||||
NMT_FIELD_INT(m_Code, u32, 4)
|
||||
NMT_FIELD_INT(m_SessionID, u32, 2)
|
||||
NMT_FIELD(CStrW, m_Message)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(Chat, NMT_CHAT)
|
||||
NMT_FIELD(CStrW, m_Sender)
|
||||
NMT_FIELD_INT(m_Recipient, u32, 2)
|
||||
NMT_FIELD(CStrW, m_Message)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(PlayerJoin, NMT_PLAYER_JOIN)
|
||||
NMT_START_ARRAY(m_Clients)
|
||||
NMT_FIELD_INT(m_SessionID, u32, 2)
|
||||
NMT_FIELD(CStr, m_Name)
|
||||
NMT_END_ARRAY()
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(PlayerLeave, NMT_PLAYER_LEAVE)
|
||||
NMT_FIELD_INT(m_SessionID, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(GameSetup, NMT_GAME_SETUP)
|
||||
NMT_START_ARRAY(m_Values)
|
||||
NMT_FIELD(CStrW, m_Name)
|
||||
NMT_FIELD(CStrW, m_Value)
|
||||
NMT_END_ARRAY()
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(AssignPlayerSlot, NMT_ASSIGN_PLAYER_SLOT)
|
||||
NMT_FIELD_INT(m_SlotID, u32, 2)
|
||||
NMT_FIELD_INT(m_Assignment, u32, 1)
|
||||
NMT_FIELD_INT(m_SessionID, u32, 2) // Only applicable for PS_ASSIGN_SESSION
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(PlayerConfig, NMT_PLAYER_CONFIG)
|
||||
NMT_FIELD_INT(m_PlayerID, u32, 2)
|
||||
NMT_START_ARRAY(m_Values)
|
||||
NMT_FIELD(CStrW, m_Name)
|
||||
NMT_FIELD(CStrW, m_Value)
|
||||
NMT_END_ARRAY()
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(GameStart, NMT_GAME_START)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(EndCommandBatch, NMT_END_COMMAND_BATCH)
|
||||
NMT_FIELD_INT(m_TurnLength, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
START_NMT_CLASS_(Command, NMT_INVALID)
|
||||
NMT_FIELD(CEntityList, m_Entities)
|
||||
NMT_FIELD_INT(m_IsQueued, u32, 1)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, Goto, NMT_GOTO)
|
||||
NMT_FIELD_INT(m_TargetX, u32, 2)
|
||||
NMT_FIELD_INT(m_TargetY, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, Run, NMT_RUN)
|
||||
NMT_FIELD_INT(m_TargetX, u32, 2)
|
||||
NMT_FIELD_INT(m_TargetY, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, Patrol, NMT_PATROL)
|
||||
NMT_FIELD_INT(m_TargetX, u32, 2)
|
||||
NMT_FIELD_INT(m_TargetY, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, AddWaypoint, NMT_ADD_WAYPOINT)
|
||||
NMT_FIELD_INT(m_TargetX, u32, 2)
|
||||
NMT_FIELD_INT(m_TargetY, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, Generic, NMT_GENERIC)
|
||||
NMT_FIELD(HEntity, m_Target)
|
||||
NMT_FIELD_INT(m_Action, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, Produce, NMT_PRODUCE)
|
||||
NMT_FIELD_INT(m_Type, u32, 4)
|
||||
NMT_FIELD(CStrW, m_Name)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, PlaceObject, NMT_PLACE_OBJECT)
|
||||
NMT_FIELD(CStrW, m_Template)
|
||||
NMT_FIELD_INT(m_X, u32, 4)
|
||||
NMT_FIELD_INT(m_Y, u32, 4)
|
||||
NMT_FIELD_INT(m_Z, u32, 4)
|
||||
NMT_FIELD_INT(m_Angle, u32, 4) // Orientation angle
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, NotifyRequest, NMT_NOTIFY_REQUEST)
|
||||
NMT_FIELD(HEntity, m_Target)
|
||||
NMT_FIELD_INT(m_Action, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, FormationGoto, NMT_FORMATION_GOTO)
|
||||
NMT_FIELD_INT(m_TargetX, u32, 2)
|
||||
NMT_FIELD_INT(m_TargetY, u32, 2)
|
||||
END_NMT_CLASS()
|
||||
|
||||
DERIVE_NMT_CLASS_(Command, FormationGeneric, NMT_FORMATION_GENERIC)
|
||||
NMT_FIELD(HEntity, m_Target)
|
||||
NMT_FIELD_INT(m_Action, u32, 4)
|
||||
END_NMT_CLASS()
|
||||
|
||||
END_NMTS()
|
||||
|
||||
#else
|
||||
#ifndef ALLNETMSGS_DONT_CREATE_NMTS
|
||||
|
||||
# ifdef ALLNETMSGS_IMPLEMENT
|
||||
# define NMT_CREATOR_IMPLEMENT
|
||||
# endif
|
||||
|
||||
# define NMT_CREATE_HEADER_NAME "NetMessages.h"
|
||||
# include "NMTCreator.h"
|
||||
|
||||
#endif // #ifndef ALLNETMSGS_DONT_CREATE_NMTS
|
||||
#endif // #ifdef CREATING_NMT
|
1039
source/network/NetServer.cpp
Normal file
1039
source/network/NetServer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
343
source/network/NetServer.h
Normal file
343
source/network/NetServer.h
Normal file
@ -0,0 +1,343 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetServer.h
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Network server class interface file
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef NETSERVER_H
|
||||
#define NETSERVER_H
|
||||
|
||||
// INCLUDES
|
||||
#include "Network.h"
|
||||
#include "NetSession.h"
|
||||
#include "simulation/TurnManager.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
#include "ps/GameAttributes.h"
|
||||
#include "ps/scripting/JSMap.h"
|
||||
#include "ps/Player.h"
|
||||
#include "ps/Game.h"
|
||||
#include "simulation/ScriptObject.h"
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
// DECLARATIONS
|
||||
#define SERVER_SESSIONID 10
|
||||
#define CLIENT_MIN_SESSIONID 100
|
||||
#define MAX_CLIENTS 8
|
||||
#define MAX_OBSERVERS 5
|
||||
#define DEFAULT_SERVER_SESSION_ID 1
|
||||
#define DEFAULT_SERVER_NAME L"Noname Server"
|
||||
#define DEFAULT_PLAYER_NAME L"Noname Player"
|
||||
#define DEFAULT_WELCOME_MESSAGE L"Noname Server Welcome Message"
|
||||
#define DEFAULT_HOST_PORT 0x5073
|
||||
|
||||
enum NetServerState
|
||||
{
|
||||
// We haven't opened the port yet, we're just setting some stuff up.
|
||||
// This is probably equivalent to the first "Start Network Game" screen
|
||||
SERVER_STATE_PREBIND,
|
||||
|
||||
// The server is open and accepting connections. This is the screen where
|
||||
// rules are set up by the operator and where players join and select civs
|
||||
// and stuff.
|
||||
SERVER_STATE_PREGAME,
|
||||
|
||||
// The one with all the killing ;-)
|
||||
SERVER_STATE_INGAME,
|
||||
|
||||
// The game is over and someone has won. Players might linger to chat or
|
||||
// download the replay log.
|
||||
SERVER_STATE_POSTGAME
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
NSS_HANDSHAKE = 1300,
|
||||
NSS_AUTHENTICATE = 1400,
|
||||
NSS_PREGAME = 1500,
|
||||
NSS_INGAME = 1600
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
NMT_APP_PLAYER_LEAVE = NMT_LAST + 100,
|
||||
NMT_APP_PREGAME = NMT_LAST + 200,
|
||||
NMT_APP_OBSERVER = NMT_LAST + 300
|
||||
};
|
||||
|
||||
typedef std::map< uint, CNetSession* > IDSessionMap;
|
||||
typedef std::vector< CNetSession* > SessionList;
|
||||
|
||||
/*
|
||||
CLASS : CNetServer
|
||||
DESCRIPTION : CNetServer implements a network server for the game.
|
||||
It receives data and connection requests from clients.
|
||||
Under the hood, it uses ENet library to manage connected
|
||||
peers and bandwidth among these.
|
||||
NOTES :
|
||||
*/
|
||||
|
||||
class CNetServer : public CNetHost,
|
||||
public CJSObject<CNetServer>,
|
||||
public CTurnManager
|
||||
{
|
||||
public:
|
||||
|
||||
CNetServer( CGame* pGame, CGameAttributes* pGameAttributes );
|
||||
virtual ~CNetServer( void );
|
||||
|
||||
bool Start ( JSContext *pContext, uintN argc, jsval *argv );
|
||||
// void Shutdown ( void );
|
||||
|
||||
/**
|
||||
* Returns true indicating the host acts as a server
|
||||
*
|
||||
* @return Always true
|
||||
*/
|
||||
virtual bool IsServer( void ) const { return true; }
|
||||
|
||||
/**
|
||||
* Adds a new session to the list of sessions
|
||||
*
|
||||
* @param pSession New session to add
|
||||
*/
|
||||
void AddSession( CNetSession* pSession );
|
||||
|
||||
/**
|
||||
* Removes the specified session from the list of sessions. If the session
|
||||
* isn't found it returns NULL otherwise it returns the session object found.
|
||||
*
|
||||
* @param pSession Session to remove
|
||||
* @return The session object if found, NULL otherwise
|
||||
*/
|
||||
CNetSession* RemoveSession( CNetSession* pSession );
|
||||
|
||||
/**
|
||||
* Removes all the sessions managed by the network server
|
||||
*
|
||||
*/
|
||||
//void RemoveAllSessions( void );
|
||||
|
||||
/**
|
||||
* Returns the number of session the server manages
|
||||
*
|
||||
* @return The number of sessions
|
||||
*/
|
||||
//uint GetSessionCount( void ) const;
|
||||
|
||||
/**
|
||||
* Returns the session object for the specified ID
|
||||
*
|
||||
* @param sessionID The session ID
|
||||
* @return A pointer to session for the specified ID or
|
||||
* NULL if not found
|
||||
*/
|
||||
CNetSession* GetSessionByID( uint sessionID );
|
||||
|
||||
static void ScriptingInit( void );
|
||||
|
||||
protected:
|
||||
|
||||
virtual bool SetupSession ( CNetSession* pSession );
|
||||
virtual bool HandleConnect ( CNetSession* pSession );
|
||||
virtual bool HandleDisconnect ( CNetSession *pSession );
|
||||
|
||||
private:
|
||||
|
||||
// Not implemented
|
||||
CNetServer( const CNetServer& );
|
||||
CNetServer& operator=( const CNetServer& );
|
||||
|
||||
//void ClientConnect ( ENetPeer* pPeer );
|
||||
//void ClientDisconnect ( ENetPeer* pPeer );
|
||||
//void ClientReceive ( ENetPeer* pPeer, ENetPacket* pPacket );
|
||||
|
||||
/**
|
||||
* Returns the session associated with the specified ENet peer
|
||||
*
|
||||
* @param pPeer ENet peer
|
||||
* @return The session object if found or NULL
|
||||
*/
|
||||
//CNetSession* GetSessionByPeer( const ENetPeer* pPeer );
|
||||
|
||||
/**
|
||||
* Setup client game by sending the apropiate network messages. It also
|
||||
* inform the client about the other connected clients as well as player
|
||||
* slot assignment and attributes.
|
||||
*
|
||||
*/
|
||||
//void SetupNewSession( CNetSession* pSession );
|
||||
|
||||
/**
|
||||
* Loads the player properties into the specified message
|
||||
*
|
||||
* @param pMessage Message where to load player properties
|
||||
* @param pPlayer Player for which we load the properties
|
||||
*/
|
||||
void BuildPlayerConfigMessage(
|
||||
CPlayerConfigMessage* pMessage,
|
||||
CPlayer* pPlayer );
|
||||
|
||||
/**
|
||||
* Callback function used by the BuildPlayerSetupMessage to iterate over
|
||||
* the player properties. It will be called for each property of the player
|
||||
*
|
||||
* @param name Property name
|
||||
* @param pProperty Pointer to player property
|
||||
* @param pData Context pointer passed on iteration startup
|
||||
*/
|
||||
static void PlayerConfigMessageCallback(
|
||||
const CStrW& name,
|
||||
ISynchedJSProperty* pProperty,
|
||||
void* pData );
|
||||
|
||||
/**
|
||||
* Loads game properties into the specified message
|
||||
*
|
||||
* @param pMessage Message where to load game properties
|
||||
*/
|
||||
void BuildGameSetupMessage( CGameSetupMessage* pMessage );
|
||||
|
||||
/**
|
||||
* Loads player slot properties into the specified message
|
||||
*
|
||||
* @param pMessage Message where to load player properties
|
||||
* @param pPlayerSlot Player slot properties
|
||||
*/
|
||||
void BuildPlayerSlotAssignmentMessage(
|
||||
CAssignPlayerSlotMessage* pMessage,
|
||||
CPlayerSlot* pPlayerSlot );
|
||||
|
||||
/**
|
||||
* Callback function used by the BuildGameSetupMessage to iterate over the
|
||||
* game properties. It will be called for each property of the game
|
||||
*
|
||||
* @param name Property name
|
||||
* @param pProperty Pointer to game property
|
||||
* @param pData Context pointer passed on iteration startup
|
||||
*/
|
||||
// IterateCB GameSetupMessageCallbak;
|
||||
static void GameSetupMessageCallback(
|
||||
const CStrW& name,
|
||||
ISynchedJSProperty *pProperty,
|
||||
void *pData );
|
||||
|
||||
/**
|
||||
* Retrieves a free session ID from the recycled sessions list
|
||||
*
|
||||
* @return Free session ID
|
||||
*/
|
||||
uint GetFreeSessionID( void ) const;
|
||||
|
||||
IDSessionMap m_IDSessions; // List of connected ID and session pairs
|
||||
CScriptObject m_ScriptConnect; // Script client connect dispatch
|
||||
CScriptObject m_ScriptDisconnect; // Script client disconnect dispatch
|
||||
CPlayer* m_Player; // Server player
|
||||
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param addr Address where to bind
|
||||
* @return PS_OK if bind successfully, error code otherwise
|
||||
*/
|
||||
//PS_RESULT Bind( const CSocketAddress& addr );
|
||||
void SetPlayerPassword ( const CStr& password );
|
||||
CStrW GetPlayerName ( void ) const { return m_PlayerName; }
|
||||
NetServerState GetState ( void ) const { return m_State; }
|
||||
int StartGame ( void );
|
||||
|
||||
protected:
|
||||
|
||||
// Assign a session ID to the session. Do this just before calling AddSession
|
||||
void AssignSessionID( CNetSession* pSession );
|
||||
|
||||
// Add the session. This will be called after the session passes the
|
||||
// handshake and authentication stages. AssignSessionID should've been called
|
||||
// on the session prior to calling this method.
|
||||
//void AddSession( CNetServerSession* pSession );
|
||||
|
||||
// Remove the session from the server
|
||||
//void RemoveSession( CNetServerSession* pSession );
|
||||
|
||||
// Queue a command coming in from the wire. The command has been validated
|
||||
// by the caller.
|
||||
void QueueIncomingCommand( CNetMessage* pMessage );
|
||||
|
||||
// Call the JS callback for incoming events
|
||||
void OnPlayerChat ( const CStrW& from, const CStrW& message );
|
||||
void OnPlayerLeave ( CNetSession* pSession );
|
||||
void SetupPlayer ( CNetSession* pSession );
|
||||
|
||||
//static bool OnPlayerJoin ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnError ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnHandshake ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnAuthenticate ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnPreGame ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnInGame ( void* pContext, CFsmEvent* pEvent );
|
||||
static bool OnChat ( void* pContext, CFsmEvent* pEvent );
|
||||
|
||||
// OVERRIDES FROM CServerSocket
|
||||
//virtual void OnAccept( const CSocketAddress& address );
|
||||
|
||||
// OVERRIDES FROM CTurnManager
|
||||
virtual void NewTurn( void );
|
||||
virtual bool NewTurnReady( void );
|
||||
virtual void QueueLocalCommand( CNetMessage* pMessage );
|
||||
|
||||
// Will only be called from the Network Thread, by the OnAccept handler
|
||||
//virtual CNetServerSession* CreateSession( CSocketInternal* pSocketInternal);
|
||||
|
||||
// Ask the server if the session is allowed to start observing.
|
||||
//
|
||||
// Returns:
|
||||
// true if the session should be made an observer
|
||||
// false otherwise
|
||||
virtual bool AllowObserver( CNetSession* pSession );
|
||||
|
||||
private:
|
||||
|
||||
CGameAttributes* m_GameAttributes; // Stores game attributes
|
||||
//int m_LastSessionID; // Stores the last session ID
|
||||
//SessionMap m_Sessions; // Managed sessions
|
||||
CJSMap< IDSessionMap > m_JsSessions;
|
||||
CMutex m_Mutex; // Synchronization object for batches
|
||||
|
||||
/*
|
||||
All sessions that have observer status (observer as in watcher - simple
|
||||
chatters don't have an entry here, only in m_Sessions).
|
||||
Sessions are added here after they have successfully requested observer
|
||||
status.
|
||||
*/
|
||||
SessionList m_Observers;
|
||||
uint m_MaxObservers; // Maximum number of observers
|
||||
CGame* m_Game; // Pointer to actual game
|
||||
NetServerState m_State; // Holds server state
|
||||
CStrW m_Name; // Server name
|
||||
CStrW m_WelcomeMessage; // Nice welcome message
|
||||
//CPlayer* m_Player; // Pointer to 'server' player
|
||||
CStrW m_PlayerName; // Player name
|
||||
CStrW m_PlayerPassword; // Player password
|
||||
int m_Port; // The listening port
|
||||
CScriptObject m_OnChat;
|
||||
CScriptObject m_OnClientConnect;
|
||||
CScriptObject m_OnClientDisconnect;
|
||||
|
||||
// static CGameAttributes::UpdateCallback AttributeUpdate;
|
||||
// static CPlayer::UpdateCallback PlayerAttributeUpdate;
|
||||
// static PlayerSlotAssignmentCB PlayerSlotAssignmentCallback;
|
||||
|
||||
static void AttributeUpdate ( const CStrW& name, const CStrW& newValue, void* pData);
|
||||
static void PlayerAttributeUpdate ( const CStrW& name, const CStrW& value, CPlayer* pPlayer, void* pData );
|
||||
static void PlayerSlotAssignment ( void* pData, CPlayerSlot* pPlayerSlot );
|
||||
};
|
||||
|
||||
extern CNetServer *g_NetServer;
|
||||
|
||||
#endif // NETSERVER_H
|
||||
|
895
source/network/NetSession.cpp
Normal file
895
source/network/NetSession.cpp
Normal file
@ -0,0 +1,895 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetSession.cpp
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Network session class implementation
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// INCLUDES
|
||||
#include "precompiled.h"
|
||||
//#include "SessionManager.h"
|
||||
#include "NetSession.h"
|
||||
//#include "NetServer.h"
|
||||
#include "NetLog.h"
|
||||
|
||||
#pragma warning( disable : 4100 )
|
||||
|
||||
// DECLARATIONS
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetHost()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetHost::CNetHost( void )
|
||||
{
|
||||
m_Host = NULL;
|
||||
m_Buffer = NULL;
|
||||
m_BufferSize = 0;
|
||||
|
||||
// m_WorkerID = 0;
|
||||
// m_StopWorker = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CNetHost()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetHost::~CNetHost( void )
|
||||
{
|
||||
// Release host
|
||||
if ( m_Host ) enet_host_destroy( m_Host );
|
||||
if ( m_Buffer ) delete [] m_Buffer;
|
||||
|
||||
// Release running semaphore
|
||||
// if ( m_StopWorker ) sem_close( m_StopWorker );
|
||||
|
||||
m_Host = NULL;
|
||||
m_Buffer = NULL;
|
||||
m_BufferSize = 0;
|
||||
// m_WorkerID = 0;
|
||||
// m_StopWorker = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Create()
|
||||
// Desc: Creates a client host
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Create( void )
|
||||
{
|
||||
// Create ENet host
|
||||
m_Host = enet_host_create( NULL, 1, 0, 0 );
|
||||
if ( !m_Host ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Create()
|
||||
// Desc: Creates a server host
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Create( uint port, uint maxPeers )
|
||||
{
|
||||
ENetAddress addr;
|
||||
|
||||
// Bind to default host
|
||||
addr.host = ENET_HOST_ANY;
|
||||
addr.port = port;
|
||||
|
||||
// Create ENet server
|
||||
m_Host = enet_host_create( &addr, maxPeers, 0, 0 );
|
||||
if ( !m_Host ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Shutdown()
|
||||
// Desc: Shuts down network server and releases any resources
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetHost::Shutdown( void )
|
||||
{
|
||||
// Destroy server
|
||||
if ( m_Host ) enet_host_destroy( m_Host );
|
||||
|
||||
// Stop worker thread
|
||||
// sem_post( m_StopWorker );
|
||||
// if ( m_WorkerID ) pthread_join( m_WorkerID, NULL );
|
||||
|
||||
// Disconnect and release each peer
|
||||
PeerSessionList::iterator it = m_PeerSessions.begin();
|
||||
for ( ; it != m_PeerSessions.end(); it++ )
|
||||
{
|
||||
if ( !it->pSession ) continue;
|
||||
|
||||
Disconnect( it->pSession );
|
||||
|
||||
delete it->pSession;
|
||||
}
|
||||
|
||||
m_PeerSessions.clear();
|
||||
|
||||
m_Host = NULL;
|
||||
// m_WorkerID = 0;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Connect()
|
||||
// Desc: Connects to the specified remote host
|
||||
// Note: Only clients use this method for connection to server
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Connect( const CStr& host, uint port )
|
||||
{
|
||||
ENetEvent event;
|
||||
ENetAddress addr;
|
||||
PeerSession item;
|
||||
|
||||
assert( m_Host );
|
||||
|
||||
// Bind to specified host
|
||||
addr.port = port;
|
||||
if ( enet_address_set_host( &addr, host.c_str() ) < 0 ) return false;
|
||||
|
||||
// Initiate connection, allocate one channel
|
||||
ENetPeer* pPeer = enet_host_connect( m_Host, &addr, 1 );
|
||||
if ( !pPeer ) return false;
|
||||
|
||||
// Wait 3 seconds for the connection to succeed
|
||||
if ( enet_host_service( m_Host, &event, 5000 ) > 0 &&
|
||||
event.type == ENET_EVENT_TYPE_CONNECT )
|
||||
{
|
||||
// Connection succeeded
|
||||
CNetSession* pNewSession = new CNetSession( this, event.peer );
|
||||
if ( !pNewSession ) return false;
|
||||
|
||||
if ( !SetupSession( pNewSession ) ) return false;
|
||||
|
||||
// Successfully handled?
|
||||
if ( !HandleConnect( pNewSession ) )
|
||||
{
|
||||
delete pNewSession;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NET_LOG3( "Successfully connected to server %s:%d succeeded", host.c_str(), port );
|
||||
|
||||
// Store the only server session
|
||||
item.pPeer = event.peer;
|
||||
item.pSession = pNewSession;
|
||||
m_PeerSessions.push_back( item );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
NET_LOG3( "Connection to server %s:%d failed", host.c_str(), port );
|
||||
|
||||
// 3 seconds are up or a host was disconnected
|
||||
enet_peer_reset( pPeer );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Disconnect()
|
||||
// Desc: Disconnects the specified session from the host
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Disconnect( CNetSession* pSession )
|
||||
{
|
||||
ENetEvent event;
|
||||
|
||||
// Validate parameters
|
||||
if ( !pSession ) return false;
|
||||
|
||||
assert( m_Host );
|
||||
assert( pSession->m_Peer );
|
||||
|
||||
// Disconnect peer
|
||||
enet_peer_disconnect( pSession->m_Peer );
|
||||
|
||||
// Allow up to 3 seconds for the disconnect to succeed
|
||||
while ( enet_host_service( m_Host, &event, 5000 ) > 0 )
|
||||
{
|
||||
switch ( event.type )
|
||||
{
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
|
||||
// Drop any received packets
|
||||
enet_packet_destroy( event.packet );
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
|
||||
// Disconnect received for peer
|
||||
if ( !HandleDisconnect( pSession ) ) return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect attempt didn't succeed, force connection down
|
||||
enet_peer_reset( pSession->m_Peer );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Run()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
/*bool CNetHost::Run( void )
|
||||
{
|
||||
assert( m_Host );
|
||||
|
||||
// Host created?
|
||||
if ( !m_Host ) return false;
|
||||
|
||||
// Already running?
|
||||
if ( m_WorkerID != 0 ) return true;
|
||||
|
||||
// Create run semaphore
|
||||
m_StopWorker = sem_open( "//WFG_HostWorkerRun", O_CREAT | O_EXCL, 0700, 0 );
|
||||
if ( !m_StopWorker ) return false;
|
||||
|
||||
// Create worker thread
|
||||
if ( pthread_create( &m_WorkerID, 0, &WorkerFunc, this ) < 0 ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: WorkerFunc()
|
||||
// Desc: Worker thread function
|
||||
//-----------------------------------------------------------------------------
|
||||
void* CNetHost::WorkerFunc( void* pData )
|
||||
{
|
||||
ENetEvent event;
|
||||
CNetSession* pSession = NULL;
|
||||
PeerSession item;
|
||||
PeerSessionList::iterator it;
|
||||
|
||||
// Validate parameters
|
||||
if ( !pData ) return NULL;
|
||||
|
||||
CNetHost* pHost = ( CNetHost* )pData;
|
||||
|
||||
// Poll host for events
|
||||
while ( true )
|
||||
{
|
||||
// Decide whether to stop or not
|
||||
if ( !sem_timedwait( pHost->m_StopWorker, NULL ) ) break;
|
||||
|
||||
int retval = enet_host_service( pHost->m_Host, &event, 100 );
|
||||
|
||||
// Any event?
|
||||
if ( !retval ) continue;
|
||||
|
||||
// Any error?
|
||||
if ( !retval ) break;
|
||||
|
||||
// Handle occured event
|
||||
switch( event.type )
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
|
||||
// A new client has connected, handle it
|
||||
pSession = new CNetSession( pHost, event.peer );
|
||||
if ( !pSession ) return NULL;
|
||||
|
||||
// Successfully handled?
|
||||
if ( !pHost->HandleConnect( pSession ) ) return NULL;
|
||||
|
||||
// Add new item to internal list
|
||||
item.pPeer = event.peer;
|
||||
item.pSession = pSession;
|
||||
pHost->m_PeerSessions.push_back( item );
|
||||
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
|
||||
// Client has disconnected, handle it
|
||||
it = pHost->m_PeerSessions.begin();;
|
||||
for ( ; it != pHost->m_PeerSessions.end(); it++ )
|
||||
{
|
||||
// Is this our session?
|
||||
if ( it->pPeer == event.peer )
|
||||
{
|
||||
// Successfully handled?
|
||||
if ( !pHost->HandleDisconnect( it->pSession ) ) return NULL;
|
||||
|
||||
pHost->m_PeerSessions.erase( it );
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
|
||||
// A new data packet was received from client, handle message
|
||||
it = pHost->m_PeerSessions.begin();
|
||||
for ( ; it != pHost->m_PeerSessions.end(); it++ )
|
||||
{
|
||||
// Is this our session?
|
||||
if ( it->pPeer == event.peer )
|
||||
{
|
||||
// Create message from raw data
|
||||
CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength );
|
||||
if ( !pNewMessage ) return NULL;
|
||||
|
||||
// Successfully handled?
|
||||
if ( !pHost->HandleMessageReceive( pNewMessage, it->pSession ) ) return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ProcessEvents()
|
||||
// Desc: Wait for events and shuttles packets between the host and its peers
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::Poll( void )
|
||||
{
|
||||
ENetEvent event;
|
||||
CNetSession* pSession = NULL;
|
||||
PeerSession item;
|
||||
PeerSessionList::iterator it;
|
||||
|
||||
assert( m_Host );
|
||||
|
||||
// Poll host for events
|
||||
while ( enet_host_service( m_Host, &event, 0 ) > 0 )
|
||||
{
|
||||
// Handle occured event
|
||||
switch( event.type )
|
||||
{
|
||||
case ENET_EVENT_TYPE_CONNECT:
|
||||
|
||||
// A new client has connected, handle it
|
||||
pSession = new CNetSession( this, event.peer );
|
||||
if ( !pSession ) return false;
|
||||
|
||||
// Setup new session
|
||||
if ( !SetupSession( pSession ) ) return false;
|
||||
|
||||
// Successfully handled?
|
||||
if ( !HandleConnect( pSession ) ) return false;
|
||||
|
||||
NET_LOG3( "A new client connected from %x:%u", event.peer->address.host, event.peer->address.port );
|
||||
event.peer->data = pSession;
|
||||
|
||||
// Add new item to internal list
|
||||
item.pPeer = event.peer;
|
||||
item.pSession = pSession;
|
||||
m_PeerSessions.push_back( item );
|
||||
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_DISCONNECT:
|
||||
|
||||
// Client has disconnected, handle it
|
||||
it = m_PeerSessions.begin();;
|
||||
for ( ; it != m_PeerSessions.end(); it++ )
|
||||
{
|
||||
// Is this our session?
|
||||
if ( it->pPeer == event.peer )
|
||||
{
|
||||
// Successfully handled?
|
||||
if ( !HandleDisconnect( it->pSession ) ) return false;
|
||||
|
||||
m_PeerSessions.erase( it );
|
||||
|
||||
NET_LOG2( "%x disconnected", event.peer->data );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ENET_EVENT_TYPE_RECEIVE:
|
||||
|
||||
// A new data packet was received from client, handle message
|
||||
it = m_PeerSessions.begin();
|
||||
for ( ; it != m_PeerSessions.end(); it++ )
|
||||
{
|
||||
// Is this our session?
|
||||
if ( it->pPeer == event.peer )
|
||||
{
|
||||
// Create message from raw data
|
||||
CNetMessage* pNewMessage = CNetMessageFactory::CreateMessage( event.packet->data, event.packet->dataLength );
|
||||
if ( !pNewMessage ) return false;
|
||||
|
||||
// Successfully handled?
|
||||
if ( !HandleMessageReceive( pNewMessage, it->pSession ) ) return false;
|
||||
|
||||
NET_LOG4( "Message %s of size %u was received from %x", pNewMessage->ToString().c_str(), pNewMessage->GetSerializedLength(), event.peer->data );
|
||||
|
||||
// Done using the packet
|
||||
enet_packet_destroy( event.packet );
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Broadcast()
|
||||
// Desc: Broadcast the specified message to connected clients
|
||||
// Note: Reference counting for a sending message requires multithreading
|
||||
// locking mechanisms so a clone of the message is made and sent out
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetHost::Broadcast( const CNetMessage* pMessage )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pMessage ) return;
|
||||
|
||||
// Loop through the list of sessions and send the message to each
|
||||
for ( uint i = 0; i < GetSessionCount(); i++ )
|
||||
{
|
||||
CNetSession* pCurrSession = GetSession( i );
|
||||
if ( !pCurrSession ) continue;
|
||||
|
||||
SendMessage( pCurrSession, pMessage );
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ResizeBuffer()
|
||||
// Desc: Resizes the internal buffer
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetHost::ResizeBuffer( uint size )
|
||||
{
|
||||
// Already enough space?
|
||||
if ( size <= m_BufferSize ) return;
|
||||
|
||||
// Allocate enough space for the new buffer
|
||||
u8* pBuffer = new u8[ ALIGN_BLOCK( m_BufferSize + size ) ];
|
||||
if ( !pBuffer ) return;
|
||||
|
||||
// Any old data?
|
||||
if ( m_Buffer )
|
||||
{
|
||||
// Copy old data
|
||||
memcpy( pBuffer, m_Buffer, m_BufferSize );
|
||||
|
||||
delete [] m_Buffer;
|
||||
}
|
||||
|
||||
// Store new buffer
|
||||
m_Buffer = pBuffer;
|
||||
|
||||
// Store new buffer size
|
||||
m_BufferSize = ALIGN_BLOCK( m_BufferSize + size );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SendMessage()
|
||||
// Desc: Sends the specified message to peer
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::SendMessage(
|
||||
const CNetSession* pSession,
|
||||
const CNetMessage* pMessage )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pMessage || !pSession ) return false;
|
||||
|
||||
assert( pSession->m_Peer );
|
||||
assert( m_Host );
|
||||
|
||||
uint size = pMessage->GetSerializedLength();
|
||||
|
||||
// Adjust buffer for message
|
||||
ResizeBuffer( size );
|
||||
|
||||
// Save message to internal buffer
|
||||
pMessage->Serialize( m_Buffer );
|
||||
|
||||
// Create a reliable packet
|
||||
ENetPacket* pPacket = enet_packet_create( m_Buffer, size, ENET_PACKET_FLAG_RELIABLE );
|
||||
if ( !pPacket ) return false;
|
||||
|
||||
// Let ENet send the message to peer
|
||||
if ( enet_peer_send( pSession->m_Peer, ENET_DEFAULT_CHANNEL, pPacket ) < 0 )
|
||||
{
|
||||
// ENet failed to send the packet
|
||||
NET_LOG( "Failed to send ENet packet to peer" );
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
NET_LOG( "Successfully sent ENet packet to peer" );
|
||||
}
|
||||
|
||||
enet_host_flush( m_Host );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ReceiveMessage()
|
||||
// Desc: Receives a message from client if incoming packets are available
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage* CNetHost::ReceiveMessage( const CNetSession* pSession )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pSession ) return NULL;
|
||||
|
||||
assert( pSession->m_Peer );
|
||||
|
||||
// Let ENet receive a message from peer
|
||||
ENetPacket* pPacket = enet_peer_receive( pSession->m_Peer, ENET_DEFAULT_CHANNEL );
|
||||
if ( !pPacket ) return NULL;
|
||||
|
||||
// Create new message
|
||||
return CNetMessageFactory::CreateMessage( pPacket->data, pPacket->dataLength );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetupSession()
|
||||
// Desc: Setup new session upon creation
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::SetupSession( CNetSession* pSession )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleConnect()
|
||||
// Desc: Allow application to handle client connect
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::HandleConnect( CNetSession* pSession )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleDisconnect()
|
||||
// Desc: Allow application to handle client disconnect
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::HandleDisconnect( CNetSession* pSession )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleMessageReceive()
|
||||
// Desc: Allow application to handle message recive
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetHost::HandleMessageReceive(
|
||||
CNetMessage* pMessage,
|
||||
CNetSession* pSession )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pSession || !pMessage ) return false;
|
||||
|
||||
// Update FSM
|
||||
return pSession->Update( pMessage->GetType(), pMessage );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetSessionCount()
|
||||
// Desc: Returns the number of sessions the host manages
|
||||
//-----------------------------------------------------------------------------
|
||||
uint CNetHost::GetSessionCount( void ) const
|
||||
{
|
||||
return ( uint )m_PeerSessions.size();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetSession()
|
||||
// Desc: Rteurns the session for the index
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetSession* CNetHost::GetSession( uint index )
|
||||
{
|
||||
// Validate parameter
|
||||
if ( index >= GetSessionCount() ) return NULL;
|
||||
|
||||
return m_PeerSessions[ index ].pSession;
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetSession()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetSession::CNetSession( CNetHost* pHost, ENetPeer* pPeer )
|
||||
{
|
||||
m_Host = pHost;
|
||||
m_Peer = pPeer;
|
||||
m_ID = INVALID_SESSION;
|
||||
m_PlayerSlot = NULL;
|
||||
m_ReadyForTurn = false;
|
||||
|
||||
// Register the network session
|
||||
//g_SessionManager.Register( this );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CNetSession()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetSession::~CNetSession( void )
|
||||
{
|
||||
// Release any resources
|
||||
//if ( m_Host ) enet_host_destroy( m_Host );
|
||||
|
||||
//m_Host = NULL;
|
||||
m_Peer = NULL;
|
||||
|
||||
// Unregister the network session
|
||||
//g_SessionManager.Unregister( this );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetName()
|
||||
// Desc: Set a new name for the session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::SetName( const CStr& name )
|
||||
{
|
||||
m_Name = name;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetID()
|
||||
// Desc: Set new ID for this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::SetID( uint ID )
|
||||
{
|
||||
m_ID = ID;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetPlayer()
|
||||
// Desc: Set the player for this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::SetPlayer( CPlayer* pPlayer )
|
||||
{
|
||||
m_Player = pPlayer;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetPlayerSlot()
|
||||
// Desc: Set the player slot for this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot )
|
||||
{
|
||||
m_PlayerSlot = pPlayerSlot;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: StartGame()
|
||||
// Desc: Called by server after informing all clients about starting the game
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::StartGame( void )
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Push()
|
||||
// Desc: Sends a message through ENet
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::Push( CNetMessage* pMessage )
|
||||
{
|
||||
// Validate parameters
|
||||
if ( !pMessage ) return;
|
||||
|
||||
assert( m_Host );
|
||||
|
||||
m_Host->SendMessage( this, pMessage );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: TryPop()
|
||||
// Desc: Receives a message through ENet
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetMessage* CNetSession::TryPop( void )
|
||||
{
|
||||
assert( m_Host );
|
||||
|
||||
return m_Host->ReceiveMessage( this );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ScriptingInit()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetSession::ScriptingInit( void )
|
||||
{
|
||||
AddProperty( L"id", &CNetSession::m_ID );
|
||||
AddProperty( L"name", &CNetSession::m_Name );
|
||||
AddMethod<bool, &CNetSession::JSI_Close>( "close", 0 );
|
||||
|
||||
CJSObject<CNetSession>::ScriptingInit( "NetSession" );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: JSI_Close()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetSession::JSI_Close( JSContext* cx, uintN argc, jsval* argv )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandleMessage()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
//bool CNetSession::HandleMessage( CNetMessage* pMessage )
|
||||
//{
|
||||
// return true;
|
||||
//}
|
||||
|
||||
/*
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetServerSession()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetServerSession::CNetSession(
|
||||
CNetServer* pServer,
|
||||
NetMessageHandler* pHandler )
|
||||
: CNetSession( pHandler )
|
||||
{
|
||||
m_Server = pServer;
|
||||
m_Player = NULL;
|
||||
m_PlayerSlot = NULL;
|
||||
m_IsObserver = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CNetServerSession()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetServerSession::CNetSession(
|
||||
CNetServer* pServer,
|
||||
CSocketInternal* pSocketInternal,
|
||||
NetMessageHandler* pHandler )
|
||||
: CNetSession( pSocketInternal, pHandler )
|
||||
{
|
||||
m_Server = pServer;
|
||||
m_Player = NULL;
|
||||
m_PlayerSlot = NULL;
|
||||
m_IsObserver = false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CNetServerSession()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CNetServerSession::~CNetServerSession( void )
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: StartGame()
|
||||
// Desc: Called by server after informing all clients about starting the game
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetServerSession::StartGame( void )
|
||||
{
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetPlayer()
|
||||
// Desc: Set the player for this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetServerSession::SetPlayer( CPlayer* pPlayer )
|
||||
{
|
||||
m_Player = pPlayer;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetPlayerSlot()
|
||||
// Desc: Set the player slot for this session
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetServerSession::SetPlayerSlot( CPlayerSlot* pPlayerSlot )
|
||||
{
|
||||
m_PlayerSlot = pPlayerSlot;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetID()
|
||||
// Desc: Set new session ID
|
||||
//-----------------------------------------------------------------------------
|
||||
void CNetServerSession::SetID( uint ID )
|
||||
{
|
||||
m_ID = ID;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: BaseHandler()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetServerSession::BaseHandler(
|
||||
CNetMessage* pMessage,
|
||||
CNetSession* pSession )
|
||||
{
|
||||
assert( pMessage );
|
||||
assert( pSession );
|
||||
|
||||
// Validate parameters
|
||||
if ( !pMessage || !pSession ) return false;
|
||||
|
||||
CNetServerSession* pSrvSession = dynamic_cast< CNetSession* >( pSession );
|
||||
if ( pSrvSession ) return false;
|
||||
|
||||
// Handler NMT_ERROR message only
|
||||
if ( pMessage->GetType() != NMT_ERROR ) return false;
|
||||
|
||||
CNetErrorMessage* pErrMessage = dynamic_cast< CNetErrorMessage* >( pMessage );
|
||||
if ( !pErrMessage ) return false;
|
||||
|
||||
if ( pErrMessage->GetState() == SERVER_STATE_DISCONNECTED )
|
||||
{
|
||||
if ( pSrvSession->m_ID != -1 )
|
||||
{
|
||||
pSrvSession->m_Server->RemoveSession( pSrvSession );
|
||||
}
|
||||
|
||||
delete pSrvSession;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Not disconnected? Weired...
|
||||
LOG( WARNING, LOG_CAT_NET, "CNetServerSession::BaseHandler() NMT_ERROR: %s", pErrMessage->ToString().c_str() );
|
||||
}
|
||||
|
||||
delete pMessage;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: HandshakeHandler()
|
||||
// Desc:
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CNetServerSession::HandshakeHandler(
|
||||
CNetMessage* pMessage,
|
||||
CNetSession* pSession )
|
||||
{
|
||||
assert( pMessage );
|
||||
assert( pSession );
|
||||
assert( m_Server );
|
||||
|
||||
// Validate parameters
|
||||
if ( !pMessage || !pSession ) return false;
|
||||
|
||||
CNetServerSession* pSrvSession = dynamic_cast< CNetServerSession* >( pSession );
|
||||
if ( !pSrvSession ) return false;
|
||||
|
||||
LOG( NORMAL, LOG_CAT_NET, "CNetServerSession::HandshakeHandler() %s", pMessage->ToString().c_str() );
|
||||
|
||||
// Call base handler if other message thant NMT_ClientHandshake
|
||||
if ( pMessage->GetType() != NMT_ClientHandshake ) BaseHandler( pMessage, pSession );
|
||||
|
||||
CClientHandshake* pHandshakeMessage = dynamic_cast< CClientHandshake* >( pMessage );
|
||||
if ( !pHandshakeMessage ) return false;
|
||||
|
||||
if ( pHandshakeMessage->m_ProtocolVersion != PS_PROTOCOL_VERSION )
|
||||
{
|
||||
pSrvSession->Push( new CCloseRequestMessage() );
|
||||
BaseHandler( new CNetErrorMessage( PS_OK, SERVER_STATE_DISCONNECTED ), pSrvSession );
|
||||
}
|
||||
|
||||
//??? (else)
|
||||
CServerHandshakeResponse* pNewMessage = new CServerHandshakeResponse();
|
||||
pNewMessage->m_UseProtocolVersion = PS_PROTOCOL_VERSION;
|
||||
pNewMessage->m_Flags = 0;
|
||||
pNewMessage->m_Message = pSrvSession->m_Server->m_WelcomeMessage;
|
||||
|
||||
pSrvSession->Push( pNewMessage );
|
||||
|
||||
pSrvSession->m_MessageHandler = AuthHandler;
|
||||
|
||||
delete pMessage;
|
||||
|
||||
return true;
|
||||
}*/
|
341
source/network/NetSession.h
Normal file
341
source/network/NetSession.h
Normal file
@ -0,0 +1,341 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : NetSession.h
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Network session class interface file
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef NETSESSION_H
|
||||
#define NETSESSION_H
|
||||
|
||||
// INCLUDES
|
||||
#include "Network.h"
|
||||
#include "ps/Singleton.h"
|
||||
#include "ps/GameAttributes.h"
|
||||
#include "ps/Player.h"
|
||||
#include "fsm.h"
|
||||
#include "enet/enet.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
// DECLARATIONS
|
||||
#define INVALID_SESSION ( uint )( ~0 )
|
||||
#define ENET_DEFAULT_CHANNEL 0
|
||||
|
||||
class CNetSession;
|
||||
class CNetHost;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ENetPeer* pPeer;
|
||||
CNetSession* pSession;
|
||||
|
||||
} PeerSession;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
CNetHost* pHost;
|
||||
CNetSession* pSession;
|
||||
|
||||
} FsmActionCtx;
|
||||
|
||||
typedef std::vector< PeerSession > PeerSessionList;
|
||||
|
||||
/*
|
||||
CLASS : CNetHost
|
||||
DESCRIPTION : CNetHost is a wrapper around ENet host conecept
|
||||
NOTES :
|
||||
*/
|
||||
|
||||
class CNetHost : public Singleton< CNetHost >
|
||||
{
|
||||
public:
|
||||
|
||||
CNetHost( void );
|
||||
virtual ~CNetHost( void );
|
||||
|
||||
bool Create( void );
|
||||
bool Create( uint port, uint maxPeers );
|
||||
void Shutdown( void );
|
||||
|
||||
/**
|
||||
* Indicates whether the host is currently a server
|
||||
*
|
||||
* @return Boolean indicating whether the host is a server
|
||||
*/
|
||||
virtual bool IsServer( void ) const { return false; }
|
||||
|
||||
/**
|
||||
* Indicates whether the host is currently a client
|
||||
*
|
||||
* @return Boolean indicating whether the host is a client
|
||||
*/
|
||||
virtual bool IsClient( void ) const { return false; }
|
||||
|
||||
/**
|
||||
* Returns the number of sessions for the host
|
||||
*
|
||||
* @return The number of sessions
|
||||
*/
|
||||
uint GetSessionCount( void ) const;
|
||||
|
||||
/**
|
||||
* Returns the session object for the specified index
|
||||
*
|
||||
* @param index Index for session
|
||||
* @return Session object for index or NULL if not found
|
||||
*/
|
||||
CNetSession* GetSession( uint index );
|
||||
|
||||
/**
|
||||
* Connects to foreign host
|
||||
*
|
||||
* @param host Foreign host name
|
||||
* @param port Port on which the foreign host listens
|
||||
* @return true on success, false on failure
|
||||
*/
|
||||
bool Connect( const CStr& host, uint port );
|
||||
|
||||
/**
|
||||
* Disconnects session from host
|
||||
*
|
||||
* @param pSession Session representing peer
|
||||
* @return true on success, false otherwise
|
||||
*/
|
||||
bool Disconnect( CNetSession* pSession );
|
||||
|
||||
/**
|
||||
* Listens for incoming connections and dispatches host events
|
||||
*
|
||||
* @return true on exit, false dispatch failure
|
||||
*/
|
||||
//bool Run( void );
|
||||
bool Poll( void );
|
||||
|
||||
/**
|
||||
* Broadcast the specified message to connected clients
|
||||
*
|
||||
* @param pMessage Message to broadcast
|
||||
*/
|
||||
void Broadcast( const CNetMessage* pMessage );
|
||||
|
||||
/**
|
||||
* Send the specified message to client
|
||||
*
|
||||
* @param pMessage The message to send
|
||||
*/
|
||||
virtual bool SendMessage(
|
||||
const CNetSession* pSession,
|
||||
const CNetMessage* pMessage );
|
||||
|
||||
/**
|
||||
* Receive a message from client if available
|
||||
*
|
||||
*/
|
||||
virtual CNetMessage* ReceiveMessage( const CNetSession* pSession );
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Attempts to resize the internal buffer to the size indicated by the
|
||||
* passed parameter.
|
||||
*
|
||||
* @param size The new size for the buffer
|
||||
*/
|
||||
void ResizeBuffer( uint size );
|
||||
|
||||
// Allow application to handle new client connect
|
||||
virtual bool SetupSession ( CNetSession* pSession );
|
||||
virtual bool HandleConnect ( CNetSession* pSession );
|
||||
virtual bool HandleDisconnect ( CNetSession* pSession );
|
||||
virtual bool HandleMessageReceive (
|
||||
CNetMessage* pMessage,
|
||||
CNetSession* pSession );
|
||||
|
||||
/**
|
||||
* Worker thread function
|
||||
*
|
||||
* @pData Argument specified on thread creation
|
||||
* @return NULL
|
||||
*/
|
||||
//static void* WorkerFunc( void* pData );
|
||||
|
||||
private:
|
||||
|
||||
// Not implemented
|
||||
CNetHost( const CNetHost& );
|
||||
CNetHost& operator=( const CNetHost& );
|
||||
|
||||
u8* m_Buffer; // Serialize out messages buffer
|
||||
uint m_BufferSize; // Output buffer size
|
||||
ENetHost* m_Host; // Represents this host
|
||||
PeerSessionList m_PeerSessions; // Session list of connected peers
|
||||
//pthread_t m_WorkerID; // Worker thread
|
||||
//sem_t* m_StopWorker; // Worker thread stop semaphore
|
||||
};
|
||||
|
||||
/*
|
||||
CLASS : CNetSession
|
||||
DESCRIPTION : CNetSession is a wrapper class around ENet peer concept
|
||||
which represents a peer from a network connection. A
|
||||
network session is spawned by CNetServer each time a
|
||||
client connects and destroyed when it disconnects. When a
|
||||
new message is received fom a client, its representing
|
||||
session object's message handler is called for processing
|
||||
that message.
|
||||
CNetSession is also a state machine. All client requests
|
||||
are delegated to the current state. The current
|
||||
CNetSessionState object's methods will change the current
|
||||
state as appropriate.
|
||||
NOTES :
|
||||
*/
|
||||
|
||||
class CNetSession : public CFsm,
|
||||
public CJSObject< CNetSession >,
|
||||
public IMessagePipeEnd
|
||||
{
|
||||
friend class CNetHost;
|
||||
|
||||
public:
|
||||
|
||||
virtual ~CNetSession( void );
|
||||
|
||||
/**
|
||||
* Retrieves the name of the session
|
||||
*
|
||||
* @return Session name
|
||||
*/
|
||||
const CStrW& GetName( void ) const { return m_Name; }
|
||||
|
||||
/**
|
||||
* Set the new name for the session
|
||||
*
|
||||
* @param name The session new name
|
||||
*/
|
||||
void SetName( const CStr& name );
|
||||
|
||||
/**
|
||||
* Retrieves the ID of the session
|
||||
*
|
||||
* @return Session ID
|
||||
*/
|
||||
uint GetID( void ) const { return m_ID; }
|
||||
|
||||
/**
|
||||
* Set the ID for this session
|
||||
*
|
||||
* @param New session ID
|
||||
*/
|
||||
void SetID( uint ID );
|
||||
|
||||
/**
|
||||
* Allows both client and server to set a callback handler
|
||||
*
|
||||
* @param pCallbackHandler Callback handler
|
||||
*/
|
||||
//void SetCallbackHandler( ISessionCallback* pCallbackHandler );
|
||||
|
||||
/**
|
||||
* Disconnects the client from remote host
|
||||
*
|
||||
*/
|
||||
void Reset( void );
|
||||
|
||||
void SetPlayer( CPlayer* pPlayer );
|
||||
CPlayer* GetPlayer( void ) { return m_Player; }
|
||||
void SetPlayerSlot( CPlayerSlot* pPlayerSlot );
|
||||
CPlayerSlot* GetPlayerSlot( void ) { return m_PlayerSlot; }
|
||||
void StartGame( void );
|
||||
virtual void Push( CNetMessage* pMessage );
|
||||
virtual CNetMessage* TryPop( void );
|
||||
bool IsReadyForTurn( void ) const { return m_ReadyForTurn; }
|
||||
void SetReadyForTurn( bool newValue ) { m_ReadyForTurn = newValue; }
|
||||
bool JSI_Close( JSContext *cx, uintN argc, jsval *argv );
|
||||
static void ScriptingInit( void );
|
||||
|
||||
//bool HandleMessage( CNetMessage* pMessage );
|
||||
|
||||
protected:
|
||||
|
||||
/**
|
||||
* Process the message passed as parameter
|
||||
*
|
||||
* @param message The message to process
|
||||
* @return true if the message was handler
|
||||
* successufully, false otherwise
|
||||
*/
|
||||
//bool ProcessMessage( const CNetMessage& message );
|
||||
|
||||
private:
|
||||
|
||||
// Only the hosts can create sessions
|
||||
CNetSession( CNetHost* pHost, ENetPeer* pPeer );
|
||||
|
||||
// Not implemented
|
||||
CNetSession( void );
|
||||
CNetSession( const CNetSession& );
|
||||
CNetSession& operator=( const CNetSession& );
|
||||
|
||||
CNetHost* m_Host; // The associated local host
|
||||
ENetPeer* m_Peer; // Represents the peer host
|
||||
uint m_ID; // Session ID
|
||||
CStrW m_Name; // Session name
|
||||
CPlayer* m_Player;
|
||||
CPlayerSlot* m_PlayerSlot;
|
||||
bool m_ReadyForTurn; // Next turn ready flag
|
||||
};
|
||||
|
||||
/*
|
||||
CLASS : CNetServerSession
|
||||
DESCRIPTION :
|
||||
NOTES :
|
||||
*/
|
||||
|
||||
/*class CNetServerSession : public CNetSession,
|
||||
public CJSObject< CNetServerSession >
|
||||
{
|
||||
public:
|
||||
|
||||
bool IsObserver ( void ) const { return m_IsObserver; }
|
||||
CPlayer* GetPlayer ( void ) const { return m_Player; }
|
||||
CPlayerSlot* GetPlayerSlot ( void ) const { return m_PlayerSlot; }
|
||||
void StartGame ( void );
|
||||
void SetPlayer ( CPlayer* pPlayer );
|
||||
void SetPlayerSlot ( CPlayerSlot* pPlayerSlot );
|
||||
|
||||
protected:
|
||||
|
||||
CNetServerSession(
|
||||
CNetServer* pServer,
|
||||
NetMessageHandler* pHandler = m_HandshakeHandler );
|
||||
CNetServerSession(
|
||||
CNetServer* pServer,
|
||||
CSocketInternal* pSocketInternal,
|
||||
NetMessageHandler* pHandler = m_HandshakeHandler );
|
||||
virtual ~CNetServerSession( void );
|
||||
|
||||
private:
|
||||
|
||||
static void ScriptingInit ( void );
|
||||
bool JSI_Close (
|
||||
JSContext* pContext,
|
||||
uintN argc,
|
||||
jsval* argv );
|
||||
|
||||
CNetServer* m_Server;
|
||||
CPlayer* m_Player;
|
||||
CPlayerSlot* m_PlayerSlot;
|
||||
bool m_IsObserver;
|
||||
|
||||
static bool HandshakeHandler( CNetMessage* pMessage, CNetSession* pSession );
|
||||
static bool ObserverHandler ( CNetMessage* pMessage, CNetSession* pSession );
|
||||
static bool BaseHandler ( CNetMessage* pMessage, CNetSession* pSession );
|
||||
static bool AuthHandler ( CNetMessage* pMessage, CNetSession* pSession );
|
||||
static bool PreGameHandler ( CNetMessage* pMessage, CNetSession* pSession );
|
||||
static bool InGameHandler ( CNetMessage* pMessage, CNetSession* pSession );
|
||||
static bool ChatHandler ( CNetMessage* pMessage, CNetSession* pSession );
|
||||
};*/
|
||||
|
||||
#endif // NETSESSION_H
|
||||
|
@ -1,454 +0,0 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "Server.h"
|
||||
#include "ServerSession.h"
|
||||
#include "Network.h"
|
||||
#include "JSEvents.h"
|
||||
|
||||
#include "scripting/ScriptableObject.h"
|
||||
|
||||
#include "ps/Game.h"
|
||||
#include "simulation/Simulation.h"
|
||||
#include "ps/Player.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/CConsole.h"
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
#define LOG_CAT_NET "net"
|
||||
|
||||
CNetServer *g_NetServer=NULL;
|
||||
|
||||
using namespace std;
|
||||
|
||||
// NOTE: Called in network thread
|
||||
CNetServerSession *CNetServer::CreateSession(CSocketInternal *pInt)
|
||||
{
|
||||
CNetServerSession *pRet=new CNetServerSession(this, pInt);
|
||||
|
||||
CServerHandshake *pMsg=new CServerHandshake();
|
||||
pMsg->m_Magic=PS_PROTOCOL_MAGIC;
|
||||
pMsg->m_ProtocolVersion=PS_PROTOCOL_VERSION;
|
||||
pMsg->m_SoftwareVersion=PS_PROTOCOL_VERSION;
|
||||
pRet->Push(pMsg);
|
||||
|
||||
return pRet;
|
||||
}
|
||||
|
||||
// NOTE: Called in network thread
|
||||
void CNetServer::OnAccept(const CSocketAddress &addr)
|
||||
{
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetServer::OnAccept(): Accepted connection from %s port %d", addr.GetString().c_str(), addr.GetPort());
|
||||
|
||||
CSocketInternal *pInt=Accept();
|
||||
CNetServerSession *pSession=CreateSession(pInt);
|
||||
UNUSED2(pSession);
|
||||
}
|
||||
|
||||
CNetServer::CNetServer(CGame *pGame, CGameAttributes *pGameAttribs):
|
||||
m_JSI_Sessions(&m_Sessions),
|
||||
m_pGame(pGame),
|
||||
m_pGameAttributes(pGameAttribs),
|
||||
m_MaxObservers(5),
|
||||
m_LastSessionID(1),
|
||||
m_ServerPlayerName(L"Noname Server Player"),
|
||||
m_ServerName(L"Noname Server"),
|
||||
m_WelcomeMessage(L"Noname Server Welcome Message"),
|
||||
m_Port(-1)
|
||||
{
|
||||
m_pGameAttributes->SetUpdateCallback(AttributeUpdate, this);
|
||||
m_pGameAttributes->SetPlayerUpdateCallback(PlayerAttributeUpdate, this);
|
||||
m_pGameAttributes->SetPlayerSlotAssignmentCallback(PlayerSlotAssignmentCallback, this);
|
||||
|
||||
m_pGame->GetSimulation()->SetTurnManager(this);
|
||||
|
||||
// Set an incredibly long turn length for debugging - less command batch spam that way
|
||||
for (int i=0; i<3; i++)
|
||||
CTurnManager::SetTurnLength(i, CTurnManager::DEFAULT_TURN_LENGTH);
|
||||
|
||||
g_ScriptingHost.SetGlobal("g_NetServer", OBJECT_TO_JSVAL(GetScript()));
|
||||
}
|
||||
|
||||
CNetServer::~CNetServer()
|
||||
{
|
||||
g_ScriptingHost.SetGlobal("g_NetServer", JSVAL_NULL);
|
||||
|
||||
while (m_Sessions.size() > 0)
|
||||
{
|
||||
SessionMap::iterator it=m_Sessions.begin();
|
||||
delete it->second;
|
||||
m_Sessions.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServer::ScriptingInit()
|
||||
{
|
||||
CJSMap<SessionMap>::ScriptingInit("NetServer_SessionMap");
|
||||
|
||||
AddMethod<bool, &CNetServer::JSI_Open>("open", 0);
|
||||
|
||||
AddProperty(L"sessions", &CNetServer::m_JSI_Sessions);
|
||||
|
||||
AddProperty(L"serverPlayerName", &CNetServer::m_ServerPlayerName);
|
||||
AddProperty(L"serverName", &CNetServer::m_ServerName);
|
||||
AddProperty(L"welcomeMessage", &CNetServer::m_WelcomeMessage);
|
||||
|
||||
AddProperty(L"port", &CNetServer::m_Port);
|
||||
|
||||
AddProperty(L"onChat", &CNetServer::m_OnChat);
|
||||
AddProperty(L"onClientConnect", &CNetServer::m_OnClientConnect);
|
||||
AddProperty(L"onClientDisconnect", &CNetServer::m_OnClientDisconnect);
|
||||
|
||||
CJSObject<CNetServer>::ScriptingInit("NetServer");
|
||||
}
|
||||
|
||||
bool CNetServer::JSI_Open(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
|
||||
{
|
||||
CSocketAddress addr;
|
||||
if (m_Port == -1)
|
||||
GetDefaultListenAddress(addr);
|
||||
else
|
||||
addr=CSocketAddress(m_Port, /* m_UseIPv6 ? IPv6 : */ IPv4);
|
||||
|
||||
PS_RESULT res=Bind(addr);
|
||||
if (res != PS_OK)
|
||||
{
|
||||
LOG(CLogger::Error, LOG_CAT_NET, "CNetServer::JSI_Open(): Bind error: %s", res);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PS_RESULT CNetServer::Bind(const CSocketAddress &address)
|
||||
{
|
||||
PS_RESULT res=CServerSocket::Bind(address);
|
||||
if (res==PS_OK)
|
||||
m_ServerState=NSS_PreGame;
|
||||
return res;
|
||||
}
|
||||
|
||||
void FillSetGameConfigCB(const CStrW& name, ISynchedJSProperty *prop, void *userdata)
|
||||
{
|
||||
CSetGameConfig *pMsg=(CSetGameConfig *)userdata;
|
||||
size_t size=pMsg->m_Values.size();
|
||||
pMsg->m_Values.resize(size+1);
|
||||
pMsg->m_Values[size].m_Name=name;
|
||||
pMsg->m_Values[size].m_Value=prop->ToString();
|
||||
}
|
||||
|
||||
void CNetServer::FillSetGameConfig(CSetGameConfig *pMsg)
|
||||
{
|
||||
m_pGameAttributes->IterateSynchedProperties(FillSetGameConfigCB, pMsg);
|
||||
}
|
||||
|
||||
void FillSetPlayerConfigCB(const CStrW& name, ISynchedJSProperty *prop, void *userdata)
|
||||
{
|
||||
CSetPlayerConfig *pMsg=(CSetPlayerConfig *)userdata;
|
||||
size_t size=pMsg->m_Values.size();
|
||||
pMsg->m_Values.resize(size+1);
|
||||
pMsg->m_Values[size].m_Name=name;
|
||||
pMsg->m_Values[size].m_Value=prop->ToString();
|
||||
}
|
||||
|
||||
void CNetServer::FillSetPlayerConfig(CSetPlayerConfig *pMsg, CPlayer *pPlayer)
|
||||
{
|
||||
pMsg->m_PlayerID=(u32)pPlayer->GetPlayerID();
|
||||
pPlayer->IterateSynchedProperties(FillSetPlayerConfigCB, pMsg);
|
||||
}
|
||||
|
||||
void CNetServer::AssignSessionID(CNetServerSession *pSession)
|
||||
{
|
||||
int newID=++m_LastSessionID;
|
||||
pSession->SetID(newID);
|
||||
m_Sessions[newID]=pSession;
|
||||
}
|
||||
|
||||
void CNetServer::AddSession(CNetServerSession *pSession)
|
||||
{
|
||||
{
|
||||
CSetGameConfig *pMsg=new CSetGameConfig();
|
||||
FillSetGameConfig(pMsg);
|
||||
pSession->Push(pMsg);
|
||||
}
|
||||
|
||||
// Broadcast a message for the newly added player session
|
||||
CClientConnect *pMsg=new CClientConnect();
|
||||
pMsg->m_Clients.resize(1);
|
||||
pMsg->m_Clients[0].m_SessionID=pSession->GetID();
|
||||
pMsg->m_Clients[0].m_Name=pSession->GetName();
|
||||
Broadcast(pMsg);
|
||||
|
||||
pMsg=new CClientConnect();
|
||||
|
||||
// Server "client"
|
||||
pMsg->m_Clients.resize(1);
|
||||
pMsg->m_Clients.back().m_SessionID=1; // Server is always 1
|
||||
pMsg->m_Clients.back().m_Name=m_ServerPlayerName;
|
||||
|
||||
// All the other clients
|
||||
SessionMap::iterator it=m_Sessions.begin();
|
||||
for (;it!=m_Sessions.end();++it)
|
||||
{
|
||||
pMsg->m_Clients.push_back(CClientConnect::S_m_Clients());
|
||||
pMsg->m_Clients.back().m_SessionID=it->second->GetID();
|
||||
pMsg->m_Clients.back().m_Name=it->second->GetName();
|
||||
}
|
||||
pSession->Push(pMsg);
|
||||
|
||||
// Sync player slot assignments and player attributes
|
||||
for (size_t i=0;i<m_pGameAttributes->GetSlotCount();i++)
|
||||
{
|
||||
CPlayerSlot *pSlot=m_pGameAttributes->GetSlot(i);
|
||||
|
||||
pSession->Push(CreatePlayerSlotAssignmentMessage(pSlot));
|
||||
|
||||
if (pSlot->GetAssignment() == SLOT_SESSION)
|
||||
{
|
||||
CSetPlayerConfig *pMsg=new CSetPlayerConfig();
|
||||
FillSetPlayerConfig(pMsg, pSlot->GetPlayer());
|
||||
pSession->Push(pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
OnClientConnect(pSession);
|
||||
}
|
||||
|
||||
void CNetServer::AttributeUpdate(const CStrW& name, const CStrW& newValue, void *userdata)
|
||||
{
|
||||
CNetServer *pServer=(CNetServer *)userdata;
|
||||
g_Console->InsertMessage(L"AttributeUpdate: %ls = \"%ls\"", name.c_str(), newValue.c_str());
|
||||
|
||||
CSetGameConfig *pMsg=new CSetGameConfig;
|
||||
pMsg->m_Values.resize(1);
|
||||
pMsg->m_Values[0].m_Name=name;
|
||||
pMsg->m_Values[0].m_Value=newValue;
|
||||
|
||||
pServer->Broadcast(pMsg);
|
||||
}
|
||||
|
||||
void CNetServer::PlayerAttributeUpdate(const CStrW& name, const CStrW& newValue, CPlayer *pPlayer, void *userdata)
|
||||
{
|
||||
CNetServer *pServer=(CNetServer *)userdata;
|
||||
g_Console->InsertMessage(L"PlayerAttributeUpdate(%d): %ls = \"%ls\"", pPlayer->GetPlayerID(), name.c_str(), newValue.c_str());
|
||||
|
||||
CSetPlayerConfig *pMsg=new CSetPlayerConfig;
|
||||
pMsg->m_PlayerID=(u32)pPlayer->GetPlayerID();
|
||||
pMsg->m_Values.resize(1);
|
||||
pMsg->m_Values[0].m_Name=name;
|
||||
pMsg->m_Values[0].m_Value=newValue;
|
||||
|
||||
pServer->Broadcast(pMsg);
|
||||
}
|
||||
|
||||
CNetMessage *CNetServer::CreatePlayerSlotAssignmentMessage(CPlayerSlot *pSlot)
|
||||
{
|
||||
CAssignPlayerSlot *pMsg=new CAssignPlayerSlot();
|
||||
pMsg->m_SlotID=(u32)pSlot->GetSlotID();
|
||||
pMsg->m_SessionID=pSlot->GetSessionID();
|
||||
switch (pSlot->GetAssignment())
|
||||
{
|
||||
#define CASE(_a, _b) case _a: pMsg->m_Assignment=_b; break;
|
||||
CASE(SLOT_CLOSED, PS_ASSIGN_CLOSED)
|
||||
CASE(SLOT_OPEN, PS_ASSIGN_OPEN)
|
||||
CASE(SLOT_SESSION, PS_ASSIGN_SESSION)
|
||||
//CASE(SLOT_AI, PS_ASSIGN_AI)
|
||||
}
|
||||
return pMsg;
|
||||
}
|
||||
|
||||
void CNetServer::PlayerSlotAssignmentCallback(void *userdata, CPlayerSlot *pSlot)
|
||||
{
|
||||
CNetServer *pInstance=(CNetServer *)userdata;
|
||||
if (pSlot->GetAssignment() == SLOT_SESSION)
|
||||
pSlot->GetSession()->SetPlayerSlot(pSlot);
|
||||
CNetMessage *pMsg=CreatePlayerSlotAssignmentMessage(pSlot);
|
||||
g_Console->InsertMessage(L"Player Slot Assignment: %hs\n", pMsg->GetString().c_str());
|
||||
pInstance->Broadcast(pMsg);
|
||||
}
|
||||
|
||||
bool CNetServer::AllowObserver(CNetServerSession* UNUSED(pSession))
|
||||
{
|
||||
return m_Observers.size() < m_MaxObservers;
|
||||
}
|
||||
|
||||
void CNetServer::RemoveSession(CNetServerSession *pSession)
|
||||
{
|
||||
SessionMap::iterator it=m_Sessions.find(pSession->GetID());
|
||||
if (it != m_Sessions.end())
|
||||
m_Sessions.erase(it);
|
||||
|
||||
/*
|
||||
* Player sessions require some extra care:
|
||||
*
|
||||
* Pre-Game: dissociate the slot that was used by the session and
|
||||
* synchronize the disconnection of the client.
|
||||
*
|
||||
* In-Game: Revert all player's entities to Gaia control, awaiting the
|
||||
* client's reconnect attempts [if/when we implement that]
|
||||
*
|
||||
* Post-Game: Just sync disconnection - we don't have any players anymore
|
||||
* and all is fine.
|
||||
*
|
||||
* After this is done, call the JS callback if it's been set.
|
||||
*/
|
||||
if (pSession->GetPlayer())
|
||||
{
|
||||
if (m_ServerState == NSS_PreGame)
|
||||
{
|
||||
pSession->GetPlayerSlot()->AssignClosed();
|
||||
}
|
||||
else if (m_ServerState == NSS_InGame)
|
||||
{
|
||||
// TODO Reassign entities to Gaia control
|
||||
// TODO Set everything up for re-connect and resume
|
||||
SetClientPipe(pSession->GetPlayerSlot()->GetSlotID(), NULL);
|
||||
pSession->GetPlayerSlot()->AssignClosed();
|
||||
}
|
||||
}
|
||||
|
||||
CClientDisconnect *pMsg=new CClientDisconnect();
|
||||
pMsg->m_SessionID=pSession->GetID();
|
||||
Broadcast(pMsg);
|
||||
|
||||
OnClientDisconnect(pSession);
|
||||
|
||||
// TODO Correct handling of observers
|
||||
}
|
||||
|
||||
// Unfortunately, the message queueing model is made so that each message has
|
||||
// to be copied once for each socket its sent over, messages are deleted when
|
||||
// sent by CMessageSocket. We could ref-count, but that requires a lot of
|
||||
// thread safety stuff => hard work
|
||||
void CNetServer::Broadcast(CNetMessage *pMsg)
|
||||
{
|
||||
if (m_Sessions.empty())
|
||||
{
|
||||
delete pMsg;
|
||||
return;
|
||||
}
|
||||
|
||||
SessionMap::iterator it=m_Sessions.begin();
|
||||
// Skip one session
|
||||
++it;
|
||||
// Send a *copy* to all remaining sessions
|
||||
for (;it != m_Sessions.end();++it)
|
||||
{
|
||||
it->second->Push(pMsg->Copy());
|
||||
}
|
||||
// Now send to the first session, *not* copying the message
|
||||
m_Sessions.begin()->second->Push(pMsg);
|
||||
}
|
||||
|
||||
int CNetServer::StartGame()
|
||||
{
|
||||
if (m_pGame->StartGame(m_pGameAttributes) != PSRETURN_OK)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
CTurnManager::Initialize(m_pGameAttributes->GetSlotCount());
|
||||
for (size_t i=0;i<m_pGameAttributes->GetSlotCount();i++)
|
||||
{
|
||||
CPlayerSlot *pSlot=m_pGameAttributes->GetSlot(i);
|
||||
if (pSlot->GetAssignment() == SLOT_SESSION)
|
||||
CTurnManager::SetClientPipe(i, pSlot->GetSession());
|
||||
}
|
||||
m_ServerState=NSS_InGame;
|
||||
|
||||
SessionMap::iterator it=m_Sessions.begin();
|
||||
while (it != m_Sessions.end())
|
||||
{
|
||||
it->second->StartGame();
|
||||
++it;
|
||||
}
|
||||
|
||||
debug_printf("Server StartGame\n");
|
||||
Broadcast(new CStartGame());
|
||||
|
||||
// This is the signal for everyone to start their simulations.
|
||||
//SendBatch(1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServer::GetDefaultListenAddress(CSocketAddress &address)
|
||||
{
|
||||
address=CSocketAddress(PS_DEFAULT_PORT, IPv4);
|
||||
}
|
||||
|
||||
bool CNetServer::NewTurnReady()
|
||||
{
|
||||
// Wait for all clients to check in
|
||||
SessionMap::iterator it = m_Sessions.begin();
|
||||
for (; it != m_Sessions.end(); ++it)
|
||||
{
|
||||
if (!it->second->IsReadyForTurn())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CNetServer::NewTurn()
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
|
||||
// Clear ready flags on clients
|
||||
SessionMap::iterator it = m_Sessions.begin();
|
||||
for (; it != m_Sessions.end(); ++it)
|
||||
{
|
||||
it->second->SetReadyForTurn(false);
|
||||
}
|
||||
|
||||
RecordBatch(2);
|
||||
|
||||
RotateBatches();
|
||||
ClearBatch(2);
|
||||
|
||||
IterateBatch(1, CSimulation::GetMessageMask, m_pGame->GetSimulation());
|
||||
//debug_printf("In NewTurn - sending batch\n");
|
||||
SendBatch(1);
|
||||
//IterateBatch(1, SendToObservers, this);
|
||||
}
|
||||
|
||||
void CNetServer::QueueLocalCommand(CNetMessage *pMsg)
|
||||
{
|
||||
//debug_printf("Queueing command from server\n");
|
||||
QueueIncomingCommand(pMsg);
|
||||
}
|
||||
|
||||
void CNetServer::QueueIncomingCommand(CNetMessage *pMsg)
|
||||
{
|
||||
CScopeLock lock(m_Mutex);
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetServer::QueueIncomingCommand(): %s.", pMsg->GetString().c_str());
|
||||
debug_printf("Got a command! queueing it to 2 turns from now\n");
|
||||
QueueMessage(2, pMsg);
|
||||
}
|
||||
|
||||
void CNetServer::OnChat(const CStrW& from, const CStrW& message)
|
||||
{
|
||||
if (m_OnChat.Defined())
|
||||
{
|
||||
CChatEvent evt(from, message);
|
||||
m_OnChat.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServer::OnClientConnect(CNetServerSession *pSession)
|
||||
{
|
||||
if (m_OnClientConnect.Defined())
|
||||
{
|
||||
CClientConnectEvent evt(pSession);
|
||||
m_OnClientConnect.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServer::OnClientDisconnect(CNetServerSession *pSession)
|
||||
{
|
||||
if (m_OnClientDisconnect.Defined())
|
||||
{
|
||||
CClientDisconnectEvent evt(pSession->GetID(), pSession->GetName());
|
||||
m_OnClientDisconnect.DispatchEvent(GetScript(), &evt);
|
||||
}
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
#ifndef INCLUDED_NETWORK_SERVER
|
||||
#define INCLUDED_NETWORK_SERVER
|
||||
|
||||
#include "Session.h"
|
||||
#include "ps/GameAttributes.h"
|
||||
#include "simulation/TurnManager.h"
|
||||
#include "ps/scripting/JSMap.h"
|
||||
#include "simulation/ScriptObject.h"
|
||||
|
||||
class CGame;
|
||||
|
||||
enum ENetServerState
|
||||
{
|
||||
// We haven't opened the port yet, we're just setting some stuff up.
|
||||
// This is probably equivalent to the first "Start Network Game" screen
|
||||
NSS_PreBind,
|
||||
// The server is open and accepting connections. This is the screen where
|
||||
// rules are set up by the operator and where players join and select civs
|
||||
// and stuff.
|
||||
NSS_PreGame,
|
||||
// In-Game state: the one with all the killing ;-)
|
||||
NSS_InGame,
|
||||
// The game is over and someone has won. Players might linger to chat or
|
||||
// download the replay log.
|
||||
NSS_PostGame
|
||||
};
|
||||
|
||||
class CNetServerSession;
|
||||
|
||||
class CNetServer:
|
||||
protected CServerSocket,
|
||||
protected CTurnManager,
|
||||
public CJSObject<CNetServer>
|
||||
{
|
||||
private:
|
||||
typedef std::map<int, CNetServerSession *> SessionMap;
|
||||
|
||||
/*
|
||||
Every connected session is in m_Sessions as soon as the Handshake and
|
||||
Authentication stages are complete.
|
||||
*/
|
||||
SessionMap m_Sessions;
|
||||
CJSMap<SessionMap> m_JSI_Sessions;
|
||||
/*
|
||||
All sessions that have observer status (observer as in watcher - simple
|
||||
chatters don't have an entry here, only in m_Sessions).
|
||||
Sessions are added here after they have successfully requested observer
|
||||
status.
|
||||
*/
|
||||
std::vector <CNetServerSession *> m_Observers;
|
||||
|
||||
ENetServerState m_ServerState;
|
||||
|
||||
CGame *m_pGame;
|
||||
CGameAttributes *m_pGameAttributes;
|
||||
|
||||
size_t m_MaxObservers;
|
||||
int m_LastSessionID;
|
||||
|
||||
CPlayer *m_pServerPlayer;
|
||||
|
||||
CStrW m_Password;
|
||||
CStrW m_ServerPlayerName;
|
||||
CStrW m_ServerName;
|
||||
CStrW m_WelcomeMessage;
|
||||
|
||||
int m_Port;
|
||||
|
||||
CScriptObject m_OnChat;
|
||||
CScriptObject m_OnClientConnect;
|
||||
CScriptObject m_OnClientDisconnect;
|
||||
|
||||
static CGameAttributes::UpdateCallback AttributeUpdate;
|
||||
static CPlayer::UpdateCallback PlayerAttributeUpdate;
|
||||
static PlayerSlotAssignmentCB PlayerSlotAssignmentCallback;
|
||||
|
||||
void FillSetGameConfig(CSetGameConfig *pMsg);
|
||||
void FillSetPlayerConfig(CSetPlayerConfig *pMsg, CPlayer *pPlayer);
|
||||
static CNetMessage *CreatePlayerSlotAssignmentMessage(CPlayerSlot *slot);
|
||||
|
||||
// JS Interface Methods
|
||||
bool JSI_Open(JSContext *cx, uintN argc, jsval *argv);
|
||||
|
||||
// Synchronization object for batches
|
||||
CMutex m_Mutex;
|
||||
|
||||
protected:
|
||||
friend class CNetServerSession;
|
||||
|
||||
// Assign a session ID to the session. Do this just before calling AddSession
|
||||
void AssignSessionID(CNetServerSession *pSession);
|
||||
// Add the session. This will be called after the session passes the
|
||||
// handshake and authentication stages. AssignSessionID should've been called
|
||||
// on the session prior to calling this method.
|
||||
void AddSession(CNetServerSession *pSession);
|
||||
|
||||
// Remove the session from the server
|
||||
void RemoveSession(CNetServerSession *pSession);
|
||||
|
||||
// Queue a command coming in from the wire. The command has been validated
|
||||
// by the caller.
|
||||
void QueueIncomingCommand(CNetMessage *pMsg);
|
||||
|
||||
// Call the JS callback for incoming events
|
||||
void OnChat(const CStrW& from, const CStrW& message);
|
||||
void OnClientConnect(CNetServerSession *pSession);
|
||||
void OnClientDisconnect(CNetServerSession *pSession);
|
||||
|
||||
// OVERRIDES FROM CServerSocket
|
||||
virtual void OnAccept(const CSocketAddress &);
|
||||
|
||||
// OVERRIDES FROM CTurnManager
|
||||
virtual bool NewTurnReady();
|
||||
virtual void NewTurn();
|
||||
virtual void QueueLocalCommand(CNetMessage *pMsg);
|
||||
|
||||
// OVERRIDABLES
|
||||
// Will only be called from the Network Thread, by the OnAccept handler
|
||||
virtual CNetServerSession *CreateSession(CSocketInternal *pInt);
|
||||
|
||||
// Ask the server if the session is allowed to start observing.
|
||||
//
|
||||
// Returns:
|
||||
// true if the session should be made an observer
|
||||
// false otherwise
|
||||
virtual bool AllowObserver(CNetServerSession *pSession);
|
||||
|
||||
public:
|
||||
CNetServer(CGame *pGame, CGameAttributes *pGameAttribs);
|
||||
virtual ~CNetServer();
|
||||
|
||||
static void GetDefaultListenAddress(CSocketAddress &address);
|
||||
PS_RESULT Bind(const CSocketAddress &address);
|
||||
|
||||
inline void SetPassword(const CStr& password)
|
||||
{ m_Password=password; }
|
||||
|
||||
inline const CStrW& GetServerPlayerName()
|
||||
{ return m_ServerPlayerName; }
|
||||
|
||||
inline ENetServerState GetServerState()
|
||||
{ return m_ServerState; }
|
||||
|
||||
int StartGame();
|
||||
|
||||
void Broadcast(CNetMessage *);
|
||||
|
||||
static void ScriptingInit();
|
||||
};
|
||||
|
||||
extern CNetServer *g_NetServer;
|
||||
|
||||
#endif // INCLUDED_NETWORK_SERVER
|
@ -1,214 +0,0 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "ServerSession.h"
|
||||
#include "Server.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/CConsole.h"
|
||||
|
||||
CNetServerSession::CNetServerSession(CNetServer *pServer, CSocketInternal *pInt,
|
||||
MessageHandler *pMsgHandler):
|
||||
CNetSession(pInt, pMsgHandler),
|
||||
m_pServer(pServer),
|
||||
m_pPlayer(NULL),
|
||||
m_pPlayerSlot(NULL),
|
||||
m_IsObserver(false),
|
||||
m_ID(-1),
|
||||
m_ReadyForTurn(false)
|
||||
{
|
||||
}
|
||||
|
||||
CNetServerSession::~CNetServerSession()
|
||||
{
|
||||
}
|
||||
|
||||
void CNetServerSession::StartGame()
|
||||
{
|
||||
if (m_pMessageHandler==PreGameHandler)
|
||||
m_pMessageHandler=InGameHandler;
|
||||
}
|
||||
|
||||
#define UNHANDLED(_pMsg) return false;
|
||||
#define HANDLED(_pMsg) delete _pMsg; return true;
|
||||
#define TAKEN(_pMsg) return true;
|
||||
|
||||
bool CNetServerSession::BaseHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
CNetServerSession *pSession=(CNetServerSession *)pNetSession;
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ERROR:
|
||||
{
|
||||
CNetErrorMessage *msg=(CNetErrorMessage *)pMsg;
|
||||
if (msg->m_State == SS_UNCONNECTED)
|
||||
{
|
||||
if (pSession->m_ID != -1)
|
||||
pSession->m_pServer->RemoveSession(pSession);
|
||||
delete pSession;
|
||||
}
|
||||
else // error, but not disconnected? something weird is up...
|
||||
LOG(CLogger::Warning, LOG_CAT_NET, "CNetServerSession::BaseHandler(): NMT_ERROR: %s", msg->GetString().c_str());
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetServerSession::HandshakeHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
CNetServerSession *pSession=(CNetServerSession *)pNetSession;
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::HandshakeHandler(): %s.", pMsg->GetString().c_str());
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_ClientHandshake:
|
||||
{
|
||||
CClientHandshake *msg=(CClientHandshake *)pMsg;
|
||||
|
||||
if (msg->m_ProtocolVersion != PS_PROTOCOL_VERSION)
|
||||
{
|
||||
pSession->Push(new CCloseRequestMessage());
|
||||
BaseHandler(new CNetErrorMessage(PS_OK, SS_UNCONNECTED), pSession);
|
||||
}
|
||||
|
||||
CServerHandshakeResponse *retmsg=new CServerHandshakeResponse();
|
||||
retmsg->m_UseProtocolVersion=PS_PROTOCOL_VERSION;
|
||||
retmsg->m_Flags=0;
|
||||
retmsg->m_Message=pSession->m_pServer->m_WelcomeMessage;
|
||||
pSession->Push(retmsg);
|
||||
|
||||
pSession->m_pMessageHandler=AuthenticateHandler;
|
||||
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
|
||||
default:
|
||||
return BaseHandler(pMsg, pNetSession);
|
||||
}
|
||||
}
|
||||
|
||||
bool CNetServerSession::AuthenticateHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
CNetServerSession *pSession=(CNetServerSession *)pNetSession;
|
||||
CNetServer *pServer=pSession->m_pServer;
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): %s.", pMsg->GetString().c_str());
|
||||
if (pMsg->GetType() == NMT_Authenticate)
|
||||
{
|
||||
CAuthenticate *msg=(CAuthenticate *)pMsg;
|
||||
|
||||
if (msg->m_Password == pSession->m_pServer->m_Password)
|
||||
{
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Successful");
|
||||
pSession->m_Name=msg->m_Name;
|
||||
|
||||
pServer->AssignSessionID(pSession);
|
||||
|
||||
CAuthenticationResult *msg=new CAuthenticationResult();
|
||||
msg->m_Code=NRC_OK;
|
||||
msg->m_SessionID=pSession->m_ID;
|
||||
msg->m_Message=L"Logged in";
|
||||
pSession->Push(msg);
|
||||
|
||||
pServer->AddSession(pSession);
|
||||
if (pServer->GetServerState() == NSS_PreGame)
|
||||
{
|
||||
pSession->m_pMessageHandler=PreGameHandler;
|
||||
}
|
||||
else // We're not in pre-game. The session becomes a chatter/observer here.
|
||||
{
|
||||
pSession->m_pMessageHandler=ObserverHandler;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG(CLogger::Warning, LOG_CAT_NET, "CNetServerSession::AuthenticateHandler(): Login Failed");
|
||||
CAuthenticationResult *msg=new CAuthenticationResult();
|
||||
msg->m_Code=NRC_PasswordInvalid;
|
||||
msg->m_SessionID=0;
|
||||
msg->m_Message=L"Invalid Password";
|
||||
pSession->Push(msg);
|
||||
}
|
||||
|
||||
HANDLED(pMsg);
|
||||
}
|
||||
return BaseHandler(pMsg, pNetSession);
|
||||
}
|
||||
|
||||
bool CNetServerSession::PreGameHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
return ChatHandler(pMsg, pNetSession);
|
||||
}
|
||||
|
||||
bool CNetServerSession::ObserverHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
printf("CNetServerSession::ObserverHandler(): %s.\n", pMsg->GetString().c_str());
|
||||
|
||||
// TODO Implement observers and chatter => observer promotion
|
||||
/*
|
||||
if (pMsg->GetType() == NMT_RequestObserve)
|
||||
*/
|
||||
|
||||
return ChatHandler(pMsg, pNetSession);
|
||||
}
|
||||
|
||||
bool CNetServerSession::ChatHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
CNetServerSession *pSession=(CNetServerSession *)pNetSession;
|
||||
if (pMsg->GetType() == NMT_ChatMessage)
|
||||
{
|
||||
CChatMessage *msg=(CChatMessage *)pMsg;
|
||||
msg->m_Sender=pSession->m_Name;
|
||||
g_Console->ReceivedChatMessage(pSession->GetName().c_str(), msg->m_Message.c_str());
|
||||
pSession->m_pServer->OnChat(msg->m_Sender, msg->m_Message);
|
||||
pSession->m_pServer->Broadcast(msg);
|
||||
|
||||
TAKEN(pMsg);
|
||||
}
|
||||
|
||||
return BaseHandler(pMsg, pNetSession);
|
||||
}
|
||||
|
||||
bool CNetServerSession::InGameHandler(CNetMessage *pMsg, CNetSession *pNetSession)
|
||||
{
|
||||
CNetServerSession *pSession=(CNetServerSession *)pNetSession;
|
||||
if (pMsg->GetType() != NMT_EndCommandBatch)
|
||||
LOG(CLogger::Normal, LOG_CAT_NET, "CNetServerSession::InGameHandler(): %s.", pMsg->GetString().c_str());
|
||||
|
||||
if (BaseHandler(pMsg, pNetSession))
|
||||
return true;
|
||||
|
||||
if (ChatHandler(pMsg, pNetSession))
|
||||
return true;
|
||||
|
||||
if (pMsg->GetType() >= NMT_COMMAND_FIRST && pMsg->GetType() < NMT_COMMAND_LAST)
|
||||
{
|
||||
// All Command Messages (i.e. simulation turn synchronized messages)
|
||||
//pSession->m_pPlayer->ValidateCommand(pMsg);
|
||||
pSession->m_pServer->QueueIncomingCommand(pMsg);
|
||||
TAKEN(pMsg);
|
||||
}
|
||||
|
||||
switch (pMsg->GetType())
|
||||
{
|
||||
case NMT_EndCommandBatch:
|
||||
pSession->m_ReadyForTurn = true;
|
||||
HANDLED(pMsg);
|
||||
|
||||
default:
|
||||
UNHANDLED(pMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void CNetServerSession::ScriptingInit()
|
||||
{
|
||||
AddProperty( L"id", &CNetServerSession::m_ID );
|
||||
AddProperty( L"name", (CStrW CNetServerSession::*)&CNetServerSession::m_Name );
|
||||
AddMethod<bool, &CNetServerSession::JSI_Close>("close", 0);
|
||||
|
||||
CJSObject<CNetServerSession>::ScriptingInit("NetSession");
|
||||
}
|
||||
|
||||
bool CNetServerSession::JSI_Close(JSContext* UNUSED(cx), uintN UNUSED(argc), jsval* UNUSED(argv))
|
||||
{
|
||||
return false;
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
CNetServerSession - the server's representation of a connected client
|
||||
|
||||
DESCRIPTION:
|
||||
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_NETWORK_SERVERSESSION
|
||||
#define INCLUDED_NETWORK_SERVERSESSION
|
||||
|
||||
#include "Session.h"
|
||||
#include "scripting/ScriptableObject.h"
|
||||
|
||||
class CNetServer;
|
||||
class CPlayer;
|
||||
class CPlayerSlot;
|
||||
|
||||
class CNetServerSession: public CNetSession, public CJSObject<CNetServerSession>
|
||||
{
|
||||
CNetServer *m_pServer;
|
||||
CPlayer *m_pPlayer;
|
||||
CPlayerSlot *m_pPlayerSlot;
|
||||
bool m_IsObserver;
|
||||
int m_ID;
|
||||
bool m_ReadyForTurn; // Is the last turn acknowledged?
|
||||
|
||||
// JS INTERFACE
|
||||
bool JSI_Close(JSContext *cx, uintN argc, jsval *argv);
|
||||
|
||||
protected:
|
||||
friend class CNetServer;
|
||||
|
||||
inline void SetPlayer(CPlayer *pPlayer)
|
||||
{ m_pPlayer=pPlayer; }
|
||||
|
||||
inline void SetPlayerSlot(CPlayerSlot *pPlayerSlot)
|
||||
{ m_pPlayerSlot=pPlayerSlot; }
|
||||
|
||||
inline void SetID(int id)
|
||||
{ m_ID=id; }
|
||||
|
||||
public:
|
||||
CNetServerSession(CNetServer *pServer, CSocketInternal *pInt, MessageHandler *pMsgHandler=HandshakeHandler);
|
||||
virtual ~CNetServerSession();
|
||||
|
||||
static void ScriptingInit();
|
||||
|
||||
inline bool IsObserver()
|
||||
{ return m_IsObserver; }
|
||||
inline CPlayer *GetPlayer()
|
||||
{ return m_pPlayer; }
|
||||
inline CPlayerSlot *GetPlayerSlot()
|
||||
{ return m_pPlayerSlot; }
|
||||
inline int GetID()
|
||||
{ return m_ID; }
|
||||
inline bool IsReadyForTurn()
|
||||
{ return m_ReadyForTurn; }
|
||||
inline void SetReadyForTurn(bool value)
|
||||
{ m_ReadyForTurn = value; }
|
||||
|
||||
// Called by server when starting the game, after sending NMT_StartGame to
|
||||
// all connected clients.
|
||||
void StartGame();
|
||||
|
||||
static MessageHandler BaseHandler;
|
||||
static MessageHandler HandshakeHandler;
|
||||
static MessageHandler AuthenticateHandler;
|
||||
static MessageHandler PreGameHandler;
|
||||
static MessageHandler ObserverHandler;
|
||||
static MessageHandler ChatHandler;
|
||||
static MessageHandler InGameHandler;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,8 +0,0 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "Session.h"
|
||||
|
||||
CNetSession::~CNetSession()
|
||||
{
|
||||
g_SessionManager.Deregister(this);
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
#ifndef INCLUDED_NETWORK_SESSION
|
||||
#define INCLUDED_NETWORK_SESSION
|
||||
|
||||
#include "Network.h"
|
||||
#include "SessionManager.h"
|
||||
|
||||
/*
|
||||
CNetSession
|
||||
|
||||
DESCRIPTION:
|
||||
The representation of a network session (on both the client and server
|
||||
side)
|
||||
*/
|
||||
class CNetSession: public CMessageSocket
|
||||
{
|
||||
friend class CNetClient;
|
||||
protected:
|
||||
/*
|
||||
The MessageHandler callback follows the contract of HandleMessage, see
|
||||
the documentation for that function for more information.
|
||||
*/
|
||||
typedef bool (MessageHandler)(CNetMessage *, CNetSession *);
|
||||
MessageHandler *m_pMessageHandler;
|
||||
|
||||
CStrW m_Name;
|
||||
|
||||
public:
|
||||
inline CNetSession(MessageHandler *pMsgHandler=NULL):
|
||||
m_pMessageHandler(pMsgHandler)
|
||||
{
|
||||
g_SessionManager.Register(this);
|
||||
}
|
||||
inline CNetSession(CSocketInternal *pInt, MessageHandler *pMsgHandler=NULL):
|
||||
CMessageSocket(pInt),
|
||||
m_pMessageHandler(pMsgHandler)
|
||||
{
|
||||
g_SessionManager.Register(this);
|
||||
}
|
||||
|
||||
virtual ~CNetSession();
|
||||
|
||||
/*
|
||||
Handle an incoming message.
|
||||
|
||||
THREADS:
|
||||
When used with the session manager, this method will only be
|
||||
called from the main thread.
|
||||
|
||||
ARGUMENTS:
|
||||
pMsg The incoming message
|
||||
pSocket The socket the message came from
|
||||
|
||||
RETURNS:
|
||||
TRUE if the message was handled by this class or another protocol
|
||||
that this class knows of. FALSE if the message was not handled.
|
||||
*/
|
||||
inline bool HandleMessage(CNetMessage *pMsg)
|
||||
{
|
||||
if (m_pMessageHandler)
|
||||
return (m_pMessageHandler)(pMsg, this);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
inline const CStrW& GetName()
|
||||
{
|
||||
return m_Name;
|
||||
}
|
||||
};
|
||||
|
||||
#endif //INCLUDED_NETWORK_SESSION
|
@ -1,86 +0,0 @@
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "Session.h"
|
||||
#include "Network.h"
|
||||
#include "ps/CLogger.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
void CSessionManager::Register(CNetSession *pSession)
|
||||
{
|
||||
CScopeLock scopeLock(m_Mutex);
|
||||
SessionMap::iterator it;
|
||||
|
||||
// The total number of references to this session should be 1 and only 1
|
||||
// Either there's one in m_Sessions or there's one in m_AddQueue. There
|
||||
// shall be no references in the remove queue.
|
||||
// m_Sessions is Read-Only
|
||||
|
||||
if ((it = m_RemoveQueue.find(pSession)) != m_RemoveQueue.end())
|
||||
m_RemoveQueue.erase(it);
|
||||
if (m_Sessions.find(pSession) == m_Sessions.end())
|
||||
m_AddQueue[pSession]=pSession;
|
||||
}
|
||||
|
||||
void CSessionManager::Deregister(CNetSession *pSession)
|
||||
{
|
||||
CScopeLock scopeLock(m_Mutex);
|
||||
SessionMap::iterator it;
|
||||
|
||||
// The total number of references to this session should be 2 or 0:
|
||||
// One in m_Sessions and one in the remove queue or 0 in both. None in
|
||||
// m_AddQueue.
|
||||
// m_Sessions is Read-Only
|
||||
|
||||
if ((it = m_AddQueue.find(pSession)) != m_AddQueue.end())
|
||||
m_AddQueue.erase(it);
|
||||
if (m_Sessions.find(pSession) != m_Sessions.end())
|
||||
m_RemoveQueue.insert(make_pair(pSession, pSession));
|
||||
}
|
||||
|
||||
void CSessionManager::Poll()
|
||||
{
|
||||
m_Mutex.Lock();
|
||||
SessionMap::iterator it;
|
||||
|
||||
// De/Register should've made sure that it doesn't matter which order we
|
||||
// process the two queues.
|
||||
for (it=m_AddQueue.begin();it != m_AddQueue.end();++it)
|
||||
{
|
||||
m_Sessions.insert(*it);
|
||||
}
|
||||
m_AddQueue.clear();
|
||||
for (it=m_RemoveQueue.begin();it != m_RemoveQueue.end();++it)
|
||||
{
|
||||
m_Sessions.erase(it->second);
|
||||
}
|
||||
m_RemoveQueue.clear();
|
||||
|
||||
it=m_Sessions.begin();
|
||||
while (it != m_Sessions.end())
|
||||
{
|
||||
CNetMessage *pMsg;
|
||||
|
||||
// Extraneous? well, sessions are perfectly able to delete other
|
||||
// sessions (or cause them to be deleted) in their message handler
|
||||
if (m_RemoveQueue.find(it->second) != m_RemoveQueue.end())
|
||||
continue;
|
||||
|
||||
while ((pMsg = it->second->TryPop()) != NULL)
|
||||
{
|
||||
CNetSession *pSess=it->second;
|
||||
m_Mutex.Unlock();
|
||||
if (!pSess->HandleMessage(pMsg))
|
||||
{
|
||||
LOG(CLogger::Warning, LOG_CAT_NET, "CSessionManager::Poll(): Unhandled message %s.", pMsg->GetString().c_str());
|
||||
delete pMsg;
|
||||
}
|
||||
m_Mutex.Lock();
|
||||
|
||||
if (m_RemoveQueue.find(it->second) != m_RemoveQueue.end())
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
m_Mutex.Unlock();
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
#ifndef INCLUDED_NETWORK_SESSIONMANAGER
|
||||
#define INCLUDED_NETWORK_SESSIONMANAGER
|
||||
|
||||
#include "ps/ThreadUtil.h"
|
||||
|
||||
class CNetSession;
|
||||
|
||||
/*
|
||||
NAME: CSessionManager
|
||||
|
||||
The central nexus of network message handling. Contains the entry point
|
||||
called from the main thread.
|
||||
CNetSession's are registered and when the Poll method finds that the session
|
||||
has a pending message, the session object's HandleMessage method is called
|
||||
to handle it. Any unhandled messages (HandleMessage returns false) are
|
||||
logged to the system log.
|
||||
*/
|
||||
class CSessionManager: public Singleton<CSessionManager>
|
||||
{
|
||||
typedef std::map <CNetSession *, CNetSession *> SessionMap;
|
||||
|
||||
SessionMap m_Sessions;
|
||||
SessionMap m_AddQueue;
|
||||
SessionMap m_RemoveQueue;
|
||||
CMutex m_Mutex;
|
||||
|
||||
public:
|
||||
|
||||
/*
|
||||
Poll all registered sessions and pass all messages to their
|
||||
message handlers.
|
||||
|
||||
THREADS: Call from Main Thread only
|
||||
*/
|
||||
void Poll();
|
||||
|
||||
/*
|
||||
Register a network session with the session manager. Future calls to
|
||||
Poll() will poll this session's socket and pass any messages to
|
||||
its message handler function.
|
||||
|
||||
THREADS: Safe from all threads
|
||||
*/
|
||||
void Register(CNetSession *pSession);
|
||||
|
||||
/*
|
||||
Delete the protocol context associated with the specified socket.
|
||||
|
||||
THREADS: Safe from all threads
|
||||
*/
|
||||
void Deregister(CNetSession *pSession);
|
||||
};
|
||||
#define g_SessionManager (CSessionManager::GetSingleton())
|
||||
|
||||
#endif
|
445
source/network/fsm.cpp
Normal file
445
source/network/fsm.cpp
Normal file
@ -0,0 +1,445 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : fsm.cpp
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Finite state machine class implementation
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
// INCLUDES
|
||||
#include "precompiled.h"
|
||||
#include "fsm.h"
|
||||
|
||||
// DECLARATIONS
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CFsmEvent()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmEvent::CFsmEvent( unsigned int type )
|
||||
{
|
||||
m_Type = type;
|
||||
m_Param = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name; ~CFsmEvent()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmEvent::~CFsmEvent( void )
|
||||
{
|
||||
m_Param = NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetParamRef()
|
||||
// Desc: Sets the parameter for the event
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsmEvent::SetParamRef( void* pParam )
|
||||
{
|
||||
m_Param = pParam;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CFsmTransition()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmTransition::CFsmTransition( unsigned int state )
|
||||
{
|
||||
m_CurrState = state;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CFsmTransition()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmTransition::~CFsmTransition( void )
|
||||
{
|
||||
m_Actions.clear();
|
||||
m_Conditions.clear();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: RegisterAction()
|
||||
// Desc: Adds action that will be executed when the transition will occur
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsmTransition::RegisterAction( void* pAction, void* pContext )
|
||||
{
|
||||
CallbackFunction callback;
|
||||
|
||||
// Add action at the end of actions list
|
||||
callback.pFunction = pAction;
|
||||
callback.pContext = pContext;
|
||||
|
||||
m_Actions.push_back( callback );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: AddCondition()
|
||||
// Desc: Adds condition which will be evaluated when the transition will occurs
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsmTransition::RegisterCondition( void* pCondition, void* pContext )
|
||||
{
|
||||
CallbackFunction callback;
|
||||
|
||||
// Add condition at the end of conditions list
|
||||
callback.pFunction = pCondition;
|
||||
callback.pContext = pContext;
|
||||
|
||||
m_Conditions.push_back( callback );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetEvent()
|
||||
// Desc: Set event for which transition will occur
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsmTransition::SetEvent( CFsmEvent* pEvent )
|
||||
{
|
||||
m_Event = pEvent;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetNextState()
|
||||
// Desc: Set next state the transition will switch the system to
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsmTransition::SetNextState( unsigned int nextState )
|
||||
{
|
||||
m_NextState = nextState;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ApplyConditions()
|
||||
// Desc: Evaluate conditions for the transition
|
||||
// Note: If there are no conditions, assume true
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsmTransition::ApplyConditions( void ) const
|
||||
{
|
||||
bool eval = true;
|
||||
|
||||
CallbackList::const_iterator it = m_Conditions.begin();
|
||||
for( ; it != m_Conditions.end(); it++ )
|
||||
{
|
||||
if ( it->pFunction )
|
||||
{
|
||||
// Evaluate condition
|
||||
CONDITION Condition = ( CONDITION )it->pFunction;
|
||||
eval &= Condition( it->pContext );
|
||||
}
|
||||
}
|
||||
|
||||
return eval;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: RunActions()
|
||||
// Desc: Execur actions for the transition
|
||||
// Note: If there are no actions, assume true
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsmTransition::RunActions( void ) const
|
||||
{
|
||||
bool result = true;
|
||||
|
||||
CallbackList::const_iterator it = m_Actions.begin();
|
||||
for( ; it != m_Actions.end(); it++ )
|
||||
{
|
||||
if ( it->pFunction )
|
||||
{
|
||||
// Run action
|
||||
ACTION Action = ( ACTION )it->pFunction;
|
||||
result &= Action( it->pContext, m_Event );
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: CFsm()
|
||||
// Desc: Constructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsm::CFsm( void )
|
||||
{
|
||||
m_Done = false;
|
||||
m_FirstState = ( unsigned int )FSM_INVALID_STATE;
|
||||
m_CurrState = ( unsigned int )FSM_INVALID_STATE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: ~CFsm()
|
||||
// Desc: Destructor
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsm::~CFsm( void )
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Setup()
|
||||
// Desc: Setup events, actions and state transitions
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsm::Setup( void )
|
||||
{
|
||||
// Does nothing by default
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Reset()
|
||||
// Desc: Shuts down the state machine and releases any resources
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsm::Shutdown( void )
|
||||
{
|
||||
// Release transitions
|
||||
TransitionList::iterator itTransition = m_Transitions.begin();
|
||||
for ( ; itTransition < m_Transitions.end(); itTransition++ )
|
||||
{
|
||||
CFsmTransition* pCurrTransition = *itTransition;
|
||||
if ( !pCurrTransition ) continue;
|
||||
|
||||
delete pCurrTransition;
|
||||
}
|
||||
|
||||
// Release events
|
||||
EventMap::iterator itEvent = m_Events.begin();
|
||||
for( ; itEvent != m_Events.end(); itEvent++ )
|
||||
{
|
||||
CFsmEvent* pCurrEvent = itEvent->second;
|
||||
if ( !pCurrEvent ) continue;
|
||||
|
||||
delete pCurrEvent;
|
||||
}
|
||||
|
||||
m_States.clear();
|
||||
m_Events.clear();
|
||||
m_Transitions.clear();
|
||||
|
||||
m_Done = false;
|
||||
m_FirstState = ( unsigned int )FSM_INVALID_STATE;
|
||||
m_CurrState = ( unsigned int )FSM_INVALID_STATE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: AddState()
|
||||
// Desc: Adds the specified state to the internal list of states
|
||||
// Note: If a state with the specified ID exists, the state is not added
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsm::AddState( unsigned int state )
|
||||
{
|
||||
m_States.insert( state );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: AddEvent()
|
||||
// Desc: Adds the specified event to the internal list of events
|
||||
// Note: If an eveny with the specified ID exists, the event is not added
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmEvent* CFsm::AddEvent( unsigned int eventType )
|
||||
{
|
||||
CFsmEvent* pEvent = NULL;
|
||||
|
||||
// Lookup event by type
|
||||
EventMap::iterator it = m_Events.find( eventType );
|
||||
if ( it != m_Events.end() )
|
||||
{
|
||||
pEvent = it->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
pEvent = new CFsmEvent( eventType );
|
||||
if ( !pEvent ) return NULL;
|
||||
|
||||
// Store new event into internal map
|
||||
m_Events[ eventType ] = pEvent;
|
||||
}
|
||||
|
||||
return pEvent;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: AddTransition()
|
||||
// Desc: Adds a new transistion to the state machine
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmTransition* CFsm::AddTransition(
|
||||
unsigned int state,
|
||||
unsigned int eventType,
|
||||
unsigned int nextState )
|
||||
{
|
||||
// Make sure we store the current state
|
||||
AddState( state );
|
||||
|
||||
// Make sure we store the next state
|
||||
AddState( nextState );
|
||||
|
||||
// Make sure we store the event
|
||||
CFsmEvent* pEvent = AddEvent( eventType );
|
||||
if ( !pEvent ) return NULL;
|
||||
|
||||
// Create new transition
|
||||
CFsmTransition* pNewTransition = new CFsmTransition( state );
|
||||
if ( !pNewTransition ) return NULL;
|
||||
|
||||
// Setup new transition
|
||||
pNewTransition->SetEvent( pEvent );
|
||||
pNewTransition->SetNextState( nextState );
|
||||
|
||||
// Store new transition
|
||||
m_Transitions.push_back( pNewTransition );
|
||||
|
||||
return pNewTransition;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: AddTransition()
|
||||
// Desc: Adds a new transistion to the state machine
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmTransition* CFsm::AddTransition(
|
||||
unsigned int state,
|
||||
unsigned int eventType,
|
||||
unsigned int nextState,
|
||||
void* pAction,
|
||||
void* pContext )
|
||||
{
|
||||
CFsmTransition* pTransition = AddTransition( state, eventType, nextState );
|
||||
if ( !pTransition ) return NULL;
|
||||
|
||||
// If action specified, register it
|
||||
if ( pAction )
|
||||
pTransition->RegisterAction( pAction, pContext );
|
||||
|
||||
return pTransition;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: GetTransition()
|
||||
// Desc: Lookup transition given the state, event and next state to transition
|
||||
//-----------------------------------------------------------------------------
|
||||
CFsmTransition* CFsm::GetTransition(
|
||||
unsigned int state,
|
||||
unsigned int eventType ) const
|
||||
{
|
||||
// Valid state?
|
||||
if ( !IsValidState( state ) ) return NULL;
|
||||
|
||||
// Valid event?
|
||||
if ( !IsValidEvent( eventType ) ) return NULL;
|
||||
|
||||
// Loop through the list of transitions
|
||||
TransitionList::const_iterator it = m_Transitions.begin();
|
||||
for ( ; it != m_Transitions.end(); it++ )
|
||||
{
|
||||
CFsmTransition* pCurrTransition = *it;
|
||||
if ( !pCurrTransition ) continue;
|
||||
|
||||
CFsmEvent* pCurrEvent = pCurrTransition->GetEvent();
|
||||
if ( !pCurrEvent ) continue;
|
||||
|
||||
// Is it our transition?
|
||||
if ( pCurrTransition->GetCurrState() == state &&
|
||||
pCurrEvent->GetType() == eventType )
|
||||
{
|
||||
return pCurrTransition;
|
||||
}
|
||||
}
|
||||
|
||||
// No transition found
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetFirstState()
|
||||
// Desc: Set initial state for FSM
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsm::SetFirstState( unsigned int firstState )
|
||||
{
|
||||
m_FirstState = firstState;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: SetCurrState()
|
||||
// Desc: Set current state and update last state to current state
|
||||
//-----------------------------------------------------------------------------
|
||||
void CFsm::SetCurrState( unsigned int state )
|
||||
{
|
||||
m_CurrState = state;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: IsFirstTime()
|
||||
// Desc: Verifies if the state machine has been already updated
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsm::IsFirstTime( void ) const
|
||||
{
|
||||
return ( m_CurrState == FSM_INVALID_STATE );
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: Update()
|
||||
// Desc: Updates state machine and retrieves next state
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsm::Update( unsigned int eventType, void* pEventParam )
|
||||
{
|
||||
// Valid event?
|
||||
if ( !IsValidEvent( eventType ) )
|
||||
return false;
|
||||
|
||||
// First time update?
|
||||
if ( IsFirstTime() )
|
||||
m_CurrState = m_FirstState;
|
||||
|
||||
// Lookup transition
|
||||
CFsmTransition* pTransition = GetTransition( m_CurrState, eventType );
|
||||
if ( !pTransition ) return false;
|
||||
|
||||
// Setup event parameter
|
||||
EventMap::iterator it = m_Events.find( eventType );
|
||||
if ( it != m_Events.end() )
|
||||
{
|
||||
CFsmEvent* pEvent = it->second;
|
||||
if ( pEvent ) pEvent->SetParamRef( pEventParam );
|
||||
}
|
||||
|
||||
// Valid transition?
|
||||
if ( !pTransition->ApplyConditions() ) return false;
|
||||
|
||||
// Run transition actions
|
||||
if ( !pTransition->RunActions() ) return false;
|
||||
|
||||
// Switch state
|
||||
SetCurrState( pTransition->GetNextState() );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: IsDone()
|
||||
// Desc: Tests whether the state machine has finished its work
|
||||
// Note: This is state machine specific
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsm::IsDone( void ) const
|
||||
{
|
||||
// By default the internal flag m_Done is tested
|
||||
return m_Done;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: IsValidState()
|
||||
// Desc: Verifies whether the specified state is managed by FSM
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsm::IsValidState( unsigned int state ) const
|
||||
{
|
||||
StateSet::const_iterator it = m_States.find( state );
|
||||
if ( it == m_States.end() ) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Name: IsValidEvent()
|
||||
// Desc: Verifies whether the specified event is managed by FSM
|
||||
//-----------------------------------------------------------------------------
|
||||
bool CFsm::IsValidEvent( unsigned int eventType ) const
|
||||
{
|
||||
EventMap::const_iterator it = m_Events.find( eventType );
|
||||
if ( it == m_Events.end() ) return false;
|
||||
|
||||
return true;
|
||||
}
|
193
source/network/fsm.h
Normal file
193
source/network/fsm.h
Normal file
@ -0,0 +1,193 @@
|
||||
/**
|
||||
*-----------------------------------------------------------------------------
|
||||
* FILE : fsm.h
|
||||
* PROJECT : 0 A.D.
|
||||
* DESCRIPTION : Finite state machine class definitions
|
||||
*-----------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef FSM_H
|
||||
#define FSM_H
|
||||
|
||||
// INCLUDES
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
// DECLARATIONS
|
||||
#define FSM_INVALID_STATE ( unsigned int )( ~0 )
|
||||
|
||||
class CFsmEvent;
|
||||
class CFsmTransition;
|
||||
class CFsm;
|
||||
|
||||
typedef bool ( *CONDITION ) ( void* pContext );
|
||||
typedef bool ( *ACTION ) ( void* pContext, const CFsmEvent* pEvent );
|
||||
|
||||
typedef struct
|
||||
{
|
||||
void* pFunction;
|
||||
void* pContext;
|
||||
|
||||
} CallbackFunction;
|
||||
|
||||
typedef std::set< unsigned int > StateSet;
|
||||
typedef std::map< unsigned int, CFsmEvent* > EventMap;
|
||||
typedef std::vector< CFsmTransition* > TransitionList;
|
||||
typedef std::vector< CallbackFunction > CallbackList;
|
||||
|
||||
/*
|
||||
CLASS : CFsmEvent
|
||||
DESCRIPTION : CFsmEvent class represents a signal in the state machine
|
||||
that a change has occured.
|
||||
NOTES : The CFsmEvent objects are under the control of CFsm so
|
||||
they are created and deleted via CFsm.
|
||||
*/
|
||||
|
||||
class CFsmEvent
|
||||
{
|
||||
public:
|
||||
|
||||
CFsmEvent( unsigned int type );
|
||||
~CFsmEvent( void );
|
||||
|
||||
unsigned int GetType ( void ) const { return m_Type; }
|
||||
void* GetParamRef ( void ) { return m_Param; }
|
||||
void SetParamRef ( void* pParam );
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
|
||||
// Not implemented
|
||||
CFsmEvent( const CFsmEvent& );
|
||||
CFsmEvent& operator=( const CFsmEvent& );
|
||||
|
||||
unsigned int m_Type; // Event type
|
||||
void* m_Param; // Event paramater
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
CLASS : CFsmTransition
|
||||
DESCRIPTION : The CFsmTransition class is an association of event, condition,
|
||||
action and next state.
|
||||
NOTES :
|
||||
*/
|
||||
|
||||
class CFsmTransition
|
||||
{
|
||||
public:
|
||||
|
||||
CFsmTransition( unsigned int state );
|
||||
~CFsmTransition( void );
|
||||
|
||||
void RegisterAction (
|
||||
void* pAction,
|
||||
void* pContext );
|
||||
void RegisterCondition (
|
||||
void* pCondition,
|
||||
void* pContext );
|
||||
void SetEvent ( CFsmEvent* pEvent );
|
||||
CFsmEvent* GetEvent ( void ) const { return m_Event; }
|
||||
void SetNextState ( unsigned int nextState );
|
||||
unsigned int GetNextState ( void ) const { return m_NextState; }
|
||||
unsigned int GetCurrState ( void ) const { return m_CurrState; }
|
||||
const CallbackList& GetActions ( void ) const { return m_Actions; }
|
||||
const CallbackList& GetConditions ( void ) const { return m_Conditions; }
|
||||
bool ApplyConditions ( void ) const;
|
||||
bool RunActions ( void ) const;
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
|
||||
// Not implemented
|
||||
CFsmTransition( const CFsmTransition& );
|
||||
CFsmTransition& operator=( const CFsmTransition& );
|
||||
|
||||
unsigned int m_CurrState; // Current state
|
||||
unsigned int m_NextState; // Next state
|
||||
CFsmEvent* m_Event; // Transition event
|
||||
CallbackList m_Actions; // List of actions for transition
|
||||
CallbackList m_Conditions; // List of conditions for transition
|
||||
};
|
||||
|
||||
/*
|
||||
CLASS : CFsm
|
||||
DESCRIPTION : CFsm manages states, events, actions and transitions
|
||||
between states. It provides an interface for advertising
|
||||
events and track the current state. The implementation is
|
||||
a Mealy state machine, so the system respond to events
|
||||
and execute some action.
|
||||
NOTES : A Mealy state machine has behaviour associated with state
|
||||
transitions; Mealy machines are event driven where an
|
||||
event triggers a state transition
|
||||
*/
|
||||
|
||||
class CFsm
|
||||
{
|
||||
public:
|
||||
|
||||
CFsm( void );
|
||||
~CFsm( void );
|
||||
|
||||
/**
|
||||
* Constructs the state machine. This method must be overriden so that
|
||||
* connections are constructed for the particular state machine implemented
|
||||
*
|
||||
*/
|
||||
virtual void Setup( void );
|
||||
|
||||
/**
|
||||
* Clear event, action and condition lists and reset state machine
|
||||
*
|
||||
*/
|
||||
void Shutdown( void );
|
||||
|
||||
void AddState ( unsigned int state );
|
||||
CFsmEvent* AddEvent ( unsigned int eventType );
|
||||
CFsmTransition* AddTransition (
|
||||
unsigned int state,
|
||||
unsigned int eventType,
|
||||
unsigned int nextState );
|
||||
CFsmTransition* AddTransition (
|
||||
unsigned int state,
|
||||
unsigned int eventType,
|
||||
unsigned int nextState,
|
||||
void* pAction,
|
||||
void* pContext );
|
||||
CFsmTransition* GetTransition (
|
||||
unsigned int state,
|
||||
unsigned int eventType ) const;
|
||||
CFsmTransition* GetEventTransition ( unsigned int eventType ) const;
|
||||
void SetFirstState ( unsigned int firstState );
|
||||
unsigned int GetCurrState ( void ) const { return m_CurrState; }
|
||||
const StateSet& GetStates ( void ) const { return m_States; }
|
||||
const EventMap& GetEvents ( void ) const { return m_Events; }
|
||||
const TransitionList& GetTransitions ( void ) const { return m_Transitions; }
|
||||
bool Update ( unsigned int eventType, void* pEventData );
|
||||
bool IsValidState ( unsigned int state ) const;
|
||||
bool IsValidEvent ( unsigned int eventType ) const;
|
||||
virtual bool IsDone ( void ) const;
|
||||
|
||||
protected:
|
||||
|
||||
private:
|
||||
|
||||
// Not implemented
|
||||
CFsm( const CFsm& );
|
||||
CFsm& operator=( const CFsm& );
|
||||
|
||||
void SetCurrState ( unsigned int state );
|
||||
bool IsFirstTime ( void ) const;
|
||||
|
||||
bool m_Done; // FSM work is done
|
||||
unsigned int m_FirstState; // Initial state
|
||||
unsigned int m_CurrState; // Current state
|
||||
StateSet m_States; // List of states
|
||||
EventMap m_Events; // List of events
|
||||
TransitionList m_Transitions; // State transitions
|
||||
};
|
||||
|
||||
#endif // FSM_H
|
||||
|
Loading…
Reference in New Issue
Block a user