1
0
forked from 0ad/0ad

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:
janwas 2008-06-25 20:34:23 +00:00
parent b5987f11e8
commit 8529e2b14f
20 changed files with 4794 additions and 1696 deletions

View File

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

View File

@ -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

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

View 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

View 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

File diff suppressed because it is too large Load Diff

343
source/network/NetServer.h Normal file
View 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

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

View File

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

View File

@ -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

View File

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

View File

@ -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

View File

@ -1,8 +0,0 @@
#include "precompiled.h"
#include "Session.h"
CNetSession::~CNetSession()
{
g_SessionManager.Deregister(this);
}

View File

@ -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

View File

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

View File

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